@ornexus/neocortex 4.60.66 → 4.60.67

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.
@@ -0,0 +1,653 @@
1
+ import { yoloopDomainHash } from './canonical.js';
2
+ import { validateReleaseLifecycle, } from '../compatibility/index.js';
3
+ /** P223.03 -- first-class terminal non-success for one release attempt. */
4
+ export const YOLOOP_RELEASE_BLOCKED_TOKEN = '[YOLOOP_RELEASE_BLOCKED]';
5
+ export const YOLOOP_RELEASE_BLOCKED_REASON_CODE = 'YOLOOP_RELEASE_BLOCKED';
6
+ export const MAX_RELEASE_BLOCKERS = 16;
7
+ export const MAX_RELEASE_DOCTOR_FINDINGS = 32;
8
+ export const MAX_RELEASE_EVIDENCE_REFS = 64;
9
+ export const RELEASE_GATE_RECEIPT_SCHEMA = 'ReleaseGateReceipt/1.0';
10
+ export const RELEASE_GATE_REQUEST_SCHEMA = 'ReleaseGateRequest/1.0';
11
+ export const RELEASE_LIFECYCLE_DIMENSIONS = [
12
+ 'implementationComplete',
13
+ 'betaOperable',
14
+ 'releaseReady',
15
+ 'productionPromoted',
16
+ ];
17
+ export const RELEASE_BLOCKED_REASON_CODES = [
18
+ 'BETA_EVIDENCE_MISSING',
19
+ 'BETA_NOT_OPERABLE',
20
+ 'RELEASE_GATES_INCOMPLETE',
21
+ 'PRODUCTION_PROMOTION_UNAUTHORIZED',
22
+ 'COMPATIBILITY_REVALIDATION_REQUIRED',
23
+ 'DOCTOR_FINDING_UNRESOLVED',
24
+ 'CANDIDATE_IDENTITY_MISMATCH',
25
+ 'LINEAGE_MISMATCH',
26
+ 'EVIDENCE_STALE',
27
+ 'EVIDENCE_UNKNOWN',
28
+ 'OPERATOR_ACTION_REQUIRED',
29
+ ];
30
+ export const RELEASE_BLOCKED_LIFECYCLE_REASON_CODES = [
31
+ 'EVIDENCE_VERIFIED',
32
+ 'EVIDENCE_NEGATIVE',
33
+ 'EVIDENCE_NOT_EVALUATED',
34
+ ...RELEASE_BLOCKED_REASON_CODES,
35
+ ];
36
+ export const RELEASE_RESUME_CONDITIONS = [
37
+ 'REVALIDATE_COMPATIBILITY',
38
+ 'REVALIDATE_CANDIDATE_IDENTITY',
39
+ 'REVALIDATE_LINEAGE',
40
+ 'REFRESH_EVIDENCE',
41
+ 'RESOLVE_BLOCKING_CONDITION',
42
+ ];
43
+ const SAFE_ID = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,159}$/;
44
+ const SHA256 = /^[a-f0-9]{64}$/;
45
+ const SAFE_REF = /^[A-Za-z0-9][A-Za-z0-9._:/-]{0,239}$/;
46
+ const BLOCKED_REASON_SET = new Set(RELEASE_BLOCKED_REASON_CODES);
47
+ const LIFECYCLE_REASON_SET = new Set(RELEASE_BLOCKED_LIFECYCLE_REASON_CODES);
48
+ const DIMENSION_SET = new Set(RELEASE_LIFECYCLE_DIMENSIONS);
49
+ const RESUME_CONDITION_SET = new Set(RELEASE_RESUME_CONDITIONS);
50
+ const REQUIRED_REVALIDATIONS = Object.freeze([
51
+ 'compatibility',
52
+ 'candidate',
53
+ 'lineage',
54
+ 'freshness',
55
+ 'blockers',
56
+ ]);
57
+ export function createReleaseBlockedResult(input) {
58
+ assertSafeId(input.attemptId, 'attemptId');
59
+ assertIso(input.createdAt, 'createdAt');
60
+ const candidate = normalizeCandidate(input.candidate);
61
+ const authority = normalizeGateAuthority(input.authority);
62
+ const lineage = normalizeLineage(input.lineage);
63
+ const freshness = normalizeFreshness(input.freshness);
64
+ const lifecycle = normalizeLifecycle(input.lifecycle);
65
+ assertLifecycleOrdering(lifecycle);
66
+ const doctorFindings = normalizeDoctorFindings(input.doctorFindings);
67
+ const blockers = normalizeBlockers(input.blockers);
68
+ if (blockers.length === 0)
69
+ throw new Error('RELEASE_BLOCKED_BLOCKER_REQUIRED');
70
+ const preservedEvidenceRefs = normalizeRefs(input.preservedEvidenceRefs, MAX_RELEASE_EVIDENCE_REFS);
71
+ const conditionDigest = yoloopDomainHash('release-blocked-resume-condition-v1', {
72
+ authority,
73
+ candidate,
74
+ lineage,
75
+ blockers,
76
+ requiredRevalidations: REQUIRED_REVALIDATIONS,
77
+ });
78
+ const basis = {
79
+ schemaVersion: 'ReleaseBlockedResult/1.0',
80
+ kind: 'release-blocked',
81
+ outcome: 'release-blocked',
82
+ terminal: true,
83
+ successful: false,
84
+ terminationToken: YOLOOP_RELEASE_BLOCKED_TOKEN,
85
+ attemptId: input.attemptId,
86
+ createdAt: input.createdAt,
87
+ authority,
88
+ candidate,
89
+ lineage,
90
+ freshness,
91
+ lifecycle,
92
+ doctorFindings,
93
+ blockers,
94
+ preservedEvidenceRefs,
95
+ resume: {
96
+ mode: 'explicit-operator-action',
97
+ autoResume: false,
98
+ requiredRevalidations: REQUIRED_REVALIDATIONS,
99
+ conditionDigest,
100
+ },
101
+ completion: {
102
+ yoloopComplete: false,
103
+ yoloopAborted: false,
104
+ storyDone: false,
105
+ epicDone: false,
106
+ },
107
+ publicSafe: true,
108
+ };
109
+ return deepFreeze({
110
+ ...basis,
111
+ resultId: `release-blocked:${yoloopDomainHash('release-blocked-result-v1', basis)}`,
112
+ });
113
+ }
114
+ export function createReleaseGateReceipt(input) {
115
+ assertSafeId(input.gateId, 'gateId');
116
+ if (input.commandId !== 'yoloop' || !/^P[1-9][0-9]{0,11}$/.test(input.targetEpicId)) {
117
+ throw new Error('RELEASE_GATE_AUTHORITY_INVALID');
118
+ }
119
+ assertSafeId(input.attemptId, 'attemptId');
120
+ assertIso(input.createdAt, 'createdAt');
121
+ const candidate = normalizeCandidate(input.candidate);
122
+ const lineage = normalizeLineage(input.lineage);
123
+ const freshnessEvidence = normalizeFreshness(input.freshness);
124
+ if (freshnessEvidence.status !== 'fresh')
125
+ throw new Error('RELEASE_GATE_FRESHNESS_INVALID');
126
+ const lifecycleValidation = validateReleaseLifecycle(input.lifecycle);
127
+ if (!lifecycleValidation.accepted || !lifecycleValidation.projection) {
128
+ throw new Error('RELEASE_GATE_LIFECYCLE_INVALID');
129
+ }
130
+ const lifecycle = lifecycleValidation.projection;
131
+ const lifecycleInput = cloneReleaseLifecycleInput(input.lifecycle);
132
+ if (candidate.candidateId !== lifecycle.candidateId
133
+ || lineage.lineageDigest !== lifecycle.lineageSha256
134
+ || freshnessEvidence.checkedAt !== lifecycle.evaluatedAt
135
+ || lifecycle.implementationComplete.value !== true
136
+ || lifecycle.implementationComplete.freshness !== 'fresh'
137
+ || lifecycle.betaOperable.value === true
138
+ || lifecycle.releaseReady.value === true
139
+ || lifecycle.productionPromoted.value === true) {
140
+ throw new Error('RELEASE_GATE_LIFECYCLE_INVALID');
141
+ }
142
+ const doctorFindings = normalizeDoctorFindings(input.doctorFindings);
143
+ const blockers = normalizeBlockers(input.blockers);
144
+ if (blockers.length === 0)
145
+ throw new Error('RELEASE_BLOCKED_BLOCKER_REQUIRED');
146
+ for (const dimension of ['betaOperable', 'releaseReady', 'productionPromoted']) {
147
+ if (lifecycle[dimension].value !== true && !blockers.some((blocker) => blocker.dimension === dimension)) {
148
+ throw new Error('RELEASE_GATE_BLOCKER_COVERAGE_INVALID');
149
+ }
150
+ }
151
+ const preservedEvidenceRefs = normalizeRefs(input.preservedEvidenceRefs, MAX_RELEASE_EVIDENCE_REFS);
152
+ const basis = {
153
+ schemaVersion: RELEASE_GATE_RECEIPT_SCHEMA,
154
+ kind: 'authorized-release-gate-receipt',
155
+ gateId: input.gateId,
156
+ commandId: input.commandId,
157
+ targetEpicId: input.targetEpicId,
158
+ attemptId: input.attemptId,
159
+ createdAt: input.createdAt,
160
+ candidate,
161
+ lineage,
162
+ freshness: freshnessEvidence,
163
+ lifecycle: lifecycleInput,
164
+ doctorFindings,
165
+ blockers,
166
+ preservedEvidenceRefs,
167
+ publicSafe: true,
168
+ };
169
+ return deepFreeze({
170
+ ...basis,
171
+ receiptDigest: yoloopDomainHash('release-gate-receipt-v1', basis),
172
+ });
173
+ }
174
+ export function parseReleaseGateReceipt(value) {
175
+ if (!isRecord(value) || !hasExactKeys(value, [
176
+ 'schemaVersion', 'kind', 'gateId', 'commandId', 'targetEpicId', 'attemptId', 'createdAt',
177
+ 'candidate', 'lineage', 'freshness', 'lifecycle', 'doctorFindings', 'blockers',
178
+ 'preservedEvidenceRefs', 'receiptDigest', 'publicSafe',
179
+ ]) || value.schemaVersion !== RELEASE_GATE_RECEIPT_SCHEMA
180
+ || value.kind !== 'authorized-release-gate-receipt' || value.publicSafe !== true)
181
+ return undefined;
182
+ try {
183
+ const rebuilt = createReleaseGateReceipt({
184
+ gateId: value.gateId,
185
+ commandId: value.commandId,
186
+ targetEpicId: value.targetEpicId,
187
+ attemptId: value.attemptId,
188
+ createdAt: value.createdAt,
189
+ candidate: value.candidate,
190
+ lineage: value.lineage,
191
+ freshness: value.freshness,
192
+ lifecycle: value.lifecycle,
193
+ doctorFindings: value.doctorFindings,
194
+ blockers: value.blockers,
195
+ preservedEvidenceRefs: value.preservedEvidenceRefs,
196
+ });
197
+ return value.receiptDigest === rebuilt.receiptDigest ? rebuilt : undefined;
198
+ }
199
+ catch {
200
+ return undefined;
201
+ }
202
+ }
203
+ /**
204
+ * Server admission gate. It never treats elapsed time, prose, queue exhaustion,
205
+ * or a missing field as release authority.
206
+ */
207
+ export function decideReleaseGateAdmission(requestValue, authority) {
208
+ if (authority.commandId !== 'yoloop' || !authority.targetEpicId) {
209
+ return rejectedGate('RELEASE_GATE_AUTHORITY_INVALID');
210
+ }
211
+ if (!isRecord(requestValue) || requestValue.schemaVersion !== RELEASE_GATE_REQUEST_SCHEMA
212
+ || requestValue.publicSafe !== true)
213
+ return rejectedGate('RELEASE_GATE_REQUEST_INVALID');
214
+ if (requestValue.mode === 'evaluate') {
215
+ if (!hasExactKeys(requestValue, ['schemaVersion', 'mode', 'receipt', 'publicSafe'])) {
216
+ return rejectedGate('RELEASE_GATE_REQUEST_INVALID');
217
+ }
218
+ const receipt = parseReleaseGateReceipt(requestValue.receipt);
219
+ if (!receipt)
220
+ return rejectedGate('RELEASE_GATE_RECEIPT_INVALID');
221
+ if (receipt.commandId !== authority.commandId || receipt.targetEpicId !== authority.targetEpicId) {
222
+ return rejectedGate('RELEASE_GATE_TARGET_MISMATCH');
223
+ }
224
+ return deepFreeze({ kind: 'release-blocked', result: releaseBlockedResultFromGate(receipt), publicSafe: true });
225
+ }
226
+ if (requestValue.mode === 'resume') {
227
+ if (!hasOnlyOptionalKeys(requestValue, ['schemaVersion', 'mode', 'commandId', 'targetEpicId', 'previousResult', 'publicSafe'], ['action', 'revalidation']))
228
+ return rejectedGate('RELEASE_GATE_REQUEST_INVALID');
229
+ if (requestValue.commandId !== authority.commandId || requestValue.targetEpicId !== authority.targetEpicId) {
230
+ return rejectedGate('RELEASE_GATE_TARGET_MISMATCH');
231
+ }
232
+ const previousResult = parseReleaseBlockedResult(requestValue.previousResult);
233
+ if (!previousResult)
234
+ return rejectedGate('RELEASE_GATE_RECEIPT_INVALID');
235
+ if (previousResult.authority.commandId !== authority.commandId
236
+ || previousResult.authority.targetEpicId !== authority.targetEpicId) {
237
+ return rejectedGate('RELEASE_GATE_TARGET_MISMATCH');
238
+ }
239
+ const resumeDecision = decideReleaseBlockedResume({
240
+ result: previousResult,
241
+ action: requestValue.action,
242
+ revalidation: requestValue.revalidation,
243
+ });
244
+ return resumeDecision.kind === 'authorized'
245
+ ? deepFreeze({ kind: 'resume-authorized', resumeDecision, publicSafe: true })
246
+ : deepFreeze({ kind: 'release-blocked', result: previousResult, resumeDecision, publicSafe: true });
247
+ }
248
+ return rejectedGate('RELEASE_GATE_REQUEST_INVALID');
249
+ }
250
+ function releaseBlockedResultFromGate(receipt) {
251
+ const validation = validateReleaseLifecycle(receipt.lifecycle);
252
+ if (!validation.accepted || !validation.projection)
253
+ throw new Error('RELEASE_GATE_LIFECYCLE_INVALID');
254
+ return createReleaseBlockedResult({
255
+ attemptId: receipt.attemptId,
256
+ createdAt: receipt.createdAt,
257
+ authority: {
258
+ gateId: receipt.gateId,
259
+ commandId: receipt.commandId,
260
+ targetEpicId: receipt.targetEpicId,
261
+ },
262
+ candidate: receipt.candidate,
263
+ lineage: receipt.lineage,
264
+ freshness: receipt.freshness,
265
+ lifecycle: projectReleaseBlockedLifecycle(validation.projection, receipt.blockers),
266
+ doctorFindings: receipt.doctorFindings,
267
+ blockers: receipt.blockers,
268
+ preservedEvidenceRefs: receipt.preservedEvidenceRefs,
269
+ });
270
+ }
271
+ function projectReleaseBlockedLifecycle(lifecycle, blockers) {
272
+ const output = {};
273
+ for (const dimension of RELEASE_LIFECYCLE_DIMENSIONS) {
274
+ const evidence = lifecycle[dimension];
275
+ const blockerReasons = blockers
276
+ .filter((blocker) => blocker.dimension === dimension)
277
+ .map((blocker) => blocker.reasonCode);
278
+ output[dimension] = {
279
+ value: evidence.value,
280
+ reasonCodes: evidence.value === true
281
+ ? ['EVIDENCE_VERIFIED']
282
+ : blockerReasons.length > 0 ? blockerReasons : ['EVIDENCE_UNKNOWN'],
283
+ evidenceRefs: evidence.evidenceRefs,
284
+ };
285
+ }
286
+ return output;
287
+ }
288
+ function cloneReleaseLifecycleInput(value) {
289
+ const dimensions = {};
290
+ for (const dimension of RELEASE_LIFECYCLE_DIMENSIONS) {
291
+ const evidence = value[dimension];
292
+ dimensions[dimension] = Object.freeze({
293
+ value: evidence.value,
294
+ reasonCodes: Object.freeze([...evidence.reasonCodes]),
295
+ evidenceRefs: Object.freeze([...evidence.evidenceRefs]),
296
+ candidateId: evidence.candidateId,
297
+ lineageSha256: evidence.lineageSha256,
298
+ ...(evidence.observedAt ? { observedAt: evidence.observedAt } : {}),
299
+ ...(evidence.maxAgeSeconds !== undefined ? { maxAgeSeconds: evidence.maxAgeSeconds } : {}),
300
+ });
301
+ }
302
+ return Object.freeze({
303
+ schemaVersion: value.schemaVersion,
304
+ candidateId: value.candidateId,
305
+ lineageSha256: value.lineageSha256,
306
+ evaluatedAt: value.evaluatedAt,
307
+ implementationComplete: dimensions.implementationComplete,
308
+ betaOperable: dimensions.betaOperable,
309
+ releaseReady: dimensions.releaseReady,
310
+ productionPromoted: dimensions.productionPromoted,
311
+ publicSafe: true,
312
+ });
313
+ }
314
+ /** Strict wire parser. Unknown or malformed input is never interpreted as terminal success. */
315
+ export function parseReleaseBlockedResult(value) {
316
+ if (!isRecord(value) || !hasExactKeys(value, [
317
+ 'schemaVersion', 'kind', 'outcome', 'terminal', 'successful', 'terminationToken',
318
+ 'attemptId', 'resultId', 'createdAt', 'authority', 'candidate', 'lineage', 'freshness',
319
+ 'lifecycle', 'doctorFindings', 'blockers', 'preservedEvidenceRefs', 'resume',
320
+ 'completion', 'publicSafe',
321
+ ]))
322
+ return undefined;
323
+ try {
324
+ if (value.schemaVersion !== 'ReleaseBlockedResult/1.0'
325
+ || value.kind !== 'release-blocked'
326
+ || value.outcome !== 'release-blocked'
327
+ || value.terminal !== true
328
+ || value.successful !== false
329
+ || value.terminationToken !== YOLOOP_RELEASE_BLOCKED_TOKEN
330
+ || value.publicSafe !== true
331
+ || !isRecord(value.completion)
332
+ || !hasExactKeys(value.completion, ['yoloopComplete', 'yoloopAborted', 'storyDone', 'epicDone'])
333
+ || value.completion.yoloopComplete !== false
334
+ || value.completion.yoloopAborted !== false
335
+ || value.completion.storyDone !== false
336
+ || value.completion.epicDone !== false)
337
+ return undefined;
338
+ const rebuilt = createReleaseBlockedResult({
339
+ attemptId: value.attemptId,
340
+ createdAt: value.createdAt,
341
+ authority: value.authority,
342
+ candidate: value.candidate,
343
+ lineage: value.lineage,
344
+ freshness: value.freshness,
345
+ lifecycle: value.lifecycle,
346
+ doctorFindings: value.doctorFindings,
347
+ blockers: value.blockers,
348
+ preservedEvidenceRefs: value.preservedEvidenceRefs,
349
+ });
350
+ if (value.resultId !== rebuilt.resultId || !isRecord(value.resume)
351
+ || !hasExactKeys(value.resume, ['mode', 'autoResume', 'requiredRevalidations', 'conditionDigest'])
352
+ || value.resume.conditionDigest !== rebuilt.resume.conditionDigest
353
+ || value.resume.mode !== 'explicit-operator-action'
354
+ || value.resume.autoResume !== false
355
+ || !Array.isArray(value.resume.requiredRevalidations)
356
+ || value.resume.requiredRevalidations.length !== REQUIRED_REVALIDATIONS.length
357
+ || value.resume.requiredRevalidations.some((item, index) => item !== REQUIRED_REVALIDATIONS[index]))
358
+ return undefined;
359
+ return rebuilt;
360
+ }
361
+ catch {
362
+ return undefined;
363
+ }
364
+ }
365
+ export function replayReleaseBlockedResult(value) {
366
+ return parseReleaseBlockedResult(value);
367
+ }
368
+ /** Pure resume gate. Time passing alone can never authorize a successor. */
369
+ export function decideReleaseBlockedResume(input) {
370
+ const result = parseReleaseBlockedResult(input.result);
371
+ if (!result || !isRecord(input.action)
372
+ || !hasExactKeys(input.action, ['kind', 'actionId', 'requestedAt', 'operatorConfirmed'])
373
+ || input.action.kind !== 'resume-release-attempt'
374
+ || input.action.operatorConfirmed !== true || !isSafeId(input.action.actionId)
375
+ || !isIso(input.action.requestedAt)) {
376
+ return rejectedResume('RELEASE_RESUME_EXPLICIT_ACTION_REQUIRED');
377
+ }
378
+ const check = input.revalidation;
379
+ if (!isRecord(check)
380
+ || !hasExactKeys(check, ['checkedAt', 'compatibility', 'candidate', 'lineage', 'freshness', 'resolvedBlockerIds'])
381
+ || check.compatibility !== 'compatible' || check.freshness !== 'fresh'
382
+ || !isIso(check.checkedAt) || !Array.isArray(check.resolvedBlockerIds)) {
383
+ return rejectedResume('RELEASE_RESUME_REVALIDATION_INVALID');
384
+ }
385
+ let candidate;
386
+ let lineage;
387
+ try {
388
+ candidate = normalizeCandidate(check.candidate);
389
+ lineage = normalizeLineage(check.lineage);
390
+ }
391
+ catch {
392
+ return rejectedResume('RELEASE_RESUME_REVALIDATION_INVALID');
393
+ }
394
+ if (candidate.candidateId !== result.candidate.candidateId
395
+ || candidate.candidateDigest !== result.candidate.candidateDigest) {
396
+ return rejectedResume('RELEASE_RESUME_CANDIDATE_MISMATCH');
397
+ }
398
+ if (lineage.lineageId !== result.lineage.lineageId
399
+ || lineage.lineageDigest !== result.lineage.lineageDigest
400
+ || lineage.parentDigest !== result.lineage.parentDigest) {
401
+ return rejectedResume('RELEASE_RESUME_LINEAGE_MISMATCH');
402
+ }
403
+ if (Date.parse(check.checkedAt) < Date.parse(result.createdAt)
404
+ || Date.parse(input.action.requestedAt) < Date.parse(result.createdAt)) {
405
+ return rejectedResume('RELEASE_RESUME_STALE_REVALIDATION');
406
+ }
407
+ const resolved = new Set(check.resolvedBlockerIds.filter(isSafeId));
408
+ if (resolved.size !== check.resolvedBlockerIds.length
409
+ || result.blockers.some((blocker) => !resolved.has(blocker.blockerId))) {
410
+ return rejectedResume('RELEASE_RESUME_BLOCKERS_UNRESOLVED');
411
+ }
412
+ const resumeBasis = {
413
+ previousResultId: result.resultId,
414
+ actionId: input.action.actionId,
415
+ requestedAt: input.action.requestedAt,
416
+ checkedAt: check.checkedAt,
417
+ conditionDigest: result.resume.conditionDigest,
418
+ };
419
+ const digest = yoloopDomainHash('release-blocked-resume-v1', resumeBasis);
420
+ return deepFreeze({
421
+ kind: 'authorized',
422
+ previousResultId: result.resultId,
423
+ resumeId: `release-resume:${digest}`,
424
+ nextAttemptId: `release-attempt:${digest}`,
425
+ publicSafe: true,
426
+ });
427
+ }
428
+ /** Existing clients already understand CLIENT_UPGRADE_REQUIRED as non-success. */
429
+ export function buildLegacyReleaseBlockedFallback(resultValue) {
430
+ const result = parseReleaseBlockedResult(resultValue);
431
+ if (!result)
432
+ throw new Error('RELEASE_BLOCKED_RESULT_INVALID');
433
+ return deepFreeze({
434
+ success: false,
435
+ exitCode: 3,
436
+ errorCode: 'CLIENT_UPGRADE_REQUIRED',
437
+ instructions: [
438
+ '# Release Blocked -- Client Upgrade Required',
439
+ '',
440
+ 'This release attempt ended without success. Upgrade the client, then explicitly resume after revalidation.',
441
+ 'Run: npm install -g @ornexus/neocortex@latest',
442
+ '',
443
+ YOLOOP_RELEASE_BLOCKED_TOKEN,
444
+ ].join('\n'),
445
+ metadata: {
446
+ mode: 'error',
447
+ errorCode: 'CLIENT_UPGRADE_REQUIRED',
448
+ terminalClass: 'release-blocked',
449
+ yoloopComplete: false,
450
+ yoloopAborted: false,
451
+ upgradeRequired: true,
452
+ releaseBlockedResultId: result.resultId,
453
+ publicSafe: true,
454
+ },
455
+ });
456
+ }
457
+ function normalizeCandidate(value) {
458
+ if (!isRecord(value) || !hasExactKeys(value, ['candidateId', 'candidateDigest'])) {
459
+ throw new Error('RELEASE_BLOCKED_CANDIDATE_INVALID');
460
+ }
461
+ assertSafeId(value.candidateId, 'candidateId');
462
+ assertDigest(value.candidateDigest, 'candidateDigest');
463
+ return Object.freeze({ candidateId: value.candidateId, candidateDigest: value.candidateDigest });
464
+ }
465
+ function normalizeGateAuthority(value) {
466
+ if (!isRecord(value)
467
+ || !hasExactKeys(value, ['gateId', 'commandId', 'targetEpicId'])
468
+ || !isSafeId(value.gateId)
469
+ || value.commandId !== 'yoloop'
470
+ || typeof value.targetEpicId !== 'string'
471
+ || !/^P[1-9][0-9]{0,11}$/.test(value.targetEpicId)) {
472
+ throw new Error('RELEASE_GATE_AUTHORITY_INVALID');
473
+ }
474
+ return Object.freeze({
475
+ gateId: value.gateId,
476
+ commandId: value.commandId,
477
+ targetEpicId: value.targetEpicId,
478
+ });
479
+ }
480
+ function normalizeLineage(value) {
481
+ if (!isRecord(value)
482
+ || !hasOnlyOptionalKeys(value, ['lineageId', 'lineageDigest'], ['parentDigest'])) {
483
+ throw new Error('RELEASE_BLOCKED_LINEAGE_INVALID');
484
+ }
485
+ assertSafeId(value.lineageId, 'lineageId');
486
+ assertDigest(value.lineageDigest, 'lineageDigest');
487
+ if (value.parentDigest !== undefined)
488
+ assertDigest(value.parentDigest, 'parentDigest');
489
+ return Object.freeze({
490
+ lineageId: value.lineageId,
491
+ lineageDigest: value.lineageDigest,
492
+ ...(value.parentDigest ? { parentDigest: value.parentDigest } : {}),
493
+ });
494
+ }
495
+ function normalizeFreshness(value) {
496
+ if (!isRecord(value)
497
+ || !hasOnlyOptionalKeys(value, ['status', 'checkedAt'], ['validUntil'])
498
+ || !['fresh', 'stale', 'unknown'].includes(String(value.status))) {
499
+ throw new Error('RELEASE_BLOCKED_FRESHNESS_INVALID');
500
+ }
501
+ assertIso(value.checkedAt, 'checkedAt');
502
+ if (value.validUntil !== undefined) {
503
+ assertIso(value.validUntil, 'validUntil');
504
+ if (Date.parse(value.validUntil) < Date.parse(value.checkedAt))
505
+ throw new Error('RELEASE_BLOCKED_FRESHNESS_INVALID');
506
+ }
507
+ return Object.freeze({
508
+ status: value.status,
509
+ checkedAt: value.checkedAt,
510
+ ...(value.validUntil ? { validUntil: value.validUntil } : {}),
511
+ });
512
+ }
513
+ function normalizeLifecycle(value) {
514
+ if (!isRecord(value) || !hasExactKeys(value, RELEASE_LIFECYCLE_DIMENSIONS)) {
515
+ throw new Error('RELEASE_BLOCKED_LIFECYCLE_INVALID');
516
+ }
517
+ const output = {};
518
+ for (const dimension of RELEASE_LIFECYCLE_DIMENSIONS) {
519
+ const evidence = value[dimension];
520
+ if (!isRecord(evidence) || !hasExactKeys(evidence, ['value', 'reasonCodes', 'evidenceRefs'])
521
+ || ![true, false, 'unknown'].includes(evidence.value)
522
+ || !Array.isArray(evidence.reasonCodes) || evidence.reasonCodes.length < 1 || evidence.reasonCodes.length > 8
523
+ || evidence.reasonCodes.some((code) => typeof code !== 'string' || !LIFECYCLE_REASON_SET.has(code))) {
524
+ throw new Error('RELEASE_BLOCKED_LIFECYCLE_INVALID');
525
+ }
526
+ output[dimension] = Object.freeze({
527
+ value: evidence.value,
528
+ reasonCodes: Object.freeze([...new Set(evidence.reasonCodes)].sort()),
529
+ evidenceRefs: normalizeRefs(evidence.evidenceRefs, MAX_RELEASE_EVIDENCE_REFS),
530
+ });
531
+ }
532
+ return Object.freeze(output);
533
+ }
534
+ function assertLifecycleOrdering(value) {
535
+ if (value.productionPromoted.value === true && value.releaseReady.value !== true) {
536
+ throw new Error('RELEASE_BLOCKED_LIFECYCLE_ORDER_INVALID');
537
+ }
538
+ if (value.releaseReady.value === true
539
+ && (value.implementationComplete.value !== true || value.betaOperable.value !== true)) {
540
+ throw new Error('RELEASE_BLOCKED_LIFECYCLE_ORDER_INVALID');
541
+ }
542
+ if (value.productionPromoted.value === true)
543
+ throw new Error('RELEASE_BLOCKED_ALREADY_PROMOTED');
544
+ }
545
+ function normalizeDoctorFindings(values) {
546
+ if (!Array.isArray(values) || values.length > MAX_RELEASE_DOCTOR_FINDINGS) {
547
+ throw new Error('RELEASE_BLOCKED_DOCTOR_FINDINGS_INVALID');
548
+ }
549
+ const byId = new Map();
550
+ for (const value of values) {
551
+ if (!isRecord(value)
552
+ || !hasExactKeys(value, ['findingId', 'reasonCode', 'dimension', 'status', 'evidenceRefs'])
553
+ || !isSafeId(value.findingId) || !BLOCKED_REASON_SET.has(String(value.reasonCode))
554
+ || !DIMENSION_SET.has(String(value.dimension)) || !['open', 'resolved', 'unknown'].includes(String(value.status))) {
555
+ throw new Error('RELEASE_BLOCKED_DOCTOR_FINDINGS_INVALID');
556
+ }
557
+ if (byId.has(value.findingId))
558
+ throw new Error('RELEASE_BLOCKED_DOCTOR_FINDINGS_INVALID');
559
+ byId.set(value.findingId, Object.freeze({
560
+ findingId: value.findingId,
561
+ reasonCode: value.reasonCode,
562
+ dimension: value.dimension,
563
+ status: value.status,
564
+ evidenceRefs: normalizeRefs(value.evidenceRefs, MAX_RELEASE_EVIDENCE_REFS),
565
+ }));
566
+ }
567
+ return Object.freeze([...byId.values()].sort((a, b) => a.findingId.localeCompare(b.findingId)));
568
+ }
569
+ function normalizeBlockers(values) {
570
+ if (!Array.isArray(values) || values.length > MAX_RELEASE_BLOCKERS) {
571
+ throw new Error('RELEASE_BLOCKED_BLOCKERS_INVALID');
572
+ }
573
+ const byId = new Map();
574
+ for (const value of values) {
575
+ if (!isRecord(value)
576
+ || !hasExactKeys(value, ['blockerId', 'reasonCode', 'dimension', 'evidenceRefs', 'retryability', 'resumeCondition'])
577
+ || !isSafeId(value.blockerId) || !BLOCKED_REASON_SET.has(String(value.reasonCode))
578
+ || !DIMENSION_SET.has(String(value.dimension))
579
+ || !['operator-action', 'evidence-refresh', 'not-retryable'].includes(String(value.retryability))
580
+ || !RESUME_CONDITION_SET.has(String(value.resumeCondition))) {
581
+ throw new Error('RELEASE_BLOCKED_BLOCKERS_INVALID');
582
+ }
583
+ if (byId.has(value.blockerId))
584
+ throw new Error('RELEASE_BLOCKED_BLOCKERS_INVALID');
585
+ byId.set(value.blockerId, Object.freeze({
586
+ blockerId: value.blockerId,
587
+ reasonCode: value.reasonCode,
588
+ dimension: value.dimension,
589
+ evidenceRefs: normalizeRefs(value.evidenceRefs, MAX_RELEASE_EVIDENCE_REFS),
590
+ retryability: value.retryability,
591
+ resumeCondition: value.resumeCondition,
592
+ }));
593
+ }
594
+ return Object.freeze([...byId.values()].sort((a, b) => a.blockerId.localeCompare(b.blockerId)));
595
+ }
596
+ function normalizeRefs(value, max) {
597
+ if (!Array.isArray(value) || value.length > max)
598
+ throw new Error('RELEASE_BLOCKED_EVIDENCE_REFS_INVALID');
599
+ const refs = value.map((item) => {
600
+ if (typeof item !== 'string' || !SAFE_REF.test(item) || item.includes('..')
601
+ || item.startsWith('/') || item.includes('://') || item.includes('\\')) {
602
+ throw new Error('RELEASE_BLOCKED_EVIDENCE_REFS_INVALID');
603
+ }
604
+ return item;
605
+ });
606
+ return Object.freeze([...new Set(refs)].sort());
607
+ }
608
+ function rejectedResume(reasonCode) {
609
+ return Object.freeze({ kind: 'rejected', reasonCode, publicSafe: true });
610
+ }
611
+ function rejectedGate(reasonCode) {
612
+ return Object.freeze({ kind: 'rejected', reasonCode, publicSafe: true });
613
+ }
614
+ function hasExactKeys(value, expected) {
615
+ const keys = Object.keys(value).sort();
616
+ const sorted = [...expected].sort();
617
+ return keys.length === sorted.length && keys.every((key, index) => key === sorted[index]);
618
+ }
619
+ function hasOnlyOptionalKeys(value, required, optional) {
620
+ const allowed = new Set([...required, ...optional]);
621
+ return required.every((key) => Object.prototype.hasOwnProperty.call(value, key))
622
+ && Object.keys(value).every((key) => allowed.has(key));
623
+ }
624
+ function assertSafeId(value, field) {
625
+ if (typeof value !== 'string' || !isSafeId(value))
626
+ throw new Error(`RELEASE_BLOCKED_${field.toUpperCase()}_INVALID`);
627
+ }
628
+ function isSafeId(value) {
629
+ return typeof value === 'string' && SAFE_ID.test(value);
630
+ }
631
+ function assertDigest(value, field) {
632
+ if (typeof value !== 'string' || !SHA256.test(value))
633
+ throw new Error(`RELEASE_BLOCKED_${field.toUpperCase()}_INVALID`);
634
+ }
635
+ function assertIso(value, field) {
636
+ if (!isIso(value))
637
+ throw new Error(`RELEASE_BLOCKED_${field.toUpperCase()}_INVALID`);
638
+ }
639
+ function isIso(value) {
640
+ return typeof value === 'string' && /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/.test(value)
641
+ && Number.isFinite(Date.parse(value));
642
+ }
643
+ function isRecord(value) {
644
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
645
+ }
646
+ function deepFreeze(value) {
647
+ if (value && typeof value === 'object' && !Object.isFrozen(value)) {
648
+ Object.freeze(value);
649
+ for (const child of Object.values(value))
650
+ deepFreeze(child);
651
+ }
652
+ return value;
653
+ }
@@ -1,4 +1,4 @@
1
- # 🧠 Neocortex v4.60.66 | OrNexus Team
1
+ # 🧠 Neocortex v4.60.67 | OrNexus Team
2
2
 
