@open-agent-toolkit/cli 0.2.54 → 0.2.56

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/assets/bundle-metadata.json +1 -1
  2. package/assets/docs/cli-utilities/configuration.md +12 -2
  3. package/assets/docs/docs-tooling/commands.md +36 -5
  4. package/assets/docs/reference/oat-directory-structure.md +56 -4
  5. package/assets/docs/workflows/skills/recon.md +28 -12
  6. package/assets/public-package-versions.json +4 -4
  7. package/assets/skills/oat-dispatch-subagents/SKILL.md +4 -105
  8. package/assets/skills/oat-dispatch-subagents/references/record-schema.md +0 -226
  9. package/assets/skills/oat-project-autonomous/references/gate-inventory.md +2 -2
  10. package/assets/skills/oat-project-document/references/docs/autonomy-contract.md +2 -2
  11. package/assets/skills/oat-project-implement/references/docs/autonomy-contract.md +2 -2
  12. package/assets/skills/oat-project-pr-final/references/docs/autonomy-contract.md +2 -2
  13. package/assets/skills/oat-project-quick-start/references/docs/autonomy-contract.md +2 -2
  14. package/assets/skills/recon/SKILL.md +76 -38
  15. package/assets/skills/recon/references/packet-contract.md +138 -105
  16. package/assets/skills/recon/scripts/lib/contracts.mjs +181 -674
  17. package/assets/skills/recon/scripts/lib/validated-run.mjs +10 -16
  18. package/assets/skills/recon/scripts/render-packet.mjs +7 -20
  19. package/assets/skills/recon/scripts/validate-packet.mjs +177 -344
  20. package/dist/commands/config/index.d.ts +1 -0
  21. package/dist/commands/config/index.d.ts.map +1 -1
  22. package/dist/commands/config/index.js +51 -4
  23. package/dist/commands/docs/index-generate/generator.d.ts +43 -1
  24. package/dist/commands/docs/index-generate/generator.d.ts.map +1 -1
  25. package/dist/commands/docs/index-generate/generator.js +169 -3
  26. package/dist/commands/docs/index-generate/index.d.ts +8 -3
  27. package/dist/commands/docs/index-generate/index.d.ts.map +1 -1
  28. package/dist/commands/docs/index-generate/index.js +340 -24
  29. package/dist/commands/docs/init/scaffold.d.ts +11 -1
  30. package/dist/commands/docs/init/scaffold.d.ts.map +1 -1
  31. package/dist/commands/docs/init/scaffold.js +11 -2
  32. package/dist/config/oat-config.d.ts +14 -0
  33. package/dist/config/oat-config.d.ts.map +1 -1
  34. package/dist/config/oat-config.js +61 -0
  35. package/dist/fs/assets.d.ts +33 -4
  36. package/dist/fs/assets.d.ts.map +1 -1
  37. package/dist/fs/assets.js +127 -12
  38. package/package.json +2 -2
@@ -4,13 +4,9 @@ import { readFile, rm } from 'node:fs/promises';
4
4
  import { join, resolve } from 'node:path';
5
5
  import { pathToFileURL } from 'node:url';
6
6
 
