codex-workflow-v2 2.0.0-beta.1 → 2.0.0-beta.11

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 (187) hide show
  1. package/README.md +94 -12
  2. package/dist/src/alpha6/adoption.js +13 -3
  3. package/dist/src/alpha6/adoption.js.map +1 -1
  4. package/dist/src/alpha6/handoff.d.ts +26 -2
  5. package/dist/src/alpha6/handoff.js +277 -29
  6. package/dist/src/alpha6/handoff.js.map +1 -1
  7. package/dist/src/alpha6/mechanical-feasibility.d.ts +7 -0
  8. package/dist/src/alpha6/mechanical-feasibility.js +303 -0
  9. package/dist/src/alpha6/mechanical-feasibility.js.map +1 -0
  10. package/dist/src/alpha6/milestone.d.ts +37 -2
  11. package/dist/src/alpha6/milestone.js +472 -33
  12. package/dist/src/alpha6/milestone.js.map +1 -1
  13. package/dist/src/alpha6/plan-integrity.d.ts +14 -0
  14. package/dist/src/alpha6/plan-integrity.js +127 -0
  15. package/dist/src/alpha6/plan-integrity.js.map +1 -0
  16. package/dist/src/alpha6/plan-risk.d.ts +1 -0
  17. package/dist/src/alpha6/plan-risk.js +15 -7
  18. package/dist/src/alpha6/plan-risk.js.map +1 -1
  19. package/dist/src/alpha6/remediation.d.ts +2 -1
  20. package/dist/src/alpha6/remediation.js +186 -9
  21. package/dist/src/alpha6/remediation.js.map +1 -1
  22. package/dist/src/alpha6/review.d.ts +3 -0
  23. package/dist/src/alpha6/review.js +24 -0
  24. package/dist/src/alpha6/review.js.map +1 -1
  25. package/dist/src/cli.js +486 -46
  26. package/dist/src/cli.js.map +1 -1
  27. package/dist/src/contracts.d.ts +238 -6
  28. package/dist/src/contracts.js +1 -1
  29. package/dist/src/credential-output.d.ts +10 -0
  30. package/dist/src/credential-output.js +73 -0
  31. package/dist/src/credential-output.js.map +1 -0
  32. package/dist/src/dependency-provenance.d.ts +19 -0
  33. package/dist/src/dependency-provenance.js +185 -0
  34. package/dist/src/dependency-provenance.js.map +1 -0
  35. package/dist/src/errors.d.ts +1 -1
  36. package/dist/src/errors.js.map +1 -1
  37. package/dist/src/git.d.ts +1 -1
  38. package/dist/src/git.js +13 -5
  39. package/dist/src/git.js.map +1 -1
  40. package/dist/src/index.d.ts +2 -1
  41. package/dist/src/index.js +2 -1
  42. package/dist/src/index.js.map +1 -1
  43. package/dist/src/lifecycle/canonical-hash.d.ts +6 -0
  44. package/dist/src/lifecycle/canonical-hash.js +24 -0
  45. package/dist/src/lifecycle/canonical-hash.js.map +1 -0
  46. package/dist/src/lifecycle/catalog.d.ts +26 -0
  47. package/dist/src/lifecycle/catalog.js +208 -0
  48. package/dist/src/lifecycle/catalog.js.map +1 -0
  49. package/dist/src/lifecycle/compiler-inspection.d.ts +48 -0
  50. package/dist/src/lifecycle/compiler-inspection.js +29 -0
  51. package/dist/src/lifecycle/compiler-inspection.js.map +1 -0
  52. package/dist/src/lifecycle/core-static-readiness.d.ts +6 -0
  53. package/dist/src/lifecycle/core-static-readiness.js +130 -0
  54. package/dist/src/lifecycle/core-static-readiness.js.map +1 -0
  55. package/dist/src/lifecycle/corrective-replan-authority.d.ts +53 -0
  56. package/dist/src/lifecycle/corrective-replan-authority.js +139 -0
  57. package/dist/src/lifecycle/corrective-replan-authority.js.map +1 -0
  58. package/dist/src/lifecycle/corrective-replan-binding-manifest.d.ts +21 -0
  59. package/dist/src/lifecycle/corrective-replan-binding-manifest.js +35 -0
  60. package/dist/src/lifecycle/corrective-replan-binding-manifest.js.map +1 -0
  61. package/dist/src/lifecycle/corrective-replan-credential-core.d.ts +86 -0
  62. package/dist/src/lifecycle/corrective-replan-credential-core.js +563 -0
  63. package/dist/src/lifecycle/corrective-replan-credential-core.js.map +1 -0
  64. package/dist/src/lifecycle/corrective-replan-credential-schema.d.ts +17 -0
  65. package/dist/src/lifecycle/corrective-replan-credential-schema.js +44 -0
  66. package/dist/src/lifecycle/corrective-replan-credential-schema.js.map +1 -0
  67. package/dist/src/lifecycle/corrective-replan-credentials.d.ts +47 -0
  68. package/dist/src/lifecycle/corrective-replan-credentials.js +103 -0
  69. package/dist/src/lifecycle/corrective-replan-credentials.js.map +1 -0
  70. package/dist/src/lifecycle/corrective-replan.d.ts +64 -0
  71. package/dist/src/lifecycle/corrective-replan.js +553 -0
  72. package/dist/src/lifecycle/corrective-replan.js.map +1 -0
  73. package/dist/src/lifecycle/evaluator.d.ts +2 -0
  74. package/dist/src/lifecycle/evaluator.js +35 -0
  75. package/dist/src/lifecycle/evaluator.js.map +1 -0
  76. package/dist/src/lifecycle/fingerprint.d.ts +31 -0
  77. package/dist/src/lifecycle/fingerprint.js +388 -0
  78. package/dist/src/lifecycle/fingerprint.js.map +1 -0
  79. package/dist/src/lifecycle/immutable.d.ts +1 -0
  80. package/dist/src/lifecycle/immutable.js +10 -0
  81. package/dist/src/lifecycle/immutable.js.map +1 -0
  82. package/dist/src/lifecycle/implementation-table.d.ts +29 -0
  83. package/dist/src/lifecycle/implementation-table.js +87 -0
  84. package/dist/src/lifecycle/implementation-table.js.map +1 -0
  85. package/dist/src/lifecycle/index.d.ts +4 -0
  86. package/dist/src/lifecycle/index.js +5 -0
  87. package/dist/src/lifecycle/index.js.map +1 -0
  88. package/dist/src/lifecycle/schema-artifact.d.ts +12 -0
  89. package/dist/src/lifecycle/schema-artifact.js +214 -0
  90. package/dist/src/lifecycle/schema-artifact.js.map +1 -0
  91. package/dist/src/lifecycle/semantic-registry.d.ts +6 -0
  92. package/dist/src/lifecycle/semantic-registry.js +489 -0
  93. package/dist/src/lifecycle/semantic-registry.js.map +1 -0
  94. package/dist/src/lifecycle/types.d.ts +718 -0
  95. package/dist/src/lifecycle/types.js +4 -0
  96. package/dist/src/lifecycle/types.js.map +1 -0
  97. package/dist/src/observation.d.ts +20 -0
  98. package/dist/src/observation.js +171 -0
  99. package/dist/src/observation.js.map +1 -0
  100. package/dist/src/reviewer.d.ts +43 -1
  101. package/dist/src/reviewer.js +203 -50
  102. package/dist/src/reviewer.js.map +1 -1
  103. package/dist/src/state/corrective-replan-executor.d.ts +64 -0
  104. package/dist/src/state/corrective-replan-executor.js +608 -0
  105. package/dist/src/state/corrective-replan-executor.js.map +1 -0
  106. package/dist/src/state/corrective-replan-public-schema.d.ts +3 -0
  107. package/dist/src/state/corrective-replan-public-schema.js +43 -0
  108. package/dist/src/state/corrective-replan-public-schema.js.map +1 -0
  109. package/dist/src/state/corrective-replan-public.d.ts +95 -0
  110. package/dist/src/state/corrective-replan-public.js +665 -0
  111. package/dist/src/state/corrective-replan-public.js.map +1 -0
  112. package/dist/src/state/corrective-replan-transaction.d.ts +160 -0
  113. package/dist/src/state/corrective-replan-transaction.js +730 -0
  114. package/dist/src/state/corrective-replan-transaction.js.map +1 -0
  115. package/dist/src/state/corrective-yield-executor.d.ts +31 -0
  116. package/dist/src/state/corrective-yield-executor.js +236 -0
  117. package/dist/src/state/corrective-yield-executor.js.map +1 -0
  118. package/dist/src/state/corrective-yield-transaction.d.ts +97 -0
  119. package/dist/src/state/corrective-yield-transaction.js +591 -0
  120. package/dist/src/state/corrective-yield-transaction.js.map +1 -0
  121. package/dist/src/state/lock.d.ts +76 -3
  122. package/dist/src/state/lock.js +611 -30
  123. package/dist/src/state/lock.js.map +1 -1
  124. package/dist/src/state/store.d.ts +3 -0
  125. package/dist/src/state/store.js +65 -12
  126. package/dist/src/state/store.js.map +1 -1
  127. package/dist/src/transition-core.d.ts +67 -0
  128. package/dist/src/transition-core.js +318 -0
  129. package/dist/src/transition-core.js.map +1 -0
  130. package/dist/src/version.d.ts +1 -1
  131. package/dist/src/version.js +1 -1
  132. package/dist/src/version.js.map +1 -1
  133. package/dist/src/workflow.d.ts +88 -24
  134. package/dist/src/workflow.js +1647 -270
  135. package/dist/src/workflow.js.map +1 -1
  136. package/docs/autonomy-guardrails.md +61 -1
  137. package/docs/beta11-plan-integrity-recovery-brief.md +38 -0
  138. package/docs/beta2-initial-assembly-navigation-brief.md +616 -0
  139. package/docs/delegated-approval.md +26 -1
  140. package/docs/development-flow.md +63 -8
  141. package/docs/lifecycle/state-machine-stabilization.md +641 -0
  142. package/docs/pdf/README.md +24 -0
  143. package/docs/pdf/codex-workflow-v2-architecture-ru.pdf +0 -0
  144. package/docs/pdf/codex-workflow-v2-chat-only-guide-ru.pdf +0 -0
  145. package/docs/pdf/codex-workflow-v2-technical-reference-ru.pdf +0 -0
  146. package/docs/pdf/requirements.txt +1 -0
  147. package/docs/pdf/sources/codex-workflow-v2-architecture-ru.md +234 -0
  148. package/docs/pdf/sources/codex-workflow-v2-chat-only-guide-ru.md +334 -0
  149. package/docs/pdf/sources/codex-workflow-v2-technical-reference-ru.md +413 -0
  150. package/docs/problem-briefs/01-pre-implementation-integrity.md +478 -0
  151. package/docs/problem-briefs/02-minimal-step-integrity.md +411 -0
  152. package/docs/problem-briefs/03-minimal-agent-context-integrity.md +358 -0
  153. package/docs/problem-briefs/04-task-dependency-and-structural-replacement-integrity.md +566 -0
  154. package/docs/problem-briefs/BRIEF-TEMPLATE.md +56 -0
  155. package/docs/problem-briefs/README.md +120 -0
  156. package/docs/problem-briefs/evidence/p01-mechanical-feasibility-corpus.md +90 -0
  157. package/docs/problem-briefs/evidence/signal-v4-pre-m3-replay.md +246 -0
  158. package/docs/release.md +30 -6
  159. package/docs/split-required-recovery.md +19 -25
  160. package/docs/stable-release-defect-register.md +465 -3
  161. package/docs/updating-existing-project.md +59 -4
  162. package/docs/validation-report.md +124 -56
  163. package/package.json +9 -1
  164. package/plugins/codex-workflow-gateway/.codex-plugin/plugin.json +10 -3
  165. package/plugins/codex-workflow-gateway/references/protocol.md +92 -4
  166. package/plugins/codex-workflow-gateway/scripts/chat-registry.mjs +376 -0
  167. package/plugins/codex-workflow-gateway/skills/codex-workflow-gateway/SKILL.md +263 -4
  168. package/references/state-machine.md +13 -6
  169. package/roles/delivery-coordinator.md +39 -0
  170. package/schemas/adoption-posture-event.schema.json +1 -1
  171. package/schemas/authorization-event.schema.json +64 -1
  172. package/schemas/context-envelope.schema.json +1 -1
  173. package/schemas/corrective-decision-event.schema.json +48 -3
  174. package/schemas/corrective-replan-credentials.private.schema.json +98 -0
  175. package/schemas/corrective-replan-execution-event.private.schema.json +53 -0
  176. package/schemas/corrective-replan-public.schema.json +142 -0
  177. package/schemas/corrective-replan-task-transaction.private.schema.json +151 -0
  178. package/schemas/corrective-yield-task-transaction.private.schema.json +98 -0
  179. package/schemas/milestone-scope-change-event.schema.json +6 -1
  180. package/schemas/milestone.schema.json +6 -1
  181. package/schemas/remediation-event.schema.json +11 -1
  182. package/schemas/task-handoff-event.schema.json +19 -3
  183. package/schemas/task.schema.json +24 -1
  184. package/schemas/transition-definition.schema.json +2609 -0
  185. package/schemas/transition-payloads.schema.json +140 -0
  186. package/scripts/generate-pdf-docs.py +512 -0
  187. package/scripts/run-pdf-docs.mjs +62 -0
