mantis-dedupe

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Deduplicator (/mantis-dedupe)

Deduplicator (/mantis-dedupe)

System Goal

系统目标

Duplicate Finding Merger. Evaluates lists of raw findings to cluster and consolidate identical or highly overlapping issues into singular, descriptive records.
重复检测结果合并工具。评估原始检测结果列表,将相同或高度重叠的问题聚类并合并为单一、描述清晰的记录。

Command Definition

命令定义

  • Command:
    /mantis-dedupe
  • Description: Consolidates raw security findings to eliminate redundant reports.
  • Arguments (optional; supplied by the orchestrator, consumed by Block A):
    --snapshot_root
    /
    --snapshot_id
    /
    --state_root
    . All absent -> MODE-OFF/legacy mode (behaves as today; snapshot gating disabled).
  • 命令:
    /mantis-dedupe
  • 描述: 合并原始安全检测结果,消除冗余报告。
  • 参数(可选;由编排器提供,供Block A使用):
    --snapshot_root
    /
    --snapshot_id
    /
    --state_root
    。若全部缺失则进入MODE-OFF/传统模式(与当前行为一致;禁用快照门控)。

Input/Output Contract

输入/输出约定

  • Reads:
    • workspace/findings/
      (raw finding JSON files, ignoring
      .trash/
      ).
    • workspace/archive/findings_pass_*/*.json
      and
      workspace/archive/loop*_findings/*.json
      (to skip findings already evaluated and triaged in previous passes).
    • workspace/.mantis_state.json
      (to track current loop pass).
  • Writes:
    • Moves duplicate findings to
      workspace/findings/.trash/
      after setting
      "status": "DUPLICATE"
      and
      "duplicate_of"
      .
    • Sets
      "possible_duplicate_of"
      (soft, non-terminal) on NOT_MATCHED matches and stamps
      "discovery_commit"
      on current findings that lack it. Reads
      active_snapshot
      /
      snapshot_pinned
      from
      .mantis_state.json
      .
    • Appends transaction logs to
      workspace/.tx_log.jsonl
      .
    • Generates/executes merging script
      workspace/helpers/merge_findings.py
      .
    • Updates primary finding
      workspace/findings/<primary_id>.json
      (merges fields and history).
  • Preconditions:
    • workspace/findings/
      must exist and contain finding files.
  • Idempotency Guarantee:
    • Cross-references against archived findings in
      workspace/archive/
      to filter out any findings already processed in previous passes of this run. Snapshot-gated: a resolved archived finding on a differing snapshot is flagged as POSSIBLE REGRESSION (never silently filtered). Logs transactions to
      workspace/.tx_log.jsonl
      to support tracking and potential rollbacks. Deterministic merging rules implemented in
      merge_findings.py
      .
  • 读取:
    • workspace/findings/
      (原始检测结果JSON文件,忽略
      .trash/
      目录)。
    • workspace/archive/findings_pass_*/*.json
      workspace/archive/loop*_findings/*.json
      (用于跳过之前轮次中已评估和分类的检测结果)。
    • workspace/.mantis_state.json
      (用于跟踪当前循环轮次)。
  • 写入:
    • 将重复检测结果设置
      "status": "DUPLICATE"
      "duplicate_of"
      字段后,移动至
      workspace/findings/.trash/
      目录。
    • 对未匹配的结果设置
      "possible_duplicate_of"
      (软标记,非终结性),并为缺少
      "discovery_commit"
      字段的当前检测结果添加该字段。从
      .mantis_state.json
      读取
      active_snapshot
      /
      snapshot_pinned
      信息。
    • workspace/.tx_log.jsonl
      追加事务日志。
    • 生成并执行合并脚本
      workspace/helpers/merge_findings.py
    • 更新主检测结果文件
      workspace/findings/<primary_id>.json
      (合并字段和历史记录)。
  • 前置条件:
    • workspace/findings/
      目录必须存在且包含检测结果文件。
  • 幂等性保证:
    • workspace/archive/
      中的归档检测结果进行交叉引用,过滤掉本次运行中之前轮次已处理的检测结果。 快照门控:不同快照上已解决的归档检测结果会被标记为POSSIBLE REGRESSION(不会被静默过滤)。将事务记录到
      workspace/.tx_log.jsonl
      以支持跟踪和潜在回滚。 在
      merge_findings.py
      中实现确定性合并规则。

Instructions

操作说明

Step 0: Locator Resolution (run first)

步骤0:定位器解析(首先执行)

LOCATOR RESOLUTION (before reading ANY target code or artifact):
0. ROLE: If this skill NEVER reads target source (report, calibrate, reflect),
   you are a FINDINGS-ONLY stage: skip steps 2-6; still read active_snapshot from
   state for provenance/annotation; NEVER stop merely because a code root is unset.
1. Determine CODE_ROOT, in this priority order:
   a. If --target_root is passed on THIS invocation, CODE_ROOT = --target_root.
      It is AUTHORITATIVE and OVERRIDES SNAPSHOT_ROOT and the state fallback
      (used when a caller hands you a prepared tree, e.g. a patched shadow).
   b. Else if --snapshot_root (or SNAPSHOT_ROOT) is passed, use it.
   c. Else read state_root/workspace/.mantis_state.json (state_root from
      --state_root if passed, else ./workspace/... relative to the current dir)
      -> active_snapshot.root / .snapshot_id / .snapshot_pinned.
   d. Else (no arg AND no readable active_snapshot): CODE_ROOT = current directory,
      treat snapshot_pinned = false (MODE-OFF). Do NOT stop.
2. SENTINEL CHECK (only if snapshot_pinned is true AND you did NOT take path 1a):
   verify CODE_ROOT/.mantis_snapshot_id exists and equals SNAPSHOT_ID. If missing
   or different -> STOP "snapshot sentinel mismatch". (A --target_root tree (1a) is
   deliberately mutated and is sentinel-EXEMPT.)
3. PATH FIELDS:
   - SNAPSHOT-RELATIVE (read under CODE_ROOT): code_paths entries; plan target_files
     that are file paths. Strip ONLY a trailing ":<digits>". A code_paths entry
     containing "://" is a URL/endpoint, NOT a file read. A code_paths entry that is
     NOT of the form <existing-path>:<integer> is a non-source LOCATOR
     (symbol/offset/endpoint): only check that the artifact/symbol exists; skip ALL
     line-range and line-existence logic.
   - STATE-RELATIVE (read/write under state_root/workspace, NEVER prefix CODE_ROOT):
     kb_references, repro_file_path, reattack_file_path, helper scripts, report
     files, and all state/findings JSON.
4. Never WRITE under CODE_ROOT when snapshot_pinned is true. Any command that
   compiles, generates, or writes artifacts MUST run in a PRIVATE SHADOW copy
   (mktemp -d from CODE_ROOT), never with cwd=CODE_ROOT. Read-only inspection may
   cd into CODE_ROOT.
5. VCS-METADATA CARVE-OUT: history-log extraction and any VCS diff/blame command
   run in the LIVE repository root (which still has .git/.hg/.repo), NOT CODE_ROOT
   (the snapshot copy strips VCS metadata). Do NOT stop merely because CODE_ROOT
   lacks .git/.hg/.repo.
6. Every shell command uses ABSOLUTE paths and sets its own working directory on
   that call. Do NOT assume the working directory persists between calls.
[!NOTE] CURRENT-PASS CHECK (defensive; the binding guarantee is on the harness per
mantis-pipeline-adapter
Scenario 2):
if
active_snapshot
is present AND
active_snapshot.pass != state.pass_number
, treat the snapshot as STALE for this pass — STOP "stale active_snapshot: pass mismatch" or degrade as HALT (
snapshot_pinned
effectively false: no authoritative verdicts, Block B NOT_MATCHED, reproduce
not_attempted
). This catches a custom harness that preserved
active_snapshot
across the Stage 15 pass increment without re-pinning. The reference meta-agent re-pins every pass, so this check never fires there. Block B itself cannot detect this (it is
snapshot_id
-only, not
pass
-aware).
Notes:
workspace/findings/
,
workspace/archive/
,
workspace/.tx_log.jsonl
,
workspace/helpers/
and
.mantis_state.json
are STATE-RELATIVE (under --state_root). Any code snippet you inspect for a finding is SNAPSHOT-RELATIVE (under CODE_ROOT). Never write under CODE_ROOT.
Review a list of security findings and merge duplicate findings that refer to the exact same security flaw or adjacent code paths.
Execute your task as follows:
  1. Load Raw Findings & Archived Findings Queue:
    • List the contents of the directory and read the files in
      workspace/findings/
      . If the directory is empty or does not exist, notify the user and exit.
    • Important: Ignore hidden files and directories (such as the
      .trash/
      subdirectory) when listing or processing findings.
    • Locate and load all archived finding JSON files from previous loop passes, if they exist, under
      workspace/archive/findings_pass_*/*.json
      and
      workspace/archive/loop*_findings/*.json
      . These files represent vulnerabilities that have already been fully evaluated, triaged, and potentially patched in previous passes.
    • Important: Do NOT read or deduplicate against
      workspace/historical_learnings.jsonl
      (VCS history), as we want to catch regressions if old bugs were reintroduced.
  2. Filter Loop Duplicates (snapshot-gated). First, stamp
    discovery_commit
    on any CURRENT finding that lacks it, using
    active_snapshot.snapshot_id
    from
    .mantis_state.json
    (skip when unpinned). Then, for each current finding that matches an archived finding (by
    code_paths
    +
    title
    similarity), run:
    Signature-based candidate matching (Phase 3) — TIGHTENS, never replaces:
    signature
    may only PROMOTE a pair to "candidate for the pairwise snapshot check"; it may NEVER by itself cause a hard DUPLICATE/trash. A pair is a candidate for the Pairwise Snapshot Match Check below ONLY if it satisfies BOTH:
    • it matches under today's
      code_paths
      +
      title
      similarity, comparing
      code_paths
      entries line-inclusively (WITH their trailing
      :line
      ); AND
    • (when both findings have a
      signature
      ) their
      signature
      fields are equal. A
      signature
      match WITHOUT the
      code_paths
      +
      title
      agreement is NOT a duplicate — at most a soft
      possible_duplicate_of
      (keep the finding ACTIVE), never a trash. Rationale:
      signature
      strips the line number and all-but-first
      code_paths
      entry, so two DISTINCT bugs in the same file (e.g.
      parser.c:100
      vs
      parser.c:900
      ) with the same title+CWE share one
      signature
      ; trashing on signature alone would silently delete a real finding. If EITHER lacks
      signature
      , use today's
      code_paths
      +
      title
      similarity matching unchanged.
    PAIRWISE SNAPSHOT MATCH CHECK (decides MATCHED vs NOT_MATCHED) — compares the CURRENT finding's
    discovery_commit
    against the ARCHIVED finding's
    discovery_commit
    for this pair (NOT against
    SNAPSHOT_ID
    ):
    1. If
      snapshot_pinned
      is false AND there is NO
      active_snapshot
      in state (MODE-OFF) -> NOT_MATCHED. Stop. (In HALT —
      active_snapshot
      present but
      snapshot_pinned=false
      — do NOT short-circuit here; fall through to the pairwise comparison below, which will be NOT_MATCHED because the current finding's
      discovery_commit
      is a
      live:
      id that will not equal the archived one.)
    2. Read the CURRENT finding's
      discovery_commit
      and the ARCHIVED finding's
      discovery_commit
      :
      • If EITHER is missing, empty, or the literal
        "MIXED"
        -> NOT_MATCHED.
      • If they are NOT byte-for-byte equal to each other -> NOT_MATCHED.
      • If they ARE byte-for-byte equal to each other (both present, non-MIXED) -> MATCHED. There is no other route to MATCHED; never fuzzy-compare. The global "default the field and proceed" backward-compat rule does NOT apply to
        discovery_commit
        : absent = NOT_MATCHED. (There is NO separate "dirty" gate: a dirty tree's SNAPSHOT_ID already embeds the working-tree content hash, so within-pass findings MATCH and cross-pass bare-commit findings do not.) Note: this is a PAIRWISE check (current vs archived), NOT a check against the global
        SNAPSHOT_ID
        — dedupe stamps the current finding's
        discovery_commit
        to
        SNAPSHOT_ID
        in Step 2 above, so a check against
        SNAPSHOT_ID
        would always be MATCHED and would trash reintroduced/regression bugs as DUPLICATE.
    Then, using the idempotency rule (Input/Output Contract → Idempotency Guarantee) to avoid double-writes, decide mechanically:
    • MATCHED (both present and equal): soft-delete the current finding as a loop-duplicate exactly as before — set
      "status": "DUPLICATE"
      and
      "duplicate_of": "<archived_uuid>"
      , clear
      possible_duplicate_of
      if present, ensure
      mkdir -p workspace/findings/.trash/
      , move it there, and log a
      loop_filter
      transaction in
      workspace/.tx_log.jsonl
      . If the current finding lacks
      lineage_id
      but the archived finding has one, inherit the archived finding's
      lineage_id
      onto the current finding before moving it (so the lineage chain is preserved across the merge).
    • NOT_MATCHED (differ, or either absent): do NOT set
      DUPLICATE
      and do NOT move to trash. Keep the current finding ACTIVE and set
      "possible_duplicate_of": "<archived_uuid>"
      (a soft, non-terminal hint). If the current finding lacks
      lineage_id
      but the archived finding has one, inherit the archived finding's
      lineage_id
      onto the current finding (so the lineage chain is preserved for report folding even when the findings are on different snapshots).
    [!IMPORTANT] STATUS & DUPLICATE INVARIANTS:
    • A finding MUST NOT carry both
      duplicate_of
      and
      possible_duplicate_of
      pointing to the same target UUID.
    • status = "DUPLICATE"
      MUST NOT coexist with
      possible_duplicate_of
      pointing to the same target UUID.
    • Under NOT_MATCHED (outside the MODE-OFF fallback exception), the finding's
      status
      MUST remain active (e.g.
      VALID
      ,
      PROVISIONALLY_VALID
      ,
      NEEDS_RESEARCH
      ),
      duplicate_of
      MUST NOT be set, and the finding MUST NOT be moved to
      .trash/
      . Setting
      possible_duplicate_of
      is a non-terminal hint only.
    • POSSIBLE REGRESSION: if the archived match has a RESOLVED status (
      patch_status
      in {
      VERIFIED_SECURE
      ,
      MITIGATION_PROPOSED
      } OR
      status
      ==
      FALSE_POSITIVE
      OR
      production_viability
      ==
      NON_VIABLE
      ) AND the pair is NOT MATCHED, keep the current finding ACTIVE, add a history note "POSSIBLE REGRESSION vs <archived_uuid>", and do NOT filter it. (A reverted fix re-discovered on new code must never be trashed.)
    • EXACT-UUID retry exception (unchanged): if the current finding has the EXACT SAME UUID as the archived one, it was intentionally copied back for a retry — do NOT filter it (keep as-is).
    • Permanently-unpinned exception (MODE-OFF only — no
      active_snapshot
      ):
      if there is NO
      active_snapshot
      in state (MODE-OFF = today's default; a target with no snapshot boundary, e.g. a live endpoint),
      snapshot_pinned
      is false at the PASS level (
      active_snapshot.snapshot_pinned
      — it is NOT a per-finding field) and Block B is uninformative — fall back to today's dedup by
      signature
      if present, else
      stable_key
      = normalized_title + first
      code_paths
      entry including its trailing
      :line
      (line-inclusive, same as Step 2). Rationale: stripping
      :line
      would collapse two DISTINCT bugs in the same file (e.g.
      parser.c:100
      vs
      parser.c:900
      ) with the same title+CWE into one — silently deleting a real finding. Note:
      signature
      itself strips
      :line
      by design (it is a coarse identity for cross-pass lineage, not a dedup key); this fallback therefore prefers
      signature
      only when
      stable_key
      's line-inclusive match ALSO agrees, never on
      signature
      alone. This preserves dedup for targets that can never MATCH. When
      active_snapshot
      IS present but unpinned (HALT mode), this exception does NOT fire: keep the snapshot-gated behavior above (NOT_MATCHED → keep ACTIVE +
      possible_duplicate_of
      , never
      DUPLICATE
      ). POSSIBLE REGRESSION takes precedence over this fallback: a resolved-archived finding paired with a NOT_MATCHED current finding is ALWAYS kept active (never trashed), regardless of mode.
  3. Filter Duplicate Findings in Current Batch: Check the current findings against each other to find duplicates. Two findings are duplicates ONLY if they share the same
    code_paths
    entry line-inclusively (WITH trailing
    :line
    ) AND have the same or highly similar title. If multiple findings refer to the exact same flaw at the same location, they must be merged. Findings at different lines in the same file are DISTINCT — never merge them.
  4. Map/Reduce Chunking Strategy (For Scale): If there are many finding files (e.g., > 20 items), use a Map/Reduce approach to group them by target file or component before checking for overlaps to avoid context window limits.
  5. Token-Optimized Consolidation and Merging: To minimize LLM output tokens and prevent data loss, do not manually rewrite or output the merged JSON files in your response. Instead, follow this pattern:
    1. Identify Duplicates: Internally map which findings are duplicates of a primary finding.
    2. Reusable Deterministic Scripting (versioned): Write a reusable helper script (e.g.
      workspace/helpers/merge_findings.py
      ) whose FIRST LINE is exactly
      # MANTIS_HELPER_VERSION = 2
      . Before reusing an existing helper, grep its first lines for
      MANTIS_HELPER_VERSION = 2
      ; if that marker is absent or a different integer (a helper left by an older pipeline version), REGENERATE the helper. Only reuse it when the marker matches. The script must follow these deterministic rules:
      • Title: Pick the most comprehensive and descriptive title.
      • ID: Preserve the unique
        "id"
        of the primary finding being kept.
      • Severity: Pick the highest severity level specified among the merged items.
      • Privileges Required: Inherit the most severe privilege requirement (priority:
        NONE
        >
        LOW
        >
        HIGH
        ).
      • Attacker Position: Inherit the most critical position requirement (priority:
        EXTERNAL
        >
        INTERNAL_NETWORK
        >
        IN_CLUSTER
        >
        LOCAL
        >
        HOST_SYSTEM
        >
        SUPPLY_CHAIN
        >
        PHYSICAL_TEMPORARY
        >
        PHYSICAL_LONG_TERM
        ).
      • User Interaction: Inherit the most severe user interaction requirement (priority:
        NONE
        >
        REQUIRED
        ).
      • Code Paths: Collect and deduplicate all file paths and line numbers into a single unique array.
      • Description, Mitigation, & Impact: Concatenate cleanly.
      • History: Concatenate and preserve all
        "history"
        entries from the merged findings. Append a new entry to the
        "history"
        array for this merge action conforming to the schema (containing
        "stage": "dedupe"
        ,
        "action": "merge"
        ,
        "details": "Merged duplicate findings: [comma-separated-ids]"
        ,
        "pass_number": <current_pass_number>
        , and
        "timestamp": "<current_iso8601_timestamp>"
        ).
      • Unknown keys & provenance (MANDATORY): The script MUST copy through EVERY key it does not explicitly handle (including
        discovery_commit
        ,
        repro_snapshot_id
        ,
        patch_base_snapshot
        ,
        possible_duplicate_of
        ,
        signature
        ,
        lineage_id
        ,
        cwe
        ) from the primary finding onto the merged object — never drop unknown fields. It MUST REFUSE to merge two findings whose
        discovery_commit
        values differ (they describe different code versions); leave them separate and log the refusal. When merging findings that all share one
        discovery_commit
        , preserve it unchanged.
    3. Execute the Script: Run your script to update the primary finding's file (
      workspace/findings/<primary_id>.json
      ) on disk.
  6. Transactional Staged Clean Up: Do not permanently delete redundant files. Ensure the trash directory exists (e.g.,
    mkdir -p workspace/findings/.trash/
    ). Before moving, the script must update the duplicate finding files, setting
    "status": "DUPLICATE"
    and
    "duplicate_of": "<primary_uuid>"
    . Move the merged duplicate
    .json
    files to the trash staging directory (
    workspace/findings/.trash/
    ). For every file moved, append a transaction record to
    workspace/.tx_log.jsonl
    .

    Transaction Log Schema Format (
    workspace/.tx_log.jsonl
    )

    Each line must be a self-contained JSON object documenting the transaction:
    json
    {"timestamp": "2026-07-14T15:13:00Z", "action": "loop_filter | dedupe_merge", "primary_uuid": "[UUID] (or null for loop_filter)", "moved_uuid": "[UUID]"}
    This cleans up the directory for downstream stages while preserving rollback capability.
When complete, notify the user.
LOCATOR RESOLUTION (before reading ANY target code or artifact):
0. ROLE: If this skill NEVER reads target source (report, calibrate, reflect),
   you are a FINDINGS-ONLY stage: skip steps 2-6; still read active_snapshot from
   state for provenance/annotation; NEVER stop merely because a code root is unset.
1. Determine CODE_ROOT, in this priority order:
   a. If --target_root is passed on THIS invocation, CODE_ROOT = --target_root.
      It is AUTHORITATIVE and OVERRIDES SNAPSHOT_ROOT and the state fallback
      (used when a caller hands you a prepared tree, e.g. a patched shadow).
   b. Else if --snapshot_root (or SNAPSHOT_ROOT) is passed, use it.
   c. Else read state_root/workspace/.mantis_state.json (state_root from
      --state_root if passed, else ./workspace/... relative to the current dir)
      -> active_snapshot.root / .snapshot_id / .snapshot_pinned.
   d. Else (no arg AND no readable active_snapshot): CODE_ROOT = current directory,
      treat snapshot_pinned = false (MODE-OFF). Do NOT stop.
2. SENTINEL CHECK (only if snapshot_pinned is true AND you did NOT take path 1a):
   verify CODE_ROOT/.mantis_snapshot_id exists and equals SNAPSHOT_ID. If missing
   or different -> STOP "snapshot sentinel mismatch". (A --target_root tree (1a) is
   deliberately mutated and is sentinel-EXEMPT.)
3. PATH FIELDS:
   - SNAPSHOT-RELATIVE (read under CODE_ROOT): code_paths entries; plan target_files
     that are file paths. Strip ONLY a trailing ":<digits>". A code_paths entry
     containing "://" is a URL/endpoint, NOT a file read. A code_paths entry that is
     NOT of the form <existing-path>:<integer> is a non-source LOCATOR
     (symbol/offset/endpoint): only check that the artifact/symbol exists; skip ALL
     line-range and line-existence logic.
   - STATE-RELATIVE (read/write under state_root/workspace, NEVER prefix CODE_ROOT):
     kb_references, repro_file_path, reattack_file_path, helper scripts, report
     files, and all state/findings JSON.
4. Never WRITE under CODE_ROOT when snapshot_pinned is true. Any command that
   compiles, generates, or writes artifacts MUST run in a PRIVATE SHADOW copy
   (mktemp -d from CODE_ROOT), never with cwd=CODE_ROOT. Read-only inspection may
   cd into CODE_ROOT.
5. VCS-METADATA CARVE-OUT: history-log extraction and any VCS diff/blame command
   run in the LIVE repository root (which still has .git/.hg/.repo), NOT CODE_ROOT
   (the snapshot copy strips VCS metadata). Do NOT stop merely because CODE_ROOT
   lacks .git/.hg/.repo.
6. Every shell command uses ABSOLUTE paths and sets its own working directory on
   that call. Do NOT assume the working directory persists between calls.
[!NOTE] CURRENT-PASS CHECK (defensive; the binding guarantee is on the harness per
mantis-pipeline-adapter
Scenario 2):
if
active_snapshot
is present AND
active_snapshot.pass != state.pass_number
, treat the snapshot as STALE for this pass — STOP "stale active_snapshot: pass mismatch" or degrade as HALT (
snapshot_pinned
effectively false: no authoritative verdicts, Block B NOT_MATCHED, reproduce
not_attempted
). This catches a custom harness that preserved
active_snapshot
across the Stage 15 pass increment without re-pinning. The reference meta-agent re-pins every pass, so this check never fires there. Block B itself cannot detect this (it is
snapshot_id
-only, not
pass
-aware).
注意:
workspace/findings/
workspace/archive/
workspace/.tx_log.jsonl
workspace/helpers/
.mantis_state.json
均为STATE-RELATIVE(位于
--state_root
下)。为检测结果检查的任何代码片段均为SNAPSHOT-RELATIVE(位于CODE_ROOT下)。请勿在CODE_ROOT下写入内容。
审核安全检测结果列表,合并指向完全相同安全漏洞或相邻代码路径的重复检测结果。
按以下步骤执行任务:
  1. 加载原始检测结果与归档检测结果队列:
    • 列出目录内容并读取
      workspace/findings/
      中的文件。若目录为空或不存在,通知用户并退出。
    • 重要提示: 列出或处理检测结果时,忽略隐藏文件和目录(如
      .trash/
      子目录)。
    • 定位并加载之前循环轮次中所有已归档的检测结果JSON文件(若存在),路径为
      workspace/archive/findings_pass_*/*.json
      workspace/archive/loop*_findings/*.json
      。这些文件代表之前轮次中已完全评估、分类并可能已修复的漏洞。
    • 重要提示: 请勿读取或针对
      workspace/historical_learnings.jsonl
      (版本控制系统历史)进行去重,因为我们需要捕获旧漏洞重新引入的回归情况。
  2. 过滤循环重复项(快照门控)。 首先,使用
    .mantis_state.json
    中的
    active_snapshot.snapshot_id
    为所有缺少
    discovery_commit
    字段的当前检测结果添加该字段(未固定快照时跳过)。然后,对于每个与归档检测结果匹配(通过
    code_paths
    +
    title
    相似度)的当前检测结果,执行:
    基于签名的候选匹配(阶段3)——仅收紧规则,绝不替代:
    signature
    仅可将配对提升为“候选以进行成对快照检查”;绝不能仅通过
    signature
    直接标记为硬DUPLICATE/移入回收站。仅当同时满足以下两个条件时,配对才符合下方成对快照匹配检查的候选要求:
    • 符合当前的
      code_paths
      +
      title
      相似度匹配规则,对比
      code_paths
      条目时包含行号(包括末尾的
      :line
      );并且
    • (当两个检测结果均包含
      signature
      字段时)它们的
      signature
      字段完全相等。仅
      signature
      匹配但
      code_paths
      +
      title
      不匹配的情况不属于重复——最多标记为软
      possible_duplicate_of
      (保持检测结果为ACTIVE状态),绝不移入回收站。理由:
      signature
      会去除行号和除第一个外的所有
      code_paths
      条目,因此同一文件中两个不同的漏洞(如
      parser.c:100
      vs
      parser.c:900
      )若标题和CWE相同,会共享同一个
      signature
      ;仅基于签名去重会静默删除真实的检测结果。若任一检测结果缺少
      signature
      ,则直接使用当前的
      code_paths
      +
      title
      相似度匹配规则。
    成对快照匹配检查(决定MATCHED vs NOT_MATCHED)——对比该配对中当前检测结果的
    discovery_commit
    与归档检测结果的
    discovery_commit
    (而非与全局
    SNAPSHOT_ID
    对比):
    1. snapshot_pinned
      为false且状态中无
      active_snapshot
      (MODE-OFF)→ NOT_MATCHED。停止操作。(在HALT模式下——存在
      active_snapshot
      snapshot_pinned=false
      ——请勿在此处短路;继续执行下方的成对对比,由于当前检测结果的
      discovery_commit
      live:
      格式的ID,与归档检测结果的ID不相等,因此结果为NOT_MATCHED。)
    2. 读取当前检测结果的
      discovery_commit
      和归档检测结果的
      discovery_commit
      • 若任一字段缺失、为空或为字面量
        "MIXED"
        → NOT_MATCHED。
      • 若两者并非逐字节完全相等 → NOT_MATCHED。
      • 若两者逐字节完全相等(均存在且非MIXED)→ MATCHED。没有其他途径可判定为MATCHED;绝不进行模糊对比。全局“默认字段并继续”的向后兼容规则不适用于
        discovery_commit
        :缺失即判定为NOT_MATCHED。(没有单独的“脏数据”门控:脏树的SNAPSHOT_ID已嵌入工作树内容哈希,因此同轮次的检测结果会匹配,跨轮次的裸提交检测结果则不会匹配。)注意:这是成对检查(当前 vs 归档),而非与全局
        SNAPSHOT_ID
        的检查——去重步骤会在步骤2中将当前检测结果的
        discovery_commit
        标记为
        SNAPSHOT_ID
        ,因此与
        SNAPSHOT_ID
        的检查始终会判定为MATCHED,从而将重新引入/回归的漏洞误标记为DUPLICATE并删除。
    然后,使用幂等性规则(输入/输出约定 → 幂等性保证)避免重复写入,机械性地做出判定:
    • MATCHED(两者均存在且相等):将当前检测结果作为循环重复项进行软删除——设置
      "status": "DUPLICATE"
      "duplicate_of": "<archived_uuid>"
      ,若存在
      possible_duplicate_of
      则清除该字段,确保
      mkdir -p workspace/findings/.trash/
      目录存在,将文件移至该目录,并在
      workspace/.tx_log.jsonl
      中记录
      loop_filter
      事务。若当前检测结果缺少
      lineage_id
      但归档检测结果包含该字段,在移动前将归档检测结果的
      lineage_id
      继承到当前检测结果中(以保留合并后的 lineage 链)。
    • NOT_MATCHED(两者不同,或任一缺失):不设置
      DUPLICATE
      标记,不移入回收站。保持当前检测结果为ACTIVE状态,并设置
      "possible_duplicate_of": "<archived_uuid>"
      (软标记,仅作为提示)。若当前检测结果缺少
      lineage_id
      但归档检测结果包含该字段,将归档检测结果的
      lineage_id
      继承到当前检测结果中(以便在检测结果位于不同快照时,仍能保留 lineage 链用于报告折叠)。
    [!IMPORTANT] 状态与重复项不变量:
    • 检测结果不得同时包含指向同一目标UUID的
      duplicate_of
      possible_duplicate_of
      字段。
    • status = "DUPLICATE"
      不得与指向同一目标UUID的
      possible_duplicate_of
      字段共存。
    • NOT_MATCHED情况下(除MODE-OFF回退例外),检测结果的
      status
      必须保持活跃状态(如
      VALID
      PROVISIONALLY_VALID
      NEEDS_RESEARCH
      ),不得设置
      duplicate_of
      ,且不得移入
      .trash/
      目录。设置
      possible_duplicate_of
      仅作为非终结性提示。
    • POSSIBLE REGRESSION: 若归档匹配项的状态为已解决(
      patch_status
      属于{
      VERIFIED_SECURE
      ,
      MITIGATION_PROPOSED
      } 或
      status
      ==
      FALSE_POSITIVE
      production_viability
      ==
      NON_VIABLE
      )且配对结果为NOT_MATCHED,保持当前检测结果为ACTIVE状态,添加历史记录备注"POSSIBLE REGRESSION vs <archived_uuid>",且不得过滤该结果。(新代码中重新发现的已回滚修复绝不能被删除。)
    • 精确UUID重试例外(保持不变): 若当前检测结果的UUID与归档检测结果完全相同,说明是有意复制回来进行重试——请勿过滤该结果(保持原样)。
    • 永久未固定例外(仅MODE-OFF模式——无
      active_snapshot
      ):
      若状态中无
      active_snapshot
      (MODE-OFF = 当前默认模式;无快照边界的目标,如实时端点),
      snapshot_pinned
      在PASS级别为false(
      active_snapshot.snapshot_pinned
      ——这并非每个检测结果的字段)且Block B无有效信息——回退到当前基于
      signature
      的去重规则(若存在
      signature
      ),否则使用
      stable_key
      = 标准化标题 + 第一个
      code_paths
      条目包括末尾的
      :line
      (包含行号,与步骤2相同)。理由:去除
      :line
      会将同一文件中两个不同的漏洞(如
      parser.c:100
      vs
      parser.c:900
      )若标题和CWE相同则合并为一个——静默删除真实的检测结果。注意:
      signature
      本身设计为去除
      :line
      (用于跨轮次lineage的粗粒度标识,而非去重键);因此仅当
      stable_key
      的包含行号的匹配也一致时,才优先使用
      signature
      ,绝不单独基于
      signature
      去重。这为永远无法MATCH的目标保留了去重功能。当存在
      active_snapshot
      但未固定(HALT模式)时,该例外不生效:保持上述快照门控行为(NOT_MATCHED → 保持ACTIVE +
      possible_duplicate_of
      ,绝不标记为
      DUPLICATE
      )。 POSSIBLE REGRESSION优先于此回退规则: 已解决的归档检测结果与NOT_MATCHED的当前检测结果配对时,始终保持活跃状态(绝不删除),无论处于何种模式。
  3. 过滤当前批次中的重复检测结果: 将当前检测结果两两对比,找出重复项。仅当两个检测结果包含完全相同的
    code_paths
    条目包含行号(包括末尾的
    :line
    )且标题相同或高度相似时,才判定为重复项。若多个检测结果指向同一位置的完全相同漏洞,必须进行合并。同一文件中不同行的检测结果为不同项——绝不能合并。
  4. Map/Reduce分块策略(针对大规模场景): 若检测结果文件数量较多(如超过20个),使用Map/Reduce方法按目标文件或组件分组,再检查重叠情况,避免超出上下文窗口限制。
  5. 优化Token的合并与整合: 为最小化LLM输出Token并防止数据丢失,请勿在响应中手动重写或输出合并后的JSON文件。请遵循以下流程:
    1. 识别重复项: 在内部映射哪些检测结果是主检测结果的重复项。
    2. 可复用的确定性脚本(版本化): 编写可复用的辅助脚本(如
      workspace/helpers/merge_findings.py
      ),其第一行必须为
      # MANTIS_HELPER_VERSION = 2
      。复用现有辅助脚本前,检查其首行是否包含
      MANTIS_HELPER_VERSION = 2
      ;若标记缺失或为不同整数(由旧版本流水线留下的脚本),则重新生成辅助脚本。仅当标记匹配时才可复用。脚本必须遵循以下确定性规则:
      • 标题: 选择最全面、描述最清晰的标题。
      • ID: 保留要保留的主检测结果的唯一
        "id"
      • 严重程度: 选择合并项中最高的严重级别。
      • 所需权限: 继承最严格的权限要求(优先级:
        NONE
        >
        LOW
        >
        HIGH
        )。
      • 攻击者位置: 继承最关键的位置要求(优先级:
        EXTERNAL
        >
        INTERNAL_NETWORK
        >
        IN_CLUSTER
        >
        LOCAL
        >
        HOST_SYSTEM
        >
        SUPPLY_CHAIN
        >
        PHYSICAL_TEMPORARY
        >
        PHYSICAL_LONG_TERM
        )。
      • 用户交互: 继承最严格的用户交互要求(优先级:
        NONE
        >
        REQUIRED
        )。
      • 代码路径: 收集并去重所有文件路径和行号,合并为单一唯一数组。
      • 描述、缓解措施与影响: 清晰地拼接内容。
      • 历史记录: 拼接并保留所有合并检测结果的
        "history"
        条目。在
        "history"
        数组中添加一条新的合并操作记录,符合以下模式(包含
        "stage": "dedupe"
        "action": "merge"
        "details": "Merged duplicate findings: [逗号分隔的ID列表]"
        "pass_number": <当前轮次编号>
        "timestamp": "<当前ISO8601时间戳>"
        )。
      • 未知键与来源(必填): 脚本必须将主检测结果中所有未明确处理的键(包括
        discovery_commit
        repro_snapshot_id
        patch_base_snapshot
        possible_duplicate_of
        signature
        lineage_id
        cwe
        )复制到合并后的对象中——绝不丢弃未知字段。若两个检测结果的
        discovery_commit
        值不同(描述不同的代码版本),脚本必须拒绝合并;保留它们并记录拒绝操作。合并所有共享同一
        discovery_commit
        的检测结果时,保持该字段不变。
    3. 执行脚本: 运行脚本以更新磁盘上的主检测结果文件(
      workspace/findings/<primary_id>.json
      )。
  6. 事务性阶段清理: 请勿永久删除冗余文件。确保回收站目录存在(如
    mkdir -p workspace/findings/.trash/
    )。移动前,脚本必须更新重复检测结果文件,设置
    "status": "DUPLICATE"
    "duplicate_of": "<primary_uuid>"
    。将合并后的重复
    .json
    文件移至回收站暂存目录(
    workspace/findings/.trash/
    )。对于每个移动的文件,向
    workspace/.tx_log.jsonl
    追加一条事务记录。

    事务日志格式(
    workspace/.tx_log.jsonl

    每一行必须是一个独立的JSON对象,记录事务信息:
    json
    {"timestamp": "2026-07-14T15:13:00Z", "action": "loop_filter | dedupe_merge", "primary_uuid": "[UUID] (or null for loop_filter)", "moved_uuid": "[UUID]"}
    此操作可为下游阶段清理目录,同时保留回滚能力。
完成后,通知用户。