ai-delivery-workflow 0.5.0 → 0.6.0

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 (81) hide show
  1. package/README.md +5 -6
  2. package/bin/ai-delivery.mjs +2 -24
  3. package/docs/CLI-PARAMETER-REFERENCE.zh-CN.md +6 -67
  4. package/docs/DUAL-REPOSITORY-WORKSPACE.zh-CN.md +1 -1
  5. package/docs/FILE-REFERENCE.zh-CN.md +6 -28
  6. package/docs/MAINTENANCE-VERIFICATION.zh-CN.md +2 -2
  7. package/docs/PROJECT-MANUAL.zh-CN.md +23 -38
  8. package/docs/STATE-CLI-MAINTENANCE.zh-CN.md +8 -10
  9. package/docs/STATE-CLI-USER-GUIDE.zh-CN.md +6 -16
  10. package/docs/agents/workflow-manager-acceptance-matrix.md +9 -15
  11. package/docs/agents/workflow-manager-current-state.md +12 -15
  12. package/lib/communication-confirmation.mjs +177 -0
  13. package/lib/delivery-state.mjs +59 -910
  14. package/lib/fs-utils.mjs +0 -16
  15. package/lib/project-bootstrap.mjs +288 -61
  16. package/lib/project-installer.mjs +49 -181
  17. package/lib/project-repair.mjs +217 -0
  18. package/lib/toml-hooks.mjs +2 -4
  19. package/lib/workflow-manager-governance.mjs +3 -47
  20. package/lib/workspace.mjs +2 -3
  21. package/package.json +2 -2
  22. package/skills/ai-delivery-bootstrap/SKILL.md +5 -1
  23. package/skills/ai-delivery-bootstrap/references/bootstrap-contract.md +1 -1
  24. package/skills/ai-delivery-checkpoint-task/SKILL.md +2 -4
  25. package/skills/ai-delivery-checkpoint-task/references/checkpoint-contract.md +4 -2
  26. package/skills/ai-delivery-checkpoint-task/scripts/hook-event.mjs +2 -8
  27. package/skills/ai-delivery-checkpoint-task/scripts/task-state.mjs +26 -46
  28. package/skills/ai-delivery-close-version/SKILL.md +13 -10
  29. package/skills/ai-delivery-close-version/references/version-closeout-contract.md +2 -1
  30. package/skills/ai-delivery-define-product/SKILL.md +6 -3
  31. package/skills/ai-delivery-design-architecture/SKILL.md +10 -5
  32. package/skills/ai-delivery-design-architecture/assets/architecture-template/architecture-gate.yaml +2 -0
  33. package/skills/ai-delivery-design-architecture/assets/architecture-template/prototype-architecture-validation.yaml +2 -0
  34. package/skills/ai-delivery-design-architecture/references/architecture-contract.md +4 -0
  35. package/skills/ai-delivery-design-experience/SKILL.md +8 -6
  36. package/skills/ai-delivery-design-experience/assets/experience-template/product-prototype-reconciliation.yaml +2 -0
  37. package/skills/ai-delivery-design-experience/assets/experience-template/ui-handoff.yaml +3 -0
  38. package/skills/ai-delivery-design-experience/references/experience-contract.md +3 -13
  39. package/skills/ai-delivery-design-experience/references/prototype-management-contract.md +1 -1
  40. package/skills/ai-delivery-design-tests/SKILL.md +4 -0
  41. package/skills/ai-delivery-develop-iteration/SKILL.md +6 -1
  42. package/skills/ai-delivery-develop-iteration/assets/development-template/candidate-manifest.yaml +2 -0
  43. package/skills/ai-delivery-execute-work-package/SKILL.md +4 -2
  44. package/skills/ai-delivery-orchestrate/SKILL.md +7 -7
  45. package/skills/ai-delivery-orchestrate/assets/codebase-template/AGENTS.md +14 -0
  46. package/skills/ai-delivery-orchestrate/assets/codebase-template/CODEBASE_GUIDE.md +124 -0
  47. package/skills/ai-delivery-orchestrate/assets/codebase-template/docs/CODEBASE_INDEX.md +87 -0
  48. package/skills/ai-delivery-orchestrate/assets/communication-template/confirmation-examples.yaml +67 -0
  49. package/skills/ai-delivery-orchestrate/assets/communication-template/confirmation-summary.yaml +42 -0
  50. package/skills/ai-delivery-orchestrate/assets/project-template/.workflow/iterations/ITER-0001/02-solution-design/product-impact-assessment.yaml +42 -0
  51. package/skills/ai-delivery-orchestrate/assets/project-template/.workflow/tools/hooks/hook-event.mjs +2 -8
  52. package/skills/ai-delivery-orchestrate/assets/project-template/AGENTS.md +4 -5
  53. package/skills/ai-delivery-orchestrate/references/codebase-document-contract.md +91 -0
  54. package/skills/ai-delivery-orchestrate/references/communication-confirmation-contract.md +52 -0
  55. package/skills/ai-delivery-orchestrate/references/formal-state-contract.md +11 -20
  56. package/skills/ai-delivery-orchestrate/references/product-impact-assessment-contract.md +87 -0
  57. package/skills/ai-delivery-orchestrate/references/workflow-model.md +1 -3
  58. package/skills/ai-delivery-orchestrate/scripts/confirmation-state.mjs +30 -0
  59. package/skills/ai-delivery-orchestrate-release/SKILL.md +3 -1
  60. package/skills/ai-delivery-plan-iteration/SKILL.md +6 -1
  61. package/skills/ai-delivery-prepare-platform/SKILL.md +1 -1
  62. package/skills/ai-delivery-prepare-release/SKILL.md +2 -0
  63. package/skills/ai-delivery-prepare-release/assets/release-template/release-manifest.yaml +0 -2
  64. package/skills/ai-delivery-review-change/SKILL.md +3 -1
  65. package/skills/ai-delivery-validate-artifacts/SKILL.md +10 -7
  66. package/skills/ai-delivery-validate-artifacts/references/artifact-contract.md +2 -1
  67. package/skills/ai-delivery-verify-candidate/SKILL.md +5 -2
  68. package/skills/ai-delivery-verify-candidate/references/candidate-verification-contract.md +1 -1
  69. package/docs/PROTOTYPE-MIGRATION.zh-CN.md +0 -164
  70. package/docs/VIEWER-MAINTENANCE.zh-CN.md +0 -150
  71. package/docs/VIEWER-USER-GUIDE.zh-CN.md +0 -113
  72. package/lib/project-migration.mjs +0 -1193
  73. package/lib/prototype-migration.mjs +0 -713
  74. package/skills/ai-delivery-design-experience/assets/experience-template/LEGACY-COMPATIBILITY.md +0 -16
  75. package/skills/ai-delivery-design-experience/assets/experience-template/canvas-catalog.csv +0 -1
  76. package/skills/ai-delivery-design-experience/assets/experience-template/canvas-pages.csv +0 -1
  77. package/skills/ai-delivery-design-experience/assets/experience-template/page-catalog.csv +0 -1
  78. package/skills/ai-delivery-design-experience/assets/experience-template/prototype-file-terminals.csv +0 -1
  79. package/skills/ai-delivery-design-experience/assets/experience-template/prototype-files.csv +0 -1
  80. package/skills/ai-delivery-design-experience/assets/experience-template/prototype-manifest.yaml +0 -76
  81. package/skills/ai-delivery-design-experience/assets/experience-template/prototype-set.yaml +0 -24
@@ -1,13 +1,12 @@
1
1
  import crypto from "node:crypto";
2
2
  import fs from "node:fs";
3
3
  import path from "node:path";
4
- import zlib from "node:zlib";
5
4
  import {
6
5
  historyAnchorId,
7
6
  readVerifiedHistoryAnchor,
8
7
  structuredDocumentChecksum,
9
8
  } from "./history-anchor.mjs";
10
- import { assertPathInside, atomicWrite } from "./fs-utils.mjs";
9
+ import { atomicWrite } from "./fs-utils.mjs";
11
10
 