@@ -0,0 +1,376 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ existsSync,
4
+ mkdirSync,
5
+ readFileSync,
6
+ renameSync,
7
+ rmSync,
8
+ statSync,
9
+ writeFileSync,
10
+ } from 'node:fs';
11
+ import os from 'node:os';
12
+ import path from 'node:path';
13
+ import process from 'node:process';
14
+ import { randomUUID } from 'node:crypto';
15
+
16
+ const TYPES = new Set(['coordinator', 'task', 'step-review', 'final-review', 'corrective-audit', 'plan-audit']);
17
+ const LOCK_STALE_MS = 30_000;
18
+ const LOCK_WAIT_MS = 2_000;
19
+
20
+ export function allocateChat(options) {
21
+ validateProjectId(options.projectId);
22
+ if (!TYPES.has(options.type)) throw new Error(`Unsupported chat type: ${options.type}`);
23
+ return withRegistryLock(options.projectId, options.registryHome, (file) => {
24
+ const registry = readRegistry(file, options.projectId);
25
+ const sequence = registry.nextSequence;
26
+ const reservationId = randomUUID();
27
+ const candidates = buildTitleCandidates({ ...options, sequence });
28
+ const entry = {
29
+ sequence,
30
+ reservationId,
31
+ type: options.type,
32
+ milestoneOrdinal: requiredOrdinal(options.milestoneOrdinal, 'M'),
33
+ taskOrdinal: optionalOrdinal(options.taskOrdinal, 'T'),
34
+ stepOrdinal: optionalOrdinal(options.stepOrdinal, 'S'),
35
+ entityId: requiredText(options.entityId, 'entityId'),
36
+ semanticTitle: requiredText(options.semanticTitle, 'semanticTitle'),
37
+ attempt: positiveInteger(options.attempt ?? 1, 'attempt'),
38
+ candidates,
39
+ candidateIndex: 0,
40
+ requestedTitle: candidates[0],
41
+ observedTitle: null,
42
+ titleVerified: false,
43
+ threadId: null,
44
+ hostId: null,
45
+ status: 'reserved',
46
+ createdAt: new Date().toISOString(),
47
+ updatedAt: new Date().toISOString(),
48
+ };
49
+ registry.nextSequence += 1;
50
+ registry.entries.push(entry);
51
+ writeRegistry(file, registry);
52
+ return publicEntry(entry);
53
+ });
54
+ }
55
+
56
+ export function readbackChat(options) {
57
+ validateProjectId(options.projectId);
58
+ return withRegistryLock(options.projectId, options.registryHome, (file) => {
59
+ const registry = readRegistry(file, options.projectId);
60
+ const entry = requireEntry(registry, options.reservationId);
61
+ const observedTitle = requiredText(options.observedTitle, 'observedTitle');
62
+ entry.observedTitle = observedTitle;
63
+ entry.updatedAt = new Date().toISOString();
64
+ if (observedTitle === entry.candidates[entry.candidateIndex]) {
65
+ entry.titleVerified = true;
66
+ entry.status = entry.threadId ? 'bound' : 'verified';
67
+ writeRegistry(file, registry);
68
+ return { accepted: true, renameRequired: false, ...publicEntry(entry) };
69
+ }
70
+ entry.titleVerified = false;
71
+ if (entry.candidateIndex + 1 >= entry.candidates.length) {
72
+ entry.status = 'blocked';
73
+ writeRegistry(file, registry);
74
+ return {
75
+ accepted: false,
76
+ renameRequired: false,
77
+ blocker: 'No deterministic title candidate survived exact app readback.',
78
+ ...publicEntry(entry),
79
+ };
80
+ }
81
+ entry.candidateIndex += 1;
82
+ entry.requestedTitle = entry.candidates[entry.candidateIndex];
83
+ entry.status = 'rename-required';
84
+ writeRegistry(file, registry);
85
+ return { accepted: false, renameRequired: true, renameTitle: entry.requestedTitle, ...publicEntry(entry) };
86
+ });
87
+ }
88
+
89
+ export function bindChat(options) {
90
+ validateProjectId(options.projectId);
91
+ return withRegistryLock(options.projectId, options.registryHome, (file) => {
92
+ const registry = readRegistry(file, options.projectId);
93
+ const entry = requireEntry(registry, options.reservationId);
94
+ if (!entry.titleVerified) throw new Error('Chat title must pass exact readback before binding.');
95
+ entry.threadId = requiredText(options.threadId, 'threadId');
96
+ entry.hostId = requiredText(options.hostId, 'hostId');
97
+ entry.status = 'bound';
98
+ entry.updatedAt = new Date().toISOString();
99
+ writeRegistry(file, registry);
100
+ return publicEntry(entry);
101
+ });
102
+ }
103
+
104
+ export function retitleChat(options) {
105
+ validateProjectId(options.projectId);
106
+ return withRegistryLock(options.projectId, options.registryHome, (file) => {
107
+ const registry = readRegistry(file, options.projectId);
108
+ const entry = requireEntry(registry, options.reservationId);
109
+ entry.entityId = requiredText(options.entityId, 'entityId');
110
+ entry.semanticTitle = requiredText(options.semanticTitle, 'semanticTitle');
111
+ entry.candidates = buildTitleCandidates({
112
+ sequence: entry.sequence,
113
+ type: entry.type,
114
+ milestoneOrdinal: entry.milestoneOrdinal,
115
+ taskOrdinal: entry.taskOrdinal,
116
+ stepOrdinal: entry.stepOrdinal,
117
+ entityId: entry.entityId,
118
+ semanticTitle: entry.semanticTitle,
119
+ attempt: entry.attempt,
120
+ });
121
+ entry.candidateIndex = 0;
122
+ entry.requestedTitle = entry.candidates[0];
123
+ entry.observedTitle = null;
124
+ entry.titleVerified = false;
125
+ entry.status = 'rename-required';
126
+ entry.updatedAt = new Date().toISOString();
127
+ writeRegistry(file, registry);
128
+ return { renameRequired: true, renameTitle: entry.requestedTitle, ...publicEntry(entry) };
129
+ });
130
+ }
131
+
132
+ export function abandonChat(options) {
133
+ validateProjectId(options.projectId);
134
+ return withRegistryLock(options.projectId, options.registryHome, (file) => {
135
+ const registry = readRegistry(file, options.projectId);
136
+ const entry = requireEntry(registry, options.reservationId);
137
+ entry.status = 'abandoned';
138
+ entry.updatedAt = new Date().toISOString();
139
+ writeRegistry(file, registry);
140
+ return publicEntry(entry);
141
+ });
142
+ }
143
+
144
+ export function showRegistry(options) {
145
+ validateProjectId(options.projectId);
146
+ const file = registryFile(options.projectId, options.registryHome);
147
+ return readRegistry(file, options.projectId);
148
+ }
149
+
150
+ export function buildTitleCandidates(options) {
151
+ const prefix = `#${String(positiveInteger(options.sequence, 'sequence')).padStart(3, '0')}`;
152
+ const milestone = requiredOrdinal(options.milestoneOrdinal, 'M');
153
+ const task = optionalOrdinal(options.taskOrdinal, 'T');
154
+ const step = optionalOrdinal(options.stepOrdinal, 'S');
155
+ const entityId = requiredText(options.entityId, 'entityId');
156
+ const semantic = compactWhitespace(requiredText(options.semanticTitle, 'semanticTitle'));
157
+ const attempt = positiveInteger(options.attempt ?? 1, 'attempt');
158
+ validateTypeBindings(options.type, task, step, entityId);
159
+ const scope = options.type === 'coordinator'
160
+ ? milestone
161
+ : options.type === 'step-review' || options.type === 'corrective-audit'
162
+ ? [milestone, task, step].filter(Boolean).join('/')
163
+ : [milestone, task].filter(Boolean).join('/');
164
+ const role = {
165
+ coordinator: 'Coord',
166
+ task: 'Task',
167
+ 'step-review': `Step Review A${attempt}`,
168
+ 'final-review': `Final Review A${attempt}`,
169
+ 'corrective-audit': `Corrective A${attempt}`,
170
+ 'plan-audit': `Plan Audit A${attempt}`,
171
+ }[options.type];
172
+ if (!role) throw new Error(`Unsupported chat type: ${options.type}`);
173
+ return unique([
174
+ `${prefix} · ${scope} · ${role} · ${semantic} · ${entityId}`,
175
+ `${prefix} · ${scope} · ${role} · ${entityId}`,
176
+ `${prefix}·${scope}·${shortRole(options.type, attempt)}·${entityId}`,
177
+ ]);
178
+ }
179
+
180
+ function shortRole(type, attempt) {
181
+ return {
182
+ coordinator: 'C',
183
+ task: 'T',
184
+ 'step-review': `SR${attempt}`,
185
+ 'final-review': `FR${attempt}`,
186
+ 'corrective-audit': `CA${attempt}`,
187
+ 'plan-audit': `PA${attempt}`,
188
+ }[type];
189
+ }
190
+
191
+ function withRegistryLock(projectId, registryHome, operation) {
192
+ const file = registryFile(projectId, registryHome);
193
+ mkdirSync(path.dirname(file), { recursive: true });
194
+ const lock = `${file}.lock`;
195
+ const started = Date.now();
196
+ while (true) {
197
+ try {
198
+ mkdirSync(lock);
199
+ writeFileSync(path.join(lock, 'owner.json'), `${JSON.stringify({ pid: process.pid, acquiredAt: new Date().toISOString() })}\n`);
200
+ break;
201
+ } catch (error) {
202
+ if (error?.code !== 'EEXIST') throw error;
203
+ if (lockIsStale(lock)) {
204
+ rmSync(lock, { recursive: true, force: true });
205
+ continue;
206
+ }
207
+ if (Date.now() - started >= LOCK_WAIT_MS) throw new Error(`Chat registry is locked for project ${projectId}.`);
208
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 20);
209
+ }
210
+ }
211
+ try {
212
+ return operation(file);
213
+ } finally {
214
+ rmSync(lock, { recursive: true, force: true });
215
+ }
216
+ }
217
+
218
+ function lockIsStale(lock) {
219
+ try {
220
+ return Date.now() - statSync(lock).mtimeMs > LOCK_STALE_MS;
221
+ } catch {
222
+ return false;
223
+ }
224
+ }
225
+
226
+ function registryFile(projectId, registryHome) {
227
+ const root = registryHome
228
+ ? path.resolve(registryHome)
229
+ : process.env.CODEX_WORKFLOW_CHAT_REGISTRY_HOME
230
+ ? path.resolve(process.env.CODEX_WORKFLOW_CHAT_REGISTRY_HOME)
231
+ : path.join(os.homedir(), '.codex', 'workflow-chat-registry', 'v1');
232
+ return path.join(root, 'projects', projectId, 'registry.json');
233
+ }
234
+
235
+ function readRegistry(file, projectId) {
236
+ if (!existsSync(file)) return { schemaVersion: 1, projectId, nextSequence: 1, entries: [] };
237
+ const value = JSON.parse(readFileSync(file, 'utf8'));
238
+ if (value.schemaVersion !== 1 || value.projectId !== projectId
239
+ || !Number.isSafeInteger(value.nextSequence) || value.nextSequence < 1) {
240
+ throw new Error('Chat registry failed schema or project binding validation.');
241
+ }
242
+ if (!Array.isArray(value.entries)) throw new Error('Chat registry entries must be an array.');
243
+ const sequences = value.entries.map((entry) => entry?.sequence);
244
+ const reservations = value.entries.map((entry) => entry?.reservationId);
245
+ if (sequences.some((sequence) => !Number.isSafeInteger(sequence) || sequence < 1 || sequence >= value.nextSequence)
246
+ || new Set(sequences).size !== sequences.length
247
+ || reservations.some((reservation) => typeof reservation !== 'string' || !reservation)
248
+ || new Set(reservations).size !== reservations.length) {
249
+ throw new Error('Chat registry monotonic sequence or reservation integrity failed.');
250
+ }
251
+ return value;
252
+ }
253
+
254
+ function writeRegistry(file, registry) {
255
+ const temporary = `${file}.${process.pid}.${randomUUID()}.tmp`;
256
+ writeFileSync(temporary, `${JSON.stringify(registry, null, 2)}\n`, { flag: 'wx' });
257
+ renameSync(temporary, file);
258
+ }
259
+
260
+ function requireEntry(registry, reservationId) {
261
+ const entry = registry.entries.find((candidate) => candidate.reservationId === reservationId);
262
+ if (!entry) throw new Error(`Unknown chat reservation: ${reservationId}`);
263
+ return entry;
264
+ }
265
+
266
+ function publicEntry(entry) {
267
+ return {
268
+ sequence: entry.sequence,
269
+ ordinal: `#${String(entry.sequence).padStart(3, '0')}`,
270
+ reservationId: entry.reservationId,
271
+ type: entry.type,
272
+ requestedTitle: entry.requestedTitle,
273
+ observedTitle: entry.observedTitle,
274
+ titleVerified: entry.titleVerified,
275
+ threadId: entry.threadId,
276
+ hostId: entry.hostId,
277
+ status: entry.status,
278
+ };
279
+ }
280
+
281
+ function validateProjectId(projectId) {
282
+ if (!/^[a-f0-9]{24}$/.test(projectId ?? '')) throw new Error('projectId must be a 24-character lowercase hex value.');
283
+ }
284
+
285
+ function validateTypeBindings(type, taskOrdinal, stepOrdinal, entityId) {
286
+ if (!TYPES.has(type)) throw new Error(`Unsupported chat type: ${type}`);
287
+ if (type !== 'coordinator' && !taskOrdinal) throw new Error(`${type} requires a Task membership ordinal.`);
288
+ if ((type === 'step-review' || type === 'corrective-audit') && !stepOrdinal) {
289
+ throw new Error(`${type} requires a Step membership ordinal.`);
290
+ }
291
+ const validEntity = type === 'coordinator'
292
+ ? entityId === 'DISCOVERY' || /^MS-[0-9A-HJKMNP-TV-Z]{26}$/.test(entityId)
293
+ : /^TASK-[0-9A-HJKMNP-TV-Z]{26}$/.test(entityId);
294
+ if (!validEntity) throw new Error(`${type} requires its full authoritative entity ID.`);
295
+ }
296
+
297
+ function requiredOrdinal(value, prefix) {
298
+ const text = requiredText(value, `${prefix} ordinal`).toUpperCase();
299
+ if (!new RegExp(`^${prefix}\\d{2,}$`).test(text)) throw new Error(`${prefix} ordinal must use ${prefix}<NN>.`);
300
+ return text;
301
+ }
302
+
303
+ function optionalOrdinal(value, prefix) {
304
+ return value === undefined || value === null || value === '' ? null : requiredOrdinal(value, prefix);
305
+ }
306
+
307
+ function requiredText(value, name) {
308
+ if (typeof value !== 'string' || !value.trim()) throw new Error(`${name} is required.`);
309
+ return value.trim();
310
+ }
311
+
312
+ function positiveInteger(value, name) {
313
+ const number = Number(value);
314
+ if (!Number.isSafeInteger(number) || number < 1) throw new Error(`${name} must be a positive integer.`);
315
+ return number;
316
+ }
317
+
318
+ function compactWhitespace(value) {
319
+ return value.replace(/\s+/g, ' ').trim();
320
+ }
321
+
322
+ function unique(values) {
323
+ return [...new Set(values)];
324
+ }
325
+
326
+ function option(args, name) {
327
+ const index = args.indexOf(`--${name}`);
328
+ return index >= 0 ? args[index + 1] : undefined;
329
+ }
330
+
331
+ function cli() {
332
+ const [action, ...args] = process.argv.slice(2);
333
+ const common = {
334
+ projectId: option(args, 'project-id'),
335
+ registryHome: option(args, 'registry-home'),
336
+ };
337
+ if (action === 'allocate') return allocateChat({
338
+ ...common,
339
+ type: option(args, 'type'),
340
+ milestoneOrdinal: option(args, 'milestone'),
341
+ taskOrdinal: option(args, 'task'),
342
+ stepOrdinal: option(args, 'step'),
343
+ entityId: option(args, 'entity-id'),
344
+ semanticTitle: option(args, 'semantic-title'),
345
+ attempt: option(args, 'attempt') ?? 1,
346
+ });
347
+ if (action === 'readback') return readbackChat({
348
+ ...common,
349
+ reservationId: option(args, 'reservation-id'),
350
+ observedTitle: option(args, 'observed-title'),
351
+ });
352
+ if (action === 'bind') return bindChat({
353
+ ...common,
354
+ reservationId: option(args, 'reservation-id'),
355
+ threadId: option(args, 'thread-id'),
356
+ hostId: option(args, 'host-id'),
357
+ });
358
+ if (action === 'retitle') return retitleChat({
359
+ ...common,
360
+ reservationId: option(args, 'reservation-id'),
361
+ entityId: option(args, 'entity-id'),
362
+ semanticTitle: option(args, 'semantic-title'),
363
+ });
364
+ if (action === 'abandon') return abandonChat({ ...common, reservationId: option(args, 'reservation-id') });
365
+ if (action === 'show') return showRegistry(common);
366
+ throw new Error('Use allocate, retitle, readback, bind, abandon, or show.');
367
+ }
368
+
369
+ if (process.argv[1] && path.resolve(process.argv[1]) === path.resolve(new URL(import.meta.url).pathname)) {
370
+ try {
371
+ process.stdout.write(`${JSON.stringify(cli())}\n`);
372
+ } catch (error) {
373
+ process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
374
+ process.exitCode = 1;
375
+ }
376
+ }