@ornexus/neocortex 4.60.40 → 4.60.42
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.
- package/dist/sbom.cdx.json +31 -31
- package/install.ps1 +1 -1
- package/install.sh +1 -1
- package/package.json +14 -2
- package/packages/client/dist/commands/invoke.d.ts +12 -0
- package/packages/client/dist/commands/invoke.js +45 -44
- package/packages/client/dist/generated/command-registry.generated.d.ts +66 -23
- package/packages/client/dist/generated/command-registry.generated.js +1 -1
- package/packages/client/dist/generated/public-projections.generated.d.ts +100 -0
- package/packages/client/dist/generated/public-projections.generated.js +1 -0
- package/packages/client/dist/git/git-lifecycle-store.d.ts +17 -0
- package/packages/client/dist/git/git-lifecycle-store.js +4 -4
- package/packages/client/dist/index.d.ts +1 -0
- package/packages/client/dist/index.js +1 -1
- package/packages/client/dist/migrations/project-identity.js +7 -7
- package/packages/client/dist/migrations/state-json.d.ts +14 -0
- package/packages/client/dist/migrations/state-json.js +4 -0
- package/packages/client/dist/runner/headless/epic-git-reconciliation-inventory.d.ts +2 -0
- package/packages/client/dist/runner/headless/epic-git-reconciliation-inventory.js +1 -1
- package/packages/client/dist/runner/headless/epic-terminal-git-cli-adapter.js +8 -8
- package/packages/client/dist/state/state-json-repair.js +3 -3
- package/packages/client/dist/yoloop/invoke-hooks.d.ts +2 -3
- package/packages/client/dist/yoloop/invoke-hooks.js +9 -8
- package/packages/client/dist/yoloop/shared-yoloop-types.d.ts +87 -1
- package/packages/client/dist/yoloop/yoloop-client-state-store.d.ts +27 -3
- package/packages/client/dist/yoloop/yoloop-client-state-store.js +1 -1
- package/packages/shared/dist/command-registry-contract.js +38 -0
- package/packages/shared/dist/continuity/goal-operation-authority.js +48 -0
- package/packages/shared/dist/executable-graph-catalog/contracts.js +935 -0
- package/packages/shared/dist/generated/command-registry.generated.js +82 -23
- package/packages/shared/dist/generated/invocation-schema.generated.js +12 -4
- package/packages/shared/dist/generated/public-projections.generated.js +149 -0
- package/packages/shared/dist/invocation/executable-policy-recovery.d.ts +83 -0
- package/packages/shared/dist/invocation/executable-policy-recovery.js +272 -0
- package/packages/shared/dist/invocation/index.d.ts +3 -1
- package/packages/shared/dist/invocation/index.js +40 -1
- package/packages/shared/dist/invocation/recovery-route-compiler.d.ts +56 -0
- package/packages/shared/dist/invocation/recovery-route-compiler.js +214 -0
- package/packages/shared/dist/state/index.d.ts +1 -0
- package/packages/shared/dist/state/index.js +1 -0
- package/packages/shared/dist/state/state-migration.d.ts +58 -0
- package/packages/shared/dist/state/state-migration.js +404 -0
- package/packages/shared/dist/yoloop/canonical.js +15 -0
- package/packages/shared/dist/yoloop/operational-budget.js +164 -0
- package/packages/shared/dist/yoloop/recovery-types.js +39 -0
- package/targets-stubs/antigravity/gemini.md +1 -1
- package/targets-stubs/antigravity/skill/SKILL.md +1 -1
- package/targets-stubs/claude-code/neocortex-root.agent.yaml +1 -1
- package/targets-stubs/claude-code/neocortex-root.md +2 -2
- package/targets-stubs/claude-code/neocortex.agent.yaml +1 -1
- package/targets-stubs/claude-code/neocortex.md +2 -2
- package/targets-stubs/codex/AGENTS.md +11 -2
- package/targets-stubs/codex/README.md +7 -4
- package/targets-stubs/codex/neocortex.toml +53 -6
- package/targets-stubs/cursor/agent.md +2 -2
- package/targets-stubs/gemini-cli/agent.md +2 -2
- package/targets-stubs/opencode/neocortex-root.md +1 -1
- 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
|
+
}
|