-
Notifications
You must be signed in to change notification settings - Fork 49
Expand file tree
/
Copy pathschema.json
More file actions
1005 lines (1005 loc) · 49.2 KB
/
Copy pathschema.json
File metadata and controls
1005 lines (1005 loc) · 49.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
{
"$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/<uuid>.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 <SNAPSHOT_ROOT>/.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); (5) runs every stage with --snapshot_root=<SNAPSHOT_ROOT> --snapshot_id=<SNAPSHOT_ID> --state_root=<workspace parent>; (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).\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/<uuid>.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 the reproducer agent on how to trigger the bug."
},
"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. `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."
},
"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."
}
},
"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"]
}
}