agentic-workflow-manager 9.7.1 → 9.9.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 (107) hide show
  1. package/README.md +5 -1
  2. package/dist/src/commands/job/exec-wrapper.js +53 -3
  3. package/dist/src/commands/job/gate.js +18 -0
  4. package/dist/src/commands/job/index.js +61 -0
  5. package/dist/src/commands/model-policy/index.js +73 -0
  6. package/dist/src/commands/plan/index.js +182 -7
  7. package/dist/src/commands/sensors/bootstrap.js +36 -5
  8. package/dist/src/commands/sensors/compatibility/contract.js +19 -4
  9. package/dist/src/commands/sensors/compatibility/discovery.js +23 -3
  10. package/dist/src/commands/sensors/compatibility/live.js +18 -1
  11. package/dist/src/commands/sensors/compatibility/probe.js +8 -2
  12. package/dist/src/commands/sensors/compatibility/resolve.js +5 -2
  13. package/dist/src/commands/sensors/coverage/evidence.js +19 -10
  14. package/dist/src/commands/sensors/coverage/index.js +11 -2
  15. package/dist/src/commands/sensors/coverage/resolve.js +10 -0
  16. package/dist/src/commands/sensors/exec.js +16 -1
  17. package/dist/src/commands/sensors/migrate.js +62 -2
  18. package/dist/src/commands/sensors/prepare.js +2 -0
  19. package/dist/src/commands/sensors/result.js +2 -0
  20. package/dist/src/commands/sensors/run.js +64 -1
  21. package/dist/src/commands/sensors/status.js +6 -4
  22. package/dist/src/commands/watch/apply.js +32 -0
  23. package/dist/src/commands/watch/archive-unused.js +138 -0
  24. package/dist/src/commands/watch/generations.js +32 -9
  25. package/dist/src/commands/watch/index.js +108 -2
  26. package/dist/src/commands/watch/init.js +149 -18
  27. package/dist/src/commands/watch/supervisor.js +167 -13
  28. package/dist/src/commands/watch/teardown-driver.js +13 -13
  29. package/dist/src/commands/watch/track-state.js +59 -0
  30. package/dist/src/commands/watch/tracks.js +38 -102
  31. package/dist/src/core/admission/index.js +231 -0
  32. package/dist/src/core/admission/registry-contracts.js +290 -0
  33. package/dist/src/core/journal/fingerprint.js +35 -1
  34. package/dist/src/core/journal/paths.js +11 -0
  35. package/dist/src/core/journal/requests.js +1 -1
  36. package/dist/src/core/journal/store.js +146 -4
  37. package/dist/src/core/journal/types.js +57 -2
  38. package/dist/src/core/migration/historical-provenance.js +18 -0
  39. package/dist/src/core/migration/index.js +322 -0
  40. package/dist/src/core/model-policy/canonical.js +18 -0
  41. package/dist/src/core/model-policy/capabilities.js +197 -0
  42. package/dist/src/core/model-policy/capability-types.js +2 -0
  43. package/dist/src/core/model-policy/journal.js +124 -0
  44. package/dist/src/core/model-policy/paths.js +12 -0
  45. package/dist/src/core/model-policy/resolve.js +116 -0
  46. package/dist/src/core/model-policy/store.js +182 -0
  47. package/dist/src/core/model-policy/types.js +2 -0
  48. package/dist/src/core/model-policy/validate.js +112 -0
  49. package/dist/src/core/plan/identity.js +77 -0
  50. package/dist/src/core/plan/validate.js +191 -78
  51. package/dist/src/index.js +2 -0
  52. package/dist/tests/commands/job/exec-wrapper.test.js +16 -0
  53. package/dist/tests/commands/job/gate-reconcile.test.js +40 -10
  54. package/dist/tests/commands/job/gate.test.js +9 -0
  55. package/dist/tests/commands/job/routing-report-cli.test.js +46 -0
  56. package/dist/tests/commands/job/routing.test.js +11 -0
  57. package/dist/tests/commands/model-policy/index.test.js +57 -0
  58. package/dist/tests/commands/pin.test.js +23 -23
  59. package/dist/tests/commands/plan/index.test.js +682 -14
  60. package/dist/tests/commands/sensors/bootstrap.test.js +57 -4
  61. package/dist/tests/commands/sensors/compatibility/contract.test.js +21 -0
  62. package/dist/tests/commands/sensors/compatibility/discovery.test.js +19 -0
  63. package/dist/tests/commands/sensors/compatibility/probe.test.js +14 -1
  64. package/dist/tests/commands/sensors/compatibility/resolve.test.js +17 -0
  65. package/dist/tests/commands/sensors/coverage/evidence.test.js +51 -27
  66. package/dist/tests/commands/sensors/coverage/resolve.test.js +51 -0
  67. package/dist/tests/commands/sensors/exec.test.js +112 -0
  68. package/dist/tests/commands/sensors/run-changed.test.js +11 -0
  69. package/dist/tests/commands/sensors/run.test.js +15 -2
  70. package/dist/tests/commands/sensors/status.test.js +3 -3
  71. package/dist/tests/commands/watch/apply.test.js +12 -0
  72. package/dist/tests/commands/watch/e2e-crash.test.js +126 -11
  73. package/dist/tests/commands/watch/generations.test.js +113 -4
  74. package/dist/tests/commands/watch/plan-lifecycle.test.js +263 -0
  75. package/dist/tests/commands/watch/supervisor-loop.test.js +247 -49
  76. package/dist/tests/commands/watch/track-finalize.test.js +20 -3
  77. package/dist/tests/commands/watch/track-freeze.test.js +43 -30
  78. package/dist/tests/commands/watch/track-state-dependency.test.js +15 -0
  79. package/dist/tests/commands/watch/watch-init.test.js +244 -0
  80. package/dist/tests/core/admission/index.test.js +140 -0
  81. package/dist/tests/core/journal/fingerprint.test.js +22 -0
  82. package/dist/tests/core/journal/store.test.js +53 -0
  83. package/dist/tests/core/journal/types.test.js +7 -0
  84. package/dist/tests/core/migration/facts.test.js +289 -0
  85. package/dist/tests/core/migration/historical-provenance.test.js +25 -0
  86. package/dist/tests/core/model-policy/capabilities.test.js +365 -0
  87. package/dist/tests/core/model-policy/journal.test.js +46 -0
  88. package/dist/tests/core/model-policy/policy.test.js +47 -0
  89. package/dist/tests/core/model-policy/resolve.test.js +111 -0
  90. package/dist/tests/core/model-policy/store.test.js +177 -0
  91. package/dist/tests/core/plan/validate.test.js +360 -8
  92. package/dist/tests/core/secure-fs/native-bridge.test.js +81 -4
  93. package/dist/tests/integration/pack-e2e.test.js +4 -1
  94. package/dist/tests/integration/preflight-json-pipe.e2e.test.js +7 -3
  95. package/dist/tests/integration/sensor-bootstrap.e2e.test.js +37 -0
  96. package/dist/tests/integration/sensor-structured-run.e2e.test.js +12 -10
  97. package/dist/tests/providers/injection-config.test.js +14 -6
  98. package/dist/tests/structural/directory-fsync-is-single-source.test.js +97 -0
  99. package/dist/tests/structural/jest-environment-is-isolated.test.js +29 -24
  100. package/dist/tests/structural/native-release-artifacts.test.js +30 -0
  101. package/package.json +4 -3
  102. package/prebuilds/darwin-arm64/secure_fs.node +0 -0
  103. package/prebuilds/darwin-x64/secure_fs.node +0 -0
  104. package/prebuilds/linux-arm64/secure_fs.node +0 -0
  105. package/prebuilds/linux-x64/secure_fs.node +0 -0
  106. package/prebuilds/win32-arm64/secure_fs.node +0 -0
  107. package/prebuilds/win32-x64/secure_fs.node +0 -0