3
3
  This project uses Neocortex, a Development Orchestrator.
4
4
 
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: neocortex
3
- description: "🧠 Neocortex v4.60.66 | OrNexus Team"
3
+ description: "🧠 Neocortex v4.60.67 | OrNexus Team"
4
4
  ---
5
5
 
6
6
  <!--
@@ -160,7 +160,8 @@ while true; do
160
160
  echo "$RESPONSE"
161
161
  echo "$RESPONSE" | grep -q '\[YOLOOP_COMPLETE\]' && break
162
162
  echo "$RESPONSE" | grep -q '\[YOLOOP_ABORTED\]' && break
163
+ echo "$RESPONSE" | grep -q '\[YOLOOP_RELEASE_BLOCKED\]' && exit 1
163
164
  done
164
165
  ```
165
166
 
166
- Termination tokens: `[YOLOOP_COMPLETE]` (success) and `[YOLOOP_ABORTED]` (failure).
167
+ Termination tokens: `[YOLOOP_COMPLETE]` (success), `[YOLOOP_ABORTED]` (failure), and `[YOLOOP_RELEASE_BLOCKED]` (terminal non-success).
@@ -4,7 +4,7 @@ agent:
4
4
  name: 'Neocortex Root Agent'
5
5
  title: 'Development Orchestrator (Root)'
6
6
  icon: '>'
7
- version: '4.60.66'
7
+ version: '4.60.67'
8
8
  architecture: 'thin-client'
9
9
  module: stand-alone
10
10
  hasSidecar: false