codex-workflow-v2 2.0.0-beta.13.9 → 2.0.0-beta.14

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 (271) hide show
  1. package/README.md +27 -388
  2. package/dist/reviewer-runtime-build.json +89 -29
  3. package/dist/src/alpha6/adoption.d.ts +2 -0
  4. package/dist/src/alpha6/adoption.js +22 -0
  5. package/dist/src/alpha6/adoption.js.map +1 -1
  6. package/dist/src/alpha6/captured-check-evidence.d.ts +51 -0
  7. package/dist/src/alpha6/captured-check-evidence.js +152 -0
  8. package/dist/src/alpha6/captured-check-evidence.js.map +1 -0
  9. package/dist/src/alpha6/component-owner.d.ts +15 -1
  10. package/dist/src/alpha6/component-owner.js +44 -2
  11. package/dist/src/alpha6/component-owner.js.map +1 -1
  12. package/dist/src/alpha6/corrective-decision-boundary.d.ts +4 -0
  13. package/dist/src/alpha6/corrective-decision-boundary.js +47 -0
  14. package/dist/src/alpha6/corrective-decision-boundary.js.map +1 -0
  15. package/dist/src/alpha6/downstream-proof.d.ts +1 -25
  16. package/dist/src/alpha6/downstream-proof.js +0 -171
  17. package/dist/src/alpha6/downstream-proof.js.map +1 -1
  18. package/dist/src/alpha6/literal-test-invocation.d.ts +2 -0
  19. package/dist/src/alpha6/literal-test-invocation.js +106 -0
  20. package/dist/src/alpha6/literal-test-invocation.js.map +1 -0
  21. package/dist/src/alpha6/milestone.d.ts +2 -0
  22. package/dist/src/alpha6/milestone.js +10 -1
  23. package/dist/src/alpha6/milestone.js.map +1 -1
  24. package/dist/src/alpha6/navigation-dirty-carryover.d.ts +11 -0
  25. package/dist/src/alpha6/navigation-dirty-carryover.js +130 -0
  26. package/dist/src/alpha6/navigation-dirty-carryover.js.map +1 -0
  27. package/dist/src/alpha6/plan-integrity.js +7 -5
  28. package/dist/src/alpha6/plan-integrity.js.map +1 -1
  29. package/dist/src/alpha6/remediation.d.ts +6 -21
  30. package/dist/src/alpha6/remediation.js +161 -476
  31. package/dist/src/alpha6/remediation.js.map +1 -1
  32. package/dist/src/alpha6/root-cause-replan-carryover.d.ts +4 -1
  33. package/dist/src/alpha6/root-cause-replan-carryover.js +29 -5
  34. package/dist/src/alpha6/root-cause-replan-carryover.js.map +1 -1
  35. package/dist/src/beta1/project-transaction.d.ts +3 -1
  36. package/dist/src/beta1/project-transaction.js +6 -2
  37. package/dist/src/beta1/project-transaction.js.map +1 -1
  38. package/dist/src/checks/runner.d.ts +26 -0
  39. package/dist/src/checks/runner.js +223 -0
  40. package/dist/src/checks/runner.js.map +1 -0
  41. package/dist/src/checks/task-sync.d.ts +10 -0
  42. package/dist/src/checks/task-sync.js +41 -0
  43. package/dist/src/checks/task-sync.js.map +1 -0
  44. package/dist/src/checks/worker.d.ts +1 -0
  45. package/dist/src/checks/worker.js +154 -0
  46. package/dist/src/checks/worker.js.map +1 -0
  47. package/dist/src/cli-actions.d.ts +2 -2
  48. package/dist/src/cli-actions.js +3 -15
  49. package/dist/src/cli-actions.js.map +1 -1
  50. package/dist/src/cli.js +14 -98
  51. package/dist/src/cli.js.map +1 -1
  52. package/dist/src/contracts.d.ts +35 -32
  53. package/dist/src/dependency-provenance.d.ts +2 -2
  54. package/dist/src/dependency-provenance.js +12 -69
  55. package/dist/src/dependency-provenance.js.map +1 -1
  56. package/dist/src/domain/base-sync-conflict.d.ts +13 -0
  57. package/dist/src/domain/base-sync-conflict.js +32 -0
  58. package/dist/src/domain/base-sync-conflict.js.map +1 -0
  59. package/dist/src/domain/step-start-admission.d.ts +4 -0
  60. package/dist/src/domain/step-start-admission.js +9 -0
  61. package/dist/src/domain/step-start-admission.js.map +1 -0
  62. package/dist/src/domain/virgin-registration.d.ts +3 -0
  63. package/dist/src/domain/virgin-registration.js +65 -0
  64. package/dist/src/domain/virgin-registration.js.map +1 -0
  65. package/dist/src/errors.d.ts +1 -1
  66. package/dist/src/errors.js.map +1 -1
  67. package/dist/src/gateway-handshake.js +0 -8
  68. package/dist/src/gateway-handshake.js.map +1 -1
  69. package/dist/src/git.d.ts +3 -1
  70. package/dist/src/git.js +41 -14
  71. package/dist/src/git.js.map +1 -1
  72. package/dist/src/graph.js +25 -3
  73. package/dist/src/graph.js.map +1 -1
  74. package/dist/src/index.d.ts +1 -0
  75. package/dist/src/navigation-actions.d.ts +9 -0
  76. package/dist/src/navigation-actions.js +72 -0
  77. package/dist/src/navigation-actions.js.map +1 -0
  78. package/dist/src/navigation-update-artifact.d.ts +5 -0
  79. package/dist/src/navigation-update-artifact.js +227 -0
  80. package/dist/src/navigation-update-artifact.js.map +1 -0
  81. package/dist/src/navigation-update.d.ts +39 -0
  82. package/dist/src/navigation-update.js +82 -0
  83. package/dist/src/navigation-update.js.map +1 -0
  84. package/dist/src/observation.js +52 -29
  85. package/dist/src/observation.js.map +1 -1
  86. package/dist/src/observed-routes.js +5 -6
  87. package/dist/src/observed-routes.js.map +1 -1
  88. package/dist/src/pending-review-update.d.ts +0 -13
  89. package/dist/src/pending-review-update.js +1 -6
  90. package/dist/src/pending-review-update.js.map +1 -1
  91. package/dist/src/reviewer.d.ts +1 -1
  92. package/dist/src/reviewer.js +20 -17
  93. package/dist/src/reviewer.js.map +1 -1
  94. package/dist/src/runtime.d.ts +3 -0
  95. package/dist/src/runtime.js +6 -0
  96. package/dist/src/runtime.js.map +1 -0
  97. package/dist/src/state/corrective-replan-executor.d.ts +10 -0
  98. package/dist/src/state/corrective-replan-executor.js +37 -1
  99. package/dist/src/state/corrective-replan-executor.js.map +1 -1
  100. package/dist/src/state/corrective-replan-public-schema.js +15 -2
  101. package/dist/src/state/corrective-replan-public-schema.js.map +1 -1
  102. package/dist/src/state/corrective-replan-public.js +1 -1
  103. package/dist/src/state/corrective-replan-public.js.map +1 -1
  104. package/dist/src/state/corrective-replan-transaction.d.ts +1 -0
  105. package/dist/src/state/corrective-replan-transaction.js +12 -11
  106. package/dist/src/state/corrective-replan-transaction.js.map +1 -1
  107. package/dist/src/state/corrective-yield-transaction.d.ts +1 -0
  108. package/dist/src/state/corrective-yield-transaction.js +11 -10
  109. package/dist/src/state/corrective-yield-transaction.js.map +1 -1
  110. package/dist/src/state/lock.d.ts +4 -0
  111. package/dist/src/state/lock.js +21 -0
  112. package/dist/src/state/lock.js.map +1 -1
  113. package/dist/src/version.d.ts +1 -1
  114. package/dist/src/version.js +1 -1
  115. package/dist/src/version.js.map +1 -1
  116. package/dist/src/workflow-blocker-route.d.ts +8 -0
  117. package/dist/src/workflow-blocker-route.js +106 -0
  118. package/dist/src/workflow-blocker-route.js.map +1 -0
  119. package/dist/src/workflow.d.ts +48 -63
  120. package/dist/src/workflow.js +666 -1460
  121. package/dist/src/workflow.js.map +1 -1
  122. package/docs/autonomy-guardrails.md +26 -304
  123. package/docs/decisions.md +14 -112
  124. package/docs/development-flow.md +35 -238
  125. package/docs/project-memory.md +31 -50
  126. package/docs/release-app-evidence.md +190 -0
  127. package/docs/release.md +95 -388
  128. package/docs/updating-existing-project.md +19 -717
  129. package/package.json +11 -13
  130. package/plugins/codex-workflow-gateway/.codex-plugin/plugin.json +2 -2
  131. package/plugins/codex-workflow-gateway/references/chat-dispatch.md +73 -197
  132. package/plugins/codex-workflow-gateway/references/codebase-memory-routing.md +57 -0
  133. package/plugins/codex-workflow-gateway/references/protocol.md +42 -445
  134. package/plugins/codex-workflow-gateway/scripts/chat-dispatch.mjs +7 -1
  135. package/plugins/codex-workflow-gateway/scripts/chat-model-policy.mjs +10 -8
  136. package/plugins/codex-workflow-gateway/scripts/chat-registry.mjs +23 -5
  137. package/plugins/codex-workflow-gateway/skills/codex-workflow-gateway/SKILL.md +66 -757
  138. package/references/state-machine.md +4 -4
  139. package/roles/technical-planner.md +1 -1
  140. package/schemas/corrective-decision-event.schema.json +3 -1
  141. package/schemas/project-knowledge-map.schema.json +3 -1
  142. package/schemas/remediation-event.schema.json +10 -1
  143. package/schemas/task.schema.json +46 -2
  144. package/schemas/transition-payloads.schema.json +16 -1
  145. package/src/alpha6/adoption.ts +1123 -0
  146. package/src/alpha6/captured-check-evidence.ts +132 -0
  147. package/src/alpha6/check-support-anchor.ts +128 -0
  148. package/src/alpha6/component-owner.ts +168 -0
  149. package/src/alpha6/corrective-decision-boundary.ts +39 -0
  150. package/src/alpha6/downstream-proof.ts +520 -0
  151. package/src/alpha6/failed-step-planning-recovery.ts +88 -0
  152. package/src/alpha6/handoff.ts +1443 -0
  153. package/src/alpha6/journal.ts +473 -0
  154. package/src/alpha6/literal-test-invocation.ts +84 -0
  155. package/src/alpha6/mechanical-feasibility.ts +488 -0
  156. package/src/alpha6/milestone.ts +2192 -0
  157. package/src/alpha6/navigation-dirty-carryover.ts +154 -0
  158. package/src/alpha6/npm-check-contract.ts +47 -0
  159. package/src/alpha6/plan-integrity.ts +298 -0
  160. package/src/alpha6/plan-risk.ts +1480 -0
  161. package/src/alpha6/preexecution-replan.ts +187 -0
  162. package/src/alpha6/remediation-cause.ts +98 -0
  163. package/src/alpha6/remediation.ts +2438 -0
  164. package/src/alpha6/review.ts +1198 -0
  165. package/src/alpha6/root-cause-replan-carryover.ts +491 -0
  166. package/src/alpha6/store-sidecars.ts +335 -0
  167. package/src/alpha7/autonomy.ts +411 -0
  168. package/src/alpha7/corrective-recovery.ts +1332 -0
  169. package/src/artifacts.ts +130 -0
  170. package/src/beta1/project-transaction.ts +355 -0
  171. package/src/change-explanation.ts +153 -0
  172. package/src/checks/runner.ts +245 -0
  173. package/src/checks/task-sync.ts +41 -0
  174. package/src/checks/worker.ts +149 -0
  175. package/src/cli-actions.ts +107 -0
  176. package/src/cli.ts +1457 -0
  177. package/src/contracts.ts +1494 -0
  178. package/src/credential-output.ts +89 -0
  179. package/src/credential-transport.ts +215 -0
  180. package/src/delegation.ts +190 -0
  181. package/src/dependency-provenance.ts +472 -0
  182. package/src/diagnostics.ts +93 -0
  183. package/src/domain/base-sync-conflict.ts +35 -0
  184. package/src/domain/completed-step-carryover.ts +76 -0
  185. package/src/domain/discovery.ts +27 -0
  186. package/src/domain/plan-semantics.ts +58 -0
  187. package/src/domain/step-start-admission.ts +10 -0
  188. package/src/domain/validation.ts +177 -0
  189. package/src/domain/virgin-registration.ts +46 -0
  190. package/src/errors.ts +24 -0
  191. package/src/fs-utils.ts +61 -0
  192. package/src/gateway-handshake.ts +95 -0
  193. package/src/git.ts +183 -0
  194. package/src/graph.ts +342 -0
  195. package/src/historical-step-provenance.ts +136 -0
  196. package/src/index.ts +23 -0
  197. package/src/lifecycle/canonical-hash.ts +28 -0
  198. package/src/lifecycle/catalog.ts +202 -0
  199. package/src/lifecycle/compiler-inspection.ts +29 -0
  200. package/src/lifecycle/core-static-readiness.ts +132 -0
  201. package/src/lifecycle/corrective-replan-authority.ts +136 -0
  202. package/src/lifecycle/corrective-replan-binding-manifest.ts +51 -0
  203. package/src/lifecycle/corrective-replan-credential-core.ts +408 -0
  204. package/src/lifecycle/corrective-replan-credential-schema.ts +54 -0
  205. package/src/lifecycle/corrective-replan-credentials.ts +48 -0
  206. package/src/lifecycle/corrective-replan.ts +843 -0
  207. package/src/lifecycle/evaluator.ts +48 -0
  208. package/src/lifecycle/fingerprint.ts +488 -0
  209. package/src/lifecycle/immutable.ts +8 -0
  210. package/src/lifecycle/implementation-table.ts +118 -0
  211. package/src/lifecycle/index.ts +8 -0
  212. package/src/lifecycle/schema-artifact.ts +263 -0
  213. package/src/lifecycle/semantic-registry.ts +572 -0
  214. package/src/lifecycle/types.ts +838 -0
  215. package/src/memory.ts +273 -0
  216. package/src/migration.ts +161 -0
  217. package/src/navigation-actions.ts +70 -0
  218. package/src/navigation-update-artifact.ts +198 -0
  219. package/src/navigation-update.ts +121 -0
  220. package/src/observation.ts +225 -0
  221. package/src/observed-routes.ts +660 -0
  222. package/src/operational-contract.ts +125 -0
  223. package/src/pending-review-update.ts +175 -0
  224. package/src/repository.ts +99 -0
  225. package/src/reviewer.ts +1879 -0
  226. package/src/runtime.ts +6 -0
  227. package/src/state/corrective-replan-executor.ts +818 -0
  228. package/src/state/corrective-replan-public-schema.ts +83 -0
  229. package/src/state/corrective-replan-public.ts +908 -0
  230. package/src/state/corrective-replan-transaction.ts +949 -0
  231. package/src/state/corrective-yield-executor.ts +327 -0
  232. package/src/state/corrective-yield-transaction.ts +730 -0
  233. package/src/state/lock.ts +902 -0
  234. package/src/state/store.ts +567 -0
  235. package/src/transition-core.ts +330 -0
  236. package/src/ulid.ts +24 -0
  237. package/src/version.ts +2 -0
  238. package/src/workflow-blocker-route.ts +109 -0
  239. package/src/workflow.ts +10172 -0
  240. package/docs/alpha7.1-implementation-brief.md +0 -268
  241. package/docs/alpha7.2-corrective-context-refresh-brief.md +0 -484
  242. package/docs/alpha7.2.1-remediation-recovery-brief.md +0 -86
  243. package/docs/beta1-stabilization-brief.md +0 -165
  244. package/docs/beta11-plan-integrity-recovery-brief.md +0 -38
  245. package/docs/beta13.2-signal-review-recovery.md +0 -38
  246. package/docs/beta2-initial-assembly-navigation-brief.md +0 -616
  247. package/docs/change-model.md +0 -118
  248. package/docs/delegated-approval.md +0 -254
  249. package/docs/lifecycle/state-machine-stabilization.md +0 -641
  250. package/docs/pdf/README.md +0 -24
  251. package/docs/pdf/codex-workflow-v2-architecture-ru.pdf +0 -0
  252. package/docs/pdf/codex-workflow-v2-chat-only-guide-ru.pdf +0 -0
  253. package/docs/pdf/codex-workflow-v2-technical-reference-ru.pdf +0 -0
  254. package/docs/pdf/requirements.txt +0 -1
  255. package/docs/pdf/sources/codex-workflow-v2-architecture-ru.md +0 -478
  256. package/docs/pdf/sources/codex-workflow-v2-chat-only-guide-ru.md +0 -506
  257. package/docs/pdf/sources/codex-workflow-v2-technical-reference-ru.md +0 -778
  258. package/docs/pending-review-update.md +0 -15
  259. package/docs/problem-briefs/01-pre-implementation-integrity.md +0 -482
  260. package/docs/problem-briefs/02-minimal-step-integrity.md +0 -411
  261. package/docs/problem-briefs/03-minimal-agent-context-integrity.md +0 -358
  262. package/docs/problem-briefs/04-task-dependency-and-structural-replacement-integrity.md +0 -573
  263. package/docs/problem-briefs/BRIEF-TEMPLATE.md +0 -56
  264. package/docs/problem-briefs/README.md +0 -120
  265. package/docs/problem-briefs/evidence/p01-mechanical-feasibility-corpus.md +0 -90
  266. package/docs/problem-briefs/evidence/signal-v4-pre-m3-replay.md +0 -246
  267. package/docs/split-required-recovery.md +0 -47
  268. package/docs/stable-release-defect-register.md +0 -730
  269. package/docs/validation-report.md +0 -182
  270. package/scripts/generate-pdf-docs.py +0 -524
  271. package/scripts/run-pdf-docs.mjs +0 -62
