@ornexus/neocortex 4.60.41 → 4.60.43

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 (62) hide show
  1. package/dist/sbom.cdx.json +31 -31
  2. package/install.ps1 +1 -1
  3. package/install.sh +1 -1
  4. package/package.json +14 -2
  5. package/packages/client/dist/commands/invoke.js +57 -56
  6. package/packages/client/dist/generated/command-registry.generated.d.ts +66 -23
  7. package/packages/client/dist/generated/command-registry.generated.js +1 -1
  8. package/packages/client/dist/generated/public-projections.generated.d.ts +100 -0
  9. package/packages/client/dist/generated/public-projections.generated.js +1 -0
  10. package/packages/client/dist/git/git-lifecycle-store.d.ts +17 -0
  11. package/packages/client/dist/git/git-lifecycle-store.js +4 -4
  12. package/packages/client/dist/index.d.ts +1 -0
  13. package/packages/client/dist/index.js +1 -1
  14. package/packages/client/dist/migrations/project-identity.js +7 -7
  15. package/packages/client/dist/migrations/state-json.d.ts +14 -0
  16. package/packages/client/dist/migrations/state-json.js +4 -0
  17. package/packages/client/dist/runner/headless/epic-git-reconciliation-inventory.d.ts +2 -0
  18. package/packages/client/dist/runner/headless/epic-git-reconciliation-inventory.js +1 -1
  19. package/packages/client/dist/runner/headless/epic-terminal-git-cli-adapter.js +8 -8
  20. package/packages/client/dist/runner/headless/run-yoloop.d.ts +7 -1
  21. package/packages/client/dist/runner/headless/run-yoloop.js +2 -2
  22. package/packages/client/dist/state/project-state-snapshot.js +1 -1
  23. package/packages/client/dist/state/state-json-repair.js +3 -3
  24. package/packages/client/dist/yoloop/invoke-hooks.d.ts +2 -3
  25. package/packages/client/dist/yoloop/invoke-hooks.js +9 -8
  26. package/packages/client/dist/yoloop/shared-discover-epics.d.ts +3 -3
  27. package/packages/client/dist/yoloop/shared-discover-epics.js +1 -1
  28. package/packages/client/dist/yoloop/shared-yoloop-types.d.ts +87 -1
  29. package/packages/client/dist/yoloop/yoloop-client-state-store.d.ts +27 -3
  30. package/packages/client/dist/yoloop/yoloop-client-state-store.js +1 -1
  31. package/packages/shared/dist/command-registry-contract.js +38 -0
  32. package/packages/shared/dist/continuity/goal-operation-authority.js +48 -0
  33. package/packages/shared/dist/executable-graph-catalog/contracts.js +935 -0
  34. package/packages/shared/dist/generated/command-registry.generated.js +82 -23
  35. package/packages/shared/dist/generated/invocation-schema.generated.js +12 -4
  36. package/packages/shared/dist/generated/public-projections.generated.js +149 -0
  37. package/packages/shared/dist/invocation/executable-policy-recovery.d.ts +83 -0
  38. package/packages/shared/dist/invocation/executable-policy-recovery.js +272 -0
  39. package/packages/shared/dist/invocation/index.d.ts +3 -1
  40. package/packages/shared/dist/invocation/index.js +40 -1
  41. package/packages/shared/dist/invocation/recovery-route-compiler.d.ts +56 -0
  42. package/packages/shared/dist/invocation/recovery-route-compiler.js +214 -0
  43. package/packages/shared/dist/state/index.d.ts +1 -0
  44. package/packages/shared/dist/state/index.js +1 -0
  45. package/packages/shared/dist/state/state-migration.d.ts +58 -0
  46. package/packages/shared/dist/state/state-migration.js +404 -0
  47. package/packages/shared/dist/yoloop/canonical.js +15 -0
  48. package/packages/shared/dist/yoloop/operational-budget.js +164 -0
  49. package/packages/shared/dist/yoloop/recovery-types.js +39 -0
  50. package/targets-stubs/antigravity/gemini.md +1 -1
  51. package/targets-stubs/antigravity/skill/SKILL.md +1 -1
  52. package/targets-stubs/claude-code/neocortex-root.agent.yaml +1 -1
  53. package/targets-stubs/claude-code/neocortex-root.md +2 -2
  54. package/targets-stubs/claude-code/neocortex.agent.yaml +1 -1
  55. package/targets-stubs/claude-code/neocortex.md +2 -2
  56. package/targets-stubs/codex/AGENTS.md +11 -2
  57. package/targets-stubs/codex/README.md +7 -4
  58. package/targets-stubs/codex/neocortex.toml +53 -6
  59. package/targets-stubs/cursor/agent.md +2 -2
  60. package/targets-stubs/gemini-cli/agent.md +2 -2
  61. package/targets-stubs/opencode/neocortex-root.md +1 -1
  62. package/targets-stubs/vscode/neocortex.agent.md +2 -2