7
+ import { hashCanonicalJson, hashFile, sha256 } from './lib/canonical-json.mjs';
7
8
  import {
8
- canonicalJson,
9
- hashCanonicalJson,
10
- hashFile,
11
- sha256,
12
- } from './lib/canonical-json.mjs';
13
- import {
9
+ approvalFingerprintInput,
14
10
  isDigest,
15
11
  isObject,
16
12
  issue,
@@ -25,13 +21,11 @@ import {
25
21
  } from './lib/safe-path.mjs';
26
22
  import { createValidatedRun } from './lib/validated-run.mjs';
27
23
 
28
- const requiredStages = {
29
- quick: ['map', 'gather', 'compile', 'locator-validation'],
24
+ const requiredPasses = {
25
+ quick: ['map', 'gather'],
30
26
  standard: [
31
27
  'map',
32
28
  'gather',
33
- 'compile',
34
- 'locator-validation',
35
29
  'semantic-verification',
36
30
  'adversarial',
37
31
  'coverage',
@@ -40,8 +34,6 @@ const requiredStages = {
40
34
  thorough: [
41
35
  'map',
42
36
  'gather',
43
- 'compile',
44
- 'locator-validation',
45
37
  'semantic-verification',
46
38
  'adversarial',
47
39
  'coverage',
@@ -664,11 +656,9 @@ async function reopenEvidence(
664
656
  return true;
665
657
  }
666
658
 
667
- const stageArtifactContract = {
659
+ const passContracts = {
668
660
  map: { kind: 'recon.raw-dossier', mode: 'map' },
669
661
  gather: { kind: 'recon.raw-dossier', mode: 'gather' },
670
- compile: { kind: 'recon.raw-dossier', mode: 'compile' },
671
- 'locator-validation': { kind: 'recon.raw-dossier', mode: 'verify' },
672
662
  'semantic-verification': {
673
663
  kind: 'recon.review-result',
674
664
  reviewKind: 'semantic',
@@ -679,7 +669,6 @@ const stageArtifactContract = {
679
669
  kind: 'recon.review-result',
680
670
  reviewKind: 'reconciliation',
681
671
  },
682
- 'redundant-gather': { kind: 'recon.raw-dossier', mode: 'gather' },
683
672
  'redundant-verification': {
684
673
  kind: 'recon.review-result',
685
674
  reviewKind: 'redundant-verification',
@@ -690,259 +679,185 @@ const stageArtifactContract = {
690
679
  },
691
680
  };
692
681
 
693
- function stageArtifactIsComplete(
694
- stage,
695
- artifactsById,
696
- runId,
697
- approvalFingerprint,
698
- approvalExecution,
699
- ) {
700
- const approvalProjection = approvalExecution?.approvalProjection;
701
- const contract = stageArtifactContract[stage.mode];
702
- if (!contract || stage.status !== 'complete') return false;
703
- if (!Array.isArray(stage.artifactIds) || stage.artifactIds.length !== 1) {
704
- return false;
705
- }
706
- const artifact = artifactsById.get(stage.artifactIds[0])?.value;
707
- if (
708
- !artifact ||
709
- artifact.kind !== contract.kind ||
710
- artifact.runId !== runId ||
711
- (contract.mode && artifact.mode !== contract.mode) ||
712
- (contract.reviewKind && artifact.reviewKind !== contract.reviewKind) ||
713
- (artifact.laneId ?? artifact.reviewerLane) !== stage.laneId ||
714
- ('status' in artifact && artifact.status !== 'complete') ||
715
- ('outcome' in artifact && artifact.outcome !== 'complete')
716
- ) {
717
- return false;
718
- }
719
- if (
720
- !Array.isArray(stage.dispatchReceiptIds) ||
721
- stage.dispatchReceiptIds.length !== 4
722
- ) {
723
- return false;
724
- }
725
- const receipts = stage.dispatchReceiptIds.map(
726
- (id) => artifactsById.get(id)?.value,
727
- );
728
- const approvedReceipt = receipts.find(
729
- (receipt) => receipt?.state === 'approved',
730
- );
731
- const acceptedReceipt = receipts.find(
732
- (receipt) => receipt?.state === 'accepted',
733
- );
734
- const completedReceipt = receipts.find(
735
- (receipt) => receipt?.state === 'completed',
736
- );
737
- const approvalTime = Date.parse(approvalExecution?.approvedAt);
738
- const recheckTime = Date.parse(
739
- approvalExecution?.catalogRecheck?.observed_at,
740
- );
741
- const acceptedTime = Date.parse(
742
- acceptedReceipt?.launchAcceptance?.acceptedAt,
743
- );
744
- const completedTime = Date.parse(
745
- completedReceipt?.terminalOutcome?.completedAt,
682
+ const assuranceReviewKinds = new Set([
683
+ 'semantic',
684
+ 'adversarial',
685
+ 'coverage',
686
+ 'redundant-verification',
687
+ 'contradiction-resolution',
688
+ ]);
689
+
690
+ // A gap names a pass only when the mode appears as a whole token; `gather`
691
+ // must not be satisfied by a `redundant-gather` gap.
692
+ function gapNamesMode(gap, mode) {
693
+ return (
694
+ (gap.code === 'PASS_FAILED' || gap.code === 'PASS_OMITTED') &&
695
+ gap.material === true &&
696
+ typeof gap.message === 'string' &&
697
+ new RegExp(`(?:^|[^a-z-])${mode}(?:$|[^a-z-])`).test(gap.message)
746
698
  );
747
- const originalCatalog = approvalProjection?.catalog_observation;
748
- if (
749
- receipts.some(
750
- (receipt) =>
751
- receipt?.kind !== 'recon.dispatch-receipt' ||
752
- receipt.runId !== runId ||
753
- receipt.stageId !== stage.id ||
754
- receipt.laneId !== stage.laneId ||
755
- !receipt.approvalProjection ||
756
- typeof receipt.approvalCanonicalJson !== 'string' ||
757
- receipt.approvalFingerprint !== approvalFingerprint ||
758
- receipt.approvalCanonicalJson !== canonicalJson(approvalProjection) ||
759
- hashCanonicalJson(receipt.approvalProjection) !== approvalFingerprint ||
760
- receipt.approvalCanonicalJson !==
761
- canonicalJson(receipt.approvalProjection) ||
762
- canonicalJson(receipt.approvalProjection) !==
763
- canonicalJson(approvalProjection) ||
764
- (receipt.state !== 'prepared' &&
765
- (receipt.approvedAt !== approvalExecution.approvedAt ||
766
- !approvalExecution.approvalEvidence ||
767
- !receipt.approvalEvidence ||
768
- canonicalJson(receipt.approvalEvidence) !==
769
- canonicalJson(approvalExecution.approvalEvidence))) ||
770
- (['accepted', 'completed'].includes(receipt.state) &&
771
- (!approvalExecution.catalogRecheck ||
772
- !receipt.catalogRecheck ||
773
- canonicalJson(receipt.catalogRecheck) !==
774
- canonicalJson(approvalExecution.catalogRecheck))),
775
- ) ||
776
- !receipts.some((receipt) => receipt.state === 'prepared') ||
777
- !approvedReceipt ||
778
- !acceptedReceipt ||
779
- !completedReceipt ||
780
- JSON.stringify(completedReceipt.artifactIds) !==
781
- JSON.stringify(stage.artifactIds) ||
782
- !acceptedReceipt.launchAcceptance ||
783
- !completedReceipt.launchAcceptance ||
784
- canonicalJson(acceptedReceipt.launchAcceptance) !==
785
- canonicalJson(completedReceipt.launchAcceptance) ||
786
- !originalCatalog ||
787
- !approvalExecution.catalogRecheck ||
788
- approvalExecution.catalogRecheck.id === originalCatalog.id ||
789
- !Number.isFinite(approvalTime) ||
790
- !Number.isFinite(recheckTime) ||
791
- !Number.isFinite(acceptedTime) ||
792
- !Number.isFinite(completedTime) ||
793
- recheckTime <= approvalTime ||
794
- recheckTime >= acceptedTime ||
795
- completedTime < acceptedTime
796
- ) {
797
- return false;
798
- }
799
- return true;
800
699
  }
801
700
 
802
- function approvedRequiredLanes(approvalProjection) {
803
- return (approvalProjection?.execution?.waves ?? []).flatMap((wave) =>
804
- (wave.lanes ?? [])
805
- .filter(() => wave.conditional === false)
806
- .map((lane) => ({ waveId: wave.wave_id, laneId: lane.lane_id })),
701
+ function artifactIsComplete(artifact) {
702
+ return (
703
+ (!('status' in artifact) || artifact.status === 'complete') &&
704
+ (!('outcome' in artifact) || artifact.outcome === 'complete')
807
705
  );
808
706
  }
809
707
 
810
- function validateStageTopology(
811
- manifest,
812
- artifactsById,
813
- runId,
814
- approvalFingerprint,
815
- errors,
816
- ) {
817
- const requiredLanes = approvedRequiredLanes(
818
- manifest.execution?.approvalProjection,
819
- );
820
- const plannedByLane = new Map(
821
- (manifest.execution?.approvalProjection?.execution?.waves ?? []).flatMap(
822
- (wave) =>
823
- (wave.lanes ?? []).map((lane) => [
824
- lane.lane_id,
825
- { waveId: wave.wave_id, required: !wave.conditional },
826
- ]),
827
- ),
828
- );
829
- const stageByLane = new Map();
830
- const completeArtifactIdsByMode = new Map();
831
- for (const stage of manifest.stages ?? []) {
832
- const planned = plannedByLane.get(stage.laneId);
833
- if (!planned || planned.waveId !== stage.waveId) {
708
+ const reviewWaveMode = {
709
+ semantic: 'semantic-verification',
710
+ adversarial: 'adversarial',
711
+ coverage: 'coverage',
712
+ reconciliation: 'reconciliation',
713
+ 'redundant-verification': 'redundant-verification',
714
+ 'contradiction-resolution': 'contradiction-resolution',
715
+ };
716
+
717
+ function laneWaveMatches(wave, value) {
718
+ if (value.kind === 'recon.raw-dossier') {
719
+ return wave.mode === 'redundant-gather'
720
+ ? value.mode === 'gather'
721
+ : wave.mode === value.mode;
722
+ }
723
+ return reviewWaveMode[value.reviewKind] === wave.mode;
724
+ }
725
+
726
+ function validateApprovedLanes(manifest, artifactsById, errors) {
727
+ const lanes = new Map();
728
+ for (const wave of manifest.execution?.waves ?? []) {
729
+ for (const lane of wave.lanes ?? []) {
730
+ lanes.set(lane.laneId, { wave, lane });
731
+ }
732
+ }
733
+ const written = new Set();
734
+ for (const [id, { reference, value }] of artifactsById) {
735
+ if (value.runId !== manifest.run.id) continue;
736
+ const laneId =
737
+ value.kind === 'recon.raw-dossier'
738
+ ? value.laneId
739
+ : value.kind === 'recon.review-result'
740
+ ? value.reviewerLane
741
+ : null;
742
+ if (laneId === null) continue;
743
+ const approved = lanes.get(laneId);
744
+ if (
745
+ !approved ||
746
+ (value.kind === 'recon.raw-dossier' &&
747
+ approved.wave.waveId !== value.waveId) ||
748
+ !laneWaveMatches(approved.wave, value)
749
+ ) {
834
750
  errors.push(
835
751
  issue(
836
- 'UNAPPROVED_STAGE_TOPOLOGY',
837
- `Stage ${stage.id} is not the exact approved wave and lane`,
838
- stage.id,
752
+ 'UNAPPROVED_LANE',
753
+ `Artifact ${id} was not written by an approved lane of a matching wave`,
754
+ id,
839
755
  ),
840
756
  );
841
757
  continue;
842
758
  }
843
- if (stageByLane.has(stage.laneId)) {
759
+ const root = approved.lane.writeRoot;
760
+ if (reference.path !== root && !reference.path.startsWith(`${root}/`)) {
844
761
  errors.push(
845
762
  issue(
846
- 'DUPLICATE_LANE_STAGE',
847
- `Lane ${stage.laneId} has multiple terminal stages`,
848
- stage.id,
763
+ 'LANE_WRITE_PATH_VIOLATION',
764
+ `Artifact ${id} is outside its approved lane write root ${root}`,
765
+ id,
849
766
  ),
850
767
  );
768
+ }
769
+ // Only a complete artifact settles a lane; a failed or partial artifact
770
+ // still needs a material outcome gap.
771
+ if (artifactIsComplete(value)) written.add(laneId);
772
+ }
773
+ for (const [laneId, { wave }] of lanes) {
774
+ // The canonical ledger is the compile outcome; it carries no lane identity.
775
+ if (wave.conditional || wave.mode === 'compile' || written.has(laneId)) {
851
776
  continue;
852
777
  }
853
- stageByLane.set(stage.laneId, stage);
854
- const artifactIsComplete = stageArtifactIsComplete(
855
- stage,
856
- artifactsById,
857
- runId,
858
- approvalFingerprint,
859
- manifest.execution,
778
+ const hasOutcomeEvidence = (manifest.gaps ?? []).some((gap) =>
779
+ gapNamesMode(gap, wave.mode),
860
780
  );
861
- if (stage.status === 'complete' && !artifactIsComplete) {
781
+ if (!hasOutcomeEvidence) {
862
782
  errors.push(
863
783
  issue(
864
- 'INCOMPLETE_DECLARED_STAGE',
865
- `Stage ${stage.id} is declared complete without its exact artifact and receipt contract`,
866
- stage.id,
784
+ 'MISSING_LANE_OUTCOME',
785
+ `Approved lane ${laneId} has neither a result nor a material ${wave.mode} outcome gap`,
786
+ `lane:${laneId}`,
867
787
  ),
868
788
  );
869
789
  }
870
- if (stage.status !== 'complete') {
871
- const expectedGapCode =
872
- stage.status === 'failed' ? 'PASS_FAILED' : 'PASS_OMITTED';
873
- const hasOutcomeEvidence =
874
- typeof stage.message === 'string' &&
875
- stage.message.length > 0 &&
876
- (manifest.gaps ?? []).some(
877
- (gap) =>
878
- gap.code === expectedGapCode &&
879
- gap.material === true &&
880
- gap.message?.includes(stage.mode),
881
- );
882
- if (!hasOutcomeEvidence) {
883
- errors.push(
884
- issue(
885
- 'MISSING_STAGE_OUTCOME_EVIDENCE',
886
- `Stage ${stage.id} lacks material ${expectedGapCode} diagnostics`,
887
- stage.id,
888
- ),
889
- );
790
+ }
791
+ }
792
+
793
+ function collectCompletePasses(artifactsById, runId) {
794
+ const passes = new Map();
795
+ for (const [id, { value }] of artifactsById) {
796
+ if (value.runId !== runId || !artifactIsComplete(value)) continue;
797
+ for (const [mode, contract] of Object.entries(passContracts)) {
798
+ if (
799
+ value.kind === contract.kind &&
800
+ (contract.mode ? value.mode === contract.mode : true) &&
801
+ (contract.reviewKind ? value.reviewKind === contract.reviewKind : true)
802
+ ) {
803
+ const ids = passes.get(mode) ?? [];
804
+ ids.push(id);
805
+ passes.set(mode, ids);
890
806
  }
891
807
  }
892
- if (artifactIsComplete) {
893
- const ids = completeArtifactIdsByMode.get(stage.mode) ?? [];
894
- ids.push(stage.artifactIds[0]);
895
- completeArtifactIdsByMode.set(stage.mode, ids);
896
- }
897
808
  }
898
- for (const lane of requiredLanes) {
899
- if (!stageByLane.has(lane.laneId)) {
900
- errors.push(
901
- issue(
902
- 'MISSING_APPROVED_LANE_STAGE',
903
- `Approved required lane ${lane.laneId} has no terminal stage`,
904
- `lane:${lane.laneId}`,
905
- ),
906
- );
809
+ const gatherLanes = new Set(
810
+ (passes.get('gather') ?? []).map(
811
+ (id) => artifactsById.get(id).value.laneId,
812
+ ),
813
+ );
814
+ if (gatherLanes.size >= 2)
815
+ passes.set('redundant-gather', passes.get('gather'));
816
+ for (const ids of passes.values()) ids.sort();
817
+ return passes;
818
+ }
819
+
820
+ function deriveAchievedProfile(passes) {
821
+ let achieved = null;
822
+ for (const profile of profiles) {
823
+ if (requiredPasses[profile].every((mode) => passes.has(mode))) {
824
+ achieved = profile;
907
825
  }
908
826
  }
909
- for (const mode of requiredStages[manifest.run.requestedProfile] ?? []) {
910
- if (![...stageByLane.values()].some((stage) => stage.mode === mode)) {
827
+ return achieved;
828
+ }
829
+
830
+ function validatePassOutcomes(manifest, passes, errors) {
831
+ for (const mode of requiredPasses[manifest.run.requestedProfile] ?? []) {
832
+ if (passes.has(mode)) continue;
833
+ const hasOutcomeEvidence = (manifest.gaps ?? []).some((gap) =>
834
+ gapNamesMode(gap, mode),
835
+ );
836
+ if (!hasOutcomeEvidence) {
911
837
  errors.push(
912
838
  issue(
913
- 'MISSING_REQUESTED_STAGE_OUTCOME',
914
- `Requested ${manifest.run.requestedProfile} profile lacks an explicit ${mode} outcome`,
915
- `stage:${mode}`,
839
+ 'MISSING_PASS_OUTCOME_EVIDENCE',
840
+ `Requested ${manifest.run.requestedProfile} profile lacks a complete ${mode} result and a material PASS_FAILED or PASS_OMITTED gap naming it`,
841
+ `pass:${mode}`,
916
842
  ),
917
843
  );
918
844
  }
919
845
  }
920
- return { requiredLanes, stageByLane, completeArtifactIdsByMode };
921
846
  }
922
847
 
923
- function deriveAchievedProfile(topology) {
924
- let achieved = null;
925
- for (const profile of profiles) {
926
- const complete = requiredStages[profile].every((mode) => {
927
- const lanes = topology.requiredLanes.filter(
928
- (lane) => topology.stageByLane.get(lane.laneId)?.mode === mode,
929
- );
930
- return (
931
- lanes.length > 0 &&
932
- lanes.every((lane) => {
933
- const stage = topology.stageByLane.get(lane.laneId);
934
- return (
935
- stage &&
936
- topology.completeArtifactIdsByMode
937
- .get(mode)
938
- ?.includes(stage.artifactIds[0])
939
- );
940
- })
941
- );
942
- });
943
- if (complete) achieved = profile;
848
+ function collectAssuranceReviewIds(artifactsById, runId) {
849
+ const ids = new Set();
850
+ for (const [id, { value }] of artifactsById) {
851
+ if (
852
+ value.kind === 'recon.review-result' &&
853
+ value.runId === runId &&
854
+ assuranceReviewKinds.has(value.reviewKind) &&
855
+ artifactIsComplete(value)
856
+ ) {
857
+ ids.add(id);
858
+ }
944
859
  }
945
- return achieved;
860
+ return ids;
946
861
  }
947
862
 
948
863
  function sameReference(left, right) {
@@ -952,12 +867,11 @@ function sameReference(left, right) {
952
867
  function resolveTerminalReconciliation(
953
868
  artifactsById,
954
869
  artifactsByPath,
955
- topology,
870
+ passes,
956
871
  reconciliationRequired,
957
872
  errors,
958
873
  ) {
959
- const terminalId =
960
- topology.completeArtifactIdsByMode.get('reconciliation')?.[0];
874
+ const terminalId = passes.get('reconciliation')?.[0];
961
875
  const reconciliationResults = [...artifactsById.values()].filter(
962
876
  ({ value }) =>
963
877
  value.kind === 'recon.review-result' &&
@@ -972,7 +886,7 @@ function resolveTerminalReconciliation(
972
886
  errors.push(
973
887
  issue(
974
888
  'SHADOW_RECONCILIATION',
975
- 'The packet must contain exactly its one receipted terminal reconciliation',
889
+ 'The packet must contain exactly its one complete terminal reconciliation',
976
890
  terminalId ?? '$.artifacts',
977
891
  ),
978
892
  );
@@ -1165,47 +1079,13 @@ function validateReviewBindings(
1165
1079
  }
1166
1080
  }
1167
1081
 
1168
- const assuranceStageModeByReviewKind = new Map([
1169
- ['semantic', 'semantic-verification'],
1170
- ['adversarial', 'adversarial'],
1171
- ['coverage', 'coverage'],
1172
- ['redundant-verification', 'redundant-verification'],
1173
- ['contradiction-resolution', 'contradiction-resolution'],
1174
- ]);
1175
-
1176
- function validateReceiptedAssuranceReviews(artifactsById, topology, errors) {
1177
- const receipted = new Set();
1178
- for (const [reviewKind, mode] of assuranceStageModeByReviewKind) {
1179
- for (const id of topology.completeArtifactIdsByMode.get(mode) ?? []) {
1180
- const artifact = artifactsById.get(id)?.value;
1181
- if (artifact?.reviewKind === reviewKind) receipted.add(id);
1182
- }
1183
- }
1184
- for (const { value } of artifactsById.values()) {
1185
- if (
1186
- value.kind === 'recon.review-result' &&
1187
- assuranceStageModeByReviewKind.has(value.reviewKind) &&
1188
- !receipted.has(value.id)
1189
- ) {
1190
- errors.push(
1191
- issue(
1192
- 'UNRECEIPTED_ASSURANCE_REVIEW',
1193
- `Assurance result ${value.id} is not the exact artifact of a completed approved stage`,
1194
- value.id,
1195
- ),
1196
- );
1197
- }
1198
- }
1199
- return receipted;
1200
- }
1201
-
1202
1082
  function validateReconciliation(
1203
1083
  manifest,
1204
1084
  ledger,
1205
1085
  artifactsById,
1206
1086
  reconciliationContext,
1207
- topology,
1208
- receiptedReviewIds,
1087
+ passes,
1088
+ assuranceReviewIds,
1209
1089
  exactEvidence,
1210
1090
  reconciliationRequired,
1211
1091
  errors,
@@ -1295,14 +1175,14 @@ function validateReconciliation(
1295
1175
  if (
1296
1176
  !authorization ||
1297
1177
  authorization.disposition !== 'rejected' ||
1298
- !receiptedReviewIds.has(authorization.reviewId) ||
1178
+ !assuranceReviewIds.has(authorization.reviewId) ||
1299
1179
  !reconciliation.incorporatedReviewIds.includes(authorization.reviewId) ||
1300
1180
  disposition?.disposition !== 'rejected'
1301
1181
  ) {
1302
1182
  errors.push(
1303
1183
  issue(
1304
1184
  'UNAUTHORIZED_CLAIM_REMOVAL',
1305
- `Claim ${claimId} lacks a receipted typed review disposition authorizing removal`,
1185
+ `Claim ${claimId} lacks a complete typed review disposition authorizing removal`,
1306
1186
  claimId,
1307
1187
  ),
1308
1188
  );
@@ -1520,8 +1400,8 @@ function validateReconciliation(
1520
1400
  }
1521
1401
  const incorporated = new Set(reconciliation.incorporatedReviewIds ?? []);
1522
1402
  if (
1523
- receiptedReviewIds.size !== incorporated.size ||
1524
- [...receiptedReviewIds].some((id) => !incorporated.has(id))
1403
+ assuranceReviewIds.size !== incorporated.size ||
1404
+ [...assuranceReviewIds].some((id) => !incorporated.has(id))
1525
1405
  ) {
1526
1406
  errors.push(
1527
1407
  issue(
@@ -1532,9 +1412,7 @@ function validateReconciliation(
1532
1412
  );
1533
1413
  }
1534
1414
 
1535
- const coverageResults = (
1536
- topology.completeArtifactIdsByMode.get('coverage') ?? []
1537
- )
1415
+ const coverageResults = (passes.get('coverage') ?? [])
1538
1416
  .map((id) => artifactsById.get(id)?.value)
1539
1417
  .filter(Boolean);
1540
1418
  const coverageDispositions = new Map(
@@ -1678,7 +1556,7 @@ function validateDerivedSourceGaps(manifest, ledger, errors) {
1678
1556
  function validateAssurance(validatedRun, errors) {
1679
1557
  const { manifest, ledger, achievedProfile } = validatedRun;
1680
1558
  const exactEvidence = new Set(validatedRun.exactEvidenceIds);
1681
- const receiptedReviewIds = new Set(validatedRun.receiptedReviewIds);
1559
+ const assuranceReviewIds = new Set(validatedRun.assuranceReviewIds);
1682
1560
  const artifactsById = new Map(
1683
1561
  validatedRun.artifacts.map((artifact) => [artifact.id, artifact]),
1684
1562
  );
@@ -1784,11 +1662,11 @@ function validateAssurance(validatedRun, errors) {
1784
1662
  );
1785
1663
  continue;
1786
1664
  }
1787
- if (!receiptedReviewIds.has(reviewId)) {
1665
+ if (!assuranceReviewIds.has(reviewId)) {
1788
1666
  errors.push(
1789
1667
  issue(
1790
- 'UNRECEIPTED_ASSURANCE_REVIEW',
1791
- `Review ${reviewId} is not the exact receipted stage result`,
1668
+ 'INCOMPLETE_INDEPENDENT_REVIEW',
1669
+ `Review ${reviewId} is not a complete same-run assurance result`,
1792
1670
  claim.id,
1793
1671
  ),
1794
1672
  );
@@ -2041,56 +1919,16 @@ export async function compileValidatedRun(packetDirectory) {
2041
1919
  errors.push(issue('RUN_ID_MISMATCH', 'Manifest and ledger run IDs differ'));
2042
1920
  }
2043
1921
 
2044
- if (manifest?.execution?.approvalProjection) {
2045
- const projection = manifest.execution.approvalProjection;
2046
- const expected = hashCanonicalJson(projection);
2047
- if (
2048
- expected !== manifest.execution.approvalFingerprint ||
2049
- canonicalJson(projection) !== manifest.execution.approvalCanonicalJson
2050
- ) {
1922
+ if (isObject(manifest?.execution?.approval)) {
1923
+ const expected = hashCanonicalJson(
1924
+ approvalFingerprintInput(manifest.execution),
1925
+ );
1926
+ if (manifest.execution.approval.fingerprint !== expected) {
2051
1927
  errors.push(
2052
1928
  issue(
2053
1929
  'APPROVAL_FINGERPRINT_MISMATCH',
2054
- 'Prepared projection no longer matches its canonical approval fingerprint',
2055
- '$.execution.approvalFingerprint',
2056
- ),
2057
- );
2058
- }
2059
- if (
2060
- manifest.execution.approvalEvidence?.fingerprint !== expected ||
2061
- manifest.execution.approvalEvidence?.type !== 'explicit-user-approval'
2062
- ) {
2063
- errors.push(
2064
- issue(
2065
- 'APPROVAL_EVIDENCE_MISMATCH',
2066
- 'Approval evidence does not bind the canonical prepared projection',
2067
- '$.execution.approvalEvidence',
2068
- ),
2069
- );
2070
- }
2071
- const observation = projection.catalog_observation;
2072
- const recheck = manifest.execution.catalogRecheck;
2073
- if (
2074
- !recheck ||
2075
- recheck.source !== observation?.source ||
2076
- recheck.dispatch_context !== observation?.dispatch_context ||
2077
- recheck.relevant_catalog_fingerprint !==
2078
- observation?.relevant_catalog_fingerprint
2079
- ) {
2080
- errors.push(
2081
- issue(
2082
- 'CATALOG_RECHECK_MISMATCH',
2083
- 'Catalog recheck does not retain the approved live catalog identity',
2084
- '$.execution.catalogRecheck',
2085
- ),
2086
- );
2087
- }
2088
- if (projection.run_id !== manifest.run?.id) {
2089
- errors.push(
2090
- issue(
2091
- 'RUN_ID_MISMATCH',
2092
- 'Prepared projection and packet manifest run IDs differ',
2093
- '$.execution.approvalProjection.run_id',
1930
+ 'Approved execution envelope no longer matches its approval fingerprint',
1931
+ '$.execution.approval.fingerprint',
2094
1932
  ),
2095
1933
  );
2096
1934
  }
@@ -2209,25 +2047,20 @@ export async function compileValidatedRun(packetDirectory) {
2209
2047
  exactEvidence.add(evidence.id);
2210
2048
  }
2211
2049
  }
2212
- const topology = validateStageTopology(
2213
- manifest,
2050
+ validateApprovedLanes(manifest, artifactsById, errors);
2051
+ const passes = collectCompletePasses(artifactsById, manifest.run.id);
2052
+ const achievedProfile = deriveAchievedProfile(passes);
2053
+ validatePassOutcomes(manifest, passes, errors);
2054
+ const assuranceReviewIds = collectAssuranceReviewIds(
2214
2055
  artifactsById,
2215
2056
  manifest.run.id,
2216
- manifest.execution.approvalFingerprint,
2217
- errors,
2218
- );
2219
- const achievedProfile = deriveAchievedProfile(topology);
2220
- const receiptedReviewIds = validateReceiptedAssuranceReviews(
2221
- artifactsById,
2222
- topology,
2223
- errors,
2224
2057
  );
2225
2058
  const reconciliationRequired =
2226
2059
  achievedProfile === 'standard' || achievedProfile === 'thorough';
2227
2060
  const reconciliationContext = resolveTerminalReconciliation(
2228
2061
  artifactsById,
2229
2062
  artifactsByPath,
2230
- topology,
2063
+ passes,
2231
2064
  reconciliationRequired,
2232
2065
  errors,
2233
2066
  );
@@ -2244,8 +2077,8 @@ export async function compileValidatedRun(packetDirectory) {
2244
2077
  ledger,
2245
2078
  artifactsById,
2246
2079
  reconciliationContext,
2247
- topology,
2248
- receiptedReviewIds,
2080
+ passes,
2081
+ assuranceReviewIds,
2249
2082
  exactEvidence,
2250
2083
  reconciliationRequired,
2251
2084
  errors,
@@ -2278,9 +2111,9 @@ export async function compileValidatedRun(packetDirectory) {
2278
2111
  ledger,
2279
2112
  artifactsById,
2280
2113
  exactEvidence,
2281
- topology,
2114
+ passes,
2282
2115
  achievedProfile,
2283
- receiptedReviewIds,
2116
+ assuranceReviewIds,
2284
2117
  reconciliationContext,
2285
2118
  });
2286
2119
  validateAssurance(validatedRun, errors);