@@ -0,0 +1,89 @@
1
+ const CREDENTIAL_FIELD_NAMES = new Set(['token', 'writerToken', 'claimToken']);
2
+
3
+ export interface CredentialOutputAllowance {
4
+ path: string;
5
+ }
6
+
7
+ export class UnsafeCredentialOutputError extends Error {
8
+ readonly paths: string[];
9
+
10
+ constructor(paths: string[]) {
11
+ super('CLI output contained credentials outside an exact issuance receipt allowance.');
12
+ this.name = 'UnsafeCredentialOutputError';
13
+ this.paths = [...new Set(paths)].sort();
14
+ }
15
+ }
16
+
17
+ export function assertCliSuccessCredentialSafety(
18
+ value: unknown,
19
+ allowances: readonly CredentialOutputAllowance[],
20
+ sensitiveInputs: readonly string[],
21
+ ): void {
22
+ const allowedPaths = new Set(allowances.map((allowance) => allowance.path));
23
+ const secrets = new Set(sensitiveInputs.filter(Boolean));
24
+ const violations: string[] = [];
25
+ visit(value, '$', (path, key, candidate) => {
26
+ const credentialField = key !== null
27
+ && CREDENTIAL_FIELD_NAMES.has(key)
28
+ && candidate !== null
29
+ && candidate !== undefined;
30
+ const containingSecrets = typeof candidate === 'string'
31
+ ? [...secrets].filter((secret) => candidate.includes(secret))
32
+ : [];
33
+ const exactAllowedIssuanceValue = credentialField
34
+ && allowedPaths.has(path)
35
+ && typeof candidate === 'string'
36
+ && containingSecrets.some((secret) => candidate === secret);
37
+ if (credentialField && !allowedPaths.has(path)) violations.push(path);
38
+ if (containingSecrets.length > 0 && !exactAllowedIssuanceValue) violations.push(path);
39
+ });
40
+ if (violations.length > 0) throw new UnsafeCredentialOutputError(violations);
41
+ }
42
+
43
+ export function projectCliErrorValue(value: unknown, sensitiveInputs: readonly string[]): unknown {
44
+ const secrets = new Set(sensitiveInputs.filter(Boolean));
45
+ return project(value, secrets);
46
+ }
47
+
48
+ export function projectCliErrorMessage(message: string, sensitiveInputs: readonly string[]): string {
49
+ return sensitiveInputs
50
+ .filter(Boolean)
51
+ .sort((left, right) => right.length - left.length)
52
+ .reduce((projected, secret) => projected.replaceAll(secret, '[REDACTED_CREDENTIAL]'), message);
53
+ }
54
+
55
+ function project(value: unknown, secrets: ReadonlySet<string>): unknown {
56
+ if (typeof value === 'string') {
57
+ return [...secrets]
58
+ .sort((left, right) => right.length - left.length)
59
+ .reduce((projected, secret) => projected.replaceAll(secret, '[REDACTED_CREDENTIAL]'), value);
60
+ }
61
+ if (Array.isArray(value)) return value.map((entry) => project(entry, secrets));
62
+ if (!isRecord(value)) return value;
63
+ return Object.fromEntries(
64
+ Object.entries(value)
65
+ .filter(([key]) => !CREDENTIAL_FIELD_NAMES.has(key))
66
+ .map(([key, candidate]) => [key, project(candidate, secrets)]),
67
+ );
68
+ }
69
+
70
+ function visit(
71
+ value: unknown,
72
+ path: string,
73
+ visitor: (path: string, key: string | null, value: unknown) => void,
74
+ key: string | null = null,
75
+ ): void {
76
+ visitor(path, key, value);
77
+ if (Array.isArray(value)) {
78
+ value.forEach((entry, index) => visit(entry, `${path}[${index}]`, visitor));
79
+ return;
80
+ }
81
+ if (!isRecord(value)) return;
82
+ for (const [childKey, child] of Object.entries(value)) {
83
+ visit(child, `${path}.${childKey}`, visitor, childKey);
84
+ }
85
+ }
86
+
87
+ function isRecord(value: unknown): value is Record<string, unknown> {
88
+ return typeof value === 'object' && value !== null;
89
+ }
@@ -0,0 +1,215 @@
1
+ import { createHash, randomUUID } from 'node:crypto';
2
+ import {
3
+ chmodSync,
4
+ existsSync,
5
+ lstatSync,
6
+ readFileSync,
7
+ unlinkSync,
8
+ } from 'node:fs';
9
+ import path from 'node:path';
10
+ import { WorkflowError } from './errors.js';
11
+ import { ensureDirectory, writeJsonAtomic } from './fs-utils.js';
12
+
13
+ export type CredentialKind = 'writer-lease' | 'claim-token';
14
+
15
+ interface StoredCredentialReference {
16
+ schemaVersion: 1;
17
+ referenceId: string;
18
+ kind: CredentialKind;
19
+ projectId: string;
20
+ entityId: string;
21
+ actor: string;
22
+ allowedActions: string[];
23
+ token: string;
24
+ tokenSha256: string;
25
+ createdAt: string;
26
+ expiresAt: string;
27
+ oneTime: boolean;
28
+ }
29
+
30
+ export interface PublicCredentialReference {
31
+ credentialRef: string;
32
+ kind: CredentialKind;
33
+ entityId: string;
34
+ actor: string;
35
+ allowedActions: string[];
36
+ expiresAt: string;
37
+ transport: 'local-vault-v1';
38
+ oneTime: boolean;
39
+ }
40
+
41
+ const REFERENCE_PATTERN = /^CREF-([0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12})$/;
42
+
43
+ export function issueCredentialReference(input: {
44
+ stateRoot: string;
45
+ projectId: string;
46
+ entityId: string;
47
+ actor: string;
48
+ kind: CredentialKind;
49
+ token: string;
50
+ allowedActions: readonly string[];
51
+ expiresAt: string;
52
+ oneTime: boolean;
53
+ now?: Date;
54
+ }): PublicCredentialReference {
55
+ const token = input.token.trim();
56
+ if (!token) throw new WorkflowError('STATE_CORRUPT', 'Cannot issue an empty credential reference.');
57
+ const referenceId = `CREF-${randomUUID()}`;
58
+ const actor = input.actor.trim();
59
+ const allowedActions = [...new Set(input.allowedActions.map((value) => value.trim()).filter(Boolean))].sort();
60
+ if (!actor || allowedActions.length === 0) {
61
+ throw new WorkflowError('STATE_CORRUPT', 'Credential reference requires an actor and at least one action.');
62
+ }
63
+ const record: StoredCredentialReference = {
64
+ schemaVersion: 1,
65
+ referenceId,
66
+ kind: input.kind,
67
+ projectId: input.projectId,
68
+ entityId: input.entityId,
69
+ actor,
70
+ allowedActions,
71
+ token,
72
+ tokenSha256: createHash('sha256').update(token).digest('hex'),
73
+ createdAt: (input.now ?? new Date()).toISOString(),
74
+ expiresAt: new Date(input.expiresAt).toISOString(),
75
+ oneTime: input.oneTime,
76
+ };
77
+ const file = credentialFile(input.stateRoot, input.projectId, referenceId);
78
+ writeJsonAtomic(file, record);
79
+ chmodSync(file, 0o600);
80
+ return publicReference(record);
81
+ }
82
+
83
+ export function resolveCredentialReference(input: {
84
+ stateRoot: string;
85
+ projectId: string;
86
+ reference: string;
87
+ kind: CredentialKind;
88
+ entityId: string;
89
+ actor?: string | null;
90
+ action: string;
91
+ now?: Date;
92
+ }): { token: string; reference: PublicCredentialReference } {
93
+ const file = credentialFile(input.stateRoot, input.projectId, input.reference);
94
+ if (!existsSync(file)) {
95
+ throw new WorkflowError('NOT_FOUND', 'Local credential reference is missing or already consumed.', {
96
+ credentialRef: input.reference,
97
+ });
98
+ }
99
+ const metadata = lstatSync(file);
100
+ if (metadata.isSymbolicLink() || !metadata.isFile() || (metadata.mode & 0o077) !== 0) {
101
+ throw new WorkflowError('STATE_CORRUPT', 'Local credential reference must be a private regular file.', {
102
+ credentialRef: input.reference,
103
+ });
104
+ }
105
+ let value: unknown;
106
+ try {
107
+ value = JSON.parse(readFileSync(file, 'utf8')) as unknown;
108
+ } catch (error) {
109
+ throw new WorkflowError('STATE_CORRUPT', 'Local credential reference is not valid JSON.', {
110
+ credentialRef: input.reference,
111
+ cause: error instanceof Error ? error.message : String(error),
112
+ });
113
+ }
114
+ const record = validateRecord(value, input.reference);
115
+ const actor = input.actor?.trim() || record.actor;
116
+ const now = (input.now ?? new Date()).getTime();
117
+ if (
118
+ record.projectId !== input.projectId
119
+ || record.kind !== input.kind
120
+ || record.entityId !== input.entityId
121
+ || record.actor !== actor
122
+ || !record.allowedActions.includes(input.action)
123
+ ) {
124
+ throw new WorkflowError('TRANSITION_BLOCKED', 'Local credential reference is not bound to this transition.', {
125
+ credentialRef: input.reference,
126
+ expected: { kind: input.kind, entityId: input.entityId, actor, action: input.action },
127
+ actual: {
128
+ kind: record.kind,
129
+ entityId: record.entityId,
130
+ actor: record.actor,
131
+ allowedActions: record.allowedActions,
132
+ },
133
+ });
134
+ }
135
+ if (Date.parse(record.expiresAt) <= now) {
136
+ throw new WorkflowError('TRANSITION_BLOCKED', 'Local credential reference has expired.', {
137
+ credentialRef: input.reference,
138
+ expiresAt: record.expiresAt,
139
+ });
140
+ }
141
+ if (createHash('sha256').update(record.token).digest('hex') !== record.tokenSha256) {
142
+ throw new WorkflowError('STATE_CORRUPT', 'Local credential reference failed its integrity check.', {
143
+ credentialRef: input.reference,
144
+ });
145
+ }
146
+ return { token: record.token, reference: publicReference(record) };
147
+ }
148
+
149
+ export function consumeCredentialReference(stateRoot: string, projectId: string, reference: string): void {
150
+ const file = credentialFile(stateRoot, projectId, reference);
151
+ if (!existsSync(file)) return;
152
+ const metadata = lstatSync(file);
153
+ if (metadata.isSymbolicLink() || !metadata.isFile()) {
154
+ throw new WorkflowError('STATE_CORRUPT', 'Refusing to consume a non-regular credential reference.', {
155
+ credentialRef: reference,
156
+ });
157
+ }
158
+ unlinkSync(file);
159
+ }
160
+
161
+ export function assertNoLegacyCredentialArguments(optionNames: ReadonlySet<string>): void {
162
+ const legacy = ['writer-token', 'claim-token'].filter((name) => optionNames.has(name));
163
+ if (legacy.length === 0) return;
164
+ throw new WorkflowError(
165
+ 'INVALID_ARGUMENT',
166
+ 'Secret-bearing CLI options are disabled. Use --writer-credential-ref or --claim-credential-ref.',
167
+ { rejectedOptions: legacy.map((name) => `--${name}`), credentialTransport: 'local-vault-v1' },
168
+ );
169
+ }
170
+
171
+ function credentialFile(stateRoot: string, projectId: string, reference: string): string {
172
+ const match = REFERENCE_PATTERN.exec(reference);
173
+ if (!match) throw new WorkflowError('INVALID_ARGUMENT', 'Credential reference has an invalid format.');
174
+ if (!/^[a-f0-9]{24}$/.test(projectId)) throw new WorkflowError('STATE_CORRUPT', 'Credential project identity is invalid.');
175
+ const root = path.join(path.resolve(stateRoot), 'credentials', projectId);
176
+ ensureDirectory(root);
177
+ const file = path.join(root, `${reference}.json`);
178
+ if (path.dirname(file) !== root) throw new WorkflowError('STATE_CORRUPT', 'Credential reference escaped its vault.');
179
+ return file;
180
+ }
181
+
182
+ function validateRecord(value: unknown, reference: string): StoredCredentialReference {
183
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
184
+ throw new WorkflowError('STATE_CORRUPT', 'Credential reference record is invalid.', { credentialRef: reference });
185
+ }
186
+ const record = value as Partial<StoredCredentialReference>;
187
+ const valid = record.schemaVersion === 1
188
+ && record.referenceId === reference
189
+ && (record.kind === 'writer-lease' || record.kind === 'claim-token')
190
+ && typeof record.projectId === 'string'
191
+ && typeof record.entityId === 'string'
192
+ && typeof record.actor === 'string'
193
+ && Array.isArray(record.allowedActions)
194
+ && record.allowedActions.every((entry) => typeof entry === 'string')
195
+ && typeof record.token === 'string'
196
+ && typeof record.tokenSha256 === 'string'
197
+ && typeof record.createdAt === 'string'
198
+ && typeof record.expiresAt === 'string'
199
+ && typeof record.oneTime === 'boolean';
200
+ if (!valid) throw new WorkflowError('STATE_CORRUPT', 'Credential reference record has an invalid schema.', { credentialRef: reference });
201
+ return record as StoredCredentialReference;
202
+ }
203
+
204
+ function publicReference(record: StoredCredentialReference): PublicCredentialReference {
205
+ return {
206
+ credentialRef: record.referenceId,
207
+ kind: record.kind,
208
+ entityId: record.entityId,
209
+ actor: record.actor,
210
+ allowedActions: [...record.allowedActions],
211
+ expiresAt: record.expiresAt,
212
+ transport: 'local-vault-v1',
213
+ oneTime: record.oneTime,
214
+ };
215
+ }
@@ -0,0 +1,190 @@
1
+ import { createHash } from 'node:crypto';
2
+ import type {
3
+ DelegatedAuthorizationEvidence,
4
+ DelegationGrant,
5
+ DelegationGrantGate,
6
+ DelegationPolicyInput,
7
+ DelegationTransition,
8
+ MilestoneState,
9
+ ProjectKnowledgeMap,
10
+ TaskState,
11
+ } from './contracts.js';
12
+ import { WorkflowError } from './errors.js';
13
+
14
+ const TRANSITIONS: readonly DelegationTransition[] = [
15
+ 'project_memory.approve',
16
+ 'task.execution_authorize',
17
+ 'task.final_accept',
18
+ 'milestone.execution_authorize',
19
+ 'milestone.final_accept',
20
+ ];
21
+
22
+ type DelegationTarget = TaskState | MilestoneState | ProjectKnowledgeMap;
23
+
24
+ export function prepareDelegationGrantGate(
25
+ projectId: string,
26
+ input: DelegationPolicyInput,
27
+ now: Date,
28
+ ): DelegationGrantGate {
29
+ const policy = normalizeDelegationPolicy(input, now);
30
+ const policyHash = hashDelegationPolicy(projectId, policy);
31
+ const binding = ['delegation-grant-issuance-v1', projectId, policyHash].join('\0');
32
+ return {
33
+ kind: 'delegation_grant_issuance',
34
+ projectId,
35
+ policy,
36
+ policyHash,
37
+ confirmationCode: `DGA-${createHash('sha256').update(binding).digest('hex').slice(0, 24).toUpperCase()}`,
38
+ };
39
+ }
40
+
41
+ export function normalizeDelegationPolicy(
42
+ input: DelegationPolicyInput,
43
+ now: Date,
44
+ ): DelegationPolicyInput {
45
+ const principal = input.principal?.trim();
46
+ const delegate = input.delegate?.trim();
47
+ if (!principal || !delegate) {
48
+ throw new WorkflowError('INVALID_ARGUMENT', 'Delegation requires non-empty principal and delegate values.');
49
+ }
50
+ if (principal === delegate) {
51
+ throw new WorkflowError('INVALID_ARGUMENT', 'Delegation principal and delegate must be distinct audit actors.');
52
+ }
53
+ if (!input.scope || !['project', 'milestone', 'task'].includes(input.scope.kind)) {
54
+ throw new WorkflowError('INVALID_ARGUMENT', 'Delegation scope must be project, milestone, or task.');
55
+ }
56
+ const scope = normalizeScope(input.scope);
57
+ if (!Array.isArray(input.transitions) || input.transitions.length === 0) {
58
+ throw new WorkflowError('INVALID_ARGUMENT', 'Delegation requires at least one allowed transition.');
59
+ }
60
+ const transitions = [...new Set(input.transitions)];
61
+ if (transitions.some((transition) => !TRANSITIONS.includes(transition))) {
62
+ throw new WorkflowError('INVALID_ARGUMENT', 'Delegation contains an unsupported transition.', {
63
+ supportedTransitions: TRANSITIONS,
64
+ });
65
+ }
66
+ transitions.sort();
67
+ if (transitions.includes('project_memory.approve') && scope.kind !== 'project') {
68
+ throw new WorkflowError(
69
+ 'INVALID_ARGUMENT',
70
+ 'project_memory.approve requires project scope.',
71
+ );
72
+ }
73
+ const expiresAtDate = new Date(input.expiresAt);
74
+ if (!Number.isFinite(expiresAtDate.getTime())) {
75
+ throw new WorkflowError('INVALID_ARGUMENT', 'Delegation expiresAt must be a valid ISO-8601 timestamp.');
76
+ }
77
+ if (expiresAtDate.getTime() <= now.getTime()) {
78
+ throw new WorkflowError('INVALID_ARGUMENT', 'Delegation expiresAt must be in the future.');
79
+ }
80
+ return {
81
+ principal,
82
+ delegate,
83
+ scope,
84
+ transitions,
85
+ expiresAt: expiresAtDate.toISOString(),
86
+ };
87
+ }
88
+
89
+ export function hashDelegationPolicy(projectId: string, policy: DelegationPolicyInput): string {
90
+ return createHash('sha256')
91
+ .update(JSON.stringify({
92
+ version: 'delegation-policy-v1',
93
+ projectId,
94
+ principal: policy.principal,
95
+ delegate: policy.delegate,
96
+ scope: policy.scope,
97
+ transitions: policy.transitions,
98
+ expiresAt: policy.expiresAt,
99
+ }))
100
+ .digest('hex');
101
+ }
102
+
103
+ export function assertDelegationCanAuthorize(
104
+ grant: DelegationGrant,
105
+ transition: DelegationTransition,
106
+ actor: string,
107
+ target: DelegationTarget,
108
+ now: Date,
109
+ ): DelegatedAuthorizationEvidence {
110
+ const expectedPolicyHash = hashDelegationPolicy(grant.projectId, {
111
+ principal: grant.principal,
112
+ delegate: grant.delegate,
113
+ scope: grant.scope,
114
+ transitions: grant.transitions,
115
+ expiresAt: grant.expiresAt,
116
+ });
117
+ if (grant.policyHash !== expectedPolicyHash) {
118
+ throw new WorkflowError('STATE_CORRUPT', `Delegation ${grant.id} policy hash is invalid.`, {
119
+ expectedPolicyHash,
120
+ actualPolicyHash: grant.policyHash,
121
+ });
122
+ }
123
+ const expiry = new Date(grant.expiresAt).getTime();
124
+ if (!Number.isFinite(expiry)) {
125
+ throw new WorkflowError('STATE_CORRUPT', `Delegation ${grant.id} has an invalid expiresAt value.`);
126
+ }
127
+ if (grant.status !== 'active') {
128
+ throw new WorkflowError('TRANSITION_BLOCKED', `Delegation ${grant.id} is ${grant.status}.`);
129
+ }
130
+ if (expiry <= now.getTime()) {
131
+ throw new WorkflowError('TRANSITION_BLOCKED', `Delegation ${grant.id} has expired.`, {
132
+ expiresAt: grant.expiresAt,
133
+ });
134
+ }
135
+ const normalizedActor = actor.trim();
136
+ if (!normalizedActor || normalizedActor !== grant.delegate) {
137
+ throw new WorkflowError('TRANSITION_BLOCKED', `Delegation ${grant.id} does not authorize actor ${normalizedActor || '<empty>'}.`, {
138
+ expectedDelegate: grant.delegate,
139
+ });
140
+ }
141
+ if (!grant.transitions.includes(transition)) {
142
+ throw new WorkflowError('TRANSITION_BLOCKED', `Delegation ${grant.id} does not allow ${transition}.`);
143
+ }
144
+ if (!delegationScopeMatches(grant, target)) {
145
+ throw new WorkflowError('TRANSITION_BLOCKED', `Delegation ${grant.id} does not cover ${target.id}.`, {
146
+ scope: grant.scope,
147
+ });
148
+ }
149
+ return {
150
+ grantId: grant.id,
151
+ policyHash: grant.policyHash,
152
+ principal: grant.principal,
153
+ delegate: grant.delegate,
154
+ transition,
155
+ };
156
+ }
157
+
158
+ export function delegationCanAuthorize(
159
+ grant: DelegationGrant,
160
+ transition: DelegationTransition,
161
+ target: DelegationTarget,
162
+ now: Date,
163
+ ): boolean {
164
+ const expiry = new Date(grant.expiresAt).getTime();
165
+ return grant.status === 'active'
166
+ && Number.isFinite(expiry)
167
+ && expiry > now.getTime()
168
+ && grant.transitions.includes(transition)
169
+ && delegationScopeMatches(grant, target);
170
+ }
171
+
172
+ function delegationScopeMatches(grant: DelegationGrant, target: DelegationTarget): boolean {
173
+ if (grant.scope.kind === 'project') return true;
174
+ if (target.kind === 'knowledge-map') return false;
175
+ if (grant.scope.kind === 'task') return target.kind === 'task' && grant.scope.id === target.id;
176
+ if (target.kind === 'milestone') return grant.scope.id === target.id;
177
+ return target.milestoneId === grant.scope.id;
178
+ }
179
+
180
+ function normalizeScope(scope: DelegationPolicyInput['scope']): DelegationPolicyInput['scope'] {
181
+ if (scope.kind === 'project') return { kind: 'project' };
182
+ const id = scope.id?.trim();
183
+ const pattern = scope.kind === 'milestone'
184
+ ? /^MS-[0-9A-HJKMNP-TV-Z]{26}$/
185
+ : /^TASK-[0-9A-HJKMNP-TV-Z]{26}$/;
186
+ if (!id || !pattern.test(id)) {
187
+ throw new WorkflowError('INVALID_ARGUMENT', `Delegation ${scope.kind} scope requires a valid entity ID.`);
188
+ }
189
+ return { kind: scope.kind, id };
190
+ }