@@ -0,0 +1,935 @@
1
+ /** P220.15 — pure, versioned catalog/readiness/progress contract authority. */
2
+ import { createHash } from 'node:crypto';
3
+ import { DEFAULT_YOLOOP_OPERATIONAL_BUDGET_LIMITS, MAX_YOLOOP_OPERATIONAL_BUDGET_LIMITS, YOLOOP_OPERATIONAL_BUDGET_DIMENSIONS, } from '../yoloop/operational-budget.js';
4
+ import { yoloopDomainHash } from '../yoloop/canonical.js';
5
+ export const EXECUTABLE_GRAPH_CATALOG_ERROR_CODES = [
6
+ 'CATALOG_SCHEMA_INVALID',
7
+ 'CATALOG_UNKNOWN_FIELD',
8
+ 'CATALOG_VERSION_UNSUPPORTED',
9
+ 'CATALOG_IDENTITY_INVALID',
10
+ 'CATALOG_DUPLICATE_IDENTITY',
11
+ 'CATALOG_CLOSURE_INCOMPLETE',
12
+ 'CATALOG_HASH_MISMATCH',
13
+ 'CATALOG_PROJECTION_LEAK',
14
+ 'CATALOG_BASELINE_DRIFT_UNVERSIONED',
15
+ 'CATALOG_BUDGET_INVALID',
16
+ 'PROGRESS_SCHEMA_INVALID',
17
+ 'PROGRESS_CHAIN_INVALID',
18
+ 'PROGRESS_READINESS_DRIFT',
19
+ 'VALIDATION_MATRIX_INVALID',
20
+ 'VALIDATION_MATRIX_DUPLICATE',
21
+ 'graphspec_v1_1_blocked_requires_migration',
22
+ 'graphspec_version_unsupported',
23
+ ];
24
+ export class ExecutableGraphCatalogContractError extends Error {
25
+ code;
26
+ field;
27
+ constructor(code, field = null) {
28
+ super(code);
29
+ this.code = code;
30
+ this.field = field;
31
+ this.name = 'ExecutableGraphCatalogContractError';
32
+ }
33
+ }
34
+ const fail = (code, field = null) => {
35
+ throw new ExecutableGraphCatalogContractError(code, field);
36
+ };
37
+ const compareBytes = (left, right) => Buffer.compare(Buffer.from(left, 'utf8'), Buffer.from(right, 'utf8'));
38
+ function isRecord(value) {
39
+ return value !== null && typeof value === 'object' && !Array.isArray(value);
40
+ }
41
+ function exactKeys(value, expected, code = 'CATALOG_SCHEMA_INVALID') {
42
+ if (!isRecord(value))
43
+ fail(code);
44
+ const record = value;
45
+ const expectedSet = new Set(expected);
46
+ const actual = Object.keys(record);
47
+ const unknown = actual.find((key) => !expectedSet.has(key));
48
+ if (unknown)
49
+ fail('CATALOG_UNKNOWN_FIELD', isSafeToken(unknown, 96) ? unknown : 'unknown-field');
50
+ const missing = expected.find((key) => !Object.hasOwn(record, key));
51
+ if (missing || actual.length !== expected.length)
52
+ fail(code, missing ?? null);
53
+ }
54
+ function assertUnicode(value, code) {
55
+ for (let index = 0; index < value.length; index += 1) {
56
+ const current = value.charCodeAt(index);
57
+ if (current >= 0xd800 && current <= 0xdbff) {
58
+ const next = value.charCodeAt(index + 1);
59
+ if (!(next >= 0xdc00 && next <= 0xdfff))
60
+ fail(code);
61
+ index += 1;
62
+ }
63
+ else if (current >= 0xdc00 && current <= 0xdfff)
64
+ fail(code);
65
+ }
66
+ }
67
+ /** Strict I-JSON-compatible canonical form with bytewise object-key ordering. */
68
+ export function canonicalizeExecutableGraphJson(value) {
69
+ if (value === null)
70
+ return 'null';
71
+ if (typeof value === 'string') {
72
+ assertUnicode(value, 'CATALOG_SCHEMA_INVALID');
73
+ return JSON.stringify(value);
74
+ }
75
+ if (typeof value === 'boolean')
76
+ return value ? 'true' : 'false';
77
+ if (typeof value === 'number') {
78
+ if (!Number.isFinite(value) || (Number.isInteger(value) && !Number.isSafeInteger(value))) {
79
+ return fail('CATALOG_SCHEMA_INVALID');
80
+ }
81
+ return JSON.stringify(value);
82
+ }
83
+ if (Array.isArray(value)) {
84
+ return `[${value.map(canonicalizeExecutableGraphJson).join(',')}]`;
85
+ }
86
+ if (!isRecord(value))
87
+ return fail('CATALOG_SCHEMA_INVALID');
88
+ const keys = Object.keys(value).sort(compareBytes);
89
+ for (const key of keys)
90
+ assertUnicode(key, 'CATALOG_SCHEMA_INVALID');
91
+ return `{${keys.map((key) => {
92
+ if (value[key] === undefined)
93
+ return fail('CATALOG_SCHEMA_INVALID');
94
+ return `${JSON.stringify(key)}:${canonicalizeExecutableGraphJson(value[key])}`;
95
+ }).join(',')}}`;
96
+ }
97
+ export function executableGraphDomainHash(domain, value) {
98
+ if (!isSafeToken(domain, 96))
99
+ fail('CATALOG_SCHEMA_INVALID', 'domain');
100
+ return createHash('sha256')
101
+ .update(`${domain}\0${canonicalizeExecutableGraphJson(value)}`, 'utf8')
102
+ .digest('hex');
103
+ }
104
+ function deepFreeze(value) {
105
+ if (value !== null && typeof value === 'object' && !Object.isFrozen(value)) {
106
+ for (const child of Object.values(value))
107
+ deepFreeze(child);
108
+ Object.freeze(value);
109
+ }
110
+ return value;
111
+ }
112
+ const HASH = /^[a-f0-9]{64}$/;
113
+ const COMMAND_ID = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
114
+ const SAFE_TOKEN = /^[A-Za-z0-9][A-Za-z0-9._:/@+-]*$/;
115
+ const SAFE_RELATIVE_PATH = /^(?!\/)(?![A-Za-z]:[\\/])(?!.*(?:^|\/)\.\.(?:\/|$))[A-Za-z0-9._@+-]+(?:\/[A-Za-z0-9._@+-]+)*$/;
116
+ function isHash(value) {
117
+ return typeof value === 'string' && HASH.test(value);
118
+ }
119
+ function isSafeToken(value, maxLength = 256) {
120
+ return typeof value === 'string' && value.length > 0 && value.length <= maxLength
121
+ && SAFE_TOKEN.test(value);
122
+ }
123
+ function requireToken(value, code, field, maxLength = 256) {
124
+ if (!isSafeToken(value, maxLength))
125
+ fail(code, field);
126
+ return value;
127
+ }
128
+ function requireHash(value, code, field) {
129
+ if (!isHash(value))
130
+ fail(code, field);
131
+ return value;
132
+ }
133
+ function requireIso(value, code, field) {
134
+ if (typeof value !== 'string' || value.length > 40 || !Number.isFinite(Date.parse(value))) {
135
+ fail(code, field);
136
+ }
137
+ return value;
138
+ }
139
+ function requirePositiveSafeInteger(value, code, field) {
140
+ if (!Number.isSafeInteger(value) || Number(value) <= 0)
141
+ fail(code, field);
142
+ return Number(value);
143
+ }
144
+ function normalizeTokens(value, code, field, options = {}) {
145
+ if (!Array.isArray(value) || value.length > (options.maxItems ?? 512))
146
+ fail(code, field);
147
+ const result = value.map((entry) => requireToken(entry, code, field));
148
+ if (options.nonEmpty && result.length === 0)
149
+ fail(code, field);
150
+ if (new Set(result).size !== result.length)
151
+ fail(code, field);
152
+ return Object.freeze(result.sort(compareBytes));
153
+ }
154
+ export const EXECUTABLE_GRAPH_PROJECT_ID = 'neocortex_command_graphs';
155
+ export const EXECUTABLE_GRAPH_ORCHESTRATOR_PROFILE = 'neocortex-command-graphs';
156
+ export const EXECUTABLE_GRAPH_DECISION_PROFILE = 'neocortex-command-graphs-decision';
157
+ export const EXECUTABLE_GRAPH_PROFILE_TARGETS = Object.freeze([
158
+ '.codex/agents/neocortex-command-graphs.toml',
159
+ '.codex/agents/neocortex-command-graphs-decision.toml',
160
+ '.claude/agents/neocortex-command-graphs.md',
161
+ '.claude/agents/neocortex-command-graphs-decision.md',
162
+ '.opencode/agents/neocortex-command-graphs.md',
163
+ '.opencode/agents/neocortex-command-graphs-decision.md',
164
+ ]);
165
+ export const EXECUTABLE_GRAPH_PROJECT_IDENTITY = deepFreeze({
166
+ schemaVersion: 'ExecutableGraphProjectIdentity/1.0',
167
+ projectId: EXECUTABLE_GRAPH_PROJECT_ID,
168
+ orchestratorProfile: EXECUTABLE_GRAPH_ORCHESTRATOR_PROFILE,
169
+ decisionProfile: EXECUTABLE_GRAPH_DECISION_PROFILE,
170
+ profileTargets: [...EXECUTABLE_GRAPH_PROFILE_TARGETS],
171
+ });
172
+ function normalizeProjectIdentity(value) {
173
+ exactKeys(value, ['schemaVersion', 'projectId', 'orchestratorProfile', 'decisionProfile', 'profileTargets'], 'CATALOG_IDENTITY_INVALID');
174
+ const record = value;
175
+ if (record.schemaVersion !== 'ExecutableGraphProjectIdentity/1.0'
176
+ || record.projectId !== EXECUTABLE_GRAPH_PROJECT_ID
177
+ || record.orchestratorProfile !== EXECUTABLE_GRAPH_ORCHESTRATOR_PROFILE
178
+ || record.decisionProfile !== EXECUTABLE_GRAPH_DECISION_PROFILE
179
+ || !Array.isArray(record.profileTargets)
180
+ || record.profileTargets.length !== 6)
181
+ fail('CATALOG_IDENTITY_INVALID', 'project');
182
+ const targets = record.profileTargets.map((target) => {
183
+ if (typeof target !== 'string' || target.length > 256 || !SAFE_RELATIVE_PATH.test(target)
184
+ || target === '.opencode/agents/neocortex.md')
185
+ fail('CATALOG_IDENTITY_INVALID', 'profileTargets');
186
+ return target;
187
+ });
188
+ if (new Set(targets).size !== 6
189
+ || canonicalizeExecutableGraphJson(targets) !== canonicalizeExecutableGraphJson(EXECUTABLE_GRAPH_PROFILE_TARGETS)) {
190
+ fail('CATALOG_IDENTITY_INVALID', 'profileTargets');
191
+ }
192
+ return EXECUTABLE_GRAPH_PROJECT_IDENTITY;
193
+ }
194
+ export const EXECUTABLE_CLOSURE_DIMENSIONS = Object.freeze([
195
+ 'command', 'subcommand', 'aliases', 'handler', 'assets', 'resolvers', 'nodes',
196
+ 'imports', 'gates', 'terminals', 'tests', 'receipts', 'policy', 'recovery',
197
+ 'tier', 'quota', 'billing',
198
+ ]);
199
+ export const CATALOG_DESCRIPTIVE_REASON_CODES = Object.freeze([
200
+ 'not-applicable',
201
+ 'not-declared',
202
+ 'external-authority',
203
+ 'future-story-owned',
204
+ 'protected-reference-withheld',
205
+ ]);
206
+ export const CATALOG_OPERATIONAL_BASELINE_V1 = deepFreeze({
207
+ schemaVersion: 'CatalogOperationalBaseline/1.0',
208
+ defaults: { ...DEFAULT_YOLOOP_OPERATIONAL_BUDGET_LIMITS },
209
+ ceilings: { ...MAX_YOLOOP_OPERATIONAL_BUDGET_LIMITS },
210
+ promotionThresholds: {
211
+ state: 'TBD',
212
+ latencyP95Ms: null,
213
+ promotionCostThreshold: null,
214
+ ownerStoryId: 'P220.44',
215
+ },
216
+ });
217
+ function normalizeGeneration(value) {
218
+ exactKeys(value, ['schemaVersion', 'generationId', 'registryVersion', 'registryChecksum', 'sourceHash']);
219
+ const record = value;
220
+ if (record.schemaVersion !== 'ExecutableGraphCatalogGeneration/1.0'
221
+ || typeof record.registryVersion !== 'string'
222
+ || !/^\d+\.\d+\.\d+$/.test(record.registryVersion))
223
+ fail('CATALOG_VERSION_UNSUPPORTED', 'generation');
224
+ return deepFreeze({
225
+ schemaVersion: 'ExecutableGraphCatalogGeneration/1.0',
226
+ generationId: requireToken(record.generationId, 'CATALOG_IDENTITY_INVALID', 'generationId'),
227
+ registryVersion: record.registryVersion,
228
+ registryChecksum: requireHash(record.registryChecksum, 'CATALOG_HASH_MISMATCH', 'registryChecksum'),
229
+ sourceHash: requireHash(record.sourceHash, 'CATALOG_HASH_MISMATCH', 'sourceHash'),
230
+ });
231
+ }
232
+ function normalizeSourceObservation(value) {
233
+ const keys = [
234
+ 'schemaVersion', 'registryEntries', 'publicCommands', 'publicSubcommands',
235
+ 'manifestRoots', 'declaredNodes', 'literalResolutions', 'unresolvedDeclarations',
236
+ ];
237
+ exactKeys(value, keys);
238
+ const record = value;
239
+ if (record.schemaVersion !== 'CatalogSourceObservation/1.0')
240
+ fail('CATALOG_VERSION_UNSUPPORTED', 'sourceObservation');
241
+ const output = { schemaVersion: 'CatalogSourceObservation/1.0' };
242
+ for (const key of keys.slice(1)) {
243
+ if (!Number.isSafeInteger(record[key]) || Number(record[key]) < 0)
244
+ fail('CATALOG_SCHEMA_INVALID', key);
245
+ output[key] = Number(record[key]);
246
+ }
247
+ if (Number(output.publicCommands) > Number(output.registryEntries)
248
+ || Number(output.literalResolutions) + Number(output.unresolvedDeclarations) !== Number(output.declaredNodes)) {
249
+ fail('CATALOG_SCHEMA_INVALID', 'sourceObservation');
250
+ }
251
+ return deepFreeze(output);
252
+ }
253
+ function normalizeOperationalBaseline(value) {
254
+ exactKeys(value, ['schemaVersion', 'defaults', 'ceilings', 'promotionThresholds'], 'CATALOG_BUDGET_INVALID');
255
+ const record = value;
256
+ if (record.schemaVersion !== 'CatalogOperationalBaseline/1.0')
257
+ fail('CATALOG_BUDGET_INVALID', 'schemaVersion');
258
+ exactKeys(record.defaults, YOLOOP_OPERATIONAL_BUDGET_DIMENSIONS, 'CATALOG_BUDGET_INVALID');
259
+ exactKeys(record.ceilings, YOLOOP_OPERATIONAL_BUDGET_DIMENSIONS, 'CATALOG_BUDGET_INVALID');
260
+ const rawDefaults = record.defaults;
261
+ const rawCeilings = record.ceilings;
262
+ const defaults = {};
263
+ const ceilings = {};
264
+ for (const dimension of YOLOOP_OPERATIONAL_BUDGET_DIMENSIONS) {
265
+ const fallback = rawDefaults[dimension];
266
+ const ceiling = rawCeilings[dimension];
267
+ if (!Number.isSafeInteger(fallback) || Number(fallback) <= 0
268
+ || !Number.isSafeInteger(ceiling) || Number(ceiling) <= 0
269
+ || Number(fallback) > Number(ceiling))
270
+ fail('CATALOG_BUDGET_INVALID', dimension);
271
+ defaults[dimension] = Number(fallback);
272
+ ceilings[dimension] = Number(ceiling);
273
+ }
274
+ exactKeys(record.promotionThresholds, ['state', 'latencyP95Ms', 'promotionCostThreshold', 'ownerStoryId'], 'CATALOG_BUDGET_INVALID');
275
+ const thresholds = record.promotionThresholds;
276
+ if (thresholds.state !== 'TBD'
277
+ || thresholds.latencyP95Ms !== null
278
+ || thresholds.promotionCostThreshold !== null
279
+ || thresholds.ownerStoryId !== 'P220.44')
280
+ fail('CATALOG_BUDGET_INVALID', 'promotionThresholds');
281
+ return deepFreeze({
282
+ schemaVersion: 'CatalogOperationalBaseline/1.0',
283
+ defaults: defaults,
284
+ ceilings: ceilings,
285
+ promotionThresholds: { state: 'TBD', latencyP95Ms: null, promotionCostThreshold: null, ownerStoryId: 'P220.44' },
286
+ });
287
+ }
288
+ function normalizeClosure(value) {
289
+ if (!Array.isArray(value) || value.length !== EXECUTABLE_CLOSURE_DIMENSIONS.length) {
290
+ fail('CATALOG_CLOSURE_INCOMPLETE', 'closure');
291
+ }
292
+ const byDimension = new Map();
293
+ for (const rawValue of value) {
294
+ const raw = rawValue;
295
+ exactKeys(raw, ['dimension', 'disposition', 'refs', 'reasonCode'], 'CATALOG_CLOSURE_INCOMPLETE');
296
+ if (!EXECUTABLE_CLOSURE_DIMENSIONS.includes(raw.dimension)) {
297
+ fail('CATALOG_CLOSURE_INCOMPLETE', 'dimension');
298
+ }
299
+ const dimension = raw.dimension;
300
+ if (byDimension.has(dimension))
301
+ fail('CATALOG_CLOSURE_INCOMPLETE', dimension);
302
+ if (raw.disposition === 'bound') {
303
+ if (raw.reasonCode !== null)
304
+ fail('CATALOG_CLOSURE_INCOMPLETE', dimension);
305
+ const refs = normalizeTokens(raw.refs, 'CATALOG_CLOSURE_INCOMPLETE', dimension, { nonEmpty: true });
306
+ byDimension.set(dimension, deepFreeze({ dimension, disposition: 'bound', refs: refs, reasonCode: null }));
307
+ }
308
+ else if (raw.disposition === 'descriptive') {
309
+ if (!Array.isArray(raw.refs) || raw.refs.length !== 0
310
+ || !CATALOG_DESCRIPTIVE_REASON_CODES.includes(raw.reasonCode)) {
311
+ fail('CATALOG_CLOSURE_INCOMPLETE', dimension);
312
+ }
313
+ byDimension.set(dimension, deepFreeze({
314
+ dimension,
315
+ disposition: 'descriptive',
316
+ refs: [],
317
+ reasonCode: raw.reasonCode,
318
+ }));
319
+ }
320
+ else
321
+ fail('CATALOG_CLOSURE_INCOMPLETE', dimension);
322
+ }
323
+ if (byDimension.size !== EXECUTABLE_CLOSURE_DIMENSIONS.length)
324
+ fail('CATALOG_CLOSURE_INCOMPLETE', 'closure');
325
+ return Object.freeze(EXECUTABLE_CLOSURE_DIMENSIONS.map((dimension) => byDimension.get(dimension)));
326
+ }
327
+ function normalizeRouteIdentity(value) {
328
+ exactKeys(value, ['commandId', 'operationId', 'aliases', 'visibility'], 'CATALOG_IDENTITY_INVALID');
329
+ const record = value;
330
+ if (typeof record.commandId !== 'string' || !COMMAND_ID.test(record.commandId)
331
+ || (record.operationId !== null && (typeof record.operationId !== 'string' || !COMMAND_ID.test(record.operationId)))
332
+ || (record.visibility !== 'public' && record.visibility !== 'internal'))
333
+ fail('CATALOG_IDENTITY_INVALID', 'route');
334
+ if (!Array.isArray(record.aliases) || record.aliases.length > 128)
335
+ fail('CATALOG_IDENTITY_INVALID', 'aliases');
336
+ const aliases = Object.freeze(record.aliases.map((alias) => {
337
+ if (typeof alias !== 'string' || (alias !== '?' && !COMMAND_ID.test(alias))) {
338
+ fail('CATALOG_IDENTITY_INVALID', 'aliases');
339
+ }
340
+ return alias;
341
+ }).sort(compareBytes));
342
+ if (new Set(aliases).size !== aliases.length
343
+ || aliases.some((alias) => alias === record.commandId || alias === record.operationId)) {
344
+ fail('CATALOG_IDENTITY_INVALID', 'aliases');
345
+ }
346
+ return deepFreeze({
347
+ commandId: record.commandId,
348
+ operationId: record.operationId,
349
+ aliases,
350
+ visibility: record.visibility,
351
+ });
352
+ }
353
+ function normalizeRow(value) {
354
+ exactKeys(value, ['schemaVersion', 'identity', 'closure']);
355
+ const record = value;
356
+ if (record.schemaVersion !== 'ExecutableGraphCatalogRow/1.0')
357
+ fail('CATALOG_VERSION_UNSUPPORTED', 'row');
358
+ return deepFreeze({
359
+ schemaVersion: 'ExecutableGraphCatalogRow/1.0',
360
+ identity: normalizeRouteIdentity(record.identity),
361
+ closure: normalizeClosure(record.closure),
362
+ });
363
+ }
364
+ function routeKey(identity) {
365
+ return `${identity.commandId}\0${identity.operationId ?? ''}`;
366
+ }
367
+ function computeCatalogCounts(rows) {
368
+ const commands = new Set(rows.map((row) => row.identity.commandId));
369
+ const publicCommands = new Set(rows.filter((row) => row.identity.visibility === 'public').map((row) => row.identity.commandId));
370
+ const closureBindings = Object.fromEntries(EXECUTABLE_CLOSURE_DIMENSIONS.map((dimension) => [
371
+ dimension,
372
+ rows.filter((row) => row.closure.find((entry) => entry.dimension === dimension)?.disposition === 'bound').length,
373
+ ]));
374
+ return deepFreeze({
375
+ schemaVersion: 'ExecutableGraphCatalogCountVector/1.0',
376
+ routes: rows.length,
377
+ commands: commands.size,
378
+ subcommands: rows.filter((row) => row.identity.operationId !== null).length,
379
+ publicCommands: publicCommands.size,
380
+ publicSubcommands: rows.filter((row) => row.identity.visibility === 'public' && row.identity.operationId !== null).length,
381
+ aliases: rows.reduce((total, row) => total + row.identity.aliases.length, 0),
382
+ boundDimensions: rows.reduce((total, row) => total + row.closure.filter((entry) => entry.disposition === 'bound').length, 0),
383
+ descriptiveDimensions: rows.reduce((total, row) => total + row.closure.filter((entry) => entry.disposition === 'descriptive').length, 0),
384
+ closureBindings,
385
+ });
386
+ }
387
+ export function normalizeExecutableGraphCatalogSourceV1(value) {
388
+ exactKeys(value, ['schemaVersion', 'project', 'generation', 'rows', 'sourceObservation', 'operationalBaseline', 'evidenceRefs']);
389
+ const record = value;
390
+ if (record.schemaVersion !== 'ExecutableGraphCatalogSource/1.0')
391
+ fail('CATALOG_VERSION_UNSUPPORTED', 'schemaVersion');
392
+ if (!Array.isArray(record.rows) || record.rows.length === 0 || record.rows.length > 4096)
393
+ fail('CATALOG_SCHEMA_INVALID', 'rows');
394
+ const rows = record.rows.map(normalizeRow).sort((left, right) => compareBytes(routeKey(left.identity), routeKey(right.identity)));
395
+ const routes = new Set();
396
+ const commandIds = new Set(rows.map((row) => row.identity.commandId));
397
+ const aliases = new Map();
398
+ for (const row of rows) {
399
+ const key = routeKey(row.identity);
400
+ if (routes.has(key))
401
+ fail('CATALOG_DUPLICATE_IDENTITY', 'route');
402
+ routes.add(key);
403
+ for (const alias of row.identity.aliases) {
404
+ const owner = aliases.get(alias);
405
+ if (commandIds.has(alias) || (owner && owner !== key))
406
+ fail('CATALOG_DUPLICATE_IDENTITY', 'alias');
407
+ aliases.set(alias, key);
408
+ }
409
+ }
410
+ const base = {
411
+ schemaVersion: 'ExecutableGraphCatalog/1.0',
412
+ project: normalizeProjectIdentity(record.project),
413
+ generation: normalizeGeneration(record.generation),
414
+ rows: Object.freeze(rows),
415
+ sourceObservation: normalizeSourceObservation(record.sourceObservation),
416
+ operationalBaseline: normalizeOperationalBaseline(record.operationalBaseline),
417
+ evidenceRefs: normalizeTokens(record.evidenceRefs, 'CATALOG_SCHEMA_INVALID', 'evidenceRefs', { nonEmpty: true, maxItems: 128 }),
418
+ counts: computeCatalogCounts(rows),
419
+ };
420
+ return deepFreeze({
421
+ ...base,
422
+ catalogHash: executableGraphDomainHash('p220-executable-graph-catalog-v1', base),
423
+ });
424
+ }
425
+ export function projectExecutableGraphCatalogPublicV1(catalog) {
426
+ if (!isHash(catalog.catalogHash))
427
+ fail('CATALOG_HASH_MISMATCH', 'catalogHash');
428
+ const expectedCatalog = executableGraphDomainHash('p220-executable-graph-catalog-v1', (({ catalogHash: _omitted, ...rest }) => rest)(catalog));
429
+ if (expectedCatalog !== catalog.catalogHash)
430
+ fail('CATALOG_HASH_MISMATCH', 'catalogHash');
431
+ const base = {
432
+ schemaVersion: 'ExecutableGraphCatalogPublicProjection/1.0',
433
+ generationId: catalog.generation.generationId,
434
+ catalogHash: catalog.catalogHash,
435
+ project: {
436
+ projectId: catalog.project.projectId,
437
+ orchestratorProfile: catalog.project.orchestratorProfile,
438
+ decisionProfile: catalog.project.decisionProfile,
439
+ },
440
+ counts: {
441
+ publicCommands: catalog.counts.publicCommands,
442
+ publicSubcommands: catalog.counts.publicSubcommands,
443
+ aliases: catalog.rows.filter((row) => row.identity.visibility === 'public')
444
+ .reduce((total, row) => total + row.identity.aliases.length, 0),
445
+ },
446
+ rows: Object.freeze(catalog.rows
447
+ .filter((row) => row.identity.visibility === 'public')
448
+ .map((row) => deepFreeze({
449
+ commandId: row.identity.commandId,
450
+ operationId: row.identity.operationId,
451
+ aliases: Object.freeze([...row.identity.aliases]),
452
+ closure: Object.freeze(row.closure.map((entry) => deepFreeze({
453
+ dimension: entry.dimension,
454
+ disposition: entry.disposition,
455
+ evidenceCount: entry.refs.length,
456
+ reasonCode: entry.reasonCode,
457
+ }))),
458
+ }))),
459
+ contractVersions: {
460
+ progress: 'ProgressSnapshot/1.0',
461
+ validation: 'ValidationMatrix/1.1',
462
+ readiness: 'ExecutableGraphReadinessVector/1.0',
463
+ },
464
+ evidenceRefs: Object.freeze([...catalog.evidenceRefs]),
465
+ publicSafe: true,
466
+ };
467
+ const projection = deepFreeze({
468
+ ...base,
469
+ projectionHash: executableGraphDomainHash('p220-executable-graph-catalog-public-v1', base),
470
+ });
471
+ const rendered = canonicalizeExecutableGraphJson(projection);
472
+ for (const row of catalog.rows) {
473
+ for (const entry of row.closure) {
474
+ if (entry.refs.some((ref) => rendered.includes(ref)))
475
+ fail('CATALOG_PROJECTION_LEAK', entry.dimension);
476
+ }
477
+ }
478
+ return projection;
479
+ }
480
+ export function createCatalogBaselineEvidenceV1(catalog, observedAt) {
481
+ const projection = projectExecutableGraphCatalogPublicV1(catalog);
482
+ return deepFreeze({
483
+ schemaVersion: 'CatalogBaselineEvidence/1.0',
484
+ sourceSchemaVersion: 'ExecutableGraphCatalogSource/1.0',
485
+ generationId: catalog.generation.generationId,
486
+ registryVersion: catalog.generation.registryVersion,
487
+ registryChecksum: catalog.generation.registryChecksum,
488
+ sourceHash: catalog.generation.sourceHash,
489
+ catalogHash: catalog.catalogHash,
490
+ publicProjectionHash: projection.projectionHash,
491
+ counts: catalog.counts,
492
+ sourceObservation: catalog.sourceObservation,
493
+ observedAt: requireIso(observedAt, 'CATALOG_SCHEMA_INVALID', 'observedAt'),
494
+ evidenceRefs: Object.freeze([...catalog.evidenceRefs]),
495
+ publicSafe: true,
496
+ });
497
+ }
498
+ export function validateCatalogBaselineTransitionV1(previous, next) {
499
+ const countsChanged = canonicalizeExecutableGraphJson(previous.counts) !== canonicalizeExecutableGraphJson(next.counts)
500
+ || canonicalizeExecutableGraphJson(previous.sourceObservation) !== canonicalizeExecutableGraphJson(next.sourceObservation);
501
+ if (countsChanged && (previous.generationId === next.generationId
502
+ || previous.registryChecksum === next.registryChecksum
503
+ || previous.sourceHash === next.sourceHash
504
+ || previous.catalogHash === next.catalogHash
505
+ || previous.publicProjectionHash === next.publicProjectionHash)) {
506
+ fail('CATALOG_BASELINE_DRIFT_UNVERSIONED', 'counts');
507
+ }
508
+ return true;
509
+ }
510
+ export const PACKAGE_STATUSES = Object.freeze(['DESIGN_READY', 'DESIGN_BLOCKED', 'DESIGN_BUDGET_EXHAUSTED']);
511
+ export const PERSISTENCE_STATUSES = Object.freeze(['PENDING_EXPORT', 'STORED', 'OPTED_OUT']);
512
+ export const MATERIALIZATION_STATUSES = Object.freeze(['NOT_STARTED', 'READY', 'PARTIAL', 'BLOCKED', 'BUDGET_EXHAUSTED']);
513
+ export const PROFILES_STATUSES = Object.freeze(['NOT_INSTALLED', 'INSTALLED']);
514
+ export const OPERATIONAL_STATUSES = Object.freeze(['NOT_OPERATIONAL', 'READY_FOR_EXPLICIT_EXECUTION']);
515
+ const PROGRESS_INPUT_KEYS = [
516
+ 'projectId', 'workflowId', 'runId', 'commandId', 'invocationId', 'sequence',
517
+ 'revision', 'occurredAt', 'parentSnapshotHash', 'stage', 'objective',
518
+ 'progressBasis', 'gates', 'blockers', 'budgets', 'evidenceRefs',
519
+ 'lastAcceptedEvidenceRef', 'readiness', 'readinessEvidence', 'nextAction',
520
+ 'reasonCodes', 'provenance',
521
+ ];
522
+ function normalizeReadiness(value) {
523
+ exactKeys(value, ['schemaVersion', 'packageStatus', 'persistenceStatus', 'materializationStatus', 'profilesStatus', 'operationalStatus', 'graphExecution'], 'PROGRESS_SCHEMA_INVALID');
524
+ const record = value;
525
+ if (record.schemaVersion !== 'ExecutableGraphReadinessVector/1.0'
526
+ || !PACKAGE_STATUSES.includes(record.packageStatus)
527
+ || !PERSISTENCE_STATUSES.includes(record.persistenceStatus)
528
+ || !MATERIALIZATION_STATUSES.includes(record.materializationStatus)
529
+ || !PROFILES_STATUSES.includes(record.profilesStatus)
530
+ || !OPERATIONAL_STATUSES.includes(record.operationalStatus)
531
+ || record.graphExecution !== 'NOT_RUN')
532
+ fail('PROGRESS_SCHEMA_INVALID', 'readiness');
533
+ return deepFreeze({ ...record });
534
+ }
535
+ const READINESS_EVIDENCE_FIELDS = [
536
+ 'packageStatus', 'persistenceStatus', 'materializationStatus',
537
+ 'profilesStatus', 'operationalStatus', 'graphExecution',
538
+ ];
539
+ function normalizeReadinessEvidence(value) {
540
+ exactKeys(value, ['schemaVersion', ...READINESS_EVIDENCE_FIELDS], 'PROGRESS_SCHEMA_INVALID');
541
+ const record = value;
542
+ if (record.schemaVersion !== 'ExecutableGraphReadinessEvidence/1.0')
543
+ fail('PROGRESS_SCHEMA_INVALID', 'readinessEvidence');
544
+ return deepFreeze({
545
+ schemaVersion: 'ExecutableGraphReadinessEvidence/1.0',
546
+ packageStatus: normalizeTokens(record.packageStatus, 'PROGRESS_SCHEMA_INVALID', 'packageStatus', { nonEmpty: true, maxItems: 32 }),
547
+ persistenceStatus: normalizeTokens(record.persistenceStatus, 'PROGRESS_SCHEMA_INVALID', 'persistenceStatus', { nonEmpty: true, maxItems: 32 }),
548
+ materializationStatus: normalizeTokens(record.materializationStatus, 'PROGRESS_SCHEMA_INVALID', 'materializationStatus', { nonEmpty: true, maxItems: 32 }),
549
+ profilesStatus: normalizeTokens(record.profilesStatus, 'PROGRESS_SCHEMA_INVALID', 'profilesStatus', { nonEmpty: true, maxItems: 32 }),
550
+ operationalStatus: normalizeTokens(record.operationalStatus, 'PROGRESS_SCHEMA_INVALID', 'operationalStatus', { nonEmpty: true, maxItems: 32 }),
551
+ graphExecution: normalizeTokens(record.graphExecution, 'PROGRESS_SCHEMA_INVALID', 'graphExecution', { nonEmpty: true, maxItems: 32 }),
552
+ });
553
+ }
554
+ function normalizeStage(value) {
555
+ exactKeys(value, ['schemaVersion', 'id', 'label', 'ordinal', 'total', 'completedStageIds', 'remainingStageIds'], 'PROGRESS_SCHEMA_INVALID');
556
+ const record = value;
557
+ if (record.schemaVersion !== 'ProgressStage/1.0')
558
+ fail('PROGRESS_SCHEMA_INVALID', 'stage');
559
+ const ordinal = requirePositiveSafeInteger(record.ordinal, 'PROGRESS_SCHEMA_INVALID', 'ordinal');
560
+ const total = requirePositiveSafeInteger(record.total, 'PROGRESS_SCHEMA_INVALID', 'total');
561
+ if (ordinal > total || typeof record.label !== 'string' || record.label.length === 0 || record.label.length > 128) {
562
+ fail('PROGRESS_SCHEMA_INVALID', 'stage');
563
+ }
564
+ const label = record.label;
565
+ assertUnicode(label, 'PROGRESS_SCHEMA_INVALID');
566
+ const completedStageIds = normalizeTokens(record.completedStageIds, 'PROGRESS_SCHEMA_INVALID', 'completedStageIds', { maxItems: 512 });
567
+ const remainingStageIds = normalizeTokens(record.remainingStageIds, 'PROGRESS_SCHEMA_INVALID', 'remainingStageIds', { nonEmpty: true, maxItems: 512 });
568
+ if (completedStageIds.some((id) => remainingStageIds.includes(id))
569
+ || completedStageIds.length + remainingStageIds.length !== total
570
+ || completedStageIds.length !== ordinal - 1
571
+ || remainingStageIds.length !== total - ordinal + 1)
572
+ fail('PROGRESS_SCHEMA_INVALID', 'stage');
573
+ const id = requireToken(record.id, 'PROGRESS_SCHEMA_INVALID', 'stage.id');
574
+ if (!completedStageIds.includes(id) && !remainingStageIds.includes(id))
575
+ fail('PROGRESS_SCHEMA_INVALID', 'stage.id');
576
+ return deepFreeze({ schemaVersion: 'ProgressStage/1.0', id, label, ordinal, total, completedStageIds, remainingStageIds });
577
+ }
578
+ function normalizeProgressCollection(value, keys, normalize, identity) {
579
+ if (!Array.isArray(value) || value.length > 128)
580
+ fail('PROGRESS_SCHEMA_INVALID');
581
+ const result = value.map((entry) => {
582
+ exactKeys(entry, keys, 'PROGRESS_SCHEMA_INVALID');
583
+ return normalize(entry);
584
+ }).sort((left, right) => compareBytes(identity(left), identity(right)));
585
+ if (new Set(result.map(identity)).size !== result.length)
586
+ fail('PROGRESS_SCHEMA_INVALID');
587
+ return Object.freeze(result);
588
+ }
589
+ function normalizeProgressInput(value) {
590
+ exactKeys(value, PROGRESS_INPUT_KEYS, 'PROGRESS_SCHEMA_INVALID');
591
+ const record = value;
592
+ if (record.projectId !== EXECUTABLE_GRAPH_PROJECT_ID || record.progressBasis !== 'ACCEPTANCE_CRITERIA'
593
+ || !Number.isSafeInteger(record.sequence) || Number(record.sequence) <= 0
594
+ || !Number.isSafeInteger(record.revision) || Number(record.revision) <= 0
595
+ || (record.parentSnapshotHash !== null && !isHash(record.parentSnapshotHash)))
596
+ fail('PROGRESS_SCHEMA_INVALID');
597
+ if (typeof record.objective !== 'string' || record.objective.length === 0 || record.objective.length > 512)
598
+ fail('PROGRESS_SCHEMA_INVALID', 'objective');
599
+ const objective = record.objective;
600
+ assertUnicode(objective, 'PROGRESS_SCHEMA_INVALID');
601
+ const gates = normalizeProgressCollection(record.gates, ['id', 'status', 'evidenceRefs'], (item) => {
602
+ if (!['pending', 'passed', 'blocked'].includes(item.status))
603
+ fail('PROGRESS_SCHEMA_INVALID', 'gate.status');
604
+ return {
605
+ id: requireToken(item.id, 'PROGRESS_SCHEMA_INVALID', 'gate.id'),
606
+ status: item.status,
607
+ evidenceRefs: normalizeTokens(item.evidenceRefs, 'PROGRESS_SCHEMA_INVALID', 'gate.evidenceRefs', { maxItems: 32 }),
608
+ };
609
+ }, (item) => item.id);
610
+ const blockers = normalizeProgressCollection(record.blockers, ['code', 'status', 'evidenceRefs'], (item) => {
611
+ if (!['active', 'resolved'].includes(item.status))
612
+ fail('PROGRESS_SCHEMA_INVALID', 'blocker.status');
613
+ return {
614
+ code: requireToken(item.code, 'PROGRESS_SCHEMA_INVALID', 'blocker.code'),
615
+ status: item.status,
616
+ evidenceRefs: normalizeTokens(item.evidenceRefs, 'PROGRESS_SCHEMA_INVALID', 'blocker.evidenceRefs', { nonEmpty: true, maxItems: 32 }),
617
+ };
618
+ }, (item) => item.code);
619
+ const budgets = normalizeProgressCollection(record.budgets, ['kind', 'status', 'remaining', 'evidenceRefs'], (item) => {
620
+ if (!['operational', 'entitlement', 'retrieval'].includes(item.kind)
621
+ || !['available', 'exhausted', 'not-applicable'].includes(item.status)
622
+ || (item.status === 'not-applicable' ? item.remaining !== null
623
+ : (!Number.isSafeInteger(item.remaining) || Number(item.remaining) < 0)))
624
+ fail('PROGRESS_SCHEMA_INVALID', 'budget');
625
+ return {
626
+ kind: item.kind,
627
+ status: item.status,
628
+ remaining: item.remaining,
629
+ evidenceRefs: normalizeTokens(item.evidenceRefs, 'PROGRESS_SCHEMA_INVALID', 'budget.evidenceRefs', { nonEmpty: true, maxItems: 32 }),
630
+ };
631
+ }, (item) => item.kind);
632
+ if (canonicalizeExecutableGraphJson(budgets.map(({ kind }) => kind).sort(compareBytes))
633
+ !== canonicalizeExecutableGraphJson(['entitlement', 'operational', 'retrieval']))
634
+ fail('PROGRESS_SCHEMA_INVALID', 'budgets');
635
+ exactKeys(record.nextAction, ['kind', 'reasonCode'], 'PROGRESS_SCHEMA_INVALID');
636
+ const nextAction = record.nextAction;
637
+ if (!['none', 'await-evidence', 'continue-eligible', 'retry-eligible', 'blocked', 'cancelled'].includes(nextAction.kind)) {
638
+ fail('PROGRESS_SCHEMA_INVALID', 'nextAction');
639
+ }
640
+ exactKeys(record.provenance, ['producer', 'generationId', 'legacySnapshotHash'], 'PROGRESS_SCHEMA_INVALID');
641
+ const provenance = record.provenance;
642
+ if (!['catalog', 'builder', 'materializer', 'runtime', 'migration'].includes(provenance.producer)
643
+ || (provenance.legacySnapshotHash !== null && !isHash(provenance.legacySnapshotHash)))
644
+ fail('PROGRESS_SCHEMA_INVALID', 'provenance');
645
+ const evidenceRefs = normalizeTokens(record.evidenceRefs, 'PROGRESS_SCHEMA_INVALID', 'evidenceRefs', { nonEmpty: true, maxItems: 128 });
646
+ if (record.lastAcceptedEvidenceRef !== null
647
+ && (!isSafeToken(record.lastAcceptedEvidenceRef) || !evidenceRefs.includes(record.lastAcceptedEvidenceRef))) {
648
+ fail('PROGRESS_SCHEMA_INVALID', 'lastAcceptedEvidenceRef');
649
+ }
650
+ return deepFreeze({
651
+ projectId: EXECUTABLE_GRAPH_PROJECT_ID,
652
+ workflowId: requireToken(record.workflowId, 'PROGRESS_SCHEMA_INVALID', 'workflowId'),
653
+ runId: requireToken(record.runId, 'PROGRESS_SCHEMA_INVALID', 'runId'),
654
+ commandId: requireToken(record.commandId, 'PROGRESS_SCHEMA_INVALID', 'commandId'),
655
+ invocationId: requireToken(record.invocationId, 'PROGRESS_SCHEMA_INVALID', 'invocationId'),
656
+ sequence: Number(record.sequence),
657
+ revision: Number(record.revision),
658
+ occurredAt: requireIso(record.occurredAt, 'PROGRESS_SCHEMA_INVALID', 'occurredAt'),
659
+ parentSnapshotHash: record.parentSnapshotHash,
660
+ stage: normalizeStage(record.stage),
661
+ objective,
662
+ progressBasis: 'ACCEPTANCE_CRITERIA',
663
+ gates,
664
+ blockers,
665
+ budgets,
666
+ evidenceRefs,
667
+ lastAcceptedEvidenceRef: record.lastAcceptedEvidenceRef,
668
+ readiness: normalizeReadiness(record.readiness),
669
+ readinessEvidence: normalizeReadinessEvidence(record.readinessEvidence),
670
+ nextAction: deepFreeze({
671
+ kind: nextAction.kind,
672
+ reasonCode: requireToken(nextAction.reasonCode, 'PROGRESS_SCHEMA_INVALID', 'nextAction.reasonCode'),
673
+ }),
674
+ reasonCodes: normalizeTokens(record.reasonCodes, 'PROGRESS_SCHEMA_INVALID', 'reasonCodes', { maxItems: 32 }),
675
+ provenance: deepFreeze({
676
+ producer: provenance.producer,
677
+ generationId: requireToken(provenance.generationId, 'PROGRESS_SCHEMA_INVALID', 'generationId'),
678
+ legacySnapshotHash: provenance.legacySnapshotHash,
679
+ }),
680
+ });
681
+ }
682
+ export function buildProgressSnapshotV1(input) {
683
+ const normalized = normalizeProgressInput(input);
684
+ const snapshotId = executableGraphDomainHash('p220-progress-snapshot-id-v1', {
685
+ projectId: normalized.projectId,
686
+ workflowId: normalized.workflowId,
687
+ runId: normalized.runId,
688
+ invocationId: normalized.invocationId,
689
+ sequence: normalized.sequence,
690
+ });
691
+ const base = {
692
+ schemaVersion: 'ProgressSnapshot/1.0',
693
+ snapshotId,
694
+ ...normalized,
695
+ publicSafe: true,
696
+ };
697
+ return deepFreeze({
698
+ ...base,
699
+ snapshotHash: executableGraphDomainHash('p220-progress-snapshot-v1', base),
700
+ });
701
+ }
702
+ export function assertProgressSnapshotV1(value) {
703
+ exactKeys(value, ['schemaVersion', 'snapshotId', ...PROGRESS_INPUT_KEYS, 'publicSafe', 'snapshotHash'], 'PROGRESS_SCHEMA_INVALID');
704
+ if (value.schemaVersion !== 'ProgressSnapshot/1.0' || value.publicSafe !== true || !isHash(value.snapshotId) || !isHash(value.snapshotHash)) {
705
+ fail('PROGRESS_SCHEMA_INVALID');
706
+ }
707
+ const input = Object.fromEntries(PROGRESS_INPUT_KEYS.map((key) => [key, value[key]]));
708
+ const rebuilt = buildProgressSnapshotV1(input);
709
+ if (rebuilt.snapshotId !== value.snapshotId || rebuilt.snapshotHash !== value.snapshotHash
710
+ || canonicalizeExecutableGraphJson(rebuilt) !== canonicalizeExecutableGraphJson(value))
711
+ fail('PROGRESS_CHAIN_INVALID');
712
+ return rebuilt;
713
+ }
714
+ export function validateProgressSnapshotChainV1(values) {
715
+ try {
716
+ assertProgressSnapshotChainV1(values);
717
+ return true;
718
+ }
719
+ catch {
720
+ return false;
721
+ }
722
+ }
723
+ export function assertProgressSnapshotChainV1(values) {
724
+ if (!Array.isArray(values) || values.length === 0 || values.length > 4096) {
725
+ return fail('PROGRESS_CHAIN_INVALID', 'snapshots');
726
+ }
727
+ let previous;
728
+ for (const raw of values) {
729
+ const snapshot = assertProgressSnapshotV1(raw);
730
+ if (!previous) {
731
+ if (snapshot.parentSnapshotHash !== null)
732
+ fail('PROGRESS_CHAIN_INVALID', 'parentSnapshotHash');
733
+ }
734
+ else {
735
+ if (snapshot.parentSnapshotHash !== previous.snapshotHash
736
+ || snapshot.sequence <= previous.sequence
737
+ || snapshot.revision <= previous.revision
738
+ || Date.parse(snapshot.occurredAt) < Date.parse(previous.occurredAt)
739
+ || snapshot.projectId !== previous.projectId
740
+ || snapshot.workflowId !== previous.workflowId
741
+ || snapshot.runId !== previous.runId
742
+ || snapshot.stage.total !== previous.stage.total
743
+ || snapshot.stage.ordinal < previous.stage.ordinal
744
+ || previous.stage.completedStageIds.some((id) => !snapshot.stage.completedStageIds.includes(id))
745
+ || snapshot.stage.remainingStageIds.some((id) => !previous.stage.remainingStageIds.includes(id))) {
746
+ fail('PROGRESS_CHAIN_INVALID', 'snapshot');
747
+ }
748
+ for (const field of READINESS_EVIDENCE_FIELDS) {
749
+ if (snapshot.readiness[field] !== previous.readiness[field]) {
750
+ const currentEvidence = snapshot.readinessEvidence[field];
751
+ const priorEvidence = previous.readinessEvidence[field];
752
+ if (canonicalizeExecutableGraphJson(currentEvidence) === canonicalizeExecutableGraphJson(priorEvidence)
753
+ || snapshot.lastAcceptedEvidenceRef === null
754
+ || !currentEvidence.includes(snapshot.lastAcceptedEvidenceRef)) {
755
+ fail('PROGRESS_READINESS_DRIFT', field);
756
+ }
757
+ }
758
+ }
759
+ }
760
+ previous = snapshot;
761
+ }
762
+ return true;
763
+ }
764
+ function validateLegacyYoloopSnapshot(value) {
765
+ try {
766
+ exactKeys(value, [
767
+ 'schemaVersion', 'runId', 'snapshotId', 'sequence', 'revision', 'operationId',
768
+ 'boundary', 'phase', 'status', 'occurredAt', 'parentSnapshotHash',
769
+ 'snapshotHash', 'budget', 'reasonCodes', 'publicSafe',
770
+ ], 'PROGRESS_SCHEMA_INVALID');
771
+ if (value.schemaVersion !== 'ProgressSnapshot/1.0' || value.publicSafe !== true
772
+ || !isHash(value.snapshotId) || !isHash(value.snapshotHash))
773
+ return false;
774
+ const { snapshotHash, ...hashed } = value;
775
+ if (snapshotHash !== yoloopDomainHash('p220-yoloop-progress-snapshot-v1', hashed))
776
+ return false;
777
+ const expectedId = yoloopDomainHash('p220-yoloop-progress-snapshot-id-v1', {
778
+ runId: value.runId,
779
+ operationId: value.operationId,
780
+ sequence: value.sequence,
781
+ phase: value.phase,
782
+ });
783
+ return value.snapshotId === expectedId;
784
+ }
785
+ catch {
786
+ return false;
787
+ }
788
+ }
789
+ export function adaptLegacyYoloopProgressSnapshotV1(legacy, context) {
790
+ if (!validateLegacyYoloopSnapshot(legacy))
791
+ fail('PROGRESS_SCHEMA_INVALID', 'legacy');
792
+ let normalizedContext;
793
+ try {
794
+ normalizedContext = normalizeProgressInput(context);
795
+ }
796
+ catch {
797
+ return fail('PROGRESS_SCHEMA_INVALID', 'context');
798
+ }
799
+ if (normalizedContext.runId !== legacy.runId
800
+ || normalizedContext.sequence !== legacy.sequence
801
+ || normalizedContext.revision !== legacy.revision
802
+ || normalizedContext.occurredAt !== legacy.occurredAt)
803
+ fail('PROGRESS_SCHEMA_INVALID', 'context');
804
+ return buildProgressSnapshotV1({
805
+ ...normalizedContext,
806
+ provenance: {
807
+ producer: 'migration',
808
+ generationId: normalizedContext.provenance.generationId,
809
+ legacySnapshotHash: legacy.snapshotHash,
810
+ },
811
+ });
812
+ }
813
+ export const VALIDATION_MATRIX_CATEGORIES = Object.freeze([
814
+ 'happy', 'partial', 'blocked', 'failure', 'stage-contract', 'retry-exhaustion',
815
+ 'cooperative-cancellation', 'resume-replay', 'stale-or-forged-receipt',
816
+ 'budget-exhaustion', 'unknown-gate', 'missing-protected-asset',
817
+ 'unauthorized-successor', 'protected-data-leak',
818
+ ]);
819
+ export function createValidationMatrixReceiptV11(kind, input) {
820
+ if (kind !== 'isolated' && kind !== 'regression')
821
+ fail('VALIDATION_MATRIX_INVALID', 'kind');
822
+ exactKeys(input, ['graphHash', 'elementId', 'caseId', 'category', 'evidenceHash'], 'VALIDATION_MATRIX_INVALID');
823
+ const record = input;
824
+ const category = record.category;
825
+ if (!VALIDATION_MATRIX_CATEGORIES.includes(category))
826
+ fail('VALIDATION_MATRIX_INVALID', 'category');
827
+ const body = {
828
+ schemaVersion: '1.1',
829
+ kind,
830
+ graphHash: requireHash(record.graphHash, 'VALIDATION_MATRIX_INVALID', 'graphHash'),
831
+ elementId: requireToken(record.elementId, 'VALIDATION_MATRIX_INVALID', 'elementId', 96),
832
+ caseId: requireToken(record.caseId, 'VALIDATION_MATRIX_INVALID', 'caseId', 96),
833
+ category,
834
+ evidenceHash: requireHash(record.evidenceHash, 'VALIDATION_MATRIX_INVALID', 'evidenceHash'),
835
+ };
836
+ return deepFreeze({
837
+ ...body,
838
+ receiptId: executableGraphDomainHash('validation-matrix-receipt-v1.1', body),
839
+ });
840
+ }
841
+ export function isValidationMatrixReceiptV11(value, expectedKind) {
842
+ try {
843
+ exactKeys(value, ['schemaVersion', 'kind', 'receiptId', 'graphHash', 'elementId', 'caseId', 'category', 'evidenceHash'], 'VALIDATION_MATRIX_INVALID');
844
+ if (value.schemaVersion !== '1.1' || (value.kind !== 'isolated' && value.kind !== 'regression')
845
+ || (expectedKind && value.kind !== expectedKind))
846
+ return false;
847
+ const rebuilt = createValidationMatrixReceiptV11(value.kind, {
848
+ graphHash: value.graphHash,
849
+ elementId: value.elementId,
850
+ caseId: value.caseId,
851
+ category: value.category,
852
+ evidenceHash: value.evidenceHash,
853
+ });
854
+ return rebuilt.receiptId === value.receiptId
855
+ && canonicalizeExecutableGraphJson(rebuilt) === canonicalizeExecutableGraphJson(value);
856
+ }
857
+ catch {
858
+ return false;
859
+ }
860
+ }
861
+ function normalizeValidationRow(value) {
862
+ exactKeys(value, ['isolated', 'regression'], 'VALIDATION_MATRIX_INVALID');
863
+ const record = value;
864
+ if (!isValidationMatrixReceiptV11(record.isolated, 'isolated')
865
+ || !isValidationMatrixReceiptV11(record.regression, 'regression'))
866
+ fail('VALIDATION_MATRIX_INVALID');
867
+ const isolated = record.isolated;
868
+ const regression = record.regression;
869
+ if (isolated.graphHash !== regression.graphHash
870
+ || isolated.elementId !== regression.elementId
871
+ || isolated.category !== regression.category
872
+ || isolated.caseId === regression.caseId
873
+ || isolated.receiptId === regression.receiptId
874
+ || isolated.evidenceHash === regression.evidenceHash)
875
+ fail('VALIDATION_MATRIX_INVALID');
876
+ return deepFreeze({ isolated, regression });
877
+ }
878
+ function validationRowKey(row) {
879
+ return [
880
+ row.isolated.graphHash,
881
+ row.isolated.elementId,
882
+ row.isolated.category,
883
+ row.isolated.caseId,
884
+ row.regression.caseId,
885
+ ].join('\0');
886
+ }
887
+ export function buildValidationMatrixV11(rowsInput) {
888
+ if (!Array.isArray(rowsInput) || rowsInput.length === 0 || rowsInput.length > 4096)
889
+ fail('VALIDATION_MATRIX_INVALID', 'rows');
890
+ const rows = rowsInput.map(normalizeValidationRow).sort((left, right) => compareBytes(validationRowKey(left), validationRowKey(right)));
891
+ if (new Set(rows.map(validationRowKey)).size !== rows.length)
892
+ fail('VALIDATION_MATRIX_DUPLICATE', 'rows');
893
+ const receiptIds = rows.flatMap((row) => [row.isolated.receiptId, row.regression.receiptId]);
894
+ if (new Set(receiptIds).size !== receiptIds.length)
895
+ fail('VALIDATION_MATRIX_DUPLICATE', 'receiptId');
896
+ const base = {
897
+ schemaVersion: 'ValidationMatrix/1.1',
898
+ rows: Object.freeze(rows),
899
+ publicSafe: true,
900
+ };
901
+ return deepFreeze({
902
+ ...base,
903
+ matrixHash: executableGraphDomainHash('p220-validation-matrix-v1.1', base),
904
+ });
905
+ }
906
+ export function validateValidationMatrixV11(value) {
907
+ try {
908
+ exactKeys(value, ['schemaVersion', 'rows', 'publicSafe', 'matrixHash'], 'VALIDATION_MATRIX_INVALID');
909
+ if (value.schemaVersion !== 'ValidationMatrix/1.1' || value.publicSafe !== true || !isHash(value.matrixHash)
910
+ || !Array.isArray(value.rows))
911
+ return false;
912
+ const rebuilt = buildValidationMatrixV11(value.rows);
913
+ return rebuilt.matrixHash === value.matrixHash
914
+ && canonicalizeExecutableGraphJson(rebuilt) === canonicalizeExecutableGraphJson(value);
915
+ }
916
+ catch {
917
+ return false;
918
+ }
919
+ }
920
+ export function admitGraphSpecVersion(version, mode) {
921
+ if (mode !== 'normal' && mode !== 'read-only-migration')
922
+ fail('graphspec_version_unsupported', 'mode');
923
+ if (version === '1.1' && mode === 'normal')
924
+ fail('graphspec_v1_1_blocked_requires_migration', 'version');
925
+ if (version !== '1.1' && version !== '1.2')
926
+ fail('graphspec_version_unsupported', 'version');
927
+ return deepFreeze({
928
+ schemaVersion: 'GraphSpecVersionAdmission/1.0',
929
+ version: version,
930
+ mode,
931
+ outcome: version === '1.1' ? 'accepted-read-only' : 'accepted',
932
+ mutationAllowed: false,
933
+ readinessClaimed: false,
934
+ });
935
+ }