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

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 (297) hide show
  1. package/README.md +27 -388
  2. package/dist/reviewer-runtime-build.json +98 -34
  3. package/dist/src/alpha6/adoption.d.ts +3 -1
  4. package/dist/src/alpha6/adoption.js +48 -8
  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 +53 -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 +8 -21
  30. package/dist/src/alpha6/remediation.js +225 -474
  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/alpha7/autonomy.d.ts +9 -0
  36. package/dist/src/alpha7/autonomy.js +40 -1
  37. package/dist/src/alpha7/autonomy.js.map +1 -1
  38. package/dist/src/beta1/project-transaction.d.ts +3 -1
  39. package/dist/src/beta1/project-transaction.js +20 -6
  40. package/dist/src/beta1/project-transaction.js.map +1 -1
  41. package/dist/src/checks/runner.d.ts +26 -0
  42. package/dist/src/checks/runner.js +223 -0
  43. package/dist/src/checks/runner.js.map +1 -0
  44. package/dist/src/checks/task-sync.d.ts +10 -0
  45. package/dist/src/checks/task-sync.js +41 -0
  46. package/dist/src/checks/task-sync.js.map +1 -0
  47. package/dist/src/checks/worker.d.ts +1 -0
  48. package/dist/src/checks/worker.js +154 -0
  49. package/dist/src/checks/worker.js.map +1 -0
  50. package/dist/src/cli-actions.d.ts +3 -3
  51. package/dist/src/cli-actions.js +8 -15
  52. package/dist/src/cli-actions.js.map +1 -1
  53. package/dist/src/cli.js +43 -97
  54. package/dist/src/cli.js.map +1 -1
  55. package/dist/src/contracts.d.ts +54 -34
  56. package/dist/src/delegation.d.ts +5 -1
  57. package/dist/src/delegation.js +4 -0
  58. package/dist/src/delegation.js.map +1 -1
  59. package/dist/src/dependency-provenance.d.ts +2 -2
  60. package/dist/src/dependency-provenance.js +12 -69
  61. package/dist/src/dependency-provenance.js.map +1 -1
  62. package/dist/src/domain/base-sync-conflict.d.ts +13 -0
  63. package/dist/src/domain/base-sync-conflict.js +32 -0
  64. package/dist/src/domain/base-sync-conflict.js.map +1 -0
  65. package/dist/src/domain/step-start-admission.d.ts +4 -0
  66. package/dist/src/domain/step-start-admission.js +9 -0
  67. package/dist/src/domain/step-start-admission.js.map +1 -0
  68. package/dist/src/domain/virgin-registration.d.ts +3 -0
  69. package/dist/src/domain/virgin-registration.js +65 -0
  70. package/dist/src/domain/virgin-registration.js.map +1 -0
  71. package/dist/src/errors.d.ts +1 -1
  72. package/dist/src/errors.js.map +1 -1
  73. package/dist/src/failed-checkpoint-update.d.ts +69 -0
  74. package/dist/src/failed-checkpoint-update.js +171 -0
  75. package/dist/src/failed-checkpoint-update.js.map +1 -0
  76. package/dist/src/gateway-handshake.js +0 -8
  77. package/dist/src/gateway-handshake.js.map +1 -1
  78. package/dist/src/git.d.ts +3 -1
  79. package/dist/src/git.js +41 -14
  80. package/dist/src/git.js.map +1 -1
  81. package/dist/src/graph.js +25 -3
  82. package/dist/src/graph.js.map +1 -1
  83. package/dist/src/index.d.ts +1 -0
  84. package/dist/src/lifecycle/corrective-replan-credential-core.d.ts +2 -0
  85. package/dist/src/lifecycle/corrective-replan-credential-core.js +45 -9
  86. package/dist/src/lifecycle/corrective-replan-credential-core.js.map +1 -1
  87. package/dist/src/lifecycle/corrective-replan.js +12 -8
  88. package/dist/src/lifecycle/corrective-replan.js.map +1 -1
  89. package/dist/src/lifecycle/semantic-registry.js +4 -2
  90. package/dist/src/lifecycle/semantic-registry.js.map +1 -1
  91. package/dist/src/navigation-actions.d.ts +9 -0
  92. package/dist/src/navigation-actions.js +72 -0
  93. package/dist/src/navigation-actions.js.map +1 -0
  94. package/dist/src/navigation-update-artifact.d.ts +5 -0
  95. package/dist/src/navigation-update-artifact.js +227 -0
  96. package/dist/src/navigation-update-artifact.js.map +1 -0
  97. package/dist/src/navigation-update.d.ts +39 -0
  98. package/dist/src/navigation-update.js +82 -0
  99. package/dist/src/navigation-update.js.map +1 -0
  100. package/dist/src/observation.js +52 -29
  101. package/dist/src/observation.js.map +1 -1
  102. package/dist/src/observed-routes.js +10 -6
  103. package/dist/src/observed-routes.js.map +1 -1
  104. package/dist/src/pending-review-update.d.ts +0 -13
  105. package/dist/src/pending-review-update.js +1 -6
  106. package/dist/src/pending-review-update.js.map +1 -1
  107. package/dist/src/reviewer.d.ts +1 -1
  108. package/dist/src/reviewer.js +20 -17
  109. package/dist/src/reviewer.js.map +1 -1
  110. package/dist/src/runtime.d.ts +3 -0
  111. package/dist/src/runtime.js +6 -0
  112. package/dist/src/runtime.js.map +1 -0
  113. package/dist/src/state/corrective-replan-executor.d.ts +10 -0
  114. package/dist/src/state/corrective-replan-executor.js +38 -1
  115. package/dist/src/state/corrective-replan-executor.js.map +1 -1
  116. package/dist/src/state/corrective-replan-public-schema.js +15 -2
  117. package/dist/src/state/corrective-replan-public-schema.js.map +1 -1
  118. package/dist/src/state/corrective-replan-public.d.ts +9 -1
  119. package/dist/src/state/corrective-replan-public.js +50 -8
  120. package/dist/src/state/corrective-replan-public.js.map +1 -1
  121. package/dist/src/state/corrective-replan-transaction.d.ts +1 -0
  122. package/dist/src/state/corrective-replan-transaction.js +12 -11
  123. package/dist/src/state/corrective-replan-transaction.js.map +1 -1
  124. package/dist/src/state/corrective-yield-transaction.d.ts +1 -0
  125. package/dist/src/state/corrective-yield-transaction.js +11 -10
  126. package/dist/src/state/corrective-yield-transaction.js.map +1 -1
  127. package/dist/src/state/lock.d.ts +4 -0
  128. package/dist/src/state/lock.js +21 -0
  129. package/dist/src/state/lock.js.map +1 -1
  130. package/dist/src/version.d.ts +1 -1
  131. package/dist/src/version.js +1 -1
  132. package/dist/src/workflow-blocker-route.d.ts +8 -0
  133. package/dist/src/workflow-blocker-route.js +106 -0
  134. package/dist/src/workflow-blocker-route.js.map +1 -0
  135. package/dist/src/workflow.d.ts +88 -64
  136. package/dist/src/workflow.js +832 -1456
  137. package/dist/src/workflow.js.map +1 -1
  138. package/docs/autonomy-guardrails.md +26 -304
  139. package/docs/decisions.md +14 -112
  140. package/docs/development-flow.md +36 -237
  141. package/docs/project-memory.md +31 -50
  142. package/docs/release-app-evidence.md +190 -0
  143. package/docs/release.md +101 -388
  144. package/docs/updating-existing-project.md +65 -726
  145. package/package.json +11 -13
  146. package/plugins/codex-workflow-gateway/.codex-plugin/plugin.json +2 -2
  147. package/plugins/codex-workflow-gateway/references/chat-dispatch.md +88 -200
  148. package/plugins/codex-workflow-gateway/references/codebase-memory-routing.md +57 -0
  149. package/plugins/codex-workflow-gateway/references/protocol.md +51 -458
  150. package/plugins/codex-workflow-gateway/scripts/chat-dispatch.mjs +20 -4
  151. package/plugins/codex-workflow-gateway/scripts/chat-model-policy.mjs +21 -34
  152. package/plugins/codex-workflow-gateway/scripts/chat-registry.mjs +23 -5
  153. package/plugins/codex-workflow-gateway/skills/codex-workflow-gateway/SKILL.md +92 -778
  154. package/references/state-machine.md +7 -5
  155. package/roles/delivery-coordinator.md +31 -11
  156. package/roles/scope-lead.md +1 -1
  157. package/roles/technical-planner.md +5 -2
  158. package/schemas/adoption-posture-event.schema.json +11 -0
  159. package/schemas/authorization-event.schema.json +1 -1
  160. package/schemas/corrective-decision-event.schema.json +26 -4
  161. package/schemas/corrective-replan-credentials.private.schema.json +8 -5
  162. package/schemas/corrective-replan-execution-event.private.schema.json +9 -0
  163. package/schemas/corrective-replan-public.schema.json +2 -1
  164. package/schemas/delegation-grant.schema.json +1 -1
  165. package/schemas/project-knowledge-map.schema.json +3 -1
  166. package/schemas/remediation-event.schema.json +10 -1
  167. package/schemas/task.schema.json +64 -2
  168. package/schemas/transition-definition.schema.json +4 -0
  169. package/schemas/transition-payloads.schema.json +16 -1
  170. package/src/alpha6/adoption.ts +1146 -0
  171. package/src/alpha6/captured-check-evidence.ts +132 -0
  172. package/src/alpha6/check-support-anchor.ts +128 -0
  173. package/src/alpha6/component-owner.ts +168 -0
  174. package/src/alpha6/corrective-decision-boundary.ts +45 -0
  175. package/src/alpha6/downstream-proof.ts +520 -0
  176. package/src/alpha6/failed-step-planning-recovery.ts +88 -0
  177. package/src/alpha6/handoff.ts +1443 -0
  178. package/src/alpha6/journal.ts +473 -0
  179. package/src/alpha6/literal-test-invocation.ts +84 -0
  180. package/src/alpha6/mechanical-feasibility.ts +488 -0
  181. package/src/alpha6/milestone.ts +2192 -0
  182. package/src/alpha6/navigation-dirty-carryover.ts +154 -0
  183. package/src/alpha6/npm-check-contract.ts +47 -0
  184. package/src/alpha6/plan-integrity.ts +298 -0
  185. package/src/alpha6/plan-risk.ts +1480 -0
  186. package/src/alpha6/preexecution-replan.ts +187 -0
  187. package/src/alpha6/remediation-cause.ts +98 -0
  188. package/src/alpha6/remediation.ts +2505 -0
  189. package/src/alpha6/review.ts +1198 -0
  190. package/src/alpha6/root-cause-replan-carryover.ts +491 -0
  191. package/src/alpha6/store-sidecars.ts +335 -0
  192. package/src/alpha7/autonomy.ts +449 -0
  193. package/src/alpha7/corrective-recovery.ts +1332 -0
  194. package/src/artifacts.ts +130 -0
  195. package/src/beta1/project-transaction.ts +364 -0
  196. package/src/change-explanation.ts +153 -0
  197. package/src/checks/runner.ts +245 -0
  198. package/src/checks/task-sync.ts +41 -0
  199. package/src/checks/worker.ts +149 -0
  200. package/src/cli-actions.ts +112 -0
  201. package/src/cli.ts +1490 -0
  202. package/src/contracts.ts +1512 -0
  203. package/src/credential-output.ts +89 -0
  204. package/src/credential-transport.ts +215 -0
  205. package/src/delegation.ts +194 -0
  206. package/src/dependency-provenance.ts +472 -0
  207. package/src/diagnostics.ts +93 -0
  208. package/src/domain/base-sync-conflict.ts +35 -0
  209. package/src/domain/completed-step-carryover.ts +76 -0
  210. package/src/domain/discovery.ts +27 -0
  211. package/src/domain/plan-semantics.ts +58 -0
  212. package/src/domain/step-start-admission.ts +10 -0
  213. package/src/domain/validation.ts +177 -0
  214. package/src/domain/virgin-registration.ts +46 -0
  215. package/src/errors.ts +24 -0
  216. package/src/failed-checkpoint-update.ts +203 -0
  217. package/src/fs-utils.ts +61 -0
  218. package/src/gateway-handshake.ts +95 -0
  219. package/src/git.ts +183 -0
  220. package/src/graph.ts +342 -0
  221. package/src/historical-step-provenance.ts +136 -0
  222. package/src/index.ts +23 -0
  223. package/src/lifecycle/canonical-hash.ts +28 -0
  224. package/src/lifecycle/catalog.ts +202 -0
  225. package/src/lifecycle/compiler-inspection.ts +29 -0
  226. package/src/lifecycle/core-static-readiness.ts +132 -0
  227. package/src/lifecycle/corrective-replan-authority.ts +136 -0
  228. package/src/lifecycle/corrective-replan-binding-manifest.ts +51 -0
  229. package/src/lifecycle/corrective-replan-credential-core.ts +437 -0
  230. package/src/lifecycle/corrective-replan-credential-schema.ts +54 -0
  231. package/src/lifecycle/corrective-replan-credentials.ts +48 -0
  232. package/src/lifecycle/corrective-replan.ts +847 -0
  233. package/src/lifecycle/evaluator.ts +48 -0
  234. package/src/lifecycle/fingerprint.ts +488 -0
  235. package/src/lifecycle/immutable.ts +8 -0
  236. package/src/lifecycle/implementation-table.ts +118 -0
  237. package/src/lifecycle/index.ts +8 -0
  238. package/src/lifecycle/schema-artifact.ts +263 -0
  239. package/src/lifecycle/semantic-registry.ts +574 -0
  240. package/src/lifecycle/types.ts +838 -0
  241. package/src/memory.ts +273 -0
  242. package/src/migration.ts +161 -0
  243. package/src/navigation-actions.ts +70 -0
  244. package/src/navigation-update-artifact.ts +198 -0
  245. package/src/navigation-update.ts +121 -0
  246. package/src/observation.ts +225 -0
  247. package/src/observed-routes.ts +665 -0
  248. package/src/operational-contract.ts +125 -0
  249. package/src/pending-review-update.ts +175 -0
  250. package/src/repository.ts +99 -0
  251. package/src/reviewer.ts +1879 -0
  252. package/src/runtime.ts +6 -0
  253. package/src/state/corrective-replan-executor.ts +819 -0
  254. package/src/state/corrective-replan-public-schema.ts +83 -0
  255. package/src/state/corrective-replan-public.ts +952 -0
  256. package/src/state/corrective-replan-transaction.ts +949 -0
  257. package/src/state/corrective-yield-executor.ts +327 -0
  258. package/src/state/corrective-yield-transaction.ts +730 -0
  259. package/src/state/lock.ts +902 -0
  260. package/src/state/store.ts +567 -0
  261. package/src/transition-core.ts +330 -0
  262. package/src/ulid.ts +24 -0
  263. package/src/version.ts +2 -0
  264. package/src/workflow-blocker-route.ts +109 -0
  265. package/src/workflow.ts +10342 -0
  266. package/docs/alpha7.1-implementation-brief.md +0 -268
  267. package/docs/alpha7.2-corrective-context-refresh-brief.md +0 -484
  268. package/docs/alpha7.2.1-remediation-recovery-brief.md +0 -86
  269. package/docs/beta1-stabilization-brief.md +0 -165
  270. package/docs/beta11-plan-integrity-recovery-brief.md +0 -38
  271. package/docs/beta13.2-signal-review-recovery.md +0 -38
  272. package/docs/beta2-initial-assembly-navigation-brief.md +0 -616
  273. package/docs/change-model.md +0 -118
  274. package/docs/delegated-approval.md +0 -254
  275. package/docs/lifecycle/state-machine-stabilization.md +0 -641
  276. package/docs/pdf/README.md +0 -24
  277. package/docs/pdf/codex-workflow-v2-architecture-ru.pdf +0 -0
  278. package/docs/pdf/codex-workflow-v2-chat-only-guide-ru.pdf +0 -0
  279. package/docs/pdf/codex-workflow-v2-technical-reference-ru.pdf +0 -0
  280. package/docs/pdf/requirements.txt +0 -1
  281. package/docs/pdf/sources/codex-workflow-v2-architecture-ru.md +0 -478
  282. package/docs/pdf/sources/codex-workflow-v2-chat-only-guide-ru.md +0 -506
  283. package/docs/pdf/sources/codex-workflow-v2-technical-reference-ru.md +0 -778
  284. package/docs/pending-review-update.md +0 -15
  285. package/docs/problem-briefs/01-pre-implementation-integrity.md +0 -482
  286. package/docs/problem-briefs/02-minimal-step-integrity.md +0 -411
  287. package/docs/problem-briefs/03-minimal-agent-context-integrity.md +0 -358
  288. package/docs/problem-briefs/04-task-dependency-and-structural-replacement-integrity.md +0 -573
  289. package/docs/problem-briefs/BRIEF-TEMPLATE.md +0 -56
  290. package/docs/problem-briefs/README.md +0 -120
  291. package/docs/problem-briefs/evidence/p01-mechanical-feasibility-corpus.md +0 -90
  292. package/docs/problem-briefs/evidence/signal-v4-pre-m3-replay.md +0 -246
  293. package/docs/split-required-recovery.md +0 -47
  294. package/docs/stable-release-defect-register.md +0 -730
  295. package/docs/validation-report.md +0 -182
  296. package/scripts/generate-pdf-docs.py +0 -524
  297. 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,194 @@
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.adopt',
16
+ 'project_memory.approve',
17
+ 'task.execution_authorize',
18
+ 'task.final_accept',
19
+ 'milestone.execution_authorize',
20
+ 'milestone.final_accept',
21
+ ];
22
+
23
+ type DelegationTarget = TaskState | MilestoneState | ProjectKnowledgeMap | { kind: 'project'; id: string; milestoneId?: null };
24
+
25
+ export function prepareDelegationGrantGate(
26
+ projectId: string,
27
+ input: DelegationPolicyInput,
28
+ now: Date,
29
+ ): DelegationGrantGate {
30
+ const policy = normalizeDelegationPolicy(input, now);
31
+ const policyHash = hashDelegationPolicy(projectId, policy);
32
+ const binding = ['delegation-grant-issuance-v1', projectId, policyHash].join('\0');
33
+ return {
34
+ kind: 'delegation_grant_issuance',
35
+ projectId,
36
+ policy,
37
+ policyHash,
38
+ confirmationCode: `DGA-${createHash('sha256').update(binding).digest('hex').slice(0, 24).toUpperCase()}`,
39
+ };
40
+ }
41
+
42
+ export function normalizeDelegationPolicy(
43
+ input: DelegationPolicyInput,
44
+ now: Date,
45
+ ): DelegationPolicyInput {
46
+ const principal = input.principal?.trim();
47
+ const delegate = input.delegate?.trim();
48
+ if (!principal || !delegate) {
49
+ throw new WorkflowError('INVALID_ARGUMENT', 'Delegation requires non-empty principal and delegate values.');
50
+ }
51
+ if (principal === delegate) {
52
+ throw new WorkflowError('INVALID_ARGUMENT', 'Delegation principal and delegate must be distinct audit actors.');
53
+ }
54
+ if (!input.scope || !['project', 'milestone', 'task'].includes(input.scope.kind)) {
55
+ throw new WorkflowError('INVALID_ARGUMENT', 'Delegation scope must be project, milestone, or task.');
56
+ }
57
+ const scope = normalizeScope(input.scope);
58
+ if (!Array.isArray(input.transitions) || input.transitions.length === 0) {
59
+ throw new WorkflowError('INVALID_ARGUMENT', 'Delegation requires at least one allowed transition.');
60
+ }
61
+ const transitions = [...new Set(input.transitions)];
62
+ if (transitions.some((transition) => !TRANSITIONS.includes(transition))) {
63
+ throw new WorkflowError('INVALID_ARGUMENT', 'Delegation contains an unsupported transition.', {
64
+ supportedTransitions: TRANSITIONS,
65
+ });
66
+ }
67
+ transitions.sort();
68
+ if (transitions.includes('project.adopt') && scope.kind !== 'project') {
69
+ throw new WorkflowError('INVALID_ARGUMENT', 'project.adopt requires project scope.');
70
+ }
71
+ if (transitions.includes('project_memory.approve') && scope.kind !== 'project') {
72
+ throw new WorkflowError(
73
+ 'INVALID_ARGUMENT',
74
+ 'project_memory.approve requires project scope.',
75
+ );
76
+ }
77
+ const expiresAtDate = new Date(input.expiresAt);
78
+ if (!Number.isFinite(expiresAtDate.getTime())) {
79
+ throw new WorkflowError('INVALID_ARGUMENT', 'Delegation expiresAt must be a valid ISO-8601 timestamp.');
80
+ }
81
+ if (expiresAtDate.getTime() <= now.getTime()) {
82
+ throw new WorkflowError('INVALID_ARGUMENT', 'Delegation expiresAt must be in the future.');
83
+ }
84
+ return {
85
+ principal,
86
+ delegate,
87
+ scope,
88
+ transitions,
89
+ expiresAt: expiresAtDate.toISOString(),
90
+ };
91
+ }
92
+
93
+ export function hashDelegationPolicy(projectId: string, policy: DelegationPolicyInput): string {
94
+ return createHash('sha256')
95
+ .update(JSON.stringify({
96
+ version: 'delegation-policy-v1',
97
+ projectId,
98
+ principal: policy.principal,
99
+ delegate: policy.delegate,
100
+ scope: policy.scope,
101
+ transitions: policy.transitions,
102
+ expiresAt: policy.expiresAt,
103
+ }))
104
+ .digest('hex');
105
+ }
106
+
107
+ export function assertDelegationCanAuthorize(
108
+ grant: DelegationGrant,
109
+ transition: DelegationTransition,
110
+ actor: string,
111
+ target: DelegationTarget,
112
+ now: Date,
113
+ ): DelegatedAuthorizationEvidence {
114
+ const expectedPolicyHash = hashDelegationPolicy(grant.projectId, {
115
+ principal: grant.principal,
116
+ delegate: grant.delegate,
117
+ scope: grant.scope,
118
+ transitions: grant.transitions,
119
+ expiresAt: grant.expiresAt,
120
+ });
121
+ if (grant.policyHash !== expectedPolicyHash) {
122
+ throw new WorkflowError('STATE_CORRUPT', `Delegation ${grant.id} policy hash is invalid.`, {
123
+ expectedPolicyHash,
124
+ actualPolicyHash: grant.policyHash,
125
+ });
126
+ }
127
+ const expiry = new Date(grant.expiresAt).getTime();
128
+ if (!Number.isFinite(expiry)) {
129
+ throw new WorkflowError('STATE_CORRUPT', `Delegation ${grant.id} has an invalid expiresAt value.`);
130
+ }
131
+ if (grant.status !== 'active') {
132
+ throw new WorkflowError('TRANSITION_BLOCKED', `Delegation ${grant.id} is ${grant.status}.`);
133
+ }
134
+ if (expiry <= now.getTime()) {
135
+ throw new WorkflowError('TRANSITION_BLOCKED', `Delegation ${grant.id} has expired.`, {
136
+ expiresAt: grant.expiresAt,
137
+ });
138
+ }
139
+ const normalizedActor = actor.trim();
140
+ if (!normalizedActor || normalizedActor !== grant.delegate) {
141
+ throw new WorkflowError('TRANSITION_BLOCKED', `Delegation ${grant.id} does not authorize actor ${normalizedActor || '<empty>'}.`, {
142
+ expectedDelegate: grant.delegate,
143
+ });
144
+ }
145
+ if (!grant.transitions.includes(transition)) {
146
+ throw new WorkflowError('TRANSITION_BLOCKED', `Delegation ${grant.id} does not allow ${transition}.`);
147
+ }
148
+ if (!delegationScopeMatches(grant, target)) {
149
+ throw new WorkflowError('TRANSITION_BLOCKED', `Delegation ${grant.id} does not cover ${target.id}.`, {
150
+ scope: grant.scope,
151
+ });
152
+ }
153
+ return {
154
+ grantId: grant.id,
155
+ policyHash: grant.policyHash,
156
+ principal: grant.principal,
157
+ delegate: grant.delegate,
158
+ transition,
159
+ };
160
+ }
161
+
162
+ export function delegationCanAuthorize(
163
+ grant: DelegationGrant,
164
+ transition: DelegationTransition,
165
+ target: DelegationTarget,
166
+ now: Date,
167
+ ): boolean {
168
+ const expiry = new Date(grant.expiresAt).getTime();
169
+ return grant.status === 'active'
170
+ && Number.isFinite(expiry)
171
+ && expiry > now.getTime()
172
+ && grant.transitions.includes(transition)
173
+ && delegationScopeMatches(grant, target);
174
+ }
175
+
176
+ function delegationScopeMatches(grant: DelegationGrant, target: DelegationTarget): boolean {
177
+ if (grant.scope.kind === 'project') return true;
178
+ if (target.kind === 'knowledge-map') return false;
179
+ if (grant.scope.kind === 'task') return target.kind === 'task' && grant.scope.id === target.id;
180
+ if (target.kind === 'milestone') return grant.scope.id === target.id;
181
+ return target.milestoneId === grant.scope.id;
182
+ }
183
+
184
+ function normalizeScope(scope: DelegationPolicyInput['scope']): DelegationPolicyInput['scope'] {
185
+ if (scope.kind === 'project') return { kind: 'project' };
186
+ const id = scope.id?.trim();
187
+ const pattern = scope.kind === 'milestone'
188
+ ? /^MS-[0-9A-HJKMNP-TV-Z]{26}$/
189
+ : /^TASK-[0-9A-HJKMNP-TV-Z]{26}$/;
190
+ if (!id || !pattern.test(id)) {
191
+ throw new WorkflowError('INVALID_ARGUMENT', `Delegation ${scope.kind} scope requires a valid entity ID.`);
192
+ }
193
+ return { kind: scope.kind, id };
194
+ }