12
11
  const SEVEN_NODE_DEVELOPMENT_IDS = [
13
12
  "00-bootstrap",
@@ -18,27 +17,6 @@ const SEVEN_NODE_DEVELOPMENT_IDS = [
18
17
  "05-candidate-assurance",
19
18
  "06-version-closeout",
20
19
  ];
21
- const LEGACY_DEVELOPMENT_IDS = [
22
- "01-product-discovery",
23
- "02-product-definition",
24
- "03-architecture-initial",
25
- "04-experience-design",
26
- "05-architecture-reconcile",
27
- "06-platform-preparation",
28
- "07-iteration-planning",
29
- "08-test-design",
30
- "09-development",
31
- "10-change-review",
32
- "11-candidate-verification",
33
- "12-version-closeout",
34
- ];
35
- const LEGACY_PRODUCTION_IDS = [
36
- "12-release-preparation",
37
- "13-production-approval",
38
- "14-production-deployment",
39
- "15-production-verification",
40
- "16-version-archive",
41
- ];
42
20
  const RELEASE_NODE_IDS = [
43
21
  "R00-release-request",
44
22
  "R01-version-selection",
@@ -54,12 +32,9 @@ const RELEASE_NODE_IDS = [
54
32
  ];
55
33
  const NODE_IDS = new Set([
56
34
  ...SEVEN_NODE_DEVELOPMENT_IDS,
57
- ...LEGACY_DEVELOPMENT_IDS,
58
- ...LEGACY_PRODUCTION_IDS,
59
35
  ...RELEASE_NODE_IDS,
60
36
  ]);
61
37
  const SEVEN_NODE_DEVELOPMENT_ID_SET = new Set(SEVEN_NODE_DEVELOPMENT_IDS);
62
- const LEGACY_DEVELOPMENT_ID_SET = new Set(LEGACY_DEVELOPMENT_IDS);
63
38
  const RELEASE_NODE_ID_SET = new Set(RELEASE_NODE_IDS);
64
39
  const ARTIFACT_STATUSES = new Set([
65
40
  "draft",
@@ -86,10 +61,9 @@ const ARTIFACT_STATUSES = new Set([
86
61
  ]);
87
62
  const WORKING_DRAFT_STATUSES = new Set(["draft", "review-draft"]);
88
63
  const GATE_DECISIONS = new Set(["approved", "rejected", "blocked", "not-required"]);
89
- const UX_GATE_IDS = new Set(["UX-LF", "UX-VS", "UX-UI"]);
90
- const RETIRED_GATE_IDS = new Set(["UX-LF"]);
91
- const UX_GATE_NODES = new Set(["02-solution-design", "04-experience-design"]);
92
- const PRODUCT_TERMINAL_NODES = new Set(["01-product-shaping", "02-product-definition"]);
64
+ const UX_GATE_IDS = new Set(["UX-VS", "UX-UI"]);
65
+ const UX_GATE_NODES = new Set(["02-solution-design"]);
66
+ const PRODUCT_TERMINAL_NODES = new Set(["01-product-shaping"]);
93
67
  const SUCCESSFUL_ARTIFACT_STATUSES = new Set([
94
68
  "approved", "valid", "frozen", "immutable", "passed", "deployed", "released",
95
69
  "release-ready", "included-in-release", "archived",
@@ -102,18 +76,6 @@ const NODE_RULES = new Map([
102
76
  ["04-implementation", { scope: "development", prerequisites: ["03-delivery-readiness"], gates: [] }],
103
77
  ["05-candidate-assurance", { scope: "development", prerequisites: ["04-implementation"], gates: [] }],
104
78
  ["06-version-closeout", { scope: "development", prerequisites: ["05-candidate-assurance"], gates: [] }],
105
- ["01-product-discovery", { scope: "development", prerequisites: ["00-bootstrap"], gates: ["GATE-A"] }],
106
- ["02-product-definition", { scope: "development", prerequisites: ["01-product-discovery"], gates: ["GATE-B"] }],
107
- ["03-architecture-initial", { scope: "development", prerequisites: ["02-product-definition"], gates: [] }],
108
- ["04-experience-design", { scope: "development", prerequisites: ["03-architecture-initial"], gates: ["UX-LF", "UX-VS", "UX-UI"] }],
109
- ["05-architecture-reconcile", { scope: "development", prerequisites: ["04-experience-design"], gates: [] }],
110
- ["06-platform-preparation", { scope: "development", prerequisites: ["03-architecture-initial"], gates: [] }],
111
- ["07-iteration-planning", { scope: "development", prerequisites: ["05-architecture-reconcile", "06-platform-preparation"], gates: [] }],
112
- ["08-test-design", { scope: "development", prerequisites: ["07-iteration-planning"], gates: [] }],
113
- ["09-development", { scope: "development", prerequisites: ["08-test-design"], gates: [] }],
114
- ["10-change-review", { scope: "development", prerequisites: ["09-development"], gates: [] }],
115
- ["11-candidate-verification", { scope: "development", prerequisites: ["10-change-review"], gates: [] }],
116
- ["12-version-closeout", { scope: "development", prerequisites: ["11-candidate-verification"], gates: [] }],
117
79
  ["R00-release-request", { scope: "release", prerequisites: [], gates: [] }],
118
80
  ["R01-version-selection", { scope: "release", prerequisites: ["R00-release-request"], gates: [] }],
119
81
  ["R02-release-eligibility", { scope: "release", prerequisites: ["R01-version-selection"], gates: [] }],
@@ -129,15 +91,12 @@ const NODE_RULES = new Map([
129
91
  const WRITABLE_NODE_IDS = new Set([...SEVEN_NODE_DEVELOPMENT_IDS, ...RELEASE_NODE_IDS]);
130
92
  const SCOPED_COMPLETION_NODES = new Set([
131
93
  "03-delivery-readiness", "04-implementation", "05-candidate-assurance", "06-version-closeout",
132
- "07-iteration-planning", "08-test-design", "09-development",
133
- "10-change-review", "11-candidate-verification", "12-version-closeout",
134
94
  ]);
135
95
  const CANDIDATE_REQUIRED_NODES = new Set([
136
96
  "04-implementation", "05-candidate-assurance", "06-version-closeout",
137
- "11-candidate-verification", "12-version-closeout",
138
97
  ]);
139
98
  const CANDIDATE_BOUND_NODES = new Set(["05-candidate-assurance", "06-version-closeout"]);
140
- const SCOPE_FREEZE_NODES = new Set(["03-delivery-readiness", "07-iteration-planning"]);
99
+ const SCOPE_FREEZE_NODES = new Set(["03-delivery-readiness"]);
141
100
  const SLICE_BUDGET_LIMITS = new Map([
142
101
  ["logical-tasks", 8],
143
102
  ["active-hours", 8],
@@ -170,7 +129,7 @@ function parseArgs(argv) {
170
129
  }
171
130
 
172
131
  function lifecycleDescriptor(command, positional, flags) {
173
- if (command === "artifact" && new Set(["register", "status"]).has(positional[1])) {
132
+ if (command === "artifact" && positional[1] === "register") {
174
133
  return {
175
134
  kind: "artifact",
176
135
  action: `artifact-${positional[1]}`,
@@ -202,26 +161,12 @@ function lifecycleDescriptor(command, positional, flags) {
202
161
  };
203
162
  }
204
163
 
205
- function assertNotLegacyDevelopmentNode(node) {
206
- if (LEGACY_DEVELOPMENT_ID_SET.has(node)) {
207
- throw new Error(`Legacy workflow node is read-only: ${node}`);
208
- }
164
+ function assertWritableNode(node) {
165
+ if (!WRITABLE_NODE_IDS.has(node)) throw new Error(`Unsupported workflow node: ${node}`);
209
166
  return node;
210
167
  }
211
168
 
212
- function assertGateContractWritable(gateId) {
213
- if (RETIRED_GATE_IDS.has(gateId)) {
214
- throw new Error(`${gateId} Gate contract is retired; historical records remain read-only.`);
215
- }
216
- }
217
-
218
- function assertRetiredGateContract(gateId) {
219
- if (!RETIRED_GATE_IDS.has(gateId)) {
220
- throw new Error(`Gate contract is not retired: ${gateId}.`);
221
- }
222
- }
223
-
224
- function assertNoLegacyDevelopmentWrite(files, command, positional, flags, options) {
169
+ function assertCurrentNodeInputs(files, command, positional, flags, options) {
225
170
  let node = null;
226
171
  if ((command === "artifact" && positional[1] === "register")
227
172
  || (command === "gate" && positional[1] === "request")) {
@@ -250,7 +195,7 @@ function assertNoLegacyDevelopmentWrite(files, command, positional, flags, optio
250
195
  }
251
196
  }
252
197
  }
253
- assertNotLegacyDevelopmentNode(node);
198
+ if (node) assertWritableNode(node);
254
199
  }
255
200
 
256
201
  function dispatchLifecycle(options, files, descriptor, phase, flags, result = null, error = null) {
@@ -484,9 +429,7 @@ function statePaths(projectRoot) {
484
429
  events: path.join(delivery, "state-events.jsonl"),
485
430
  eventShards: path.join(workflow, "control", "events"),
486
431
  historySnapshots: path.join(workflow, "control", "snapshots", "formal-state"),
487
- artifactHistoryArchives: path.join(workflow, "control", "archives", "history", "artifact-registry"),
488
432
  artifactRetention: path.join(workflow, "control", "retention", "artifact-registry"),
489
- compactRuntime: path.join(delivery, "runtime", "formal-state-compact"),
490
433
  pruneRuntime: path.join(delivery, "runtime", "formal-state-prune"),
491
434
  lockRoot: path.join(delivery, "runtime", "formal-state-locks"),
492
435
  transactionJournal: path.join(delivery, "runtime", "formal-state-locks", "formal-state-transaction.json"),
@@ -517,9 +460,6 @@ function normalizeScope(value, scopeId = null) {
517
460
  node_completion_receipts: Array.isArray(value?.node_completion_receipts)
518
461
  ? value.node_completion_receipts
519
462
  : [],
520
- contract_retirement_receipts: Array.isArray(value?.contract_retirement_receipts)
521
- ? value.contract_retirement_receipts
522
- : [],
523
463
  latest_candidate_id: value?.latest_candidate_id ?? null,
524
464
  latest_release_id: value?.latest_release_id ?? null,
525
465
  updated_at: value?.updated_at ?? "",
@@ -545,67 +485,30 @@ export function findIncompatibleNodeStates(scope) {
545
485
  .map(([node, states]) => ({ node, states: [...states] }));
546
486
  }
547
487
 
548
- function exposeLegacyScopeAliases(state) {
549
- const preferred = state.last_transition?.scope === "release"
550
- ? state.release_state
551
- : state.development_state.scope_id ? state.development_state : state.release_state;
552
- const aliases = {
553
- iteration_id: preferred.scope_id,
554
- active_nodes: preferred.active_nodes,
555
- blocked_nodes: preferred.blocked_nodes,
556
- completed_nodes: preferred.completed_nodes,
557
- pending_gates: preferred.pending_gates,
558
- gate_decisions: preferred.gate_decisions,
559
- latest_candidate_id: state.development_state.latest_candidate_id
560
- || state.release_state.latest_candidate_id,
561
- latest_release_id: state.release_state.latest_release_id,
562
- };
563
- for (const [key, alias] of Object.entries(aliases)) {
564
- Object.defineProperty(state, key, { value: alias, enumerable: false, configurable: true });
565
- }
566
- return state;
567
- }
568
-
569
488
  function normalizeWorkflowState(value) {
570
- const legacyNodes = [
571
- ...(Array.isArray(value.active_nodes) ? value.active_nodes : []),
572
- ...(Array.isArray(value.blocked_nodes) ? value.blocked_nodes : []),
573
- ...(Array.isArray(value.completed_nodes) ? value.completed_nodes : []),
574
- ];
575
- const legacyRelease = legacyNodes.some((node) => String(node).startsWith("R"))
576
- || String(value.iteration_id || "").startsWith("REL-");
577
- const legacyScope = normalizeScope({
578
- scope_id: value.iteration_id ?? null,
579
- revision: value.revision ?? 0,
580
- active_nodes: value.active_nodes,
581
- blocked_nodes: value.blocked_nodes,
582
- completed_nodes: value.completed_nodes,
583
- pending_gates: value.pending_gates,
584
- gate_decisions: value.gate_decisions,
585
- latest_candidate_id: value.latest_candidate_id,
586
- latest_release_id: value.latest_release_id,
587
- updated_at: value.updated_at,
588
- last_transition: value.last_transition,
589
- });
489
+ if (value?.schema_version !== 2) {
490
+ throw new Error(`Formal workflow state schema ${value?.schema_version ?? "missing"} is unsupported; expected schema 2.`);
491
+ }
492
+ if (!value.development_state || !value.release_state
493
+ || Object.prototype.hasOwnProperty.call(value, "iteration_id")
494
+ || Object.prototype.hasOwnProperty.call(value, "active_nodes")
495
+ || Object.prototype.hasOwnProperty.call(value, "blocked_nodes")
496
+ || Object.prototype.hasOwnProperty.call(value, "completed_nodes")) {
497
+ throw new Error("Formal workflow state must use separate development_state and release_state scopes.");
498
+ }
590
499
  const state = {
591
- schema_version: value.schema_version ?? 1,
592
- revision: value.revision ?? 0,
500
+ schema_version: 2,
501
+ revision: value.revision,
593
502
  project_id: value.project_id ?? null,
594
- development_state: normalizeScope(
595
- value.development_state || (!legacyRelease ? legacyScope : null),
596
- !legacyRelease ? value.iteration_id ?? null : null,
597
- ),
598
- release_state: normalizeScope(
599
- value.release_state || (legacyRelease ? legacyScope : null),
600
- legacyRelease ? value.iteration_id ?? null : null,
601
- ),
503
+ development_state: normalizeScope(value.development_state),
504
+ release_state: normalizeScope(value.release_state),
602
505
  latest_line_version_id: value.latest_line_version_id ?? null,
603
506
  production_version_id: value.production_version_id ?? null,
604
507
  production_release_id: value.production_release_id ?? null,
605
508
  updated_at: value.updated_at ?? "",
606
509
  last_transition: value.last_transition ?? null,
607
510
  };
608
- return exposeLegacyScopeAliases(state);
511
+ return state;
609
512
  }
610
513
 
611
514
  function scopeNameForNode(node) {
@@ -660,10 +563,6 @@ function completionReceiptChecksum(receipt) {
660
563
  return checksumWithoutField(receipt, "receipt_checksum");
661
564
  }
662
565
 
663
- function contractRetirementReceiptChecksum(receipt) {
664
- return checksumWithoutField(receipt, "receipt_checksum");
665
- }
666
-
667
566
  function currentScopeManifest(scope) {
668
567
  if (!scope.current_scope_manifest_id) return null;
669
568
  return scope.scope_manifests.find((manifest) => manifest?.manifest_id === scope.current_scope_manifest_id) || null;
@@ -895,7 +794,7 @@ const FORMAL_STREAMS = new Map([
895
794
  viewFile: (files) => files.workflowState,
896
795
  normalize: normalizeWorkflowState,
897
796
  applyOperations: applyWorkflowStateOperations,
898
- allowedSchemas: new Set([1, 2]),
797
+ allowedSchemas: new Set([2]),
899
798
  }],
900
799
  ["artifact-registry", {
901
800
  snapshotFile: "artifact-registry.yaml",
@@ -1225,14 +1124,15 @@ function prepareStreamMutation(files, mutation, options, transactionId) {
1225
1124
  ...event,
1226
1125
  after_checksum: sha256(serialized),
1227
1126
  };
1228
- const shardRecord = transaction
1229
- ? {
1230
- ...eventRecord,
1231
- transaction_id: transactionId,
1232
- ...transaction,
1233
- previous_checksum: previousStreamChecksum(files, stream, transaction.before_revision, options),
1234
- }
1235
- : { ...eventRecord, after_value: value };
1127
+ if (!transaction || transaction.schema_version !== 2 || !Array.isArray(transaction.operations)) {
1128
+ throw new Error(`Formal ${stream} mutations require a schema 2 delta transaction.`);
1129
+ }
1130
+ const shardRecord = {
1131
+ ...eventRecord,
1132
+ transaction_id: transactionId,
1133
+ ...transaction,
1134
+ previous_checksum: previousStreamChecksum(files, stream, transaction.before_revision, options),
1135
+ };
1236
1136
  const record = {
1237
1137
  ...shardRecord,
1238
1138
  record_checksum: unsignedRecordChecksum(shardRecord, options.yaml),
@@ -1365,9 +1265,7 @@ function replayStreamRecords(stream, records, options, base = {}) {
1365
1265
  if (record.revision !== previousRevision + 1) {
1366
1266
  throw new Error(`Formal event shard revision gap: ${stream}/${record.revision}; expected ${previousRevision + 1}.`);
1367
1267
  }
1368
- if (record.after_value) {
1369
- value = record.after_value;
1370
- } else if (record.schema_version === 2 && Array.isArray(record.operations)) {
1268
+ if (record.schema_version === 2 && Array.isArray(record.operations)) {
1371
1269
  if (record.before_revision !== record.revision - 1 || record.after_revision !== record.revision) {
1372
1270
  throw new Error(`Formal event shard revision metadata mismatch: ${stream}/${record.revision}.`);
1373
1271
  }
@@ -1398,7 +1296,7 @@ function replayStreamRecords(stream, records, options, base = {}) {
1398
1296
  function validateEventShardRecord(record, stream, name) {
1399
1297
  const expectedName = `${String(record.revision).padStart(6, "0")}-${record.event_id}.yaml`;
1400
1298
  if (record.stream !== stream || !Number.isInteger(record.revision) || !record.event_id
1401
- || name !== expectedName || (!record.after_value && !Array.isArray(record.operations))) {
1299
+ || record.schema_version !== 2 || !Array.isArray(record.operations)) {
1402
1300
  throw new Error(`Formal event shard is invalid: ${stream}/${name}`);
1403
1301
  }
1404
1302
  return record;
@@ -1720,8 +1618,7 @@ function createFormalSnapshot(files, flags, options) {
1720
1618
  function rebuildFormalViews(files, options) {
1721
1619
  return withLock(files, "formal-state-rebuild", () => {
1722
1620
  const retentionReceipts = readArtifactRetentionReceipts(files, options);
1723
- const historyArchives = readArtifactHistoryArchives(files, options);
1724
- if (retentionReceipts.length || historyArchives.length) {
1621
+ if (retentionReceipts.length) {
1725
1622
  const trusted = readLatestHistoryAnchor(files, options);
1726
1623
  if (!trusted) throw new Error("Retained history rebuild requires a trusted history anchor.");
1727
1624
  const errors = [];
@@ -1769,17 +1666,13 @@ function rebuildFormalViews(files, options) {
1769
1666
  }
1770
1667
  const records = readEventShards(files, options);
1771
1668
  if (!records.length) {
1772
- return { command: "rebuild", rebuilt: false, event_shard_count: 0, message: "No formal event shards exist; legacy views were left unchanged." };
1669
+ return { command: "rebuild", rebuilt: false, event_shard_count: 0, message: "No formal event shards exist; current views were left unchanged." };
1773
1670
  }
1774
1671
  const workflowRecords = records.filter((record) => record.stream === "workflow-state");
1775
1672
  const registryRecords = records.filter((record) => record.stream === "artifact-registry");
1776
1673
  const workflow = workflowRecords.length ? replayStreamRecords("workflow-state", workflowRecords, options) : null;
1777
1674
  const registry = registryRecords.length ? replayStreamRecords("artifact-registry", registryRecords, options) : null;
1778
- const index = records.map((record) => {
1779
- const { after_value: ignored, ...indexed } = record;
1780
- if (record.after_value) delete indexed.record_checksum;
1781
- return JSON.stringify(indexed);
1782
- }).join("\n");
1675
+ const index = records.map((record) => JSON.stringify(record)).join("\n");
1783
1676
  const writes = [{ file: files.events, content: `${index}\n` }];
1784
1677
  if (workflow) writes.unshift({ file: files.workflowState, content: options.yaml.stringify(workflow) });
1785
1678
  if (registry) writes.unshift({ file: files.artifactRegistry, content: options.yaml.stringify(registry) });
@@ -1848,7 +1741,7 @@ function registerArtifact(files, flags, options) {
1848
1741
  }
1849
1742
  }
1850
1743
  const node = requiredFlag(flags, "--node");
1851
- assertNotLegacyDevelopmentNode(node);
1744
+ assertWritableNode(node);
1852
1745
  if (!NODE_IDS.has(node)) throw new Error(`Unknown workflow node: ${node}`);
1853
1746
  const artifactScope = scopeNameForNode(node);
1854
1747
  const artifactScopeState = scopeState(workflowState, artifactScope);
@@ -1921,63 +1814,6 @@ function registerArtifact(files, flags, options) {
1921
1814
  }, { waitForActive: true });
1922
1815
  }
1923
1816
 
1924
- function changeArtifactStatus(files, flags, options) {
1925
- const expected = expectedRevision(flags);
1926
- return withLock(files, "artifact-registry", () => {
1927
- const registry = normalizeArtifactRegistry(
1928
- readYaml(files.artifactRegistry, options.yaml, "artifact-registry.yaml"),
1929
- );
1930
- if (registry.revision !== expected) {
1931
- throw new Error(`Artifact registry revision conflict: expected ${expected}, actual ${registry.revision}.`);
1932
- }
1933
- const artifactId = requiredFlag(flags, "--id");
1934
- const artifact = registry.artifacts.find((item) => (item.artifact_id || item.id) === artifactId);
1935
- if (!artifact) throw new Error(`Artifact is not registered: ${artifactId}`);
1936
- const status = artifactStatus(flags);
1937
- const evidence = repeatedFlag(flags, "--evidence");
1938
- if (!evidence.length) throw new Error("artifact status requires at least one --evidence reference.");
1939
- if (artifact.status === status) throw new Error(`Artifact already has status ${status}: ${artifactId}`);
1940
- if (!WORKING_DRAFT_STATUSES.has(artifact.status)) {
1941
- throw new Error(`Immutable artifact revision cannot change status: ${artifact.status}`);
1942
- }
1943
- if (WORKING_DRAFT_STATUSES.has(status)) {
1944
- throw new Error("Legacy draft migration must target an immutable semantic status.");
1945
- }
1946
-
1947
- const timestamp = (options.now || (() => new Date().toISOString()))();
1948
- const previous = artifact.status || "unknown";
1949
- artifact.status = status;
1950
- artifact.status_history = [
1951
- ...(Array.isArray(artifact.status_history) ? artifact.status_history : []),
1952
- { from: previous, to: status, evidence, changed_at: timestamp },
1953
- ];
1954
- registry.schema_version = 1;
1955
- registry.revision += 1;
1956
- registry.updated_at = timestamp;
1957
- const event = {
1958
- type: "artifact-status-changed",
1959
- occurred_at: timestamp,
1960
- artifact_id: artifactId,
1961
- from: previous,
1962
- to: status,
1963
- evidence,
1964
- };
1965
- commitFormalTransaction(files, [{
1966
- stream: "artifact-registry",
1967
- file: files.artifactRegistry,
1968
- value: registry,
1969
- event,
1970
- transaction: artifactRegistryTransaction(event, registry, expected, [artifact]),
1971
- }], [], options);
1972
- return {
1973
- command: "artifact status",
1974
- revision: registry.revision,
1975
- artifact,
1976
- artifact_registry: registry,
1977
- };
1978
- }, { waitForActive: true });
1979
- }
1980
-
1981
1817
  function recoverArtifactPrune(files, apply, options) {
1982
1818
  if (!fs.existsSync(files.pruneRuntime)) return 0;
1983
1819
  const pending = fs.readdirSync(files.pruneRuntime, { withFileTypes: true })
@@ -2039,490 +1875,6 @@ function recoverArtifactPrune(files, apply, options) {
2039
1875
  return recovered;
2040
1876
  }
2041
1877
 
2042
- function legacyArtifactHistoryArchive(files, trusted, options) {
2043
- verifyFormalState(files, options, { history: true });
2044
- const anchoredRevision = trusted.anchor.streams["artifact-registry"].revision;
2045
- const shardDirectory = path.join(files.eventShards, "artifact-registry");
2046
- const legacyShards = fs.existsSync(shardDirectory)
2047
- ? fs.readdirSync(shardDirectory)
2048
- .filter((name) => /\.ya?ml$/i.test(name))
2049
- .map((name) => ({
2050
- name,
2051
- file: path.join(shardDirectory, name),
2052
- record: readEventShardRecord(files, options, "artifact-registry", name),
2053
- }))
2054
- .filter(({ record }) => record.after_value && record.revision <= anchoredRevision)
2055
- .sort((left, right) => left.record.revision - right.record.revision)
2056
- : [];
2057
- if (!legacyShards.length) {
2058
- throw new Error("Legacy artifact history compact requires anchored full-snapshot shards.");
2059
- }
2060
- const replayed = replayStreamRecords(
2061
- "artifact-registry",
2062
- legacyShards.map(({ record }) => record),
2063
- options,
2064
- );
2065
- const anchoredRegistry = trusted.snapshots["artifact-registry"];
2066
- if (JSON.stringify(stableValue(normalizeArtifactRegistry(replayed)))
2067
- !== JSON.stringify(stableValue(normalizeArtifactRegistry(anchoredRegistry)))) {
2068
- throw new Error("Legacy artifact history replay does not reproduce the anchored registry.");
2069
- }
2070
- const finalRecord = legacyShards.at(-1).record;
2071
- const anchoredChecksum = trusted.anchor.streams["artifact-registry"].view_checksum;
2072
- if (finalRecord.revision !== anchoredRevision || finalRecord.after_checksum !== anchoredChecksum) {
2073
- throw new Error("Legacy artifact history replay does not reproduce the anchored registry checksum.");
2074
- }
2075
-
2076
- const digest = crypto.createHash("sha256");
2077
- const entries = legacyShards.map(({ name, file, record }) => {
2078
- const content = fs.readFileSync(file);
2079
- digest.update(name);
2080
- digest.update("\0");
2081
- digest.update(content);
2082
- digest.update("\0");
2083
- return {
2084
- path: name,
2085
- revision: record.revision,
2086
- size_bytes: content.length,
2087
- sha256: sha256(content),
2088
- content_base64: content.toString("base64"),
2089
- };
2090
- });
2091
- const sourceHistoryRootDigest = digest.digest("hex");
2092
- const coverage = {
2093
- first_revision: legacyShards[0].record.revision,
2094
- last_revision: finalRecord.revision,
2095
- event_count: legacyShards.length,
2096
- };
2097
- const archiveId = trusted.anchor.anchor_id;
2098
- const bundle = stableValue({
2099
- schema_version: 1,
2100
- archive_id: archiveId,
2101
- stream: "artifact-registry",
2102
- source_root: portableReference(files, shardDirectory),
2103
- source_history_root_digest: sourceHistoryRootDigest,
2104
- source_history_root_digest_algorithm: "sha256-path-null-bytes-null-v1",
2105
- coverage,
2106
- anchor: {
2107
- anchor_id: trusted.anchor.anchor_id,
2108
- anchor_checksum: trusted.anchor.anchor_checksum,
2109
- registry_revision: anchoredRevision,
2110
- registry_checksum: anchoredChecksum,
2111
- },
2112
- entries,
2113
- });
2114
- const archiveBytes = zlib.gzipSync(Buffer.from(`${JSON.stringify(bundle, null, 2)}\n`), {
2115
- level: 9,
2116
- mtime: 0,
2117
- });
2118
- const archiveFile = path.join(files.artifactHistoryArchives, `${archiveId}.json.gz`);
2119
- const manifestFile = path.join(files.artifactHistoryArchives, `${archiveId}.yaml`);
2120
- const looseBytes = entries.reduce((total, entry) => total + entry.size_bytes, 0);
2121
- const compactedAt = (options.now || (() => new Date().toISOString()))();
2122
- const manifestBase = stableValue({
2123
- schema_version: 1,
2124
- archive_id: archiveId,
2125
- stream: "artifact-registry",
2126
- anchor_id: trusted.anchor.anchor_id,
2127
- anchor_checksum: trusted.anchor.anchor_checksum,
2128
- registry_revision: anchoredRevision,
2129
- registry_checksum: anchoredChecksum,
2130
- source_root: bundle.source_root,
2131
- source_history_root_digest: sourceHistoryRootDigest,
2132
- source_history_root_digest_algorithm: bundle.source_history_root_digest_algorithm,
2133
- coverage,
2134
- loose_file_count: entries.length,
2135
- loose_bytes: looseBytes,
2136
- archive_format: "gzip-json-v1",
2137
- archive_path: portableReference(files, archiveFile),
2138
- archive_checksum: sha256(archiveBytes),
2139
- compressed_bundle_bytes: archiveBytes.length,
2140
- compacted_at: compactedAt,
2141
- });
2142
- const manifest = {
2143
- ...manifestBase,
2144
- manifest_checksum: sha256(options.yaml.stringify(manifestBase)),
2145
- };
2146
- const manifestContent = options.yaml.stringify(stableValue(manifest));
2147
- const archiveStorageBytes = archiveBytes.length + Buffer.byteLength(manifestContent);
2148
- return {
2149
- bundle,
2150
- archiveBytes,
2151
- archiveFile,
2152
- entries,
2153
- manifest,
2154
- manifestContent,
2155
- manifestFile,
2156
- report: {
2157
- command: "compact",
2158
- anchor_id: trusted.anchor.anchor_id,
2159
- anchor_checksum: trusted.anchor.anchor_checksum,
2160
- source_history_root_digest: sourceHistoryRootDigest,
2161
- coverage,
2162
- loose_file_count: entries.length,
2163
- loose_bytes: looseBytes,
2164
- compressed_bundle_bytes: archiveBytes.length,
2165
- manifest_bytes: Buffer.byteLength(manifestContent),
2166
- archive_bytes: archiveStorageBytes,
2167
- expected_freed_bytes: looseBytes - archiveStorageBytes,
2168
- archive_path: portableReference(files, archiveFile),
2169
- manifest_path: portableReference(files, manifestFile),
2170
- },
2171
- };
2172
- }
2173
-
2174
- function readArtifactHistoryArchives(files, options) {
2175
- assertPathInside(files.root, files.artifactHistoryArchives);
2176
- if (!fs.existsSync(files.artifactHistoryArchives)) return [];
2177
- return fs.readdirSync(files.artifactHistoryArchives)
2178
- .filter((name) => /\.ya?ml$/i.test(name))
2179
- .sort()
2180
- .map((name) => {
2181
- const manifestFile = path.join(files.artifactHistoryArchives, name);
2182
- const manifest = readYaml(manifestFile, options.yaml, `artifact history archive manifest ${name}`);
2183
- const archiveId = historyAnchorId(manifest.archive_id);
2184
- const expectedManifestName = `${archiveId}.yaml`;
2185
- const archiveFile = path.join(files.artifactHistoryArchives, `${archiveId}.json.gz`);
2186
- const coverage = manifest.coverage;
2187
- if (name !== expectedManifestName
2188
- || manifest.schema_version !== 1
2189
- || manifest.stream !== "artifact-registry"
2190
- || manifest.anchor_id !== archiveId
2191
- || manifest.archive_format !== "gzip-json-v1"
2192
- || manifest.archive_path !== portableReference(files, archiveFile)
2193
- || !/^[a-f0-9]{64}$/.test(manifest.anchor_checksum || "")
2194
- || !/^[a-f0-9]{64}$/.test(manifest.registry_checksum || "")
2195
- || !/^[a-f0-9]{64}$/.test(manifest.source_history_root_digest || "")
2196
- || manifest.source_history_root_digest_algorithm !== "sha256-path-null-bytes-null-v1"
2197
- || !/^[a-f0-9]{64}$/.test(manifest.archive_checksum || "")
2198
- || !Number.isSafeInteger(manifest.registry_revision)
2199
- || manifest.registry_revision < 1
2200
- || !Number.isSafeInteger(coverage?.first_revision)
2201
- || !Number.isSafeInteger(coverage?.last_revision)
2202
- || !Number.isSafeInteger(coverage?.event_count)
2203
- || coverage.first_revision < 1
2204
- || coverage.last_revision < coverage.first_revision
2205
- || coverage.event_count !== coverage.last_revision - coverage.first_revision + 1
2206
- || coverage.last_revision !== manifest.registry_revision
2207
- || !Number.isSafeInteger(manifest.loose_file_count)
2208
- || manifest.loose_file_count !== coverage.event_count
2209
- || !Number.isSafeInteger(manifest.loose_bytes)
2210
- || manifest.loose_bytes < 0
2211
- || !Number.isSafeInteger(manifest.compressed_bundle_bytes)
2212
- || manifest.compressed_bundle_bytes < 1
2213
- || structuredDocumentChecksum(manifest, "manifest_checksum", options.yaml.stringify)
2214
- !== manifest.manifest_checksum
2215
- || !fs.existsSync(archiveFile)
2216
- || fs.statSync(archiveFile).size !== manifest.compressed_bundle_bytes
2217
- || sha256(fs.readFileSync(archiveFile)) !== manifest.archive_checksum) {
2218
- throw new Error(`Artifact history archive manifest is invalid: ${name}.`);
2219
- }
2220
- let bundle;
2221
- try {
2222
- bundle = JSON.parse(zlib.gunzipSync(fs.readFileSync(archiveFile)).toString("utf8"));
2223
- } catch {
2224
- throw new Error(`Artifact history archive bundle is invalid: ${archiveId}.`);
2225
- }
2226
- if (bundle?.schema_version !== 1
2227
- || bundle.archive_id !== archiveId
2228
- || bundle.stream !== "artifact-registry"
2229
- || bundle.source_root !== manifest.source_root
2230
- || bundle.source_history_root_digest !== manifest.source_history_root_digest
2231
- || bundle.source_history_root_digest_algorithm !== manifest.source_history_root_digest_algorithm
2232
- || JSON.stringify(bundle.coverage) !== JSON.stringify(manifest.coverage)
2233
- || bundle.anchor?.anchor_id !== manifest.anchor_id
2234
- || bundle.anchor?.anchor_checksum !== manifest.anchor_checksum
2235
- || bundle.anchor?.registry_revision !== manifest.registry_revision
2236
- || bundle.anchor?.registry_checksum !== manifest.registry_checksum
2237
- || !Array.isArray(bundle.entries)
2238
- || bundle.entries.length !== manifest.loose_file_count) {
2239
- throw new Error(`Artifact history archive bundle metadata is invalid: ${archiveId}.`);
2240
- }
2241
- const digest = crypto.createHash("sha256");
2242
- const names = new Set();
2243
- let entryBytes = 0;
2244
- const records = bundle.entries.map((entry, index) => {
2245
- if (!/^[^/\\]+\.ya?ml$/i.test(entry?.path || "") || names.has(entry.path)) {
2246
- throw new Error(`Artifact history archive entry path is invalid: ${archiveId}.`);
2247
- }
2248
- names.add(entry.path);
2249
- if (!Number.isSafeInteger(entry.size_bytes) || entry.size_bytes < 0
2250
- || !Number.isSafeInteger(entry.revision)
2251
- || entry.revision !== coverage.first_revision + index) {
2252
- throw new Error(`Artifact history archive entry metadata is invalid: ${archiveId}/${entry.path}.`);
2253
- }
2254
- const content = Buffer.from(String(entry.content_base64 || ""), "base64");
2255
- if (content.length !== entry.size_bytes || sha256(content) !== entry.sha256) {
2256
- throw new Error(`Artifact history archive entry checksum is invalid: ${archiveId}/${entry.path}.`);
2257
- }
2258
- entryBytes += entry.size_bytes;
2259
- digest.update(entry.path);
2260
- digest.update("\0");
2261
- digest.update(content);
2262
- digest.update("\0");
2263
- let record;
2264
- try {
2265
- record = options.yaml.parse(content.toString("utf8"));
2266
- } catch {
2267
- throw new Error(`Artifact history archive entry is unreadable: ${archiveId}/${entry.path}.`);
2268
- }
2269
- const validated = validateEventShardRecord(record, "artifact-registry", entry.path);
2270
- if (validated.revision !== entry.revision) {
2271
- throw new Error(`Artifact history archive entry revision is invalid: ${archiveId}/${entry.path}.`);
2272
- }
2273
- return validated;
2274
- });
2275
- if (digest.digest("hex") !== manifest.source_history_root_digest
2276
- || entryBytes !== manifest.loose_bytes
2277
- || records.length !== manifest.coverage?.event_count
2278
- || records[0]?.revision !== manifest.coverage?.first_revision
2279
- || records.at(-1)?.revision !== manifest.coverage?.last_revision
2280
- || records.some((record, index) => record.revision !== coverage.first_revision + index)) {
2281
- throw new Error(`Artifact history archive coverage is invalid: ${archiveId}.`);
2282
- }
2283
- const replayed = replayStreamRecords("artifact-registry", records, options);
2284
- const anchorFile = path.join(files.historySnapshots, archiveId, "anchor.yaml");
2285
- const snapshotFile = path.join(files.historySnapshots, archiveId, "artifact-registry.yaml");
2286
- const anchor = readYaml(anchorFile, options.yaml, `artifact history archive anchor ${archiveId}`);
2287
- const snapshot = normalizeArtifactRegistry(readYaml(
2288
- snapshotFile,
2289
- options.yaml,
2290
- `artifact history archive snapshot ${archiveId}`,
2291
- ));
2292
- if (anchor.anchor_checksum !== manifest.anchor_checksum
2293
- || structuredDocumentChecksum(anchor, "anchor_checksum", options.yaml.stringify)
2294
- !== anchor.anchor_checksum
2295
- || anchor.streams?.["artifact-registry"]?.revision !== manifest.registry_revision
2296
- || anchor.streams?.["artifact-registry"]?.view_checksum !== manifest.registry_checksum
2297
- || sha256(fs.readFileSync(snapshotFile)) !== manifest.registry_checksum
2298
- || JSON.stringify(stableValue(normalizeArtifactRegistry(replayed)))
2299
- !== JSON.stringify(stableValue(snapshot))) {
2300
- throw new Error(`Artifact history archive replay does not match its anchor: ${archiveId}.`);
2301
- }
2302
- return { manifest, manifestFile, archiveFile, bundle };
2303
- });
2304
- }
2305
-
2306
- function restoreCompactionStaging(files, directory, plan) {
2307
- const shardDirectory = path.join(files.eventShards, "artifact-registry");
2308
- fs.mkdirSync(shardDirectory, { recursive: true });
2309
- for (const entry of plan.entries) {
2310
- const staged = path.join(directory, entry.path);
2311
- if (!fs.existsSync(staged)) continue;
2312
- if (sha256(fs.readFileSync(staged)) !== entry.sha256) {
2313
- throw new Error(`Artifact history compact staged shard checksum mismatch: ${entry.path}.`);
2314
- }
2315
- const destination = path.join(shardDirectory, entry.path);
2316
- if (fs.existsSync(destination)) {
2317
- if (sha256(fs.readFileSync(destination)) !== entry.sha256) {
2318
- throw new Error(`Artifact history compact recovery destination conflicts: ${entry.path}.`);
2319
- }
2320
- fs.rmSync(staged, { force: true });
2321
- } else {
2322
- fs.renameSync(staged, destination);
2323
- }
2324
- }
2325
- }
2326
-
2327
- function recoverArtifactHistoryCompaction(files, apply, options) {
2328
- if (!fs.existsSync(files.compactRuntime)) return 0;
2329
- const pending = fs.readdirSync(files.compactRuntime, { withFileTypes: true })
2330
- .filter((entry) => entry.isDirectory())
2331
- .map((entry) => path.join(files.compactRuntime, entry.name));
2332
- if (pending.length && !apply) {
2333
- throw new Error("An interrupted artifact history compact requires explicit apply recovery.");
2334
- }
2335
- let recovered = 0;
2336
- for (const directory of pending) {
2337
- const planFile = path.join(directory, "plan.json");
2338
- if (!fs.existsSync(planFile)) {
2339
- const interruptedPlanWrites = fs.readdirSync(directory, { withFileTypes: true });
2340
- if (interruptedPlanWrites.every((entry) =>
2341
- entry.isFile() && /^plan\.json\.\d+\.\d+\.tmp$/.test(entry.name))) {
2342
- fs.rmSync(directory, { recursive: true, force: true });
2343
- recovered += 1;
2344
- continue;
2345
- }
2346
- throw new Error(`Artifact history compact recovery plan is invalid: ${directory}.`);
2347
- }
2348
- let plan;
2349
- try {
2350
- plan = JSON.parse(fs.readFileSync(planFile, "utf8"));
2351
- } catch {
2352
- throw new Error(`Artifact history compact recovery plan is invalid: ${directory}.`);
2353
- }
2354
- if (plan?.schema_version !== 1 || !plan.archive_id
2355
- || !/^[a-f0-9]{64}$/.test(plan.archive_checksum || "")
2356
- || !Array.isArray(plan.entries) || !plan.entries.length
2357
- || plan.entries.some((entry) => !/^[^/\\]+\.ya?ml$/i.test(entry?.path || "")
2358
- || !/^[a-f0-9]{64}$/.test(entry?.sha256 || ""))) {
2359
- throw new Error(`Artifact history compact recovery plan is invalid: ${directory}.`);
2360
- }
2361
- const plannedEntries = new Map(plan.entries.map((entry) => [entry.path, entry.sha256]));
2362
- const stagedEntries = fs.readdirSync(directory, { withFileTypes: true })
2363
- .filter((entry) => entry.name !== "plan.json");
2364
- if (plannedEntries.size !== plan.entries.length
2365
- || stagedEntries.some((entry) => !entry.isFile() || !plannedEntries.has(entry.name))) {
2366
- throw new Error(`Artifact history compact recovery plan is invalid: ${directory}.`);
2367
- }
2368
- const changedStagedEntry = stagedEntries.find((entry) =>
2369
- sha256(fs.readFileSync(path.join(directory, entry.name))) !== plannedEntries.get(entry.name));
2370
- if (changedStagedEntry) {
2371
- throw new Error(`Artifact history compact staged shard checksum mismatch: ${changedStagedEntry.name}.`);
2372
- }
2373
- let archive = null;
2374
- try {
2375
- archive = readArtifactHistoryArchives(files, options)
2376
- .find(({ manifest }) => manifest.archive_id === plan.archive_id) || null;
2377
- } catch {
2378
- archive = null;
2379
- }
2380
- const archivedEntries = new Map(
2381
- (archive?.bundle.entries || []).map((entry) => [entry.path, entry.sha256]),
2382
- );
2383
- const archiveMatches = archive?.manifest.archive_checksum === plan.archive_checksum
2384
- && plan.entries.every((entry) => archivedEntries.get(entry.path) === entry.sha256);
2385
- if (!archiveMatches) {
2386
- restoreCompactionStaging(files, directory, plan);
2387
- fs.rmSync(directory, { recursive: true, force: true });
2388
- throw new Error(`Artifact history compact recovery plan does not match its verified archive: ${plan.archive_id}.`);
2389
- }
2390
- fs.rmSync(directory, { recursive: true, force: true });
2391
- recovered += 1;
2392
- }
2393
- return recovered;
2394
- }
2395
-
2396
- function removeCompactedLooseEntries(files, archiveId, archiveChecksum, entries) {
2397
- if (!entries.length) return;
2398
- const stagingDirectory = path.join(files.compactRuntime, crypto.randomUUID());
2399
- fs.mkdirSync(stagingDirectory, { recursive: true });
2400
- atomicWrite(path.join(stagingDirectory, "plan.json"), `${JSON.stringify({
2401
- schema_version: 1,
2402
- archive_id: archiveId,
2403
- archive_checksum: archiveChecksum,
2404
- entries: entries.map((entry) => ({ path: entry.path, sha256: entry.sha256 })),
2405
- }, null, 2)}\n`);
2406
- const shardDirectory = path.join(files.eventShards, "artifact-registry");
2407
- try {
2408
- for (const entry of entries) {
2409
- const source = path.join(shardDirectory, entry.path);
2410
- if (sha256(fs.readFileSync(source)) !== entry.sha256) {
2411
- throw new Error(`Artifact history shard changed during compact: ${entry.path}.`);
2412
- }
2413
- fs.renameSync(source, path.join(stagingDirectory, entry.path));
2414
- }
2415
- fs.rmSync(stagingDirectory, { recursive: true, force: true });
2416
- } catch (error) {
2417
- restoreCompactionStaging(files, stagingDirectory, { entries });
2418
- fs.rmSync(stagingDirectory, { recursive: true, force: true });
2419
- throw error;
2420
- }
2421
- }
2422
-
2423
- function compactArtifactHistory(files, flags, options) {
2424
- const apply = flags.has("--apply");
2425
- const action = () => {
2426
- assertPathInside(files.root, files.artifactHistoryArchives);
2427
- assertPathInside(files.root, files.compactRuntime);
2428
- assertPathInside(files.root, path.join(files.eventShards, "artifact-registry"));
2429
- const recoveredCompactionCount = recoverArtifactHistoryCompaction(files, apply, options);
2430
- const trusted = readLatestHistoryAnchor(files, options);
2431
- if (!trusted) throw new Error("Legacy artifact history compact requires a trusted history anchor.");
2432
- const existingArchive = readArtifactHistoryArchives(files, options)
2433
- .find(({ manifest }) => manifest.archive_id === trusted.anchor.anchor_id);
2434
- if (existingArchive) {
2435
- verifyFormalState(files, options, { history: true });
2436
- const shardDirectory = path.join(files.eventShards, "artifact-registry");
2437
- const looseEntries = existingArchive.bundle.entries.filter((entry) => {
2438
- const looseFile = path.join(shardDirectory, entry.path);
2439
- if (!fs.existsSync(looseFile)) return false;
2440
- if (sha256(fs.readFileSync(looseFile)) !== entry.sha256) {
2441
- throw new Error(`Artifact history loose shard conflicts with verified archive: ${entry.path}.`);
2442
- }
2443
- return true;
2444
- });
2445
- const looseBytes = looseEntries.reduce((total, entry) => total + entry.size_bytes, 0);
2446
- const manifestBytes = fs.statSync(existingArchive.manifestFile).size;
2447
- const result = {
2448
- command: "compact",
2449
- anchor_id: existingArchive.manifest.anchor_id,
2450
- anchor_checksum: existingArchive.manifest.anchor_checksum,
2451
- source_history_root_digest: existingArchive.manifest.source_history_root_digest,
2452
- coverage: existingArchive.manifest.coverage,
2453
- loose_file_count: looseEntries.length,
2454
- loose_bytes: looseBytes,
2455
- compressed_bundle_bytes: existingArchive.manifest.compressed_bundle_bytes,
2456
- manifest_bytes: manifestBytes,
2457
- archive_bytes: existingArchive.manifest.compressed_bundle_bytes + manifestBytes,
2458
- expected_freed_bytes: looseBytes,
2459
- archive_path: existingArchive.manifest.archive_path,
2460
- manifest_path: portableReference(files, existingArchive.manifestFile),
2461
- mode: apply ? "apply" : "dry-run",
2462
- applied: false,
2463
- deduplicated: true,
2464
- recovered_compaction_count: recoveredCompactionCount,
2465
- };
2466
- if (!apply || !looseEntries.length) return result;
2467
- removeCompactedLooseEntries(
2468
- files,
2469
- trusted.anchor.anchor_id,
2470
- existingArchive.manifest.archive_checksum,
2471
- looseEntries,
2472
- );
2473
- return { ...result, applied: true };
2474
- }
2475
- const plan = legacyArtifactHistoryArchive(files, trusted, options);
2476
- const result = {
2477
- ...plan.report,
2478
- mode: apply ? "apply" : "dry-run",
2479
- applied: false,
2480
- deduplicated: false,
2481
- recovered_compaction_count: recoveredCompactionCount,
2482
- };
2483
- if (!apply) return result;
2484
- if (fs.existsSync(plan.manifestFile)) {
2485
- throw new Error(`Artifact history archive already exists: ${trusted.anchor.anchor_id}.`);
2486
- }
2487
- fs.mkdirSync(files.artifactHistoryArchives, { recursive: true });
2488
- const orphanArchiveExists = fs.existsSync(plan.archiveFile);
2489
- if (orphanArchiveExists
2490
- && (fs.statSync(plan.archiveFile).size !== plan.archiveBytes.length
2491
- || sha256(fs.readFileSync(plan.archiveFile)) !== plan.manifest.archive_checksum)) {
2492
- throw new Error(`Artifact history archive conflicts with the compact plan: ${trusted.anchor.anchor_id}.`);
2493
- }
2494
- let archiveWritten = false;
2495
- let manifestWritten = false;
2496
- try {
2497
- if (!orphanArchiveExists) {
2498
- atomicWrite(plan.archiveFile, plan.archiveBytes);
2499
- archiveWritten = true;
2500
- }
2501
- atomicWrite(plan.manifestFile, plan.manifestContent);
2502
- manifestWritten = true;
2503
- const verified = readArtifactHistoryArchives(files, options);
2504
- if (!verified.some(({ manifest }) => manifest.archive_id === trusted.anchor.anchor_id)) {
2505
- throw new Error(`Artifact history archive verification failed: ${trusted.anchor.anchor_id}.`);
2506
- }
2507
- } catch (error) {
2508
- if (manifestWritten) fs.rmSync(plan.manifestFile, { force: true });
2509
- if (archiveWritten) fs.rmSync(plan.archiveFile, { force: true });
2510
- throw error;
2511
- }
2512
-
2513
- removeCompactedLooseEntries(
2514
- files,
2515
- trusted.anchor.anchor_id,
2516
- plan.manifest.archive_checksum,
2517
- plan.entries,
2518
- );
2519
- return { ...result, applied: true };
2520
- };
2521
- return apply
2522
- ? withLock(files, "artifact-history-compact", action)
2523
- : withZeroWriteFormalState(files, action);
2524
- }
2525
-
2526
1878
  function pruneArtifactHistory(files, flags, options) {
2527
1879
  const apply = flags.has("--apply");
2528
1880
  const action = () => {
@@ -2621,7 +1973,7 @@ function pruneArtifactHistory(files, flags, options) {
2621
1973
  }
2622
1974
 
2623
1975
  function allowedCompletionStatuses(node) {
2624
- if (node === "06-version-closeout" || node === "12-version-closeout") {
1976
+ if (node === "06-version-closeout") {
2625
1977
  return new Set(["release-ready"]);
2626
1978
  }
2627
1979
  if (node === "R08-production-deployment") return new Set(["deployed"]);
@@ -2632,9 +1984,8 @@ function allowedCompletionStatuses(node) {
2632
1984
 
2633
1985
  function nodeModel(node) {
2634
1986
  if (SEVEN_NODE_DEVELOPMENT_ID_SET.has(node)) return "development-seven-node";
2635
- if (LEGACY_DEVELOPMENT_ID_SET.has(node)) return "development-legacy-13";
2636
1987
  if (RELEASE_NODE_ID_SET.has(node)) return "release-r00-r10";
2637
- return "legacy-production-read-only";
1988
+ return "unknown";
2638
1989
  }
2639
1990
 
2640
1991
  function nodeContractCatalog() {
@@ -2730,7 +2081,6 @@ function persistScopeMutation(files, context, options) {
2730
2081
  const handoffMutation = artifactHandoffMutation(files, transition, timestamp, options);
2731
2082
  if (handoffMutation) mutations.push(handoffMutation);
2732
2083
  commitFormalTransaction(files, mutations, extraWrites, options);
2733
- exposeLegacyScopeAliases(state);
2734
2084
  }
2735
2085
 
2736
2086
  function scopeManifestFromFlags(flags, scope, timestamp, amendment) {
@@ -2782,7 +2132,7 @@ function mutateScopeManifest(files, flags, options, amendment = false) {
2782
2132
  && ![...SCOPE_FREEZE_NODES].some((node) =>
2783
2133
  scope.active_nodes.includes(node) || scope.completed_nodes.includes(node))) {
2784
2134
  throw new Error(
2785
- "scope freeze requires Delivery Readiness (03-delivery-readiness or legacy 07-iteration-planning) to be active or completed.",
2135
+ "scope freeze requires Delivery Readiness (03-delivery-readiness) to be active or completed.",
2786
2136
  );
2787
2137
  }
2788
2138
  const timestamp = (options.now || (() => new Date().toISOString()))();
@@ -2881,7 +2231,7 @@ function recordScopeWarning(files, flags, options) {
2881
2231
  function completionReceiptFromProjection(scope, node, projection, evidence, timestamp) {
2882
2232
  const manifest = currentScopeManifest(scope);
2883
2233
  const receipt = stableValue({
2884
- receipt_id: `RECEIPT-${sha256(`${scope.scope_id}:${node}:${projection.node_attempt}:${manifest?.manifest_id || "legacy"}`).slice(0, 24)}`,
2234
+ receipt_id: `RECEIPT-${sha256(`${scope.scope_id}:${node}:${projection.node_attempt}:${manifest?.manifest_id || "none"}`).slice(0, 24)}`,
2885
2235
  iteration_id: scope.scope_id,
2886
2236
  node,
2887
2237
  node_attempt: projection.node_attempt,
@@ -2958,13 +2308,13 @@ function transitionWorkflow(files, flags, options) {
2958
2308
  const registry = normalizeArtifactRegistry(readYaml(files.artifactRegistry, options.yaml, "artifact-registry.yaml"));
2959
2309
  const artifact = registry.artifacts.find((item) => (item.artifact_id || item.id) === artifactId);
2960
2310
  if (!artifact) throw new Error(`Selected artifact is not registered: ${artifactId}`);
2961
- assertNotLegacyDevelopmentNode(artifact.node);
2311
+ assertWritableNode(artifact.node);
2962
2312
  if (artifactSuperseder(registry, artifact)) {
2963
2313
  throw new Error(`Selected artifact is superseded: ${artifactId}`);
2964
2314
  }
2965
2315
  const expectedNodes = event === "candidate-selected"
2966
- ? new Set(["04-implementation", "09-development", "R04-release-candidate"])
2967
- : new Set(["12-release-preparation", "R06-release-preparation"]);
2316
+ ? new Set(["04-implementation", "R04-release-candidate"])
2317
+ : new Set(["R06-release-preparation"]);
2968
2318
  if (!expectedNodes.has(artifact.node)) {
2969
2319
  throw new Error(`Selected artifact ${artifactId} belongs to ${artifact.node}, not ${[...expectedNodes].join(" or ")}.`);
2970
2320
  }
@@ -2975,8 +2325,7 @@ function transitionWorkflow(files, flags, options) {
2975
2325
  if (scope.scope_id !== iterationId) {
2976
2326
  throw new Error(`Iteration mismatch for ${scopeName} scope: state is ${scope.scope_id}, command is ${iterationId}.`);
2977
2327
  }
2978
- if (artifact.iteration_id && artifact.iteration_id !== iterationId
2979
- && !(event === "release-selected" && artifact.node === "12-release-preparation")) {
2328
+ if (artifact.iteration_id && artifact.iteration_id !== iterationId) {
2980
2329
  throw new Error(`Selected artifact ${artifactId} belongs to iteration ${artifact.iteration_id}, not ${iterationId}.`);
2981
2330
  }
2982
2331
  const allowed = event === "candidate-selected" ? new Set(["immutable"]) : new Set(["immutable", "approved"]);
@@ -2997,7 +2346,7 @@ function transitionWorkflow(files, flags, options) {
2997
2346
  const registry = normalizeArtifactRegistry(readYaml(files.artifactRegistry, options.yaml, "artifact-registry.yaml"));
2998
2347
  const artifact = registry.artifacts.find((item) => (item.artifact_id || item.id) === artifactId);
2999
2348
  if (!artifact) throw new Error(`Milestone artifact is not registered: ${artifactId}`);
3000
- assertNotLegacyDevelopmentNode(artifact.node);
2349
+ assertWritableNode(artifact.node);
3001
2350
  const expectedNodes = event === "version-closed"
3002
2351
  ? ["06-version-closeout"]
3003
2352
  : ["R10-release-archive"];
@@ -3033,10 +2382,7 @@ function transitionWorkflow(files, flags, options) {
3033
2382
  }
3034
2383
 
3035
2384
  const node = requiredFlag(flags, "--node");
3036
- if (!WRITABLE_NODE_IDS.has(node)) {
3037
- if (NODE_IDS.has(node)) throw new Error(`Legacy workflow node is read-only: ${node}`);
3038
- throw new Error(`Unknown workflow node: ${node}`);
3039
- }
2385
+ assertWritableNode(node);
3040
2386
  const scopeName = scopeNameForNode(node);
3041
2387
  assertWorkflowRevision(flags, state, scopeName);
3042
2388
  const scope = scopeState(state, scopeName);
@@ -3208,166 +2554,15 @@ function registeredEvidence(files, evidence, options, node = null) {
3208
2554
  });
3209
2555
  }
3210
2556
 
3211
- function retiredGateContractPlan(state, gateId) {
3212
- assertRetiredGateContract(gateId);
3213
- const affectedIterations = [];
3214
- const expectedScopeRevisions = {};
3215
- for (const [scopeName, scope] of [
3216
- ["development", state.development_state],
3217
- ["release", state.release_state],
3218
- ]) {
3219
- const requests = scope.pending_gates
3220
- .map((gate) => typeof gate === "string" ? { gate_id: gate, node: null } : gate)
3221
- .filter((gate) => gate?.gate_id === gateId);
3222
- if (!requests.length) continue;
3223
- expectedScopeRevisions[scopeName] = scope.revision;
3224
- for (const request of requests) {
3225
- affectedIterations.push({
3226
- iteration_id: request.iteration_id || scope.scope_id,
3227
- scope: scopeName,
3228
- node: request.node || null,
3229
- node_attempt: Number(request.node_attempt || scope.node_attempts[request.node] || 1),
3230
- scope_revision: scope.revision,
3231
- });
3232
- }
3233
- }
3234
- return {
3235
- command: "contract retire",
3236
- dry_run: true,
3237
- applied: false,
3238
- gate_id: gateId,
3239
- replacement_gate_id: gateId === "UX-LF" ? "UX-UI" : null,
3240
- fresh_approval_required: gateId === "UX-LF",
3241
- affected_iterations: affectedIterations,
3242
- expected_scope_revisions: expectedScopeRevisions,
3243
- };
3244
- }
3245
-
3246
- function retireGateContract(files, flags, options) {
3247
- const gateId = requiredFlag(flags, "--gate-id");
3248
- if (flags.has("--dry-run")) return withReadableFormalState(files, () => {
3249
- const state = normalizeWorkflowState(
3250
- readYaml(files.workflowState, options.yaml, "workflow-state.yaml"),
3251
- );
3252
- return retiredGateContractPlan(state, gateId);
3253
- });
3254
- return withLock(files, "workflow-state", () => {
3255
- const state = normalizeWorkflowState(
3256
- readYaml(files.workflowState, options.yaml, "workflow-state.yaml"),
3257
- );
3258
- const plan = retiredGateContractPlan(state, gateId);
3259
- if (!plan.affected_iterations.length) {
3260
- return { ...plan, dry_run: false, applied: false, receipts: [], workflow_state: state };
3261
- }
3262
- const affectedScopes = Object.keys(plan.expected_scope_revisions);
3263
- if (affectedScopes.length !== 1) {
3264
- throw new Error(`${gateId} retirement must affect exactly one formal scope per transaction.`);
3265
- }
3266
- const scopeName = affectedScopes[0];
3267
- assertWorkflowRevision(flags, state, scopeName);
3268
- const actor = requiredFlag(flags, "--actor");
3269
- if (actor !== "system:package-upgrade") {
3270
- throw new Error("Contract retirement must be executed by system:package-upgrade.");
3271
- }
3272
- const reason = requiredFlag(flags, "--reason");
3273
- const contractVersion = requiredFlag(flags, "--contract-version");
3274
- const scope = scopeState(state, scopeName);
3275
- const pending = scope.pending_gates
3276
- .map((gate) => typeof gate === "string" ? { gate_id: gate, node: null } : gate);
3277
- const retiredRequests = pending.filter((gate) => gate?.gate_id === gateId);
3278
- const timestamp = (options.now || (() => new Date().toISOString()))();
3279
- const receipts = retiredRequests.map((request) => {
3280
- const requestChecksum = sha256(JSON.stringify(stableValue(request)));
3281
- const receipt = stableValue({
3282
- receipt_id: `RETIRE-${gateId}-${requestChecksum.slice(0, 20).toUpperCase()}`,
3283
- gate_id: gateId,
3284
- iteration_id: request.iteration_id || scope.scope_id,
3285
- scope: scopeName,
3286
- node: request.node || null,
3287
- node_attempt: Number(request.node_attempt || scope.node_attempts[request.node] || 1),
3288
- request_checksum: requestChecksum,
3289
- request_evidence: [...new Set(request.evidence || [])].sort(),
3290
- contract_version: contractVersion,
3291
- disposition: "contract-retired",
3292
- human_approval_created: false,
3293
- renewed_gate_id: gateId === "UX-LF" ? "UX-UI" : null,
3294
- retired_by: actor,
3295
- reason,
3296
- retired_at: timestamp,
3297
- });
3298
- receipt.receipt_checksum = contractRetirementReceiptChecksum(receipt);
3299
- return receipt;
3300
- });
3301
- const remaining = pending.filter((gate) => gate?.gate_id !== gateId);
3302
- const existingUxUiRequests = remaining.filter((gate) => gate?.gate_id === "UX-UI");
3303
- const renewalSources = existingUxUiRequests.length ? existingUxUiRequests : retiredRequests;
3304
- const renewedRequests = gateId === "UX-LF"
3305
- ? renewalSources.map((request, index) => {
3306
- const retiredIndex = Math.max(0, retiredRequests.findIndex((candidate) =>
3307
- (candidate.iteration_id || scope.scope_id) === (request.iteration_id || scope.scope_id)));
3308
- const retiredRequest = retiredRequests[retiredIndex] || retiredRequests[index] || request;
3309
- const receipt = receipts[retiredIndex] || receipts[index];
3310
- return {
3311
- ...request,
3312
- gate_id: "UX-UI",
3313
- renewed_from_gate_id: gateId,
3314
- iteration_id: request.iteration_id || scope.scope_id,
3315
- scope: scopeName,
3316
- node: "02-solution-design",
3317
- node_attempt: Number(retiredRequest.node_attempt || request.node_attempt
3318
- || scope.node_attempts[retiredRequest.node] || 1),
3319
- evidence: [],
3320
- prototype_revision_required: true,
3321
- required_evidence_type: "prototype-revision",
3322
- retirement_receipt_checksum: receipt.receipt_checksum,
3323
- requested_at: request.requested_at || timestamp,
3324
- };
3325
- })
3326
- : [];
3327
- scope.pending_gates = [
3328
- ...remaining.filter((gate) => gate?.gate_id !== "UX-UI"),
3329
- ...renewedRequests,
3330
- ];
3331
- scope.contract_retirement_receipts = [
3332
- ...scope.contract_retirement_receipts,
3333
- ...receipts,
3334
- ];
3335
- const transition = {
3336
- event: "gate-contract-retired",
3337
- scope: scopeName,
3338
- gate_id: gateId,
3339
- iteration_id: scope.scope_id,
3340
- retirement_receipt_ids: receipts.map((receipt) => receipt.receipt_id),
3341
- renewed_gate_ids: renewedRequests.map((request) => request.gate_id),
3342
- occurred_at: timestamp,
3343
- };
3344
- persistScopeMutation(files, { state, scope, timestamp, transition }, options);
3345
- return {
3346
- ...plan,
3347
- dry_run: false,
3348
- applied: true,
3349
- receipts,
3350
- renewed_requests: renewedRequests,
3351
- revision: state.revision,
3352
- scope_revision: scope.revision,
3353
- workflow_state: state,
3354
- };
3355
- }, { waitForActive: flags.has("--expected-scope-revision") });
3356
- }
3357
-
3358
2557
  function requestGate(files, flags, options) {
3359
2558
  return withLock(files, "workflow-state", () => {
3360
2559
  const state = normalizeWorkflowState(
3361
2560
  readYaml(files.workflowState, options.yaml, "workflow-state.yaml"),
3362
2561
  );
3363
2562
  const gateId = requiredFlag(flags, "--gate-id");
3364
- assertGateContractWritable(gateId);
3365
2563
  const iterationId = requiredFlag(flags, "--iteration-id");
3366
2564
  const node = requiredFlag(flags, "--node");
3367
- if (!WRITABLE_NODE_IDS.has(node)) {
3368
- if (NODE_IDS.has(node)) throw new Error(`Legacy workflow node is read-only: ${node}`);
3369
- throw new Error(`Unknown workflow node: ${node}`);
3370
- }
2565
+ assertWritableNode(node);
3371
2566
  const scopeName = scopeNameForNode(node);
3372
2567
  assertWorkflowRevision(flags, state, scopeName);
3373
2568
  const scope = scopeState(state, scopeName);
@@ -3418,7 +2613,6 @@ function decideGate(files, flags, options) {
3418
2613
  readYaml(files.workflowState, options.yaml, "workflow-state.yaml"),
3419
2614
  );
3420
2615
  const gateId = requiredFlag(flags, "--gate-id");
3421
- assertGateContractWritable(gateId);
3422
2616
  const scopes = [state.development_state, state.release_state];
3423
2617
  let owningScope = null;
3424
2618
  let pending = [];
@@ -3435,7 +2629,7 @@ function decideGate(files, flags, options) {
3435
2629
  }
3436
2630
  }
3437
2631
  if (!request) throw new Error(`Gate is not pending: ${gateId}`);
3438
- assertNotLegacyDevelopmentNode(request.node);
2632
+ assertWritableNode(request.node);
3439
2633
  const scopeName = request.scope || scopeNameForNode(request.node);
3440
2634
  assertWorkflowRevision(flags, state, scopeName);
3441
2635
  const decision = requiredFlag(flags, "--decision").toLowerCase().replaceAll("_", "-");
@@ -3749,27 +2943,6 @@ function verifyScopeContracts(scope, scopeName, errors) {
3749
2943
  errors.push(`Node completion receipt ${receipt.receipt_id} retains blocking defects.`);
3750
2944
  }
3751
2945
  }
3752
- const retirementReceiptIds = new Set();
3753
- for (const receipt of scope.contract_retirement_receipts) {
3754
- if (!receipt?.receipt_id || retirementReceiptIds.has(receipt.receipt_id)) {
3755
- errors.push(`Workflow ${scopeName} contract retirement receipt ID is missing or duplicated.`);
3756
- continue;
3757
- }
3758
- retirementReceiptIds.add(receipt.receipt_id);
3759
- if (receipt.receipt_checksum !== contractRetirementReceiptChecksum(receipt)) {
3760
- errors.push(`Contract retirement receipt checksum mismatch: ${receipt.receipt_id}.`);
3761
- }
3762
- if (!RETIRED_GATE_IDS.has(receipt.gate_id)
3763
- || receipt.iteration_id !== scope.scope_id
3764
- || receipt.scope !== scopeName
3765
- || receipt.disposition !== "contract-retired"
3766
- || receipt.human_approval_created !== false
3767
- || receipt.retired_by !== "system:package-upgrade"
3768
- || !receipt.contract_version
3769
- || !/^[a-f0-9]{64}$/.test(receipt.request_checksum || "")) {
3770
- errors.push(`Contract retirement receipt is invalid: ${receipt.receipt_id}.`);
3771
- }
3772
- }
3773
2946
  if (scope.scope_contract_version === 1 && scope.current_scope_manifest_id) {
3774
2947
  const current = manifestsById.get(scope.current_scope_manifest_id);
3775
2948
  for (const node of scope.completed_nodes.filter((item) => SCOPED_COMPLETION_NODES.has(item))) {
@@ -3801,31 +2974,17 @@ function verifyFormalState(files, options, verification = {}) {
3801
2974
  const registry = views.get("artifact-registry").value;
3802
2975
  const errors = [];
3803
2976
  let retentionReceipts = [];
3804
- let historyArchives = [];
3805
2977
  try {
3806
2978
  retentionReceipts = readArtifactRetentionReceipts(files, options);
3807
2979
  } catch (error) {
3808
2980
  errors.push(error.message);
3809
2981
  }
3810
- if (verifyHistory) {
3811
- try {
3812
- historyArchives = readArtifactHistoryArchives(files, options);
3813
- } catch (error) {
3814
- errors.push(error.message);
3815
- }
3816
- }
3817
- const compactedHistory = verifyHistory && !sinceAnchor
3818
- && historyArchives.length > 0 && retentionReceipts.length === 0;
3819
2982
  const retainedHistory = verifyHistory && !sinceAnchor
3820
- && (retentionReceipts.length > 0 || compactedHistory);
2983
+ && retentionReceipts.length > 0;
3821
2984
  const verificationMode = verifyHistory
3822
2985
  ? (sinceAnchor
3823
2986
  ? "incremental-history"
3824
- : compactedHistory
3825
- ? "compacted-history"
3826
- : retainedHistory
3827
- ? "retained-history"
3828
- : "full-history")
2987
+ : retainedHistory ? "retained-history" : "full-history")
3829
2988
  : "routine";
3830
2989
  let trustedAnchor = null;
3831
2990
  if (sinceAnchor || !verifyHistory || retainedHistory) {
@@ -3897,7 +3056,7 @@ function verifyFormalState(files, options, verification = {}) {
3897
3056
  continue;
3898
3057
  }
3899
3058
  const indexedEvent = streamEvents.find((event) => event.revision === revision);
3900
- const { after_value: ignored, ...shardEvent } = revisionShards[0];
3059
+ const shardEvent = revisionShards[0];
3901
3060
  if (!indexedEvent?.record_checksum) delete shardEvent.record_checksum;
3902
3061
  if (!indexedEvent
3903
3062
  || JSON.stringify(stableValue(shardEvent)) !== JSON.stringify(stableValue(indexedEvent))) {
@@ -4007,7 +3166,6 @@ function verifyFormalState(files, options, verification = {}) {
4007
3166
  event_shard_count: shards.length,
4008
3167
  history_verified: verifyHistory,
4009
3168
  retention_receipt_count: retentionReceipts.length,
4010
- history_archive_count: historyArchives.length,
4011
3169
  };
4012
3170
  }
4013
3171
 
@@ -4017,10 +3175,7 @@ export function executeDeliveryStateCommand(argv, options = {}) {
4017
3175
  const command = positional[0];
4018
3176
  const projectRoot = path.resolve(String(flags.get("--project") || options.projectRoot || process.cwd()));
4019
3177
  const files = statePaths(projectRoot);
4020
- assertNoLegacyDevelopmentWrite(files, command, positional, flags, options);
4021
- if (command === "gate" && ["request", "decide"].includes(positional[1])) {
4022
- assertGateContractWritable(requiredFlag(flags, "--gate-id"));
4023
- }
3178
+ assertCurrentNodeInputs(files, command, positional, flags, options);
4024
3179
  const descriptor = lifecycleDescriptor(command, positional, flags);
4025
3180
  const before = dispatchLifecycle(options, files, descriptor, "before", flags);
4026
3181
  if (before.decision === "block") {
@@ -4032,18 +3187,12 @@ export function executeDeliveryStateCommand(argv, options = {}) {
4032
3187
  let result;
4033
3188
  if (command === "artifact" && positional[1] === "register") {
4034
3189
  result = registerArtifact(files, flags, options);
4035
- } else if (command === "artifact" && positional[1] === "status") {
4036
- result = changeArtifactStatus(files, flags, options);
4037
3190
  } else if (command === "artifact" && positional[1] === "prune") {
4038
3191
  result = pruneArtifactHistory(files, flags, options);
4039
- } else if (command === "artifact" && positional[1] === "compact") {
4040
- result = compactArtifactHistory(files, flags, options);
4041
3192
  } else if (command === "gate" && positional[1] === "request") {
4042
3193
  result = requestGate(files, flags, options);
4043
3194
  } else if (command === "gate" && positional[1] === "decide") {
4044
3195
  result = decideGate(files, flags, options);
4045
- } else if (command === "contract" && positional[1] === "retire") {
4046
- result = retireGateContract(files, flags, options);
4047
3196
  } else if (command === "scope" && positional[1] === "freeze") {
4048
3197
  result = mutateScopeManifest(files, flags, options, false);
4049
3198
  } else if (command === "scope" && positional[1] === "amend") {
@@ -4088,8 +3237,8 @@ export function executeDeliveryStateCommand(argv, options = {}) {
4088
3237
  });
4089
3238
  } else {
4090
3239
  throw new Error(
4091
- "Use inspect, verify, rebuild, snapshot create, artifact register/status/prune/compact, scope freeze/amend/warn, "
4092
- + "transition, gate request/decide, or contract retire.",
3240
+ "Use inspect, verify, rebuild, snapshot create, artifact register/prune, scope freeze/amend/warn, "
3241
+ + "transition, or gate request/decide.",
4093
3242
  );
4094
3243
  }
4095
3244
  dispatchLifecycle(options, files, descriptor, "after", flags, result);