@@ -6,12 +6,16 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
6
6
  };
7
7
  Object.defineProperty(exports, "__esModule", { value: true });
8
8
  exports.GENERATION_STATES = exports.EXECUTION_STATES = void 0;
9
+ exports.isControllerRecoveryAction = isControllerRecoveryAction;
9
10
  exports.emptyState = emptyState;
10
11
  exports.isWellFormedState = isWellFormedState;
12
+ exports.isRoutingSelection = isRoutingSelection;
13
+ exports.isRoutingEnvelope = isRoutingEnvelope;
11
14
  exports.isWellFormedProcessRef = isWellFormedProcessRef;
12
15
  exports.isWellFormedJob = isWellFormedJob;
13
16
  const crypto_1 = __importDefault(require("crypto"));
14
17
  const types_1 = require("../tracks/types");
18
+ const paths_1 = require("./paths");
15
19
  exports.EXECUTION_STATES = [
16
20
  'received', 'spawn-intent', 'claimed', 'running',
17
21
  'exited', 'cancel-requested', 'cancelled', 'orphaned',
@@ -31,6 +35,14 @@ const COHORT_PHASES = [
31
35
  ];
32
36
  const _cohortPhasesComplete = true;
33
37
  const VERIFICATION_KINDS = ['test', 'lint', 'sensors', 'review', 'qa', 'interlock', 'track-integration'];
38
+ function isControllerRecoveryAction(x) {
39
+ return isObj(x) && Object.keys(x).length === 2
40
+ && x.schema === 'controller-recovery/v1'
41
+ && typeof x.kind === 'string'
42
+ && ['resume-cycle', 'resume-next-action', 'reconcile-active-jobs'].includes(x.kind);
43
+ }
44
+ const ROUTING_ROLES = ['implementer', 'specification-reviewer', 'code-quality-reviewer', 'final-reviewer', 'architecture', 'track-a-qa', 'track-b-qa', 'controller', 'documentation', 'retro', 'finishing'];
45
+ const UNAVAILABLE_EVIDENCE = ['modelOverride', 'effortOverride', 'observedModelEvidence'];
34
46
  function emptyState(branch) {
35
47
  return {
36
48
  schema: 1, revision: 0, journalId: `j-${crypto_1.default.randomUUID()}`, branch,
@@ -48,7 +60,13 @@ function isObj(x) {
48
60
  function isWellFormedState(x) {
49
61
  if (!isObj(x))
50
62
  return false;
51
- if (x.schema !== 1)
63
+ if (x.schema !== 1 && x.schema !== 2)
64
+ return false;
65
+ if (x.schema === 2 && !isWellFormedPlanBinding(x.planBinding))
66
+ return false;
67
+ if (x.schema === 1 && (x.planBinding !== undefined || x.planBindingHistory !== undefined))
68
+ return false;
69
+ if (x.planBindingHistory !== undefined && (!Array.isArray(x.planBindingHistory) || !x.planBindingHistory.every(isWellFormedPlanBinding)))
52
70
  return false;
53
71
  if (typeof x.revision !== 'number')
54
72
  return false;
@@ -74,6 +92,10 @@ function isWellFormedState(x) {
74
92
  return false;
75
93
  if (!isObj(x.appliedRequests) || !Object.values(x.appliedRequests).every(isWellFormedAppliedRequest))
76
94
  return false;
95
+ if (x.routingAttempts !== undefined && (!Array.isArray(x.routingAttempts) || x.routingAttempts.length > 4096 || !x.routingAttempts.every(isWellFormedRoutingAttempt)))
96
+ return false;
97
+ if (x.implementationLineages !== undefined && (!Array.isArray(x.implementationLineages) || x.implementationLineages.length > 4096 || !x.implementationLineages.every(isWellFormedImplementationLineage)))
98
+ return false;
77
99
  if (!Array.isArray(x.requestProblems) || !x.requestProblems.every(isWellFormedRequestProblem))
78
100
  return false;
79
101
  if (x.custodyDecisions !== undefined && (!Array.isArray(x.custodyDecisions) || !x.custodyDecisions.every(isWellFormedCustodyDecision)))
@@ -113,6 +135,24 @@ function isWellFormedState(x) {
113
135
  return false;
114
136
  return true;
115
137
  }
138
+ function isWellFormedPlanBinding(x) {
139
+ if (!(isObj(x)
140
+ && Object.keys(x).every(key => ['path', 'digest', 'schema', 'executionMode', 'boundAt', 'executionDigest', 'executionIdentitySchema'].includes(key))
141
+ && typeof x.path === 'string' && x.path.length > 0 && x.path.length <= 4096
142
+ && typeof x.digest === 'string' && /^[a-f0-9]{64}$/.test(x.digest)
143
+ && (x.executionDigest === undefined || (typeof x.executionDigest === 'string' && /^[a-f0-9]{64}$/.test(x.executionDigest)))
144
+ && (x.executionDigest === undefined ? x.executionIdentitySchema === undefined : x.executionIdentitySchema === 'awm-plan-execution/v1')
145
+ && (x.schema === 'compact-slices/v1' || x.schema === 'compact-slices/v2')
146
+ && x.executionMode === 'desatendido'
147
+ && typeof x.boundAt === 'string' && x.boundAt.length > 0))
148
+ return false;
149
+ try {
150
+ return (0, paths_1.bindingPlanPath)(x.path) === x.path;
151
+ }
152
+ catch {
153
+ return false;
154
+ }
155
+ }
116
156
  function isWellFormedNextAction(x) {
117
157
  return isObj(x) && typeof x.actionId === 'string' && typeof x.type === 'string' && typeof x.target === 'string'
118
158
  && strings(x.preconditions) && typeof x.attempt === 'number'
@@ -149,6 +189,8 @@ function isWellFormedGeneration(x) {
149
189
  && (x.spawnNonce === undefined || typeof x.spawnNonce === 'string')
150
190
  && (x.provider === undefined || typeof x.provider === 'string')
151
191
  && (x.resumePrompt === undefined || typeof x.resumePrompt === 'string')
192
+ && (x.resumeAction === undefined || isControllerRecoveryAction(x.resumeAction))
193
+ && (x.launchArgvDigest === undefined || (typeof x.launchArgvDigest === 'string' && /^[a-f0-9]{16}$/.test(x.launchArgvDigest)))
152
194
  && (x.processRef === undefined || isWellFormedProcessRef(x.processRef))
153
195
  && (x.wrapperRef === undefined || isWellFormedProcessRef(x.wrapperRef));
154
196
  }
@@ -156,7 +198,8 @@ function isWellFormedVerdict(x) {
156
198
  return isObj(x) && typeof x.id === 'string' && typeof x.obligationId === 'string'
157
199
  && ['pass', 'fail', 'inconclusive'].includes(String(x.result))
158
200
  && typeof x.detail === 'string' && typeof x.receivedAt === 'string'
159
- && typeof x.fingerprint === 'string' && strings(x.argv) && strings(x.paths) && typeof x.cwd === 'string';
201
+ && typeof x.fingerprint === 'string' && strings(x.argv) && strings(x.paths) && typeof x.cwd === 'string'
202
+ && (x.routingAttemptId === undefined || typeof x.routingAttemptId === 'string');
160
203
  }
161
204
  function isWellFormedFix(x) {
162
205
  return isObj(x) && typeof x.id === 'string' && typeof x.verdictId === 'string' && typeof x.closed === 'boolean';
@@ -167,6 +210,18 @@ function isWellFormedAppliedRequest(x) {
167
210
  && ['applied', 'rejected-stale-generation', 'rejected-digest-mismatch', 'rejected-secret'].includes(String(x.outcome))
168
211
  && (x.resultRef === undefined || typeof x.resultRef === 'string');
169
212
  }
213
+ function isWellFormedRoutingAttempt(x) {
214
+ return isObj(x) && Object.keys(x).every(key => ['schema', 'id', 'obligationId', 'lineageId', 'attempt', 'envelope', 'envelopeDigest', 'fingerprint', 'state', 'nativeAgentId', 'observed', 'verdict', 'reasonCode', 'administrativeRepair'].includes(key))
215
+ && x.schema === 'routing-attempt/v1'
216
+ && typeof x.id === 'string' && typeof x.obligationId === 'string' && typeof x.lineageId === 'string' && Number.isSafeInteger(x.attempt) && x.attempt > 0
217
+ && typeof x.envelopeDigest === 'string' && /^[a-f0-9]{64}$/.test(x.envelopeDigest) && typeof x.fingerprint === 'string' && /^[a-f0-9]{64}$/.test(x.fingerprint)
218
+ && ['reserved', 'active', 'complete', 'blocked', 'unknown'].includes(String(x.state)) && (x.nativeAgentId === undefined || typeof x.nativeAgentId === 'string') && (x.observed === undefined || isRoutingSelection(x.observed)) && (x.verdict === undefined || ['pass', 'fail', 'inconclusive'].includes(String(x.verdict))) && (x.reasonCode === undefined || typeof x.reasonCode === 'string') && (x.administrativeRepair === undefined || x.administrativeRepair === true) && isRoutingEnvelope(x.envelope);
219
+ }
220
+ function isDigest(value) { return typeof value === 'string' && /^[a-f0-9]{64}$/.test(value); }
221
+ function isRoutingSelection(x) { return isObj(x) && Object.keys(x).every(key => key === 'selector' || key === 'effort') && isObj(x.selector) && Object.keys(x.selector).every(key => key === 'kind' || key === 'id') && ['model', 'tier'].includes(String(x.selector.kind)) && typeof x.selector.id === 'string' && x.selector.id.length > 0 && x.selector.id.length <= 128 && isObj(x.effort) && ((x.effort.kind === 'runtime-default' && Object.keys(x.effort).length === 1) || (x.effort.kind === 'explicit' && Object.keys(x.effort).length === 2 && typeof x.effort.value === 'string' && x.effort.value.length > 0 && x.effort.value.length <= 128)); }
222
+ function isRoutingEnvelope(x) { if (!isObj(x) || !Object.keys(x).every(key => ['schema', 'runtime', 'role', 'planDigest', 'executionDigest', 'sliceId', 'requestedProfile', 'effectiveProfile', 'resolved', 'policyDigest', 'capabilityDigest', 'outcome', 'unavailableEvidence'].includes(key)))
223
+ return false; const runtime = x.runtime; return x.schema === 'routing-envelope/v1' && isObj(runtime) && Object.keys(runtime).every(key => ['target', 'kind', 'version', 'accountScopeDigest'].includes(key)) && ['codex', 'claude-code', 'cursor', 'copilot', 'opencode', 'antigravity'].includes(String(runtime.target)) && typeof runtime.kind === 'string' && runtime.kind.length > 0 && runtime.kind.length <= 128 && typeof runtime.version === 'string' && runtime.version.length > 0 && runtime.version.length <= 128 && isDigest(runtime.accountScopeDigest) && typeof x.role === 'string' && ROUTING_ROLES.includes(x.role) && isDigest(x.planDigest) && isDigest(x.executionDigest) && (x.sliceId === undefined || (typeof x.sliceId === 'string' && x.sliceId.length > 0 && x.sliceId.length <= 128)) && ['mechanical', 'integration', 'judgment', 'full'].includes(String(x.requestedProfile)) && ['mechanical', 'integration', 'judgment', 'full'].includes(String(x.effectiveProfile)) && isRoutingSelection(x.resolved) && isDigest(x.policyDigest) && isDigest(x.capabilityDigest) && ['native', 'degraded'].includes(String(x.outcome)) && Array.isArray(x.unavailableEvidence) && x.unavailableEvidence.length <= UNAVAILABLE_EVIDENCE.length && x.unavailableEvidence.every(value => typeof value === 'string' && UNAVAILABLE_EVIDENCE.includes(value)); }
224
+ function isWellFormedImplementationLineage(x) { return isObj(x) && Object.keys(x).every(key => ['id', 'obligationId', 'sliceId', 'planDigest', 'executionDigest', 'initialProfile', 'initialEffort', 'attempts'].includes(key)) && typeof x.id === 'string' && x.id.length > 0 && typeof x.obligationId === 'string' && x.obligationId.length > 0 && typeof x.sliceId === 'string' && x.sliceId.length > 0 && isDigest(x.planDigest) && isDigest(x.executionDigest) && ['mechanical', 'integration', 'judgment'].includes(String(x.initialProfile)) && ['medium', 'high'].includes(String(x.initialEffort)) && Number.isSafeInteger(x.attempts) && x.attempts >= 0 && x.attempts <= 3; }
170
225
  function isWellFormedRequestProblem(x) {
171
226
  return isObj(x) && typeof x.file === 'string' && (x.kind === 'corrupt' || x.kind === 'rejected')
172
227
  && typeof x.detail === 'string' && typeof x.at === 'string';
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.hasIssue148ReviewProvenance = hasIssue148ReviewProvenance;
4
+ function hasIssue148ReviewProvenance(entries) {
5
+ if (!Array.isArray(entries))
6
+ throw new Error('historical review records must be an array');
7
+ const fields = ['ts', 'branch', 'phase', 'source_skill', 'polarity', 'signature', 'ref'];
8
+ for (const entry of entries) {
9
+ if (!entry || typeof entry !== 'object' || fields.some(field => field !== 'ts' && typeof entry[field] !== 'string') || (entry.ts !== undefined && typeof entry.ts !== 'string'))
10
+ throw new Error('historical review record is invalid');
11
+ }
12
+ const shared = { branch: 'codex/issue-148-awm-facts', phase: 'review', source_skill: 'requesting-code-review', polarity: 'win' };
13
+ const spec = { ...shared, ts: '2026-09-14T21:53:36.988Z', signature: 'facts-command-placeholder-traversal', ref: 'cli/src/core/facts/contract.ts:75' };
14
+ const quality = { ...shared, ts: '2026-09-14T22:02:34.749Z', signature: 'facts-contract-json-and-literal-validation', ref: 'cli/src/core/facts/contract.ts:37' };
15
+ const exact = (expected) => entries.some(entry => fields.every(field => entry[field] === expected[field]));
16
+ const laterAdverse = entries.some(entry => entry.branch === shared.branch && entry.polarity === 'finding' && typeof entry.ts === 'string' && entry.ts > quality.ts && /^cli\/(?:src\/core\/(?:facts\/(?:types|contract)\.ts|registries\.ts)|tests\/core\/(?:facts\/contract\.test\.ts|registry-manifest\.test\.ts))(?::\d+(?::\d+)?)?$/.test(entry.ref));
17
+ return exact(spec) && exact(quality) && !laterAdverse;
18
+ }
@@ -0,0 +1,322 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.collectMigrationFacts = collectMigrationFacts;
7
+ exports.collectIssue148HistoricalFacts = collectIssue148HistoricalFacts;
8
+ const crypto_1 = __importDefault(require("crypto"));
9
+ const fs_1 = __importDefault(require("fs"));
10
+ const path_1 = __importDefault(require("path"));
11
+ const child_process_1 = require("child_process");
12
+ const validate_1 = require("../plan/validate");
13
+ const store_1 = require("../ledger/store");
14
+ const store_2 = require("../journal/store");
15
+ const fingerprint_1 = require("../journal/fingerprint");
16
+ const native_bridge_1 = require("../secure-fs/native-bridge");
17
+ const historical_provenance_1 = require("./historical-provenance");
18
+ const ISSUE_126 = /\/issues\/126\/?$/;
19
+ const ISSUE_148 = /\/issues\/148\/?$/;
20
+ const TASK = /^### Task ([1-9][0-9]{0,3}):/gm;
21
+ const MAX = 128;
22
+ const MAX_ISSUE_LINK_BYTES = 2048;
23
+ /** Pure task-scoped gate. Callers cannot combine a test from one task with a
24
+ * review from another: every required record carries the same task identity. */
25
+ /** Local-only reconciliation. Evidence is admitted exclusively by
26
+ * collectMigrationFacts from the schema-2 journal; callers cannot fabricate
27
+ * records through the public migration API. */
28
+ function reconcileTaskEvidence(taskId, records) {
29
+ const own = records.filter(record => record.taskId === taskId);
30
+ if (own.some(record => !safeIssue(record.issue126) || !ISSUE_126.test(record.issue126)))
31
+ return { id: taskId, state: 'pending', missing: ['issue126-provenance'] };
32
+ if (own.some(record => record.result === 'fail' || record.result === 'inconclusive'))
33
+ return { id: taskId, state: 'pending', missing: ['adverse-evidence'] };
34
+ const commit = own.some(record => typeof record.commitSha === 'string' && /^[a-f0-9]{40}$/i.test(record.commitSha));
35
+ const test = own.some(record => record.verificationKind === 'test' && record.result === 'pass' && !!record.fingerprint && (record.paths?.length ?? 0) > 0);
36
+ const sensor = own.some(record => record.verificationKind === 'sensors' && record.result === 'pass' && !!record.fingerprint && (record.paths?.length ?? 0) > 0);
37
+ const spec = own.some(record => record.role === 'spec' && record.result === 'pass' && !!record.verdictId && !!record.obligationId && !!record.at);
38
+ const quality = own.some(record => record.role === 'quality' && record.result === 'pass' && !!record.verdictId && !!record.obligationId && !!record.at);
39
+ const missing = [...(commit ? [] : ['commit']), ...(test ? [] : ['tests']), ...(sensor ? [] : ['sensors']), ...(spec ? [] : ['specification-review']), ...(quality ? [] : ['quality-review'])];
40
+ return { id: taskId, state: missing.length === 0 ? 'completed' : 'pending', missing };
41
+ }
42
+ function safeIssue(link) {
43
+ // A canonical public issue URL cannot contain credentials, custom ports,
44
+ // queries, fragments, encoded delimiters or controls. Never echo rejected
45
+ // input: even a diagnostic could otherwise disclose URL credentials.
46
+ return typeof link === 'string' && Buffer.byteLength(link, 'utf8') <= MAX_ISSUE_LINK_BYTES
47
+ && /^https:\/\/github\.com\/Kodria\/agentic-workflow\/issues\/[1-9][0-9]*\/?$/.test(link)
48
+ && !/[\u0000-\u001F\u007F-\u009F]/.test(link);
49
+ }
50
+ function validateIssueLinks(issueLinks) {
51
+ if (!Array.isArray(issueLinks) || issueLinks.length === 0 || issueLinks.length > MAX
52
+ || issueLinks.some(link => !safeIssue(link)) || !issueLinks.some(link => ISSUE_126.test(link))) {
53
+ throw new Error('migration material facts require bounded canonical durable issue #126 links');
54
+ }
55
+ }
56
+ function ids(text) { const value = [...text.matchAll(TASK)].map(m => m[1]); if (value.length > MAX || new Set(value).size !== value.length)
57
+ throw new Error('migration task ownership is ambiguous'); return value; }
58
+ function taskFiles(text, taskId) {
59
+ const start = text.search(new RegExp(`^### Task ${taskId}:`, 'm'));
60
+ if (start < 0)
61
+ return [];
62
+ const end = text.indexOf('\n### Task ', start + 1);
63
+ const section = text.slice(start, end < 0 ? text.length : end);
64
+ const lines = section.split(/\r?\n/);
65
+ const heading = lines.findIndex(line => /^\s*(?:\*\*)?Files:?(?:\*\*)?\s*$/i.test(line));
66
+ if (heading < 0)
67
+ return [];
68
+ const files = [];
69
+ for (const line of lines.slice(heading + 1)) {
70
+ if (!line.trim() || /^\s*(?:#{1,6}\s|\*\*[^*]+\*\*)/.test(line))
71
+ break;
72
+ const match = /^\s*[-*]\s*(?:(?:Create|Modify):\s*)?`?([^`\r\n]+?)`?\s*$/.exec(line);
73
+ if (match)
74
+ files.push(match[1].trim());
75
+ }
76
+ return files.filter(file => file && !file.includes('..') && !path_1.default.isAbsolute(file));
77
+ }
78
+ function declaredCommit(text, taskId, cwd) {
79
+ const start = text.search(new RegExp(`^### Task ${taskId}:`, 'm'));
80
+ if (start < 0)
81
+ return undefined;
82
+ const end = text.indexOf('\n### Task ', start + 1);
83
+ const section = text.slice(start, end < 0 ? text.length : end);
84
+ const sha = /^Commit:\s*([a-f0-9]{7,40})\s*$/mi.exec(section)?.[1];
85
+ if (!sha)
86
+ return undefined;
87
+ const files = taskFiles(text, taskId);
88
+ try {
89
+ const full = (0, child_process_1.execFileSync)('git', ['rev-parse', '--verify', `${sha}^{commit}`], { cwd, encoding: 'utf8', stdio: 'pipe', timeout: 2000 }).trim();
90
+ (0, child_process_1.execFileSync)('git', ['merge-base', '--is-ancestor', full, 'HEAD'], { cwd, stdio: 'pipe', timeout: 2000 });
91
+ const changed = (0, child_process_1.execFileSync)('git', ['diff-tree', '--root', '--no-commit-id', '--name-only', '-r', full], { cwd, encoding: 'utf8', stdio: 'pipe', timeout: 2000 }).split(/\r?\n/).filter(Boolean);
92
+ return files.length > 0 && changed.length > 0 && files.every(file => changed.includes(file)) ? full : undefined;
93
+ }
94
+ catch {
95
+ return undefined;
96
+ }
97
+ }
98
+ function readContainedRegularFile(root, relative, maximum, label) {
99
+ if (path_1.default.isAbsolute(relative) || path_1.default.win32.isAbsolute(relative))
100
+ throw new Error(`${label} must be relative`);
101
+ const file = path_1.default.resolve(root, relative);
102
+ if (!file.startsWith(`${root}${path_1.default.sep}`))
103
+ throw new Error(`${label} escapes root`);
104
+ // The native primitive walks parents through directory descriptors/handles,
105
+ // refuses every reparse point, and reads the leaf through that anchor. Do
106
+ // not emulate it with lstat/open: a parent can otherwise change in between.
107
+ try {
108
+ return native_bridge_1.secureFs.readRegularFile(file, maximum).bytes;
109
+ }
110
+ catch {
111
+ throw new Error(`${label} must be a contained bounded regular file`);
112
+ }
113
+ }
114
+ function readPlan(root, relative) { return new TextDecoder('utf-8', { fatal: true }).decode(readContainedRegularFile(root, relative, 1024 * 1024, 'migration plan')); }
115
+ /** Public collector accepts no completion claims. All facts are derived locally and
116
+ * every material record carries the initiative's durable #126 reference. */
117
+ function collectMigrationFacts(planPath, cwd, issueLinks) {
118
+ validateIssueLinks(issueLinks);
119
+ const root = fs_1.default.realpathSync(cwd);
120
+ const text = readPlan(root, planPath);
121
+ const taskIds = ids(text);
122
+ // Validate the descriptor-anchored bytes above; never reopen planPath.
123
+ const plan = (0, validate_1.validatePlanSnapshot)(planPath, root, text);
124
+ if (plan.state !== 'migration-required' && plan.state !== 'valid')
125
+ return { state: 'blocked', issueLinks: [...issueLinks], tasks: [], diagnostics: ['plan-not-migratable'], facts: [] };
126
+ const issue126 = issueLinks.find(link => ISSUE_126.test(link));
127
+ const facts = [];
128
+ // Git can prove ancestry for a SHA only when the durable task obligation
129
+ // names that SHA. The current journal schema has no task commit field, so
130
+ // history is deliberately not borrowed across tasks.
131
+ try {
132
+ (0, child_process_1.execFileSync)('git', ['rev-parse', 'HEAD'], { cwd: root, encoding: 'utf8', stdio: 'pipe', timeout: 2000 });
133
+ }
134
+ catch { /* no Git fact */ }
135
+ let journal;
136
+ try {
137
+ journal = (0, store_2.readJournal)(root, (0, store_1.detectBranch)(root));
138
+ }
139
+ catch {
140
+ journal = { state: null, corrupt: true };
141
+ }
142
+ const diagnostics = [];
143
+ if (!journal.state || journal.corrupt)
144
+ diagnostics.push('journal-missing-or-corrupt');
145
+ const binding = journal.state?.schema === 2 ? journal.state.planBinding : undefined;
146
+ const digest = plan.state === 'valid' ? plan.planDigest : crypto_1.default.createHash('sha256').update(text.replace(/\r\n?/g, '\n'), 'utf8').digest('hex');
147
+ const bindingCurrent = !!binding && binding.path === planPath && binding.digest === digest && binding.executionMode === 'desatendido';
148
+ if (!bindingCurrent)
149
+ diagnostics.push('journal-plan-binding-stale');
150
+ const jobs = journal.state ? Object.values(journal.state.jobs) : [];
151
+ const observed = new Map();
152
+ const currentFingerprint = (record) => {
153
+ if (!bindingCurrent || !record.argv.length || !record.paths.length)
154
+ return null;
155
+ const key = JSON.stringify([record.argv, record.paths, record.cwd]);
156
+ if (!observed.has(key)) {
157
+ try {
158
+ observed.set(key, (0, fingerprint_1.computeFingerprint)(root, record.argv, record.paths, record.cwd));
159
+ }
160
+ catch {
161
+ observed.set(key, null);
162
+ }
163
+ }
164
+ const current = observed.get(key);
165
+ return current && current.expandedPaths.length > 0 && current.fingerprint === record.fingerprint ? current : null;
166
+ };
167
+ const currentJobs = jobs.filter(job => {
168
+ const current = currentFingerprint(job);
169
+ return job.executionState === 'exited' && job.verdict === 'pass' && current !== null
170
+ && current.commandDigest === job.commandDigest
171
+ && JSON.stringify(current.expandedPaths) === JSON.stringify(job.expandedPaths);
172
+ });
173
+ if (currentJobs.length !== jobs.length)
174
+ diagnostics.push('stale-or-nonterminal-durable-job-evidence');
175
+ const declared = new Map((journal.state?.tasks ?? []).map(task => [task.id, declaredCommit(text, task.id, root)]));
176
+ // A verifier identifier is an ownership boundary. A job must resolve to
177
+ // exactly one task; otherwise it could lend the same execution evidence
178
+ // to multiple task completions.
179
+ const verificationOwners = new Map();
180
+ for (const task of journal.state?.tasks ?? [])
181
+ for (const item of task.verificationPlan) {
182
+ verificationOwners.set(item.id, [...(verificationOwners.get(item.id) ?? []), task.id]);
183
+ }
184
+ const claimedCommits = new Set();
185
+ const duplicateCommits = new Set();
186
+ for (const sha of declared.values())
187
+ if (sha) {
188
+ if (claimedCommits.has(sha))
189
+ duplicateCommits.add(sha);
190
+ else
191
+ claimedCommits.add(sha);
192
+ }
193
+ for (const task of journal.state?.tasks ?? []) {
194
+ const commitSha = declared.get(task.id);
195
+ if (commitSha && !duplicateCommits.has(commitSha))
196
+ facts.push({ taskId: task.id, commitSha, issue126 });
197
+ const verificationIds = new Set(task.verificationPlan.map(item => item.id).filter(id => verificationOwners.get(id)?.length === 1));
198
+ const belongsToTask = (job) => {
199
+ const jobOwners = new Set((job.satisfies ?? []).flatMap(id => verificationOwners.get(id) ?? []));
200
+ return jobOwners.size === 1 && jobOwners.has(task.id);
201
+ };
202
+ for (const job of currentJobs) {
203
+ if (!belongsToTask(job))
204
+ continue;
205
+ for (const item of task.verificationPlan) {
206
+ if (verificationIds.has(item.id) && job.satisfies?.includes(item.id))
207
+ facts.push({ taskId: task.id, verificationItemId: item.id, verificationKind: item.kind, jobId: job.id, argv: job.argv, fingerprint: job.fingerprint, paths: job.paths, result: job.verdict, issue126 });
208
+ }
209
+ }
210
+ for (const obligation of task.reviewObligations) {
211
+ const verdict = obligation.verdictId ? journal.state?.verdicts.find(item => item.id === obligation.verdictId) : undefined;
212
+ const fingerprintBound = verdict && currentFingerprint(verdict) !== null && currentJobs.some(job => belongsToTask(job) && job.fingerprint === verdict.fingerprint && job.satisfies?.some(id => verificationIds.has(id)));
213
+ if (verdict && verdict.obligationId === obligation.id && obligation.taskId === task.id && fingerprintBound)
214
+ facts.push({ taskId: task.id, verdictId: verdict.id, obligationId: obligation.id, role: obligation.kind, result: verdict.result, fingerprint: verdict.fingerprint, paths: verdict.paths, at: verdict.receivedAt, issue126 });
215
+ }
216
+ }
217
+ if (jobs.some(job => job.verdict === 'fail') || journal.state?.verdicts.some(verdict => verdict.result === 'fail'))
218
+ diagnostics.push('adverse-durable-verdict');
219
+ const canonical148 = ISSUE_148.test(issueLinks.join('\n')) && planPath === 'docs/plans/2026-09-14-awm-facts-plan.md' && (0, store_1.detectBranch)(root) === 'codex/issue-148-awm-facts' && !!binding?.boundAt;
220
+ const tasks = taskIds.map(id => {
221
+ const task = journal.state?.tasks.find(candidate => candidate.id === id);
222
+ // A binding alone is not task evidence. #148 only changes once a record
223
+ // linked to this task is newer than binding.boundAt; this journal schema
224
+ // lacks per-task commit provenance, so retain the conservative state.
225
+ if (canonical148 && (id === '1' || id === '2'))
226
+ return { id, state: 'pending', missing: ['task-provenance-after-binding'] };
227
+ if (!task)
228
+ return { id, state: 'unstarted', missing: [] };
229
+ const reconciled = reconcileTaskEvidence(id, facts);
230
+ return task.status === 'done' && reconciled.state === 'completed'
231
+ ? reconciled : { ...reconciled, state: 'pending' };
232
+ });
233
+ if (canonical148 && !binding)
234
+ diagnostics.push('issue-148-checkpoint-not-durably-bound');
235
+ if (tasks.some(task => task.state === 'pending'))
236
+ diagnostics.push('missing-durable-task-evidence');
237
+ const state = diagnostics.some(d => d.startsWith('adverse')) ? 'blocked' : diagnostics.length ? 'planning-required' : tasks.some(task => task.state === 'completed') ? 'supported-completion' : 'planning-required';
238
+ return { state, ...(plan.state === 'valid' ? { planDigest: digest } : {}), issueLinks: [...issueLinks], tasks, diagnostics, facts };
239
+ }
240
+ /** Bounded #148 dry-run source. Historical roots are deliberately limited to a
241
+ * sibling worktree of this repository; arbitrary external directories are never
242
+ * treated as durable migration evidence. */
243
+ function collectIssue148HistoricalFacts(historicalRoot, issueLinks) {
244
+ validateIssueLinks(issueLinks);
245
+ if (!issueLinks.some(link => ISSUE_148.test(link)))
246
+ throw new Error('issue-148 migration requires durable #126 and #148 links');
247
+ const root = fs_1.default.realpathSync(historicalRoot);
248
+ const expected = path_1.default.join(path_1.default.dirname(fs_1.default.realpathSync(process.cwd())), 'codex-issue-148-awm-facts');
249
+ if (root !== expected)
250
+ throw new Error('historical root is not the admitted issue-148 sibling worktree');
251
+ const branch = (0, child_process_1.execFileSync)('git', ['branch', '--show-current'], { cwd: root, encoding: 'utf8', stdio: 'pipe', timeout: 2000 }).trim();
252
+ if (branch !== 'codex/issue-148-awm-facts')
253
+ throw new Error('historical root is not the admitted issue-148 branch');
254
+ const issue126 = issueLinks.find(link => ISSUE_126.test(link));
255
+ if (!issue126 || !safeIssue(issue126) || !issueLinks.some(link => safeIssue(link) && ISSUE_148.test(link)))
256
+ throw new Error('issue-148 migration requires durable #126 and #148 links');
257
+ const planPath = 'docs/plans/2026-09-14-awm-facts-plan.md';
258
+ const text = readPlan(root, planPath);
259
+ const digest = crypto_1.default.createHash('sha256').update(text, 'utf8').digest('hex');
260
+ const ledgerPath = '.awm/ledger/codex__issue-148-awm-facts.jsonl';
261
+ let ledgerEntries = [];
262
+ try {
263
+ ledgerEntries = readContainedRegularFile(root, ledgerPath, 256 * 1024, 'issue-148 ledger').toString('utf8').trim().split('\n').filter(Boolean).map(line => {
264
+ const value = JSON.parse(line);
265
+ if (!value || typeof value !== 'object')
266
+ throw new Error();
267
+ const item = value;
268
+ if (typeof item.branch !== 'string' || typeof item.phase !== 'string' || typeof item.source_skill !== 'string' || typeof item.polarity !== 'string' || typeof item.signature !== 'string' || typeof item.ref !== 'string')
269
+ throw new Error();
270
+ if (item.ts !== undefined && typeof item.ts !== 'string')
271
+ throw new Error();
272
+ return { branch: item.branch, phase: item.phase, source_skill: item.source_skill, polarity: item.polarity, signature: item.signature, ref: item.ref, ...(typeof item.ts === 'string' ? { ts: item.ts } : {}) };
273
+ });
274
+ }
275
+ catch {
276
+ ledgerEntries = [];
277
+ }
278
+ const checkpoint = '81c008c5f681e6ecfe30a3fc73bf7b79d094c094';
279
+ const ancestor = (() => {
280
+ try {
281
+ for (const commit of ['91047f5041a447b85243ea25b91652aef41151bd', '1207e61c5b206ad8693f5c5d6b9ab5ab19cd8a6f', checkpoint])
282
+ (0, child_process_1.execFileSync)('git', ['merge-base', '--is-ancestor', commit, 'HEAD'], { cwd: root, stdio: 'pipe', timeout: 2000 });
283
+ return true;
284
+ }
285
+ catch {
286
+ return false;
287
+ }
288
+ })();
289
+ const taskOneStart = text.search(/^### Task 1:/m);
290
+ const taskOneEnd = text.indexOf('\n### Task ', taskOneStart + 1);
291
+ const taskOneSection = taskOneStart < 0 ? '' : text.slice(taskOneStart, taskOneEnd < 0 ? text.length : taskOneEnd);
292
+ const taskOneChecked = /^- \[x\]/m.test(taskOneSection);
293
+ const taskOneFiles = new Set(taskFiles(text, '1'));
294
+ const currentTaskOneFiles = (() => {
295
+ if (taskOneFiles.size === 0)
296
+ return false;
297
+ try {
298
+ const checkpointFiles = (0, child_process_1.execFileSync)('git', ['ls-tree', '--name-only', '-r', checkpoint, '--', ...taskOneFiles], { cwd: root, encoding: 'utf8', stdio: 'pipe', timeout: 2000 }).trim().split('\n');
299
+ if ([...taskOneFiles].some(file => !checkpointFiles.includes(file)))
300
+ return false;
301
+ (0, child_process_1.execFileSync)('git', ['diff', '--quiet', checkpoint, '--', ...taskOneFiles], { cwd: root, stdio: 'pipe', timeout: 2000 });
302
+ (0, child_process_1.execFileSync)('git', ['diff', '--cached', '--quiet', checkpoint, '--', ...taskOneFiles], { cwd: root, stdio: 'pipe', timeout: 2000 });
303
+ return true;
304
+ }
305
+ catch {
306
+ return false;
307
+ }
308
+ })();
309
+ const ledgerReviews = (() => { try {
310
+ return (0, historical_provenance_1.hasIssue148ReviewProvenance)(ledgerEntries);
311
+ }
312
+ catch {
313
+ return false;
314
+ } })();
315
+ const taskIds = ids(text);
316
+ const proven = digest === 'c11477dd59cb19094983c671cc0b760f1d1e51b9679e13dba90f1b0c2cba48e7' && ancestor && currentTaskOneFiles && taskOneChecked && ledgerReviews;
317
+ // A checked historical antecedent without sufficient provenance is not
318
+ // unstarted. Preserve it for reconciliation; never imply permission to
319
+ // replay its implementation or borrow another task's review.
320
+ const tasks = taskIds.map(id => id === '1' && proven ? { id, state: 'completed', missing: [] } : id === '1' && taskOneChecked ? { id, state: 'pending', missing: ['historical-completion-provenance'] } : id === '2' ? { id, state: 'pending', missing: ['quality-review'] } : { id, state: 'unstarted', missing: [] });
321
+ return { state: proven ? 'planning-required' : 'blocked', planDigest: digest, issueLinks: [...issueLinks], tasks, diagnostics: proven ? ['Task 2 quality re-review remains'] : ['issue-148 historical provenance is incomplete or inconsistent'], facts: [{ taskId: '1', issue126 }] };
322
+ }
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.canonicalPolicyDigest = canonicalPolicyDigest;
4
+ const node_crypto_1 = require("node:crypto");
5
+ const validate_1 = require("./validate");
6
+ function canonical(value) {
7
+ if (value === null || typeof value !== 'object') {
8
+ const serialized = JSON.stringify(value);
9
+ if (serialized === undefined)
10
+ throw new Error('Invalid canonical value');
11
+ return serialized;
12
+ }
13
+ if (Array.isArray(value))
14
+ return `[${value.map(canonical).join(',')}]`;
15
+ const object = value;
16
+ return `{${Object.keys(object).sort().map(key => `${JSON.stringify(key)}:${canonical(object[key])}`).join(',')}}`;
17
+ }
18
+ function canonicalPolicyDigest(content) { const validated = (0, validate_1.validatePolicyContent)(content); return (0, node_crypto_1.createHash)('sha256').update(canonical(validated), 'utf8').digest('hex'); }