{ "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "Mantis Pipeline State Contract", "description": "Canonical schemas for all inter-stage state contracts in the Mantis pipeline.\n\n### Validation Guidelines:\n- Harness authors and pipeline orchestrators should validate specific state files against their corresponding sub-schemas:\n - Validate individual finding files (`workspace/findings/.json`) against `#/$defs/finding`.\n - Validate plan files (`workspace/plan.json`) against `#/$defs/plan`.\n - Validate learning entry lines (`workspace/learnings.jsonl` / `workspace/historical_learnings.jsonl`) against `#/$defs/learning_entry`.\n - Validate the orchestrator state file (`workspace/.mantis_state.json`) against `#/$defs/state`.\n - Validate trajectory/conversation transcript lines (e.g., in `transcript.jsonl` log files) against `#/$defs/execution_log_entry`.\n\n### Non-JSON Contracts:\n#### Threat Model (workspace/kb/THREAT_MODEL.md)\nWhile the threat model is a Markdown document, it serves as a contract. It must contain:\n- **System Overview Summary**: High-level design.\n- **Deployment Intent**: Must state 'Intent: PRODUCTION' or 'Intent: SAMPLE_OR_TEST_ONLY'.\n- **Trust Boundaries**: Zone definitions and entry points.\n- **Threat Actors & Vectors**: Attacker profiles.\n- **High-Risk Assets & Criticality**: Asset definitions. For DoS targets, must specify Availability Tier: 'CRITICAL' (24/7 immediate impact), 'STANDARD' (short disruptions tolerable), or 'LOW_CRITICALITY' (non-blocking).\n\n#### Pass Lifecycle Contract (harness-agnostic; taught by mantis-pipeline-adapter)\nMantis runs under multiple harnesses (various CLIs, ADK, custom deterministic pipelines), so the snapshot lifecycle is a CONTRACT, not a property of one orchestrator. A harness is CONFORMANT iff, per pass, it: (1) SYNCs first (non-destructive, opt-in) as the very first action, never mid-pass; (2) detects vcs_info and computes SNAPSHOT_ID only AFTER sync; (3) PINs an immutable snapshot copy, writes the sentinel file /.mantis_snapshot_id containing SNAPSHOT_ID, and APPENDS exactly one snapshot_history entry (the append belongs to the Pin step / Block D step 5, not RECORD); (4) records vcs_info (including snapshot_id) and active_snapshot (never records an id or pins before syncing, never overwrites prior snapshot_history entries); (4.5) optionally builds a content-addressed semantic-unit index (workspace/kb/structural_index/manifest.json + catalog.sqlite, with workspace/kb/structural_index.jsonl as a compatibility pointer and workspace/helpers/query_structural_index.py as the query interface) against the pinned snapshot or live tree — a self-contained CODE-READING stage that needs only CODE_ROOT + SNAPSHOT_ID, runs before the first code-reading analysis stage, and degrades gracefully to an empty index (grep fallback) when no backend is available or the stage is not invoked; (5) runs every stage with --snapshot_root= --snapshot_id= --state_root=; (6) archives and increments the pass, keeping each retried finding's ORIGINAL discovery_commit. A harness that does not implement this contract MUST leave active_snapshot/snapshot_pinned unset, which reproduces today's single-static-snapshot behavior byte-for-byte (sync is opt-in, default off).\n\n#### SNAPSHOT_ID ladder (computed once per pass by the orchestrator, then only read)\nSNAPSHOT_ID identifies the exact bytes a pass analyzed. It is assigned by this ladder:\n- clean git/hg: commit_hash\n- dirty git/hg: commit_hash + ':' + content_hash\n- multi-vcs (.repo): revision + ':' + content_hash (a bare manifest revision is a branch/ref name and MUST NEVER be trusted for equality)\n- none/unknown, copyable tree: 'content:' + content_hash\n- single binary/firmware: 'sha256:' + artifact_hash\n- live endpoint / uncopyable / copy failed: 'live:' + ISO8601 timestamp, with snapshot_pinned=false\ncontent_hash is a sha256 over EVERY file in the pinned copy (never a partial hash). The dirty / no-VCS / multi-vcs tiers embed content_hash specifically so an unchanged dirty or no-VCS tree compares EQUAL across passes (and gets verification + dedup), while a cross-pass bare-commit id, or a multi-vcs commit advanced under an unchanged revision string, compares UNEQUAL.\n\n#### Global backward-compat rule (applies to every field added for the snapshot model)\nEvery snapshot field added to this schema is OPTIONAL. There is intentionally NO new required entry and NO new allOf gate, so pre-existing findings/states/workspaces validate UNCHANGED. Absent/empty/null always routes the consumer to the SAFE branch: finding.discovery_commit absent -> Snapshot Match Check NOT_MATCHED (re-research/re-verify, never a trusting DUPLICATE/FALSE_POSITIVE/NON_VIABLE/VERIFIED_SECURE); state.snapshot_history previous entry absent -> Changed-Files diff UNKNOWN -> treat all files CHANGED; reached-sink evidence absent -> repro_status not_attempted / not VERIFIED_SECURE; state.active_snapshot absent → MODE-OFF (byte-for-byte today); present with snapshot_pinned=false → HALT (conservative degradation, no authoritative verdicts); present with snapshot_pinned=true → PINNED (full gating). This is safe because the finding and state objects have NO additionalProperties:false, so new keys cannot fail validation. The triage_checklist, calibration_checklist, triage_rule_evaluation, and calibration_rule_evaluation objects DO set additionalProperties:false and MUST NOT gain any snapshot field; the VERIFIED_SECURE -> reattack_status=failed_to_bypass allOf MUST remain unchanged. finding.signature/lineage_id absent -> consumers fall back to today's UUID-based behavior exactly (same untrusted-if-absent pattern as discovery_commit, but for finding-identity rather than snapshot-identity). finding.sast_provenance absent -> the finding was LLM-generated (today's behavior); present -> the finding was seeded from an external SAST tool (informational only, does not affect any invariant or gate).\n", "type": "object", "properties": { "finding": { "$ref": "#/$defs/finding" }, "plan": { "$ref": "#/$defs/plan" }, "learning_entry": { "$ref": "#/$defs/learning_entry" }, "state": { "$ref": "#/$defs/state" }, "tx_log_entry": { "$ref": "#/$defs/tx_log_entry" }, "repro_attempts": { "$ref": "#/$defs/repro_attempts" }, "execution_log_entry": { "$ref": "#/$defs/execution_log_entry" } }, "$defs": { "uuid": { "type": "string", "format": "uuid", "description": "A canonical UUIDv4 string." }, "finding": { "type": "object", "title": "Finding Object Schema", "description": "The finding object (workspace/findings/.json) is the core state unit for a discovered vulnerability, evolving sequentially as different skills process it.", "properties": { "id": { "$ref": "#/$defs/uuid", "description": "Unique identifier matching the filename." }, "title": { "type": "string", "description": "A concise summary of the vulnerability. For exploit chains, must explicitly contain 'Exploit Chain' and list impact/constituent findings." }, "description": { "type": "string", "description": "Detailed explanation of the flaw and its mechanism." }, "code_paths": { "type": "array", "items": { "type": "string" }, "description": "Exact locations of the flaw (e.g., 'src/auth.c:145'). For exploit chains, the union of all involved paths." }, "impact": { "type": "string", "description": "The potential consequence of the vulnerability." }, "severity": { "type": "string", "enum": ["CRITICAL", "HIGH", "MEDIUM", "LOW"], "description": "Initial severity estimate." }, "privileges_required": { "type": "string", "enum": ["NONE", "LOW", "HIGH"], "description": "Privilege level needed to exploit.\n- 'NONE': Unauthenticated / public access.\n- 'LOW': Standard authenticated user.\n- 'HIGH': Administrative / elevated privileges." }, "attacker_position": { "type": "string", "enum": [ "EXTERNAL", "INTERNAL_NETWORK", "IN_CLUSTER", "LOCAL", "HOST_SYSTEM", "SUPPLY_CHAIN", "PHYSICAL_TEMPORARY", "PHYSICAL_LONG_TERM" ], "description": "The starting position of the attacker required to exploit." }, "user_interaction": { "type": "string", "enum": ["NONE", "REQUIRED"], "description": "Whether user interaction is required." }, "mitigation": { "type": "string", "description": "Recommended corrective modification." }, "history": { "type": "array", "items": { "$ref": "#/$defs/history_entry" }, "description": "Chronological log of actions taken on this finding. For exploit chains, must include an entry detailing chained UUIDs." }, "status": { "type": "string", "enum": ["VALID", "FALSE_POSITIVE", "PROVISIONALLY_VALID", "NEEDS_RESEARCH", "DUPLICATE"], "description": "The validity of the finding.\n- 'VALID': Confirmed vulnerability.\n- 'FALSE_POSITIVE': Not a bug or invalid.\n- 'PROVISIONALLY_VALID': Potential issue requiring dynamic validation.\n- 'NEEDS_RESEARCH': Inconclusive, requires deeper review.\n- 'DUPLICATE': The finding is a duplicate of another finding." }, "duplicate_of": { "$ref": "#/$defs/uuid", "description": "If status is DUPLICATE, the UUID of the primary finding this is a duplicate of." }, "reasoning": { "type": "string", "description": "The reviewer's independent rationale for the status." }, "repro_hints": { "type": "string", "description": "Instructions for triggering the bug, reached-sink evidence channels, and execution telemetry (e.g. build_profile, sanitizers_used, assertions_disabled, ingress_blocked)." }, "production_viability": { "type": "string", "enum": ["VIABLE", "NON_VIABLE", "SAMPLE_OR_TEST", "CONDITIONAL_VIABLE"], "description": "Whether the bug is triggerable in a release build.\n- 'VIABLE': Triggerable in production release build.\n- 'NON_VIABLE': Compiled out, blocked by environment, or debug-only.\n- 'SAMPLE_OR_TEST': Resides in test code/examples (non-production).\n- 'CONDITIONAL_VIABLE': Exploitable only under specific non-default configs or flags." }, "critic_reasoning": { "type": "string", "description": "Rationale for viability (e.g., 'Not protected by allocator padding')." }, "repro_status": { "type": "string", "enum": ["reproduced", "statically_confirmed", "not_attempted", "failed_to_reproduce"], "description": "The outcome of the reproduction attempt. Enum is UNCHANGED. Reached-Sink Evidence Gate: record 'reproduced' or 'statically_confirmed' ONLY when reached-sink evidence is PRESENT; when evidence is ABSENT (including ANY build/compile nonzero exit, exit 127 command-not-found, or an exit-2 'No such file' error) record 'not_attempted' (retry-eligible), NEVER 'failed_to_reproduce'. 'failed_to_reproduce' means a valid harness reached the sink but the bug did not trigger." }, "repro_file_path": { "type": "string", "description": "Path to the generated PoC script or payload." }, "run_command": { "type": "string", "description": "The exact command used to execute the PoC." }, "repro_output": { "type": "string", "description": "Standard output and error from the sandbox run." }, "patch_status": { "type": "string", "enum": ["VERIFIED_SECURE", "MITIGATION_PROPOSED", "VERIFICATION_INCOMPLETE", "VERIFICATION_FAILED", "ERROR"], "description": "The outcome of the patching and re-attack attempts." }, "patch_diff": { "type": "string", "description": "The unified diff of the verified fix, or mitigation recommendation." }, "reattack_status": { "type": "string", "enum": ["bypassed_patch", "failed_to_bypass", "inconclusive_baseline_changed"], "description": "The outcome of the variant-hunting re-attack. `failed_to_bypass` requires a non-empty `reattack_variants` array with ≥ 3 valid variant inputs that ALL failed to trigger the bug on the patched shadow (not merely the original PoC); an empty or short set caps at VERIFICATION_INCOMPLETE, never failed_to_bypass. `inconclusive_baseline_changed` means the unpatched baseline no longer triggers on the current snapshot; it is mutually exclusive with `VERIFIED_SECURE` (the reproduce skill forces ERROR or VERIFICATION_INCOMPLETE when the baseline no longer triggers)." }, "reattack_file_path": { "type": "string", "description": "Path to the newly generated re-attack script." }, "reattack_run_command": { "type": "string", "description": "The exact execution command used for the re-attack." }, "reattack_output": { "type": "string", "description": "Standard output and error from the re-attack run." }, "reattack_variants": { "type": "array", "description": "Variant inputs attempted during the variant-hunting re-attack. Each entry records a mutation strategy and whether it triggered the bug on the patched shadow. A variant counts as triggered (bypass) only if it reproduces the original vulnerability class (same sink/crash type) and is a valid input per the reproduce skill's Step 4 constraints. Must be non-empty (≥ 3 entries) for reattack_status = failed_to_bypass; an empty array may only coexist with a non-failed_to_bypass status. Populated by the reproduce skill when reattack_status is set; absent for legacy findings (pre-variant-hunting).", "items": { "type": "object", "properties": { "description": { "type": "string", "description": "What the variant does (e.g., 'off-by-one: len=bound+1', 'alternate path via /api/v2/echo')." }, "triggered": { "type": "boolean", "description": "Whether this variant triggered the original vulnerability class (same sink/crash type) on the patched shadow. Unrelated crashes or errors from junk mutants are recorded as false." } }, "required": ["description", "triggered"] } }, "impact_score": { "type": "integer", "minimum": 1, "maximum": 5, "description": "Calculated technical impact (1-5) on CIA triad." }, "likelihood_score": { "type": "integer", "minimum": 1, "maximum": 5, "description": "Probability of occurrence (1-5) based on proven exploitability." }, "availability_tier": { "type": ["string", "null"], "enum": ["CRITICAL", "STANDARD", "LOW_CRITICALITY", null], "description": "Availability criticality of the component if availability impact exists." }, "inferred_exposure": { "type": "string", "enum": ["EXPOSED", "INTERNAL", "PRIVILEGED"], "description": "Resolved network/trust exposure tier based on threat model." }, "mantis_risk_score": { "type": "number", "minimum": 0.1, "maximum": 10.0, "description": "Final calculated risk score (Hazard = (Impact + Likelihood) * Multiplier)." }, "priority": { "type": "string", "enum": ["CRITICAL", "HIGH", "MEDIUM", "LOW"], "description": "Qualitative priority bucket." }, "sanity_triage_applied": { "type": ["string", "null"], "description": "Semicolon-separated list of sanity triage caps and downgrades that fired." }, "triage_checklist": { "$ref": "#/$defs/triage_checklist" }, "calibration_checklist": { "$ref": "#/$defs/calibration_checklist" }, "outrage_commentary": { "type": "string", "description": "Reasoning about the outrage factor (Risk = Hazard + Outrage)." }, "executive_summary": { "type": "string", "description": "High-level summary of the risk for stakeholders." }, "constituent_findings": { "type": "array", "items": { "$ref": "#/$defs/uuid" }, "description": "Array of finding UUIDs that constitute this exploit chain finding." }, "discovery_commit": { "type": ["string", "null"], "description": "The SNAPSHOT_ID (see the SNAPSHOT_ID ladder in the top-level description) of the snapshot this finding was FIRST discovered against; written once at creation by mantis-researcher and preserved verbatim across every retry/archival (including exploit-chain retries). This is the trust anchor for the Snapshot Match Check: a consumer trusts a prior verdict on this finding ONLY when discovery_commit is present and EXACTLY equals the current pass SNAPSHOT_ID (no fuzzy compare). Optional and backward-compatible, but UNTRUSTED-IF-ABSENT: absent / empty / null (legacy findings, or unpinned/degraded passes) is treated as NOT_MATCHED -> re-research / re-verify, and is NEVER defaulted-and-trusted. The literal string 'MIXED' (set by mantis-chain when constituents span different snapshots) is likewise treated as NOT_MATCHED." }, "repro_snapshot_id": { "type": "string", "description": "The SNAPSHOT_ID the reproduction attempt (repro_status / repro_file_path / repro_output) was executed against. Optional; absent -> reproduction provenance is unknown and downstream reproduce/patch must re-verify rather than trust repro_status." }, "reattack_snapshot_id": { "type": "string", "description": "The SNAPSHOT_ID the re-attack (reattack_status / reattack_output) was executed against (the base snapshot of the patched shadow). Optional; absent -> unknown provenance, re-verify." }, "patch_base_snapshot": { "type": "string", "description": "The SNAPSHOT_ID of the snapshot the patch_diff was generated and verified against (the unpatched baseline of the Unpatched-Baseline + Benign-Control gate). Optional; absent -> the patch's base is unknown and VERIFIED_SECURE must not be trusted across a snapshot change." }, "possible_duplicate_of": { "$ref": "#/$defs/uuid", "description": "UUID of a finding this one MIGHT duplicate but which could NOT be confirmed as a hard DUPLICATE because the two were discovered against different (or unknown) snapshots (Snapshot Match Check NOT_MATCHED). Unlike duplicate_of, this is a NON-destructive advisory pointer: the finding stays active and is NOT filtered. Optional; used by mantis-dedupe to avoid dropping a possible regression." }, "cwe": { "type": ["string", "null"], "description": "CWE identifier for this vulnerability (e.g. 'CWE-787'). Optional and backward-compatible: absent -> signature computation falls back to title + primary target symbol. Additive; NOT added to required." }, "signature": { "type": "string", "description": "Deterministic stable signature of this finding's content identity, computed once at creation by mantis-researcher (and preserved verbatim across retries/archival, like discovery_commit). Computed as: first 16 hex chars of sha256(normalized_title + '|' + cwe_part + '|' + primary_target), where normalized_title = lowercase(title) with all non-alphanumeric characters stripped (removes spaces too), cwe_part = cwe field if present and non-null else empty string, primary_target = first code_paths entry with trailing :line stripped (path-only); if the first entry is a non-source LOCATOR (URL containing '://', or a non-file symbol/offset per Block A step 3), or code_paths is empty, primary_target is empty and the fallback is sorted(code_paths).join(','). Never fuzzy-compare; byte-for-byte equality only. Optional and backward-compatible: absent -> consumers fall back to today's UUID-based behavior exactly (dedupe by code_paths+title+snapshot gate, chain by constituent UUID sequence, report fold by id, repro-cap by computed stable_key). UNTRUSTED-IF-ABSENT routing is the safe branch." }, "lineage_id": { "type": "string", "description": "Lineage identifier linking findings that represent the same underlying bug across passes/snapshots. Set by mantis-researcher at creation: if an archived finding with the SAME signature exists in workspace/archive/findings_pass_*/ or workspace/archive/loop*_findings/, copy that ancestor's lineage_id; otherwise mint a fresh UUIDv4. Optional and backward-compatible: absent -> consumers fall back to UUID-based fold/dedupe (today's behavior). Consumers that key on lineage MUST fall back to id (UUID) when lineage_id is absent." }, "sast_provenance": { "type": "object", "description": "Provenance metadata for findings seeded by external SAST tools via the SAST seeding adapter (Guideline 8 of mantis-pipeline-adapter). Optional and backward-compatible: absent -> the finding was LLM-generated by mantis-researcher (today's behavior). Present -> the finding was seeded from an external SAST tool's IR (sast_findings.jsonl) and must earn its verdict through unchanged downstream gates. This is an INFORMATIONAL field for tracking/reporting -- it is NOT a trust anchor like discovery_commit. The finding's discovery_commit (absent for unprovenanced SAST seeds) is the field that governs Block B snapshot matching. Additive; NOT added to required.", "properties": { "tool": { "type": "string", "description": "The SAST tool that produced the finding (e.g. 'codeql', 'semgrep', 'bandit')." }, "rule_id": { "type": "string", "description": "The rule identifier in the SAST tool's rule set (e.g. 'cpp/sql-injection')." }, "rule_name": { "type": "string", "description": "Human-readable rule name." }, "original_severity": { "type": "string", "description": "The severity as reported by the SAST tool, before any Mantis remapping." }, "scan_snapshot_id": { "type": "string", "description": "The SNAPSHOT_ID the scan was run against (from the IR provenance header). May differ from the finding's discovery_commit if the scan was run against a different snapshot and the adapter omitted discovery_commit." }, "line_verified": { "type": "boolean", "description": "True if the adapter verified the file:line exists under CODE_ROOT by reading the actual source. False or absent -> line not verified, finding status is NEEDS_RESEARCH." }, "message": { "type": "string", "description": "The original SAST finding message." } } } }, "required": [ "id", "title", "description", "code_paths", "impact", "severity", "mitigation", "history", "attacker_position", "privileges_required", "user_interaction", "status" ], "allOf": [ { "if": { "properties": { "repro_status": { "const": "reproduced" } }, "required": ["repro_status"] }, "then": { "required": ["repro_file_path", "run_command", "repro_output"] } }, { "if": { "allOf": [ { "not": { "properties": { "constituent_findings": { "type": "array", "minItems": 1 } }, "required": ["constituent_findings"] } }, { "properties": { "patch_status": { "enum": ["VERIFIED_SECURE", "MITIGATION_PROPOSED", "VERIFICATION_INCOMPLETE"] } }, "required": ["patch_status"] } ] }, "then": { "required": ["patch_diff"] } }, { "if": { "allOf": [ { "not": { "properties": { "constituent_findings": { "type": "array", "minItems": 1 } }, "required": ["constituent_findings"] } }, { "properties": { "patch_status": { "const": "VERIFIED_SECURE" } }, "required": ["patch_status"] } ] }, "then": { "required": ["reattack_status"], "properties": { "reattack_status": { "const": "failed_to_bypass" } } } }, { "if": { "required": ["reattack_status"] }, "then": { "required": ["reattack_file_path", "reattack_run_command", "reattack_output"] } }, { "if": { "properties": { "status": { "const": "DUPLICATE" } }, "required": ["status"] }, "then": { "required": ["duplicate_of"] } }, { "$comment": "chain-exempt: reviewer runs before the chainer, so exploit chains have no reviewer history. Do NOT mirror this onto calibration_checklist - calibrate runs after the chainer and chains ARE calibrated.", "if": { "allOf": [ { "properties": { "history": { "contains": { "properties": { "stage": { "const": "reviewer" } }, "required": ["stage"] } } }, "required": ["history"] }, { "not": { "properties": { "constituent_findings": { "type": "array", "minItems": 1 } }, "required": ["constituent_findings"] } } ] }, "then": { "required": ["triage_checklist"] } }, { "$comment": "No chain exemption (intentional): calibrate runs after the chainer, so chains are calibrated and must carry a calibration_checklist. The asymmetry with triage_checklist is deliberate.", "if": { "properties": { "history": { "contains": { "properties": { "stage": { "const": "calibrate" } }, "required": ["stage"] } } }, "required": ["history"] }, "then": { "required": ["calibration_checklist"] } }, { "if": { "allOf": [ { "properties": { "history": { "contains": { "properties": { "stage": { "const": "reviewer" } }, "required": ["stage"] } } }, "required": ["history"] }, { "properties": { "status": { "enum": ["VALID", "PROVISIONALLY_VALID", "NEEDS_RESEARCH"] } }, "required": ["status"] }, { "not": { "properties": { "constituent_findings": { "type": "array", "minItems": 1 } }, "required": ["constituent_findings"] } } ] }, "then": { "required": ["triage_checklist"], "properties": { "triage_checklist": { "patternProperties": { "^.*$": { "properties": { "outcome": { "not": { "const": "FAIL" } }, "passes": { "not": { "const": false } } } } } } } } }, { "if": { "allOf": [ { "properties": { "status": { "const": "VALID" } }, "required": ["status"] }, { "not": { "properties": { "constituent_findings": { "type": "array", "minItems": 1 } }, "required": ["constituent_findings"] } } ] }, "then": { "properties": { "triage_checklist": { "patternProperties": { "^.*$": { "properties": { "outcome": { "not": { "enum": ["FAIL", "UNKNOWN"] } }, "passes": { "not": { "const": false } } } } } } } } } ] }, "plan": { "type": "object", "title": "Planning State Schema", "description": "Schema for workspace/plan.json, written by /mantis-plan and read by /mantis-researcher.", "properties": { "investigations": { "type": "array", "items": { "type": "object", "properties": { "title": { "type": "string", "description": "Title of the investigation (e.g., 'Exhaustive Review: src/api')." }, "target_files": { "type": "array", "items": { "type": "string" }, "description": "Exact file paths to audit." }, "kb_references": { "type": "array", "items": { "type": "string" }, "description": "Paths to Markdown KB files providing context (e.g., 'workspace/kb/entities/auth.md')." }, "question": { "type": "string", "description": "Detailed prompting instructions for the researcher." } }, "required": ["title", "target_files", "kb_references", "question"] } } }, "required": ["investigations"] }, "learning_entry": { "type": "object", "title": "Learning Entry Schema", "description": "Schema for a single JSON line in workspace/learnings.jsonl (ephemeral inbox) or workspace/historical_learnings.jsonl. Can represent either a trajectory insight or a finalized finding outcome.", "oneOf": [ { "title": "Trajectory Insight", "properties": { "type": { "type": "string", "enum": ["trajectory_insight"] }, "action": { "type": "string", "enum": ["add", "update", "remove"] }, "target_entity": { "type": "string", "description": "Target component or file (e.g., 'auth_module.py')." }, "insight": { "type": "string", "description": "Description of the insight." }, "source_stage": { "type": "string", "description": "Stage that produced the insight." }, "snapshot": { "type": "string", "description": "The SNAPSHOT_ID (SNAPSHOT_ID ladder) this insight was observed against; stamped by mantis-reflect. Optional; absent -> snapshot-agnostic/legacy insight. Additive: NOT added to required." } }, "required": ["type", "action", "target_entity", "insight", "source_stage"] }, { "title": "Finding Outcome Summary", "properties": { "title": { "type": "string", "description": "Finding title." }, "code_paths": { "type": "array", "items": { "type": "string" } }, "status": { "type": "string", "enum": [ "VIABLE", "NON_VIABLE", "SAMPLE_OR_TEST", "CONDITIONAL_VIABLE", "FALSE_POSITIVE", "NEEDS_RESEARCH", "VERIFIED_SECURE", "MITIGATION_PROPOSED", "VERIFICATION_INCOMPLETE", "VERIFICATION_FAILED", "ERROR" ] }, "patch_base_snapshot": { "type": "string", "description": "The SNAPSHOT_ID the patch was verified against; written by mantis-patch (or omitted in LEGACY mode). Optional; absent -> legacy/patched-without-snapshot. Additive: NOT added to required." }, "snapshot": { "type": "string", "description": "The SNAPSHOT_ID at learning creation; written by mantis-critic and mantis-patch (or omitted in MODE-OFF). Optional; absent -> snapshot-agnostic/legacy learning. Additive: NOT added to required." } }, "required": ["title", "code_paths", "status"] }, { "title": "Historical Learning Entry", "properties": { "revision_id": { "type": "string" }, "title": { "type": "string" }, "description": { "type": "string" }, "code_paths": { "type": "array", "items": { "type": "string" } }, "vuln_type": { "type": "string" }, "mitigation_diff": { "type": "string" }, "cve": { "type": ["string", "null"] }, "history": { "type": "array", "items": { "$ref": "#/$defs/history_entry" } } }, "required": [ "revision_id", "title", "description", "code_paths", "vuln_type", "mitigation_diff", "cve", "history" ] } ] }, "state": { "type": "object", "title": "Mantis State Schema", "description": "The mantis state file (workspace/.mantis_state.json) tracks the loop context across rounds.", "properties": { "pass_number": { "type": "integer", "description": "The current sequential pass number of the pipeline." }, "last_updated": { "type": "string", "format": "date-time", "description": "ISO 8601 timestamp of when the state was last updated." }, "vcs_info": { "type": "object", "description": "Information about the target version control system state.", "properties": { "vcs_type": { "type": "string", "enum": ["git", "hg", "multi-vcs", "none", "unknown"] }, "commit_hash": { "type": "string", "description": "The current commit hash or revision ID." }, "branch": { "type": "string", "description": "The active branch name." }, "revision": { "type": "string", "description": "The revision identifier (e.g., manifest revision for multi-VCS)." }, "dirty": { "type": "boolean", "description": "True if there are uncommitted changes in the target repository." }, "snapshot_id": { "type": "string", "description": "Redundant with active_snapshot.snapshot_id; kept for legacy harnesses that read vcs_info instead of active_snapshot. The SNAPSHOT_ID computed for this pass per the SNAPSHOT_ID ladder (top-level description), assigned AFTER sync. For clean git/hg this equals commit_hash; for dirty / no-VCS / multi-vcs it embeds a whole-tree content_hash so an unchanged tree compares equal across passes. Optional; the default meta-agent does NOT write this field (it writes active_snapshot.snapshot_id instead); absent -> pass ran unpinned/legacy (degraded)." } }, "required": ["vcs_type"], "allOf": [ { "if": { "properties": { "vcs_type": { "enum": ["git", "hg"] } } }, "then": { "required": ["commit_hash", "branch", "dirty"] } }, { "if": { "properties": { "vcs_type": { "const": "multi-vcs" } } }, "then": { "required": ["revision", "dirty"] } } ] }, "active_snapshot": { "type": "object", "description": "The immutable pinned snapshot the CURRENT pass reads through. Written by the orchestrator/harness during Pin (see the Pass Lifecycle Contract in the top-level description) AFTER sync, and read by every stage's Locator Resolution to resolve CODE_ROOT and the sentinel. Optional and backward-compatible: absent (legacy state, or a harness that does not implement the Pass Lifecycle Contract) -> MODE-OFF (today's single-static-snapshot behavior; verdicts are allowed). When present with snapshot_pinned=false -> HALT (verdicts forbidden). When present with snapshot_pinned=true -> PINNED (full gating).", "properties": { "root": { "type": "string", "description": "Absolute path to the pinned snapshot root (SNAPSHOT_ROOT); stages read target code under this root. When snapshot_pinned is false this may point at the live tree." }, "snapshot_id": { "type": "string", "description": "SNAPSHOT_ID of the pinned snapshot (SNAPSHOT_ID ladder). Passed to stages as --snapshot_id and compared by the Snapshot Match Check." }, "snapshot_pinned": { "type": "boolean", "description": "True iff an immutable copy was successfully pinned and its sentinel written. False -> HALT mode (only when active_snapshot IS present): authoritative verdicts (VERIFIED_SECURE, failed_to_reproduce, DUPLICATE, FALSE_POSITIVE, NON_VIABLE) are forbidden this pass and every Snapshot Match Check returns NOT_MATCHED. When active_snapshot is ABSENT, snapshot_pinned is irrelevant (MODE-OFF, verdicts allowed)." }, "pass": { "type": "integer", "description": "The pass_number this snapshot was pinned for. Used by crash-resume (on re-entry, equal pass -> reuse the existing snapshot dir, never re-pin) AND by every consumer stage as a current-pass staleness gate: if active_snapshot.pass != state.pass_number, the snapshot is STALE for this pass (STOP or HALT-degrade: no authoritative verdicts, Block B NOT_MATCHED, reproduce not_attempted)." }, "vcs_type": { "type": "string", "enum": ["git", "hg", "multi-vcs", "none", "unknown"], "description": "The detected VCS type of the target for this pass." } } }, "snapshot_history": { "type": "array", "description": "APPEND-ONLY log of one entry per pinned pass, in pass order. The orchestrator appends exactly one entry per pass during Pin and NEVER overwrites prior entries. The Changed-Files diff reads the entry BEFORE the current pass as PREV; if that entry is missing (first pinned pass or legacy state) the diff degrades to UNKNOWN and every file is treated as CHANGED. Optional; absent -> no cross-pass diff, conservative full re-scan.", "items": { "type": "object", "properties": { "pass": { "type": "integer", "description": "The pass_number this entry records." }, "snapshot_id": { "type": "string", "description": "The SNAPSHOT_ID pinned for that pass." }, "snapshot_pinned": { "type": "boolean", "description": "Whether that pass was pinned (false -> that pass ran degraded)." }, "timestamp": { "type": "string", "format": "date-time", "description": "ISO 8601 timestamp when the entry was appended." } } } }, "kb_snapshot_id": { "type": "string", "description": "The SNAPSHOT_ID the knowledge base (workspace/kb: architecture, threat model, summaries) was last built/refreshed against. Consumers compare it to the current pass SNAPSHOT_ID to decide whether the KB is stale and must be force-refreshed after a sync. Optional; absent -> KB provenance unknown -> treat as stale (rebuild/refresh)." }, "changed_files": { "type": "array", "items": { "type": "string" }, "description": "Efficiency HINT only: repo-relative paths that changed between the previous pinned snapshot and the current one (from the Changed-Files diff, run in the live root). NEVER used to drop or skip a finding; only to prioritize/broaden re-research. Meaningful only when changed_files_status is COMPUTED. Optional; absent -> treat as UNKNOWN (all files potentially changed)." }, "changed_files_status": { "type": "string", "enum": ["COMPUTED", "UNKNOWN"], "description": "Whether changed_files is a trustworthy diff (COMPUTED) or could not be computed (UNKNOWN: no PREV entry, unpinned, vcs_type none/unknown, a content:/live:/+content_hash fallback id on either end, or a diff error). UNKNOWN MUST be treated as 'every file changed' -> never drop, never skip. Optional; absent -> UNKNOWN." }, "changed_files_pass": { "type": "integer", "description": "The pass_number when changed_files/changed_files_status were last computed. Consumers MUST compare this to state.pass_number: if absent or different, the diff is from a prior pass and MUST be treated as UNKNOWN (full rebuild / all files potentially changed). Written by mantis-plan Block E alongside changed_files. Optional; absent -> stale." } }, "required": ["pass_number", "last_updated"] }, "history_entry": { "type": "object", "title": "History Entry Schema", "properties": { "stage": { "type": "string" }, "action": { "type": "string" }, "details": { "type": "string" }, "pass_number": { "type": "integer", "description": "The sequential pass number of the pipeline loop." }, "timestamp": { "type": "string", "format": "date-time", "description": "ISO 8601 timestamp of when the history entry was recorded." }, "snapshot": { "type": "string", "description": "The SNAPSHOT_ID (SNAPSHOT_ID ladder, top-level description) this history action was performed against. Optional; absent -> legacy/unpinned action, provenance unknown. Additive: NOT added to this object's required." } }, "required": ["stage", "action", "details", "pass_number", "timestamp"] }, "triage_rule_evaluation": { "type": "object", "properties": { "passes": { "type": "boolean", "description": "DEPRECATED: True if the finding satisfies this validity constraint." }, "outcome": { "type": "string", "enum": ["PASS", "FAIL", "UNKNOWN", "NOT_APPLICABLE"], "description": "The evaluation outcome of the constraint." }, "reason": { "type": "string", "description": "The reasoning for the evaluation." } }, "oneOf": [ { "required": ["outcome"] }, { "required": ["passes"] } ], "if": { "anyOf": [ { "properties": { "outcome": { "enum": ["FAIL", "UNKNOWN", "NOT_APPLICABLE"] } }, "required": ["outcome"] }, { "properties": { "passes": { "const": false } }, "required": ["passes"] } ] }, "then": { "required": ["reason"] }, "additionalProperties": false }, "triage_checklist": { "type": "object", "title": "Triage Checklist (13 Negative Constraints)", "properties": { "ignore_hypothetical_misuse": { "$ref": "#/$defs/triage_rule_evaluation" }, "ignore_missing_hygiene": { "$ref": "#/$defs/triage_rule_evaluation" }, "require_strict_reproducibility": { "$ref": "#/$defs/triage_rule_evaluation" }, "avoid_pedantic_linting": { "$ref": "#/$defs/triage_rule_evaluation" }, "no_security_flaw_stretching": { "$ref": "#/$defs/triage_rule_evaluation" }, "evaluate_questionable_file_paths": { "$ref": "#/$defs/triage_rule_evaluation" }, "ignore_resource_exhaustion_dos": { "$ref": "#/$defs/triage_rule_evaluation" }, "intrinsic_security_flaws": { "$ref": "#/$defs/triage_rule_evaluation" }, "verify_mitigations_pragmatically": { "$ref": "#/$defs/triage_rule_evaluation" }, "refine_code_paths_strictly": { "$ref": "#/$defs/triage_rule_evaluation" }, "ignore_simd_vector_padding": { "$ref": "#/$defs/triage_rule_evaluation" }, "ensure_source_code_coherence": { "$ref": "#/$defs/triage_rule_evaluation" }, "verify_attacker_control_of_source": { "$ref": "#/$defs/triage_rule_evaluation" } }, "required": [ "ignore_hypothetical_misuse", "ignore_missing_hygiene", "require_strict_reproducibility", "avoid_pedantic_linting", "no_security_flaw_stretching", "evaluate_questionable_file_paths", "ignore_resource_exhaustion_dos", "intrinsic_security_flaws", "verify_mitigations_pragmatically", "refine_code_paths_strictly", "ignore_simd_vector_padding", "ensure_source_code_coherence", "verify_attacker_control_of_source" ], "additionalProperties": false }, "calibration_outcome": { "type": "string", "enum": ["APPLIES", "DOES_NOT_APPLY", "UNKNOWN"], "description": "The outcome of the calibration rule evaluation: APPLIES (sanity cap applies), DOES_NOT_APPLY (sanity cap does not apply), UNKNOWN (unresolved)." }, "calibration_rule_evaluation": { "type": "object", "properties": { "fires": { "type": "boolean", "description": "DEPRECATED: True if this sanity cap rule applies (fires) to this finding." }, "outcome": { "$ref": "#/$defs/calibration_outcome" }, "reason": { "type": "string" } }, "oneOf": [ { "required": ["outcome"] }, { "required": ["fires"] } ], "if": { "anyOf": [ { "properties": { "outcome": { "enum": ["APPLIES", "UNKNOWN"] } }, "required": ["outcome"] }, { "properties": { "fires": { "const": true } }, "required": ["fires"] } ] }, "then": { "required": ["reason"] }, "additionalProperties": false }, "calibration_checklist": { "type": "object", "title": "Calibration Checklist (Sanity Caps)", "properties": { "repro_failure": { "$ref": "#/$defs/calibration_rule_evaluation" }, "unreachable_inputs": { "$ref": "#/$defs/calibration_rule_evaluation" }, "third_party_reachability": { "$ref": "#/$defs/calibration_rule_evaluation" }, "minor_config_hygiene": { "$ref": "#/$defs/calibration_rule_evaluation" }, "non_security_critical": { "$ref": "#/$defs/calibration_rule_evaluation" }, "vague_code_paths": { "$ref": "#/$defs/calibration_rule_evaluation" }, "unreliable_triggers": { "$ref": "#/$defs/calibration_rule_evaluation" }, "prerequisite_shell": { "$ref": "#/$defs/calibration_rule_evaluation" }, "physical_long_term": { "$ref": "#/$defs/calibration_rule_evaluation" }, "trusted_controller_zero_delta": { "$ref": "#/$defs/calibration_rule_evaluation" }, "standard_host_attacks": { "$ref": "#/$defs/calibration_rule_evaluation" }, "static_confirmation": { "$ref": "#/$defs/calibration_rule_evaluation" }, "strict_xss": { "$ref": "#/$defs/calibration_rule_evaluation" }, "internal_nested": { "$ref": "#/$defs/calibration_rule_evaluation" }, "probabilistic_llm": { "$ref": "#/$defs/calibration_rule_evaluation" }, "supply_chain_prerequisites": { "$ref": "#/$defs/calibration_rule_evaluation" }, "non_default_config": { "$ref": "#/$defs/calibration_rule_evaluation" }, "confidential_computing_host": { "$ref": "#/$defs/calibration_rule_evaluation" }, "trusted_controller_critical_bypass": { "$ref": "#/$defs/calibration_rule_evaluation" }, "local_attack_vector": { "$ref": "#/$defs/calibration_rule_evaluation" }, "self_contained_blast": { "$ref": "#/$defs/calibration_rule_evaluation" }, "rarely_exposed": { "$ref": "#/$defs/calibration_rule_evaluation" }, "equivalent_primitives": { "$ref": "#/$defs/calibration_rule_evaluation" }, "documented_insecure_config": { "$ref": "#/$defs/calibration_rule_evaluation" }, "physical_temporary": { "$ref": "#/$defs/calibration_rule_evaluation" }, "high_privilege_external": { "$ref": "#/$defs/calibration_rule_evaluation" }, "trusted_controller_standard_bypass": { "$ref": "#/$defs/calibration_rule_evaluation" } }, "required": [ "repro_failure", "unreachable_inputs", "third_party_reachability", "minor_config_hygiene", "non_security_critical", "vague_code_paths", "unreliable_triggers", "prerequisite_shell", "physical_long_term", "trusted_controller_zero_delta", "standard_host_attacks", "static_confirmation", "strict_xss", "internal_nested", "probabilistic_llm", "supply_chain_prerequisites", "non_default_config", "confidential_computing_host", "trusted_controller_critical_bypass", "local_attack_vector", "self_contained_blast", "rarely_exposed", "equivalent_primitives", "documented_insecure_config", "physical_temporary", "high_privilege_external", "trusted_controller_standard_bypass" ], "additionalProperties": false }, "tx_log_entry": { "type": "object", "title": "Deduplicator Transaction Log Entry Schema", "properties": { "timestamp": { "type": "string", "format": "date-time" }, "action": { "type": "string" }, "primary_uuid": { "oneOf": [ { "$ref": "#/$defs/uuid" }, { "type": "null" } ], "description": "The UUID of the primary finding, or null if the action is loop_filter." }, "moved_uuid": { "$ref": "#/$defs/uuid" } }, "required": ["timestamp", "action", "primary_uuid", "moved_uuid"] }, "repro_attempts": { "type": "object", "title": "Reproduction Attempts Cache Schema", "additionalProperties": { "oneOf": [ { "type": "integer", "minimum": 0 }, { "type": "object", "properties": { "count": { "type": "integer", "minimum": 0, "description": "Number of reproduction attempts recorded for this key." }, "last_snapshot": { "type": "string", "description": "SNAPSHOT_ID (see the SNAPSHOT_ID ladder in the top-level description) of the snapshot the most recent attempt ran against; lets consumers reset the attempt cap when the snapshot changed." } }, "required": ["count"] } ] }, "description": "A map of stable finding keys to reproduction attempt counts. Each value is EITHER a bare integer (legacy form: count only, last_snapshot UNKNOWN) OR an object {count, last_snapshot} recording which SNAPSHOT_ID the attempts ran against so the cap can reset when the snapshot changes. Read rule for consumers (mantis-plan, mantis-reproduce): integer V -> {count: V, last_snapshot: UNKNOWN}; object V -> {count: V.count, last_snapshot: V.last_snapshot or UNKNOWN}. The bare-integer form stays legal for backward compatibility." }, "execution_log_entry": { "type": "object", "title": "Execution Log Entry Schema", "properties": { "step_index": { "type": "integer" }, "source": { "type": "string" }, "type": { "type": "string" }, "status": { "type": "string" }, "created_at": { "type": "string", "format": "date-time" }, "thinking": { "type": "string" }, "content": { "type": "string" }, "tool_calls": { "type": "array", "items": { "type": "object", "properties": { "name": { "type": "string" }, "args": { "type": "object" } }, "required": ["name", "args"] } } }, "required": ["step_index", "source", "type", "status"] } } }