@haposoft/cafekit 0.18.0 → 0.18.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. package/README.md +1 -1
  2. package/bin/__tests__/codex-hooks.test.js +169 -236
  3. package/bin/__tests__/codex-native.test.js +54 -114
  4. package/bin/__tests__/develop-contract.test.js +101 -1361
  5. package/bin/__tests__/legacy-authority-decoupled.test.js +14 -19
  6. package/bin/__tests__/package-inventory.test.js +1 -395
  7. package/bin/__tests__/spec-narrowing.test.js +30 -134
  8. package/bin/__tests__/specs-v2-execution-closeout.test.js +0 -372
  9. package/bin/__tests__/test-runtime-dependency-closure.cjs +1 -2
  10. package/bin/__tests__/validator-grounding.test.js +17 -2929
  11. package/bin/lib/managed-writer.js +2 -3
  12. package/bin/phases/codex-runtime.js +6 -3
  13. package/bin/phases/copy-payload.js +13 -28
  14. package/bin/phases/omp-runtime.js +6 -3
  15. package/package.json +2 -3
  16. package/src/claude/agents/code-auditor.md +9 -6
  17. package/src/claude/agents/debugger.md +3 -9
  18. package/src/claude/agents/docs-keeper.md +4 -7
  19. package/src/claude/agents/git-ops.md +2 -4
  20. package/src/claude/agents/implementer.md +6 -7
  21. package/src/claude/agents/inspector.md +2 -0
  22. package/src/claude/agents/project-manager.md +2 -2
  23. package/src/claude/agents/spec-maker.md +4 -4
  24. package/src/claude/agents/test-runner.md +8 -11
  25. package/src/claude/agents/ui-ux-designer.md +0 -1
  26. package/src/claude/hooks/__tests__/spec-gate.test.js +170 -330
  27. package/src/claude/hooks/__tests__/spec-state-touched.test.js +26 -0
  28. package/src/claude/hooks/spec-gate.cjs +8 -63
  29. package/src/claude/hooks/spec-state.cjs +47 -38
  30. package/src/claude/migration-manifest.json +12 -7
  31. package/src/claude/output-styles/coding-level-0-eli5.md +9 -9
  32. package/src/claude/output-styles/coding-level-1-junior.md +3 -3
  33. package/src/claude/output-styles/coding-level-2-mid.md +3 -3
  34. package/src/claude/output-styles/coding-level-3-senior.md +2 -2
  35. package/src/claude/output-styles/coding-level-4-lead.md +5 -5
  36. package/src/claude/output-styles/coding-level-5-god.md +6 -6
  37. package/src/claude/rules/ai-dev-rules.md +1 -2
  38. package/src/claude/rules/workflow.md +1 -1
  39. package/src/claude/scripts/spec-receipt.cjs +0 -114
  40. package/src/claude/scripts/spec-resolver.cjs +41 -457
  41. package/src/claude/scripts/workflow-policy.cjs +3 -1801
  42. package/src/claude/skills/agent-browser/SKILL.md +1 -1
  43. package/src/claude/skills/chrome-devtools/SKILL.md +14 -31
  44. package/src/claude/skills/code-review/SKILL.md +25 -108
  45. package/src/claude/skills/code-review/references/spec-compliance-review.md +4 -7
  46. package/src/claude/skills/develop/SKILL.md +22 -70
  47. package/src/claude/skills/develop/references/subagent-patterns.md +0 -6
  48. package/src/claude/skills/research/SKILL.md +24 -55
  49. package/src/claude/skills/sync/SKILL.md +12 -34
  50. package/src/claude/skills/sync/references/rebind-and-audit.md +2 -2
  51. package/src/claude/skills/test/SKILL.md +39 -123
  52. package/src/claude/skills/test/references/execution-strategy.md +11 -22
  53. package/src/claude/skills/ui-ux/SKILL.md +1 -1
  54. package/src/claude/skills/ui-ux-pro-max/SKILL.md +24 -79
  55. package/src/claude/skills/web-testing/SKILL.md +2 -20
  56. package/src/codex/hooks/lib/spec-receipt.cjs +2 -30
  57. package/src/codex/hooks/lib/spec-utils.cjs +6 -45
  58. package/src/codex/hooks/spec-gate.cjs +11 -68
  59. package/src/codex/hooks/spec-state.cjs +37 -41
  60. package/src/common/AGENTS.md +4 -10
  61. package/bin/__tests__/change-firewall.semantic-firewall.test.js +0 -2133
  62. package/bin/__tests__/finalizer-pass-gate.semantic-firewall.test.js +0 -342
  63. package/bin/__tests__/release-preflight.semantic-firewall.test.js +0 -586
  64. package/bin/__tests__/review-receipt-c2.semantic-firewall.test.js +0 -378
  65. package/bin/__tests__/specs-v2-policy-and-scaffold.test.js +0 -665
  66. package/bin/__tests__/specs-v2-validator-grounder-contract.test.js +0 -1132
  67. package/src/claude/hooks/lib/context.cjs +0 -527
  68. package/src/claude/hooks/lib/detect.cjs +0 -474
  69. package/src/claude/scripts/change-firewall.cjs +0 -1748
  70. package/src/claude/scripts/spec-authoring-digest.cjs +0 -151
  71. package/src/claude/scripts/spec-authoring-validation.cjs +0 -126
  72. package/src/claude/scripts/spec-final-state.cjs +0 -290
  73. package/src/claude/scripts/spec-ground.cjs +0 -650
  74. package/src/claude/scripts/spec-readiness.cjs +0 -425
  75. package/src/claude/scripts/spec-scaffold.cjs +0 -2317
  76. package/src/claude/scripts/spec-semantic-model.cjs +0 -396
  77. package/src/claude/scripts/validate-spec-output.cjs +0 -3914
  78. package/src/claude/skills/code-review/references/verification-gate.md +0 -41
  79. package/src/claude/skills/specs/templates/design.md +0 -82
  80. package/src/claude/skills/specs/templates/requirements-init.md +0 -8
  81. package/src/claude/skills/specs/templates/requirements.md +0 -42
  82. package/src/claude/skills/specs/templates/spec-state.json +0 -56
  83. package/src/claude/skills/specs/templates/task.md +0 -47
@@ -5,100 +5,6 @@ const fs = require('fs');
5
5
  const path = require('path');
6
6
  const PROVENANCE = require('./provenance.cjs');
7
7
 
8
- const CANONICAL_VERDICTS = Object.freeze(['PASS', 'PASS_WITH_WARNINGS', 'FAIL', 'BLOCKED']);
9
- const LEGACY_DIAGNOSTIC_VERDICTS = Object.freeze(['PARTIAL', 'NO_TESTS']);
10
- const REVIEW_VERDICTS = CANONICAL_VERDICTS;
11
- const EXECUTION_TIERS = Object.freeze(['Light', 'Standard', 'Deep']);
12
- const LANES = Object.freeze(['Direct', 'Standard', 'Critical']);
13
- const PLANNING_DEPTHS = Object.freeze(['None', 'Compact', 'Full']);
14
- const ASSURANCE_LEVELS = Object.freeze(['Routine', 'Elevated', 'Strict']);
15
- const ARTIFACT_PROFILES = Object.freeze(['targeted', 'bounded', 'strict']);
16
- const PLANNING_OBLIGATIONS = Object.freeze([
17
- 'needsRequirements',
18
- 'needsDesign',
19
- ]);
20
- const PROOF_OBLIGATIONS = Object.freeze([
21
- 'needsInspection',
22
- 'needsExecutionProof',
23
- 'needsIndependentAudit',
24
- 'needsDurableTaskState',
25
- ]);
26
- const READ_COMPAT_PROOF_OBLIGATIONS = Object.freeze([
27
- ...PROOF_OBLIGATIONS,
28
- 'needsResearchGrounding',
29
- ]);
30
- const WORKFLOW_POLICY_VERSION = '2';
31
- const CANONICAL_WORKFLOW_POLICY_VERSION = '2.1';
32
- const CANONICAL_WORKFLOW_POLICY_FIELDS = Object.freeze([
33
- 'version',
34
- 'planning_depth',
35
- 'assurance_level',
36
- 'classified_minimum',
37
- 'risks',
38
- ]);
39
- const WORKFLOW_POLICY_FIELDS = Object.freeze([
40
- 'version',
41
- 'planning_depth',
42
- 'automatic_planning_depth',
43
- 'assurance_level',
44
- 'automatic_assurance_level',
45
- 'lane',
46
- 'automatic_lane',
47
- 'risks',
48
- 'artifact_profile',
49
- 'planning_obligations',
50
- 'proof_obligations',
51
- 'actor_needs',
52
- 'override_receipt',
53
- ]);
54
- const RECEIPT_BINDING_FIELDS = Object.freeze(['expectedProvenance', 'requireProvenanceBinding']);
55
- const INDEPENDENT_AUDIT_SCHEMA_VERSION = '1';
56
- const INDEPENDENT_AUDIT_FIELDS = Object.freeze([
57
- 'schema_version',
58
- 'reviewer_session_id',
59
- 'implementation_session_id',
60
- 'expected_provenance',
61
- 'evidence',
62
- 'verdict',
63
- ]);
64
- const FLASH_PROMOTION_FIELDS = Object.freeze(['readyForSync', 'flashTransition', 'promotionReceipt']);
65
- const LEGACY_TIER_LANES = Object.freeze({ Light: 'Direct', Standard: 'Standard', Deep: 'Critical' });
66
- const V1_POLICY_FIELDS = Object.freeze([
67
- 'version', 'lane', 'automatic_lane', 'risks', 'artifact_profile',
68
- 'proof_obligations', 'actor_needs', 'override_receipt',
69
- ]);
70
- const ARTIFACT_PROFILE_BY_LANE = Object.freeze({ Direct: 'targeted', Standard: 'bounded', Critical: 'strict' });
71
- const ARTIFACT_PROFILE_BY_PLANNING = Object.freeze({ None: 'targeted', Compact: 'bounded', Full: 'strict' });
72
- const ACTOR_NEEDS_BY_OBLIGATION = Object.freeze({
73
- needsInspection: Object.freeze({ capability: 'inspection', independence: 'same-session' }),
74
- needsExecutionProof: Object.freeze({ capability: 'execution-proof', independence: 'same-session' }),
75
- needsIndependentAudit: Object.freeze({ capability: 'audit', independence: 'independent' }),
76
- needsDurableTaskState: Object.freeze({ capability: 'durable-task-state', independence: 'same-session' }),
77
- needsResearchGrounding: Object.freeze({ capability: 'research-grounding', independence: 'same-session' }),
78
- });
79
- const ACTOR_CAPABILITIES = new Set(Object.values(ACTOR_NEEDS_BY_OBLIGATION).map((need) => need.capability));
80
- const ACTOR_INDEPENDENCE = new Set(['same-session', 'independent']);
81
- const LANE_RISK_KEYS = Object.freeze({
82
- reversibility: ['reversible', 'reversibility', 'irreversible', 'non-reversible'],
83
- destructive: ['destructive', 'deletion', 'delete', 'destroy'],
84
- auth: ['auth', 'authentication', 'authorization'],
85
- payment: ['payment', 'billing', 'charge'],
86
- privacy: ['privacy', 'pii', 'personal data'],
87
- data: ['data loss', 'loss of data', 'data corruption', 'corrupt data', 'data integrity'],
88
- schema: ['schema'],
89
- migration: ['migration', 'migrate', 'database migration'],
90
- publicContract: ['publicContract', 'public_contract', 'public contract', 'api contract', 'breaking change', 'backward compatibility'],
91
- crossRuntime: ['crossRuntime', 'cross-runtime', 'cross runtime', 'cross_service', 'cross-service', 'runtime coupling', 'worker', 'webhook'],
92
- ambiguity: ['ambiguous', 'ambiguity', 'unclear', 'unknown requirements', 'underspecified'],
93
- rollback: ['rollback', 'rollback difficulty', 'hard to rollback', 'cannot rollback', 'no rollback'],
94
- });
95
-
96
- function hasOwn(value, key) {
97
- return value !== null
98
- && (typeof value === 'object' || typeof value === 'function')
99
- && Object.prototype.hasOwnProperty.call(value, key);
100
- }
101
-
102
8
  const PLACEHOLDER_TOKENS = new Set(['TBD','TODO','N/A','NA','NONE','UNKNOWN','PENDING','PLACEHOLDER','REPLACE_ME','-','?']);
103
9
  const TAP_METADATA_HEADING_RE = /^\s*#\s*(?:(?:tests?|suites?|pass|fail|cancel(?:l)?ed|skipped|todo)\s+\d+|duration(?:_ms)?\s+\d+(?:\.\d+)?)\s*$/i;
104
10
  const ARTIFACT_DECLARATION_KEY = 'artifacts';
@@ -218,22 +124,6 @@ function isConcreteProvenanceValue(value) {
218
124
  return PROVENANCE_VALUE_RE.test(normalizeProvenanceValue(value) || '');
219
125
  }
220
126
 
221
- function runtimeContextFromContext(context) {
222
- const candidates = [
223
- context?.runtime_context,
224
- context?.runtimeContext,
225
- context?.provenance_context,
226
- context?.provenanceContext,
227
- context?.receipt_binding?.runtimeContext,
228
- context?.receiptBinding?.runtimeContext,
229
- context?.provenance_binding?.runtimeContext,
230
- context?.provenanceBinding?.runtimeContext,
231
- ];
232
- const candidate = candidates.find((value) => PROVENANCE.isTrustedRuntimeContext(value));
233
- if (!candidate) return null;
234
- try { return PROVENANCE.recomputeRuntimeContext(candidate); } catch (_) { return null; }
235
- }
236
-
237
127
  function firstDefined(source, keys) {
238
128
  if (!source || typeof source !== 'object') return undefined;
239
129
  for (const key of keys) {
@@ -242,36 +132,6 @@ function firstDefined(source, keys) {
242
132
  return undefined;
243
133
  }
244
134
 
245
- function expectedProvenanceFromSource(source) {
246
- if (!source || typeof source !== 'object' || Array.isArray(source)) return null;
247
- const nested = firstDefined(source, [
248
- 'expectedProvenance',
249
- 'expected_provenance',
250
- ]);
251
- const candidate = nested && typeof nested === 'object' && !Array.isArray(nested)
252
- ? nested
253
- : source;
254
- const base = firstDefined(candidate, [
255
- 'base', 'Base', 'base_sha', 'baseSha', 'expectedBase', 'expected_base',
256
- 'expectedBaseSha', 'expected_base_sha',
257
- ]);
258
- const head = firstDefined(candidate, [
259
- 'head', 'Head', 'head_sha', 'headSha', 'expectedHead', 'expected_head',
260
- 'expectedHeadSha', 'expected_head_sha',
261
- ]);
262
- if (base === undefined && head === undefined) return null;
263
- return { base, head };
264
- }
265
-
266
- function createReceiptBinding(runtimeContext) {
267
- if (!PROVENANCE.isTrustedRuntimeContext(runtimeContext)) {
268
- throw new TypeError('Receipt binding requires a runtime-derived provenance context');
269
- }
270
- try { return PROVENANCE.createReceiptBinding(runtimeContext); } catch (error) {
271
- throw new TypeError(error.message);
272
- }
273
- }
274
-
275
135
  function receiptValidatorOptions(task = {}, {
276
136
  requireProvenanceBinding = false,
277
137
  requireExplicitBinding = false,
@@ -345,20 +205,6 @@ function parseArtifactDeclarations(body) {
345
205
  return declarations;
346
206
  }
347
207
 
348
- function artifactSha256Values(body) {
349
- return parseArtifactDeclarations(body).flatMap((declaration) => declaration.hashes);
350
- }
351
-
352
- function hasCanonicalArtifactHash(body) {
353
- const hashes = artifactSha256Values(body);
354
- return hashes.length > 0 && hashes.every((value) => /^[a-f0-9]{64}$/i.test(value));
355
- }
356
-
357
- function hasConcreteArtifactHash(body) {
358
- const hashes = artifactSha256Values(body);
359
- return hashes.length > 0 && hashes.every((value) => value !== '' && !isPlaceholderToken(value));
360
- }
361
-
362
208
  function hasExplicitFailure(body) {
363
209
  if (typeof body !== 'string') return false;
364
210
  const lines = body.split('\n');
@@ -427,839 +273,6 @@ function hasExplicitFailure(body) {
427
273
  }
428
274
  return false;
429
275
  }
430
- const trustedClassifications = new WeakSet();
431
- const trustedV1Adapters = new WeakSet();
432
-
433
- function clonePolicySnapshot(snapshot) {
434
- const clone = JSON.parse(JSON.stringify(snapshot));
435
- if (trustedV1Adapters.has(snapshot)) trustedV1Adapters.add(clone);
436
- return clone;
437
- }
438
-
439
- const APPROVAL_SCHEMA_VERSION = '2.0';
440
- const LEGACY_APPROVAL_ERROR = `Legacy approval state detected (field \`approved\`). Migration required: replace \`approved\` with \`agent_validated\` per schema v${APPROVAL_SCHEMA_VERSION} (schema_version: "${APPROVAL_SCHEMA_VERSION}"). See spec-state.json template.`;
441
-
442
- function assertLane(lane) {
443
- if (!LANES.includes(lane)) {
444
- throw new TypeError(`Unsupported workflow lane: ${String(lane)}`);
445
- }
446
- return lane;
447
- }
448
-
449
- function assertPlanningDepth(depth) {
450
- if (!PLANNING_DEPTHS.includes(depth)) {
451
- throw new TypeError(`Unsupported planning depth: ${String(depth)}`);
452
- }
453
- return depth;
454
- }
455
-
456
- function assertAssuranceLevel(level) {
457
- if (!ASSURANCE_LEVELS.includes(level)) {
458
- throw new TypeError(`Unsupported assurance level: ${String(level)}`);
459
- }
460
- return level;
461
- }
462
-
463
- function compatibilityLane(planningDepth, assuranceLevel) {
464
- assertPlanningDepth(planningDepth);
465
- assertAssuranceLevel(assuranceLevel);
466
- if (assuranceLevel === 'Strict') return 'Critical';
467
- if (planningDepth === 'None' && assuranceLevel === 'Routine') return 'Direct';
468
- return 'Standard';
469
- }
470
-
471
- function v1Axes(lane) {
472
- assertLane(lane);
473
- if (lane === 'Direct') return { planningDepth: 'None', assuranceLevel: 'Routine' };
474
- if (lane === 'Critical') return { planningDepth: 'Full', assuranceLevel: 'Strict' };
475
- return { planningDepth: 'Compact', assuranceLevel: 'Routine' };
476
- }
477
-
478
- function asTrue(value) {
479
- return value === true || value === 1 || value === 'true' || value === 'yes';
480
- }
481
-
482
- function signalValue(source, keys) {
483
- return keys.some((key) => asTrue(source[key]));
484
- }
485
-
486
- function textValue(input) {
487
- const files = Array.isArray(input.files) ? input.files : [];
488
- return [input.title, input.description, input.summary, input.operation, ...files]
489
- .filter((value) => typeof value === 'string')
490
- .join(' ')
491
- .toLowerCase();
492
- }
493
-
494
- function classifyRiskSignals(input = {}) {
495
- const nestedSignals = input.signals && typeof input.signals === 'object' ? input.signals : {};
496
- const declaredRiskValues = [input.riskSignals, input.risks]
497
- .flatMap((value) => Array.isArray(value) ? value : [])
498
- .filter((value) => typeof value === 'string');
499
- const declaredSignals = declaredRiskValues
500
- .join(' ')
501
- .toLowerCase();
502
- const explicitRisk = (terms) => declaredRiskValues.some((value) => terms.includes(value.trim().toLowerCase()));
503
- const source = {
504
- ...input,
505
- ...nestedSignals,
506
- ...(input.riskSignals && typeof input.riskSignals === 'object' && !Array.isArray(input.riskSignals) ? input.riskSignals : {}),
507
- };
508
- const text = `${textValue(input)} ${declaredSignals}`;
509
- const hasTerm = (terms) => terms.some((term) => {
510
- const escaped = term.toLowerCase().replace(/[.*+?^${}()|[\]\\]/g, '\\$&').replace(/\s+/g, '\\s+');
511
- return new RegExp(`(?:^|[^a-z0-9_])${escaped}(?:$|[^a-z0-9_])`).test(text);
512
- });
513
- const reversibility = String(source.reversibility || '').toLowerCase();
514
- const rollback = String(source.rollback || source.rollbackDifficulty || '').toLowerCase();
515
- const signals = {
516
- reversibility: source.reversible === false
517
- || ['irreversible', 'non-reversible', 'not reversible'].includes(reversibility)
518
- || hasTerm(['irreversible', 'non-reversible', 'not reversible']),
519
- destructive: signalValue(source, LANE_RISK_KEYS.destructive) || hasTerm(LANE_RISK_KEYS.destructive),
520
- auth: signalValue(source, LANE_RISK_KEYS.auth) || hasTerm(LANE_RISK_KEYS.auth),
521
- payment: signalValue(source, LANE_RISK_KEYS.payment) || hasTerm(LANE_RISK_KEYS.payment),
522
- privacy: signalValue(source, LANE_RISK_KEYS.privacy) || hasTerm(LANE_RISK_KEYS.privacy),
523
- data: signalValue(source, ['data', 'dataset', 'records'])
524
- || explicitRisk(['data', 'dataset', 'records'])
525
- || hasTerm(LANE_RISK_KEYS.data),
526
- schema: signalValue(source, LANE_RISK_KEYS.schema) || hasTerm(LANE_RISK_KEYS.schema),
527
- migration: signalValue(source, LANE_RISK_KEYS.migration) || hasTerm(LANE_RISK_KEYS.migration),
528
- publicContract: signalValue(source, LANE_RISK_KEYS.publicContract) || hasTerm(LANE_RISK_KEYS.publicContract),
529
- crossRuntime: signalValue(source, LANE_RISK_KEYS.crossRuntime) || hasTerm(LANE_RISK_KEYS.crossRuntime),
530
- ambiguity: signalValue(source, LANE_RISK_KEYS.ambiguity)
531
- || ['high', 'severe'].includes(String(source.ambiguity || '').toLowerCase())
532
- || hasTerm(LANE_RISK_KEYS.ambiguity),
533
- rollback: signalValue(source, LANE_RISK_KEYS.rollback)
534
- || ['difficult', 'hard', 'none', 'impossible'].includes(rollback)
535
- || hasTerm(LANE_RISK_KEYS.rollback),
536
- };
537
- return signals;
538
- }
539
-
540
- function explicitReversible(input = {}) {
541
- const source = {
542
- ...input,
543
- ...(input.signals && typeof input.signals === 'object' ? input.signals : {}),
544
- ...(input.riskSignals && typeof input.riskSignals === 'object' ? input.riskSignals : {}),
545
- };
546
- return source.reversible === true
547
- || String(source.reversibility || '').toLowerCase() === 'reversible';
548
- }
549
-
550
- function overrideLane(input = {}) {
551
- if (typeof input === 'string') return input;
552
- return input.override ?? input.laneOverride ?? input.requestedLane ?? input.lane ?? null;
553
- }
554
-
555
- function freezeClassification(obj) {
556
- Object.freeze(obj);
557
- if (Array.isArray(obj.warnings)) Object.freeze(obj.warnings);
558
- if (Array.isArray(obj.risks)) Object.freeze(obj.risks);
559
- if (obj.signals && typeof obj.signals === 'object') Object.freeze(obj.signals);
560
- trustedClassifications.add(obj);
561
- return obj;
562
- }
563
-
564
- function requestedAxis(input, snake, camel) {
565
- return input[snake] ?? input[camel] ?? null;
566
- }
567
-
568
- function inferredPlanningDepth(input, taskCount) {
569
- const complexity = String(input.systemComplexity ?? input.system_complexity ?? input.complexity ?? '').toLowerCase();
570
- const scope = String(input.scope ?? input.scopeSize ?? input.scope_size ?? '').toLowerCase();
571
- if (['high', 'complex', 'system', 'cross-system'].includes(complexity)
572
- || ['large', 'broad', 'multi-package', 'system'].includes(scope)
573
- || taskCount >= 5) return 'Full';
574
- if (explicitReversible(input) && input.lowRisk === true && input.isolated === true && taskCount <= 2) return 'None';
575
- return 'Compact';
576
- }
577
-
578
- function minimumAssuranceForRisks(risks = []) {
579
- const listed = Array.isArray(risks) ? risks : [];
580
- return listed.length > 0 ? 'Elevated' : 'Routine';
581
- }
582
-
583
- function classifyLane(input = {}) {
584
- if (typeof input === 'string') {
585
- const axes = v1Axes(input);
586
- return freezeClassification({
587
- ...axes,
588
- automaticPlanningDepth: axes.planningDepth,
589
- automaticAssuranceLevel: axes.assuranceLevel,
590
- lane: input,
591
- automaticLane: input,
592
- overridden: false,
593
- warnings: [], risks: [], signals: {},
594
- });
595
- }
596
- if (!input || typeof input !== 'object') throw new TypeError('Lane classification input must be an object');
597
- const taskCount = input.taskCount === undefined ? 1 : input.taskCount;
598
- if (!Number.isInteger(taskCount) || taskCount < 1) throw new RangeError('taskCount must be a positive integer');
599
-
600
- const signals = classifyRiskSignals(input);
601
- const explicitRisks = Array.isArray(input.risks)
602
- ? input.risks.filter((risk) => typeof risk === 'string' && risk.trim() !== '')
603
- : [];
604
- const risks = [...new Set([
605
- ...Object.entries(signals).filter(([, active]) => active).map(([name]) => name),
606
- ...explicitRisks,
607
- ])];
608
- const inferredPlanning = inferredPlanningDepth(input, taskCount);
609
- const automaticAssurance = minimumAssuranceForRisks(risks);
610
- const legacyRequested = overrideLane(input);
611
- const legacyAxes = legacyRequested === null || legacyRequested === undefined ? null : v1Axes(assertLane(legacyRequested));
612
- const requestedPlanning = requestedAxis(input, 'planning_depth', 'planningDepth') ?? legacyAxes?.planningDepth;
613
- const requestedAssurance = requestedAxis(input, 'assurance_level', 'assuranceLevel') ?? legacyAxes?.assuranceLevel;
614
- if (requestedPlanning !== null && requestedPlanning !== undefined) assertPlanningDepth(requestedPlanning);
615
- if (requestedAssurance !== null && requestedAssurance !== undefined) assertAssuranceLevel(requestedAssurance);
616
- if (requestedPlanning && PLANNING_DEPTHS.indexOf(requestedPlanning) < PLANNING_DEPTHS.indexOf(inferredPlanning)) {
617
- throw new Error(`planning_depth downgrade from ${inferredPlanning} to ${requestedPlanning} is not permitted`);
618
- }
619
- if (requestedAssurance && ASSURANCE_LEVELS.indexOf(requestedAssurance) < ASSURANCE_LEVELS.indexOf(automaticAssurance)) {
620
- throw new Error(`assurance_level downgrade from ${automaticAssurance} to ${requestedAssurance} is blocked for risks: ${risks.join(', ')}`);
621
- }
622
- const planningDepth = requestedPlanning || inferredPlanning;
623
- const assuranceLevel = requestedAssurance || automaticAssurance;
624
- const lane = compatibilityLane(planningDepth, assuranceLevel);
625
- const automaticLane = compatibilityLane(inferredPlanning, automaticAssurance);
626
- return freezeClassification({
627
- planningDepth,
628
- automaticPlanningDepth: inferredPlanning,
629
- assuranceLevel,
630
- automaticAssuranceLevel: automaticAssurance,
631
- lane,
632
- automaticLane,
633
- overridden: Boolean(requestedPlanning || requestedAssurance || legacyAxes),
634
- warnings: legacyAxes ? [`Legacy lane ${legacyRequested} adapted to Specs v2 axes.`] : [],
635
- risks,
636
- signals,
637
- });
638
- }
639
-
640
- function uniqueStrings(values, field) {
641
- if (!Array.isArray(values)) return { valid: false, error: `${field} must be an array` };
642
- if (values.some((value) => typeof value !== 'string' || value.trim() === '')) {
643
- return { valid: false, error: `${field} must contain non-empty strings` };
644
- }
645
- if (new Set(values).size !== values.length) return { valid: false, error: `${field} must not contain duplicates` };
646
- return { valid: true };
647
- }
648
-
649
- function planningObligationsFor(depth) {
650
- assertPlanningDepth(depth);
651
- if (depth === 'None') return [];
652
- if (depth === 'Compact') return ['needsRequirements', 'needsDesign'];
653
- return [...PLANNING_OBLIGATIONS];
654
- }
655
-
656
- function obligationsForAssurance(level, risks = []) {
657
- assertAssuranceLevel(level);
658
- const obligations = new Set(['needsExecutionProof']);
659
- if (level === 'Elevated') obligations.add('needsInspection');
660
- if (level === 'Strict') {
661
- obligations.add('needsInspection');
662
- obligations.add('needsIndependentAudit');
663
- }
664
- return PROOF_OBLIGATIONS.filter((obligation) => obligations.has(obligation));
665
- }
666
-
667
- function obligationsForLane(lane, risks = []) {
668
- return obligationsForAssurance(v1Axes(assertLane(lane)).assuranceLevel, risks);
669
- }
670
-
671
- function legacyObligationsForLane(lane, risks = []) {
672
- assertLane(lane);
673
- const riskSet = new Set(risks);
674
- const selected = new Set(['needsExecutionProof']);
675
- if (lane !== 'Direct') selected.add('needsInspection');
676
- if (lane === 'Critical') selected.add('needsIndependentAudit');
677
- if (lane === 'Critical' && [
678
- 'destructive', 'reversibility', 'rollback', 'data', 'schema', 'migration',
679
- 'privacy', 'payment', 'crossRuntime',
680
- ].some((risk) => riskSet.has(risk))) selected.add('needsDurableTaskState');
681
- if (lane === 'Critical' && [
682
- 'auth', 'payment', 'privacy', 'schema', 'migration', 'publicContract',
683
- 'crossRuntime', 'ambiguity',
684
- ].some((risk) => riskSet.has(risk))) selected.add('needsResearchGrounding');
685
- return [...PROOF_OBLIGATIONS, 'needsResearchGrounding'].filter((obligation) => selected.has(obligation));
686
- }
687
-
688
- function actorNeedsFor(obligations) {
689
- return obligations.map((obligation) => ({ ...ACTOR_NEEDS_BY_OBLIGATION[obligation] }));
690
- }
691
-
692
- function mergeProofObligations(...groups) {
693
- const selected = new Set(groups.flat());
694
- return READ_COMPAT_PROOF_OBLIGATIONS.filter((obligation) => selected.has(obligation));
695
- }
696
-
697
- function snapshotFromClassification(classification) {
698
- const planningDepth = assertPlanningDepth(classification.planningDepth);
699
- const automaticPlanningDepth = assertPlanningDepth(classification.automaticPlanningDepth || planningDepth);
700
- const assuranceLevel = assertAssuranceLevel(classification.assuranceLevel);
701
- const automaticAssuranceLevel = assertAssuranceLevel(classification.automaticAssuranceLevel || assuranceLevel);
702
- if (PLANNING_DEPTHS.indexOf(planningDepth) < PLANNING_DEPTHS.indexOf(automaticPlanningDepth)) {
703
- throw new Error(`Workflow policy cannot downgrade planning_depth ${automaticPlanningDepth} to ${planningDepth}`);
704
- }
705
- if (ASSURANCE_LEVELS.indexOf(assuranceLevel) < ASSURANCE_LEVELS.indexOf(automaticAssuranceLevel)) {
706
- throw new Error(`Workflow policy cannot downgrade assurance_level ${automaticAssuranceLevel} to ${assuranceLevel}`);
707
- }
708
- const risks = Array.isArray(classification.risks) ? [...new Set(classification.risks)] : [];
709
- const minimumAssurance = minimumAssuranceForRisks(risks);
710
- if (ASSURANCE_LEVELS.indexOf(assuranceLevel) < ASSURANCE_LEVELS.indexOf(minimumAssurance)) {
711
- throw new Error(`Workflow policy assurance_level ${assuranceLevel} is below ${minimumAssurance} for risks: ${risks.join(', ')}`);
712
- }
713
- const lane = compatibilityLane(planningDepth, assuranceLevel);
714
- const automaticLane = compatibilityLane(automaticPlanningDepth, automaticAssuranceLevel);
715
- const proofObligations = obligationsForAssurance(assuranceLevel, risks);
716
- return {
717
- version: WORKFLOW_POLICY_VERSION,
718
- planning_depth: planningDepth,
719
- automatic_planning_depth: automaticPlanningDepth,
720
- assurance_level: assuranceLevel,
721
- automatic_assurance_level: automaticAssuranceLevel,
722
- lane,
723
- automatic_lane: automaticLane,
724
- risks,
725
- artifact_profile: ARTIFACT_PROFILE_BY_PLANNING[planningDepth],
726
- planning_obligations: planningObligationsFor(planningDepth),
727
- proof_obligations: proofObligations,
728
- actor_needs: actorNeedsFor(proofObligations),
729
- override_receipt: null,
730
- };
731
- }
732
-
733
- function canonicalSnapshotFromAxes({
734
- planningDepth,
735
- assuranceLevel,
736
- automaticPlanningDepth,
737
- automaticAssuranceLevel,
738
- risks = [],
739
- }) {
740
- assertPlanningDepth(planningDepth);
741
- assertAssuranceLevel(assuranceLevel);
742
- assertPlanningDepth(automaticPlanningDepth);
743
- assertAssuranceLevel(automaticAssuranceLevel);
744
- if (PLANNING_DEPTHS.indexOf(planningDepth) < PLANNING_DEPTHS.indexOf(automaticPlanningDepth)) {
745
- throw new Error(`Workflow policy cannot downgrade planning_depth ${automaticPlanningDepth} to ${planningDepth}`);
746
- }
747
- if (ASSURANCE_LEVELS.indexOf(assuranceLevel) < ASSURANCE_LEVELS.indexOf(automaticAssuranceLevel)) {
748
- throw new Error(`Workflow policy cannot downgrade assurance_level ${automaticAssuranceLevel} to ${assuranceLevel}`);
749
- }
750
- const uniqueRisks = [...new Set(risks)];
751
- const minimumForRisks = minimumAssuranceForRisks(uniqueRisks);
752
- if (ASSURANCE_LEVELS.indexOf(assuranceLevel) < ASSURANCE_LEVELS.indexOf(minimumForRisks)) {
753
- throw new Error(`Workflow policy assurance_level ${assuranceLevel} is below ${minimumForRisks} for risks: ${uniqueRisks.join(', ')}`);
754
- }
755
- return {
756
- version: CANONICAL_WORKFLOW_POLICY_VERSION,
757
- planning_depth: planningDepth,
758
- assurance_level: assuranceLevel,
759
- classified_minimum: {
760
- planning_depth: automaticPlanningDepth,
761
- assurance_level: automaticAssuranceLevel,
762
- },
763
- risks: uniqueRisks,
764
- };
765
- }
766
-
767
- function canonicalWorkflowPolicySnapshot(input = {}) {
768
- if (input && typeof input === 'object' && hasOwn(input, 'workflow_policy')) {
769
- const view = readWorkflowPolicySnapshot(input);
770
- return canonicalSnapshotFromAxes({
771
- planningDepth: view.planning_depth,
772
- assuranceLevel: view.assurance_level,
773
- automaticPlanningDepth: view.automatic_planning_depth,
774
- automaticAssuranceLevel: view.automatic_assurance_level,
775
- risks: view.risks,
776
- });
777
- }
778
- const classification = trustedClassifications.has(input) ? input : classifyLane(input);
779
- return canonicalSnapshotFromAxes({
780
- planningDepth: classification.planningDepth,
781
- assuranceLevel: classification.assuranceLevel,
782
- automaticPlanningDepth: classification.automaticPlanningDepth,
783
- automaticAssuranceLevel: classification.automaticAssuranceLevel,
784
- risks: classification.risks,
785
- });
786
- }
787
-
788
- function isCanonicalTasklessTerminalSpec(spec, specDir) {
789
- if (!spec || typeof spec !== 'object' || Array.isArray(spec) || spec.schema_version !== '2.1') return false;
790
- const policy = spec.workflow_policy;
791
- const validation = validateWorkflowPolicySnapshot(policy);
792
- if (!validation.valid || policy.version !== CANONICAL_WORKFLOW_POLICY_VERSION
793
- || !['Compact', 'Full'].includes(policy.planning_depth)) return false;
794
- if (Object.prototype.hasOwnProperty.call(spec, 'task_files')
795
- && (!Array.isArray(spec.task_files) || spec.task_files.length !== 0)) return false;
796
- if (Object.prototype.hasOwnProperty.call(spec, 'task_registry')
797
- && (!spec.task_registry || typeof spec.task_registry !== 'object'
798
- || Array.isArray(spec.task_registry) || Object.keys(spec.task_registry).length !== 0)) return false;
799
- if (typeof specDir !== 'string' || specDir.trim() === '') return false;
800
- try {
801
- const entries = fs.readdirSync(path.join(specDir, 'tasks'));
802
- return entries.length === 0;
803
- } catch (error) {
804
- return error && error.code === 'ENOENT';
805
- }
806
- }
807
-
808
- function canonicalPolicyView(snapshot) {
809
- const planningDepth = snapshot.planning_depth;
810
- const assuranceLevel = snapshot.assurance_level;
811
- const automaticPlanningDepth = snapshot.classified_minimum.planning_depth;
812
- const automaticAssuranceLevel = snapshot.classified_minimum.assurance_level;
813
- const proofObligations = obligationsForAssurance(assuranceLevel, snapshot.risks);
814
- return {
815
- ...JSON.parse(JSON.stringify(snapshot)),
816
- automatic_planning_depth: automaticPlanningDepth,
817
- automatic_assurance_level: automaticAssuranceLevel,
818
- lane: compatibilityLane(planningDepth, assuranceLevel),
819
- automatic_lane: compatibilityLane(automaticPlanningDepth, automaticAssuranceLevel),
820
- artifact_profile: ARTIFACT_PROFILE_BY_PLANNING[planningDepth],
821
- planning_obligations: planningObligationsFor(planningDepth),
822
- proof_obligations: proofObligations,
823
- actor_needs: actorNeedsFor(proofObligations),
824
- };
825
- }
826
-
827
- function workflowPolicySnapshot(input = {}) {
828
- if (input && typeof input === 'object' && hasOwn(input, 'workflow_policy')) {
829
- return readWorkflowPolicySnapshot(input);
830
- }
831
- if (input && typeof input === 'object' && input.design_context?.execution_tier) {
832
- return readWorkflowPolicySnapshot(input);
833
- }
834
- if (input && typeof input === 'object' && isWorkflowPolicySnapshot(input)) {
835
- assertWorkflowPolicySnapshot(input);
836
- if (input.version === WORKFLOW_POLICY_VERSION) return clonePolicySnapshot(input);
837
- return readWorkflowPolicySnapshot({ workflow_policy: input });
838
- }
839
- const classification = trustedClassifications.has(input)
840
- ? input
841
- : classifyLane(input);
842
- return snapshotFromClassification(classification);
843
- }
844
-
845
- function isWorkflowPolicySnapshot(value) {
846
- return Boolean(value && typeof value === 'object' && Object.prototype.hasOwnProperty.call(value, 'version'));
847
- }
848
-
849
- function validateV1Snapshot(snapshot) {
850
- const errors = [];
851
- if (JSON.stringify(Object.keys(snapshot).sort()) !== JSON.stringify([...V1_POLICY_FIELDS].sort())) {
852
- errors.push(`workflow_policy v1 fields must be exactly ${V1_POLICY_FIELDS.join(', ')}`);
853
- }
854
- if (!LANES.includes(snapshot.lane)) errors.push(`workflow_policy.lane must be one of ${LANES.join(', ')}`);
855
- if (!LANES.includes(snapshot.automatic_lane)) errors.push(`workflow_policy.automatic_lane must be one of ${LANES.join(', ')}`);
856
- if (LANES.includes(snapshot.lane) && LANES.includes(snapshot.automatic_lane)
857
- && LANES.indexOf(snapshot.lane) < LANES.indexOf(snapshot.automatic_lane)) errors.push('workflow_policy.lane cannot be lower than automatic_lane');
858
- const risks = uniqueStrings(snapshot.risks, 'workflow_policy.risks');
859
- if (!risks.valid) errors.push(risks.error);
860
- if (risks.valid && LANES.includes(snapshot.lane)) {
861
- const minimum = compatibilityLane('None', minimumAssuranceForRisks(snapshot.risks));
862
- if (LANES.indexOf(snapshot.lane) < LANES.indexOf(minimum)) errors.push(`workflow_policy.lane must be at least ${minimum} for its risks`);
863
- }
864
- if (LANES.includes(snapshot.lane) && snapshot.artifact_profile !== ARTIFACT_PROFILE_BY_LANE[snapshot.lane]) {
865
- errors.push(`workflow_policy.artifact_profile must be ${ARTIFACT_PROFILE_BY_LANE[snapshot.lane]} for ${snapshot.lane}`);
866
- }
867
- const obligations = uniqueStrings(snapshot.proof_obligations, 'workflow_policy.proof_obligations');
868
- if (!obligations.valid) errors.push(obligations.error);
869
- else if (LANES.includes(snapshot.lane) && risks.valid) {
870
- const expected = legacyObligationsForLane(snapshot.lane, snapshot.risks);
871
- if (JSON.stringify(snapshot.proof_obligations) !== JSON.stringify(expected)) errors.push('workflow_policy.proof_obligations do not match v1 lane and risks');
872
- }
873
- if (!Array.isArray(snapshot.actor_needs)) errors.push('workflow_policy.actor_needs must be an array');
874
- else if (obligations.valid && JSON.stringify(snapshot.actor_needs) !== JSON.stringify(actorNeedsFor(snapshot.proof_obligations))) errors.push('workflow_policy.actor_needs must correspond to proof_obligations');
875
- if (snapshot.override_receipt !== null) errors.push('workflow_policy.override_receipt must be null');
876
- return { valid: errors.length === 0, errors };
877
- }
878
-
879
- function validateWorkflowPolicySnapshot(snapshot) {
880
- const errors = [];
881
- if (!snapshot || typeof snapshot !== 'object' || Array.isArray(snapshot)) {
882
- return { valid: false, errors: ['workflow_policy must be an object'] };
883
- }
884
- if (snapshot.version === CANONICAL_WORKFLOW_POLICY_VERSION) {
885
- const keys = Object.keys(snapshot).sort();
886
- if (JSON.stringify(keys) !== JSON.stringify([...CANONICAL_WORKFLOW_POLICY_FIELDS].sort())) {
887
- errors.push(`workflow_policy v2.1 fields must be exactly ${CANONICAL_WORKFLOW_POLICY_FIELDS.join(', ')}`);
888
- }
889
- if (!PLANNING_DEPTHS.includes(snapshot.planning_depth)) {
890
- errors.push(`workflow_policy.planning_depth must be one of ${PLANNING_DEPTHS.join(', ')}`);
891
- }
892
- if (!ASSURANCE_LEVELS.includes(snapshot.assurance_level)) {
893
- errors.push(`workflow_policy.assurance_level must be one of ${ASSURANCE_LEVELS.join(', ')}`);
894
- }
895
- const minimum = snapshot.classified_minimum;
896
- if (!minimum || typeof minimum !== 'object' || Array.isArray(minimum)
897
- || Object.keys(minimum).sort().join(',') !== 'assurance_level,planning_depth') {
898
- errors.push('workflow_policy.classified_minimum must contain exactly planning_depth and assurance_level');
899
- } else {
900
- if (!PLANNING_DEPTHS.includes(minimum.planning_depth)) {
901
- errors.push(`workflow_policy.classified_minimum.planning_depth must be one of ${PLANNING_DEPTHS.join(', ')}`);
902
- }
903
- if (!ASSURANCE_LEVELS.includes(minimum.assurance_level)) {
904
- errors.push(`workflow_policy.classified_minimum.assurance_level must be one of ${ASSURANCE_LEVELS.join(', ')}`);
905
- }
906
- if (PLANNING_DEPTHS.includes(snapshot.planning_depth) && PLANNING_DEPTHS.includes(minimum.planning_depth)
907
- && PLANNING_DEPTHS.indexOf(snapshot.planning_depth) < PLANNING_DEPTHS.indexOf(minimum.planning_depth)) {
908
- errors.push('workflow_policy.planning_depth cannot be lower than classified_minimum.planning_depth');
909
- }
910
- if (ASSURANCE_LEVELS.includes(snapshot.assurance_level) && ASSURANCE_LEVELS.includes(minimum.assurance_level)
911
- && ASSURANCE_LEVELS.indexOf(snapshot.assurance_level) < ASSURANCE_LEVELS.indexOf(minimum.assurance_level)) {
912
- errors.push('workflow_policy.assurance_level cannot be lower than classified_minimum.assurance_level');
913
- }
914
- }
915
- const risks = uniqueStrings(snapshot.risks, 'workflow_policy.risks');
916
- if (!risks.valid) errors.push(risks.error);
917
- if (risks.valid && ASSURANCE_LEVELS.includes(snapshot.assurance_level)) {
918
- const riskMinimum = minimumAssuranceForRisks(snapshot.risks);
919
- if (ASSURANCE_LEVELS.indexOf(snapshot.assurance_level) < ASSURANCE_LEVELS.indexOf(riskMinimum)) {
920
- errors.push(`workflow_policy.assurance_level must be at least ${riskMinimum} for its risks`);
921
- }
922
- }
923
- return { valid: errors.length === 0, errors };
924
- }
925
- if (snapshot.version === '1') return validateV1Snapshot(snapshot);
926
- const keys = Object.keys(snapshot).sort();
927
- const expectedKeys = [...WORKFLOW_POLICY_FIELDS].sort();
928
- if (JSON.stringify(keys) !== JSON.stringify(expectedKeys)) {
929
- errors.push(`workflow_policy fields must be exactly ${WORKFLOW_POLICY_FIELDS.join(', ')}`);
930
- }
931
- if (snapshot.version !== WORKFLOW_POLICY_VERSION) errors.push('workflow_policy.version must be "2"');
932
- if (!PLANNING_DEPTHS.includes(snapshot.planning_depth)) errors.push(`workflow_policy.planning_depth must be one of ${PLANNING_DEPTHS.join(', ')}`);
933
- if (!PLANNING_DEPTHS.includes(snapshot.automatic_planning_depth)) errors.push(`workflow_policy.automatic_planning_depth must be one of ${PLANNING_DEPTHS.join(', ')}`);
934
- if (!ASSURANCE_LEVELS.includes(snapshot.assurance_level)) errors.push(`workflow_policy.assurance_level must be one of ${ASSURANCE_LEVELS.join(', ')}`);
935
- if (!ASSURANCE_LEVELS.includes(snapshot.automatic_assurance_level)) errors.push(`workflow_policy.automatic_assurance_level must be one of ${ASSURANCE_LEVELS.join(', ')}`);
936
- if (!LANES.includes(snapshot.lane)) errors.push(`workflow_policy.lane must be one of ${LANES.join(', ')}`);
937
- if (!LANES.includes(snapshot.automatic_lane)) errors.push(`workflow_policy.automatic_lane must be one of ${LANES.join(', ')}`);
938
- if (PLANNING_DEPTHS.includes(snapshot.planning_depth) && PLANNING_DEPTHS.includes(snapshot.automatic_planning_depth)
939
- && PLANNING_DEPTHS.indexOf(snapshot.planning_depth) < PLANNING_DEPTHS.indexOf(snapshot.automatic_planning_depth)) errors.push('workflow_policy.planning_depth cannot be lower than automatic_planning_depth');
940
- if (ASSURANCE_LEVELS.includes(snapshot.assurance_level) && ASSURANCE_LEVELS.includes(snapshot.automatic_assurance_level)
941
- && ASSURANCE_LEVELS.indexOf(snapshot.assurance_level) < ASSURANCE_LEVELS.indexOf(snapshot.automatic_assurance_level)) errors.push('workflow_policy.assurance_level cannot be lower than automatic_assurance_level');
942
- if (PLANNING_DEPTHS.includes(snapshot.planning_depth) && ASSURANCE_LEVELS.includes(snapshot.assurance_level)
943
- && snapshot.lane !== compatibilityLane(snapshot.planning_depth, snapshot.assurance_level)) errors.push('workflow_policy.lane is not the derived compatibility lane');
944
- if (PLANNING_DEPTHS.includes(snapshot.automatic_planning_depth) && ASSURANCE_LEVELS.includes(snapshot.automatic_assurance_level)
945
- && snapshot.automatic_lane !== compatibilityLane(snapshot.automatic_planning_depth, snapshot.automatic_assurance_level)) errors.push('workflow_policy.automatic_lane is not the derived compatibility lane');
946
- if (PLANNING_DEPTHS.includes(snapshot.planning_depth) && snapshot.artifact_profile !== ARTIFACT_PROFILE_BY_PLANNING[snapshot.planning_depth]) errors.push('workflow_policy.artifact_profile does not match planning_depth');
947
- const risks = uniqueStrings(snapshot.risks, 'workflow_policy.risks');
948
- if (!risks.valid) errors.push(risks.error);
949
- if (risks.valid && ASSURANCE_LEVELS.includes(snapshot.assurance_level)) {
950
- const minimumAssurance = minimumAssuranceForRisks(snapshot.risks);
951
- if (ASSURANCE_LEVELS.indexOf(snapshot.assurance_level) < ASSURANCE_LEVELS.indexOf(minimumAssurance)) {
952
- errors.push(`workflow_policy.assurance_level must be at least ${minimumAssurance} for its risks`);
953
- }
954
- }
955
- const planning = uniqueStrings(snapshot.planning_obligations, 'workflow_policy.planning_obligations');
956
- if (!planning.valid) errors.push(planning.error);
957
- else if (PLANNING_DEPTHS.includes(snapshot.planning_depth)
958
- && JSON.stringify(snapshot.planning_obligations) !== JSON.stringify(planningObligationsFor(snapshot.planning_depth))) errors.push('workflow_policy.planning_obligations do not match planning_depth');
959
- const obligations = uniqueStrings(snapshot.proof_obligations, 'workflow_policy.proof_obligations');
960
- if (!obligations.valid) {
961
- errors.push(obligations.error);
962
- } else {
963
- for (const obligation of snapshot.proof_obligations) {
964
- if (!READ_COMPAT_PROOF_OBLIGATIONS.includes(obligation)) errors.push(`workflow_policy.proof_obligations has unknown obligation ${obligation}`);
965
- }
966
- if (!snapshot.proof_obligations.includes('needsExecutionProof')) {
967
- errors.push('workflow_policy.proof_obligations must include needsExecutionProof');
968
- }
969
- if (ASSURANCE_LEVELS.includes(snapshot.assurance_level) && risks.valid) {
970
- const expected = obligationsForAssurance(snapshot.assurance_level, snapshot.risks);
971
- const exact = JSON.stringify(snapshot.proof_obligations) === JSON.stringify(expected);
972
- const trustedCompat = trustedV1Adapters.has(snapshot)
973
- && JSON.stringify(snapshot.proof_obligations) === JSON.stringify(mergeProofObligations(expected, snapshot.proof_obligations));
974
- if (!exact && !trustedCompat) {
975
- errors.push('workflow_policy.proof_obligations do not match assurance_level and risks');
976
- }
977
- }
978
- }
979
- if (!Array.isArray(snapshot.actor_needs)) {
980
- errors.push('workflow_policy.actor_needs must be an array');
981
- } else {
982
- for (const need of snapshot.actor_needs) {
983
- if (!need || typeof need !== 'object' || Array.isArray(need)
984
- || Object.keys(need).sort().join(',') !== 'capability,independence'
985
- || !ACTOR_CAPABILITIES.has(need.capability)
986
- || !ACTOR_INDEPENDENCE.has(need.independence)) {
987
- errors.push('workflow_policy.actor_needs must describe capability and independence, never an agent name');
988
- break;
989
- }
990
- }
991
- if (ASSURANCE_LEVELS.includes(snapshot.assurance_level) && obligations.valid
992
- && JSON.stringify(snapshot.actor_needs) !== JSON.stringify(actorNeedsFor(snapshot.proof_obligations))) {
993
- errors.push('workflow_policy.actor_needs must correspond to proof_obligations');
994
- }
995
- }
996
- if (snapshot.override_receipt !== null) errors.push('legacy workflow_policy.override_receipt is inert and must be null');
997
- return { valid: errors.length === 0, errors };
998
- }
999
-
1000
- function assertWorkflowPolicySnapshot(snapshot) {
1001
- const result = validateWorkflowPolicySnapshot(snapshot);
1002
- if (!result.valid) throw new TypeError(`Invalid workflow policy snapshot: ${result.errors.join('; ')}`);
1003
- return snapshot;
1004
- }
1005
-
1006
- function readWorkflowPolicySnapshot(spec = {}) {
1007
- if (!spec || typeof spec !== 'object') throw new TypeError('Spec state must be an object');
1008
- if (hasOwn(spec, 'workflow_policy')) {
1009
- let persisted = spec.workflow_policy;
1010
- if (persisted?.version === CANONICAL_WORKFLOW_POLICY_VERSION
1011
- && hasOwn(persisted, 'override_receipt')) {
1012
- if (persisted.override_receipt !== null) {
1013
- throw new TypeError('Invalid workflow policy snapshot: legacy workflow_policy.override_receipt is inert and must be null');
1014
- }
1015
- persisted = { ...persisted };
1016
- delete persisted.override_receipt;
1017
- }
1018
- assertWorkflowPolicySnapshot(persisted);
1019
- if (persisted.version === CANONICAL_WORKFLOW_POLICY_VERSION) {
1020
- return canonicalPolicyView(persisted);
1021
- }
1022
- if (persisted.version === WORKFLOW_POLICY_VERSION) return clonePolicySnapshot(persisted);
1023
- const selected = v1Axes(persisted.lane);
1024
- const automatic = v1Axes(persisted.automatic_lane);
1025
- const adapted = snapshotFromClassification({
1026
- ...selected,
1027
- automaticPlanningDepth: automatic.planningDepth,
1028
- automaticAssuranceLevel: automatic.assuranceLevel,
1029
- risks: persisted.risks,
1030
- });
1031
- const proofObligations = mergeProofObligations(adapted.proof_obligations, persisted.proof_obligations);
1032
- const result = {
1033
- ...adapted,
1034
- proof_obligations: proofObligations,
1035
- actor_needs: actorNeedsFor(proofObligations),
1036
- };
1037
- trustedV1Adapters.add(result);
1038
- return result;
1039
- }
1040
- // Legacy state is read-compatible only. The fallback is deliberately not
1041
- // written back and cannot authorize a downgrade or override new input.
1042
- const rawLegacyTier = spec.design_context && spec.design_context.execution_tier;
1043
- const legacyTier = typeof rawLegacyTier === 'string'
1044
- ? Object.keys(LEGACY_TIER_LANES).find((tier) => tier.toLowerCase() === rawLegacyTier.toLowerCase())
1045
- : null;
1046
- const legacyLane = legacyTier ? LEGACY_TIER_LANES[legacyTier] : null;
1047
- if (legacyLane) {
1048
- const axes = v1Axes(legacyLane);
1049
- return snapshotFromClassification({ ...axes, automaticPlanningDepth: axes.planningDepth, automaticAssuranceLevel: axes.assuranceLevel, risks: [] });
1050
- }
1051
- throw new TypeError('workflow_policy snapshot is missing at the spec boundary');
1052
- }
1053
-
1054
- function persistWorkflowPolicySnapshot(spec, input = {}) {
1055
- if (!spec || typeof spec !== 'object' || Array.isArray(spec)) throw new TypeError('Spec state must be an object');
1056
- const next = { ...spec };
1057
- delete next.override_receipt;
1058
- if (hasOwn(spec, 'workflow_policy')) {
1059
- return { ...next, workflow_policy: canonicalWorkflowPolicySnapshot(spec) };
1060
- }
1061
- return { ...next, workflow_policy: canonicalWorkflowPolicySnapshot(input) };
1062
- }
1063
-
1064
- const persistWorkflowPolicy = persistWorkflowPolicySnapshot;
1065
- const createWorkflowPolicySnapshot = workflowPolicySnapshot;
1066
-
1067
- function candidateAssuranceForRisks(input, risks) {
1068
- // A risk severity label is evidence for inspection, not independent-audit
1069
- // authority. Strict is selected only through the explicit assurance axis.
1070
- const explicitRiskLevel = String(input?.riskLevel || input?.risk_level || input?.level || '').toLowerCase();
1071
- const riskLevelAssurance = ['standard', 'medium', 'high', 'critical', 'deep'].includes(explicitRiskLevel)
1072
- ? 'Elevated'
1073
- : null;
1074
- const riskLevel = minimumAssuranceForRisks(risks);
1075
- const candidates = [riskLevelAssurance, riskLevel]
1076
- .filter((level) => level !== null && level !== undefined);
1077
- if (candidates.length === 0 || candidates.every((level) => level === 'Routine')) return null;
1078
- return ASSURANCE_LEVELS[Math.max(...candidates.map((level) => ASSURANCE_LEVELS.indexOf(level)))];
1079
- }
1080
-
1081
- function escalateWorkflowPolicy(policyOrSpec, riskInput = {}) {
1082
- const canonicalInput = policyOrSpec && typeof policyOrSpec === 'object'
1083
- && (policyOrSpec.version === CANONICAL_WORKFLOW_POLICY_VERSION
1084
- || policyOrSpec.workflow_policy?.version === CANONICAL_WORKFLOW_POLICY_VERSION);
1085
- const current = policyOrSpec && typeof policyOrSpec === 'object' && hasOwn(policyOrSpec, 'workflow_policy')
1086
- ? readWorkflowPolicySnapshot(policyOrSpec)
1087
- : workflowPolicySnapshot(policyOrSpec);
1088
- const discovered = classifyRiskSignals(riskInput);
1089
- const explicitRisks = Array.isArray(riskInput?.risks)
1090
- ? riskInput.risks.filter((risk) => typeof risk === 'string' && risk.trim() !== '')
1091
- : [];
1092
- const newRisks = [...new Set([
1093
- ...Object.entries(discovered).filter(([, active]) => active).map(([name]) => name),
1094
- ...explicitRisks,
1095
- ])];
1096
- const risks = [...new Set([...current.risks, ...newRisks])];
1097
- const requestedAssurance = riskInput?.assurance_level ?? riskInput?.assuranceLevel;
1098
- if (requestedAssurance !== undefined && requestedAssurance !== null) {
1099
- assertAssuranceLevel(requestedAssurance);
1100
- }
1101
- const hasNewRisk = newRisks.some((risk) => !current.risks.includes(risk));
1102
- const hasRiskLevel = ['standard', 'medium', 'high', 'critical', 'deep'].includes(
1103
- String(riskInput?.riskLevel || riskInput?.risk_level || riskInput?.level || '').toLowerCase(),
1104
- );
1105
- const raisesRequestedAssurance = requestedAssurance
1106
- && ASSURANCE_LEVELS.indexOf(requestedAssurance) > ASSURANCE_LEVELS.indexOf(current.assurance_level);
1107
- if (!hasNewRisk && !hasRiskLevel && !raisesRequestedAssurance) return current;
1108
- const candidate = candidateAssuranceForRisks(riskInput, risks);
1109
- if (!candidate && !raisesRequestedAssurance) return current;
1110
- const automaticAssuranceLevel = ASSURANCE_LEVELS[Math.max(
1111
- ASSURANCE_LEVELS.indexOf(current.automatic_assurance_level),
1112
- ASSURANCE_LEVELS.indexOf(candidate || 'Routine'),
1113
- )];
1114
- const assuranceLevel = ASSURANCE_LEVELS[Math.max(
1115
- ASSURANCE_LEVELS.indexOf(current.assurance_level),
1116
- ASSURANCE_LEVELS.indexOf(automaticAssuranceLevel),
1117
- ASSURANCE_LEVELS.indexOf(requestedAssurance || 'Routine'),
1118
- )];
1119
- const axes = {
1120
- planningDepth: current.planning_depth,
1121
- automaticPlanningDepth: current.automatic_planning_depth,
1122
- assuranceLevel,
1123
- automaticAssuranceLevel,
1124
- risks,
1125
- };
1126
- return canonicalInput ? canonicalSnapshotFromAxes(axes) : snapshotFromClassification(axes);
1127
- }
1128
-
1129
- function policyForSpec(spec, riskInput = null) {
1130
- const snapshot = riskInput ? escalateWorkflowPolicy(spec, riskInput) : readWorkflowPolicySnapshot(spec);
1131
- return lanePolicy(snapshot);
1132
- }
1133
-
1134
- function lanePolicy(input = {}) {
1135
- const snapshot = input && typeof input === 'object' && hasOwn(input, 'workflow_policy')
1136
- ? readWorkflowPolicySnapshot(input)
1137
- : input && typeof input === 'object' && isWorkflowPolicySnapshot(input)
1138
- ? (assertWorkflowPolicySnapshot(input), clonePolicySnapshot(input))
1139
- : workflowPolicySnapshot(input);
1140
- const lane = assertLane(snapshot.lane);
1141
- return {
1142
- ...snapshot,
1143
- proof_obligations: [...snapshot.proof_obligations],
1144
- actor_needs: snapshot.actor_needs.map((need) => ({ ...need })),
1145
- planning_obligations: [...snapshot.planning_obligations],
1146
- requiresSpec: snapshot.planning_depth !== 'None',
1147
- requiresState: snapshot.planning_depth === 'Full'
1148
- || snapshot.proof_obligations.includes('needsDurableTaskState'),
1149
- qualityGate: lane === 'Direct' ? 'main-session' : lane === 'Standard' ? 'combined-feature-review' : 'strict-evidence',
1150
- shipPoint: lane === 'Direct' ? 'task' : 'feature',
1151
- evidence: snapshot.artifact_profile,
1152
- };
1153
- }
1154
-
1155
- function isLegacyApprovalObject(obj) {
1156
- return obj && typeof obj === 'object' && 'approved' in obj;
1157
- }
1158
-
1159
- function validateApprovalSchema(spec) {
1160
- if (!spec || typeof spec !== 'object' || Array.isArray(spec)) return { valid: false, absent: false, legacy: false, error: 'spec must be a non-array object' };
1161
- if (!Object.prototype.hasOwnProperty.call(spec, 'approvals')) {
1162
- return { valid: true, absent: true, legacy: false };
1163
- }
1164
- const approvals = spec.approvals;
1165
- if (approvals === null || typeof approvals !== 'object' || Array.isArray(approvals)) {
1166
- return { valid: false, absent: false, legacy: false, error: 'approvals must be a non-empty object' };
1167
- }
1168
- if (Object.keys(approvals).length === 0) {
1169
- return { valid: false, absent: false, legacy: false, error: 'approvals must not be empty' };
1170
- }
1171
- // Check for legacy field `approved` in any stage
1172
- for (const [stage, val] of Object.entries(approvals)) {
1173
- if (isLegacyApprovalObject(val)) {
1174
- return {
1175
- valid: false,
1176
- absent: false,
1177
- legacy: true,
1178
- error: `Legacy approval state at approvals.${stage}.approved. ${LEGACY_APPROVAL_ERROR}`,
1179
- };
1180
- }
1181
- if (!val || typeof val !== 'object' || Array.isArray(val)) {
1182
- return { valid: false, absent: false, legacy: false, error: `approvals.${stage} must be an approval object` };
1183
- }
1184
- const keys = Object.keys(val).sort();
1185
- if (!['agent_validated,generated', 'agent_validated,generated,user_approved'].includes(keys.join(','))) {
1186
- return { valid: false, absent: false, legacy: false, error: `approvals.${stage} must contain generated and agent_validated; legacy user_approved is readable but has zero authority` };
1187
- }
1188
- if (keys.some((key) => typeof val[key] !== 'boolean')) {
1189
- return { valid: false, absent: false, legacy: false, error: `approvals.${stage} fields must be boolean` };
1190
- }
1191
- }
1192
- // Check schema version
1193
- const version = Object.prototype.hasOwnProperty.call(spec, 'schema_version')
1194
- ? spec.schema_version
1195
- : spec.approval_schema_version;
1196
- if (version !== undefined && typeof version !== 'string') {
1197
- return { valid: false, absent: false, legacy: false, error: 'schema_version must be a string' };
1198
- }
1199
- if (version && version !== APPROVAL_SCHEMA_VERSION) {
1200
- return { valid: false, absent: false, legacy: false, error: `Unsupported schema_version ${version}, expected ${APPROVAL_SCHEMA_VERSION}` };
1201
- }
1202
- if (!version && Object.keys(approvals).length > 0) {
1203
- // No version but uses new schema - allow for backward compat but warn that version should be present
1204
- // Fail closed only if legacy detected above; otherwise allow.
1205
- return { valid: true, absent: false, legacy: false, warning: `Missing schema_version, expected ${APPROVAL_SCHEMA_VERSION}` };
1206
- }
1207
- return { valid: true, absent: false, legacy: false };
1208
- }
1209
-
1210
- function approvalState(state = {}) {
1211
- if (!state || typeof state !== 'object' || Array.isArray(state)) {
1212
- throw new TypeError('approval state must be an object');
1213
- }
1214
- if (Object.prototype.hasOwnProperty.call(state, 'approvals')) {
1215
- const validation = validateApprovalSchema(state);
1216
- if (!validation.valid) throw new Error(validation.error);
1217
- return {
1218
- generated: false,
1219
- agent_validated: false,
1220
- ready: false,
1221
- present: true,
1222
- schema_version: APPROVAL_SCHEMA_VERSION,
1223
- legacy: false,
1224
- };
1225
- }
1226
- if (isLegacyApprovalObject(state)) throw new Error(LEGACY_APPROVAL_ERROR);
1227
-
1228
- const version = Object.prototype.hasOwnProperty.call(state, 'schema_version')
1229
- ? state.schema_version
1230
- : state.approval_schema_version;
1231
- if (version !== undefined && (typeof version !== 'string' || version !== APPROVAL_SCHEMA_VERSION)) {
1232
- throw new Error(`Unsupported schema_version ${String(version)}, expected ${APPROVAL_SCHEMA_VERSION}`);
1233
- }
1234
-
1235
- const known = ['generated', 'agent_validated', 'user_approved'];
1236
- const present = known.some((key) => Object.prototype.hasOwnProperty.call(state, key));
1237
- const unknown = Object.keys(state).filter((key) => !known.includes(key) && key !== 'schema_version' && key !== 'approval_schema_version');
1238
- if (unknown.length > 0) throw new TypeError(`malformed approval state fields: ${unknown.join(', ')}`);
1239
- for (const key of known) {
1240
- if (Object.prototype.hasOwnProperty.call(state, key) && typeof state[key] !== 'boolean') {
1241
- throw new TypeError(`approval state field ${key} must be boolean`);
1242
- }
1243
- }
1244
- const result = {
1245
- generated: state.generated === true,
1246
- agent_validated: state.agent_validated === true,
1247
- };
1248
- return {
1249
- ...result,
1250
- ready: result.generated && result.agent_validated,
1251
- present,
1252
- schema_version: APPROVAL_SCHEMA_VERSION,
1253
- legacy_user_approved: state.user_approved === true,
1254
- };
1255
- }
1256
- function assertVerdict(verdict) {
1257
- if (!CANONICAL_VERDICTS.includes(verdict)) {
1258
- throw new TypeError(`Unsupported canonical verdict: ${String(verdict)}`);
1259
- }
1260
- return verdict;
1261
- }
1262
-
1263
276
  function executionPolicy({ flash = false, parallel = false } = {}) {
1264
277
  if (flash && parallel) {
1265
278
  return {
@@ -1277,613 +290,6 @@ function executionPolicy({ flash = false, parallel = false } = {}) {
1277
290
  };
1278
291
  }
1279
292
 
1280
- function delegationPlan({ tier, lane = null, mode = 'full-spec', taskCount = 1 } = {}) {
1281
- if (lane !== null && lane !== undefined) {
1282
- const policy = lanePolicy(lane);
1283
- return {
1284
- lane: policy.lane,
1285
- mode,
1286
- proof_obligations: [...policy.proof_obligations],
1287
- actor_needs: policy.actor_needs.map((need) => ({ ...need })),
1288
- qualityGate: policy.qualityGate,
1289
- shipPoint: policy.shipPoint,
1290
- };
1291
- }
1292
- if (!EXECUTION_TIERS.includes(tier)) {
1293
- throw new TypeError(`Unsupported execution tier: ${String(tier)}`);
1294
- }
1295
- if (!Number.isInteger(taskCount) || taskCount < 1) {
1296
- throw new RangeError('taskCount must be a positive integer');
1297
- }
1298
-
1299
- // Legacy tier input is read-only compatibility. It is translated once to
1300
- // the canonical lane contract and never emits a new tier authority or an
1301
- // agent sequence.
1302
- const legacyLane = LEGACY_TIER_LANES[tier];
1303
- const policy = lanePolicy(legacyLane);
1304
- return {
1305
- tier,
1306
- legacy: true,
1307
- lane: policy.lane,
1308
- mode,
1309
- proof_obligations: [...policy.proof_obligations],
1310
- actor_needs: policy.actor_needs.map((need) => ({ ...need })),
1311
- qualityGate: policy.qualityGate,
1312
- shipPoint: policy.shipPoint,
1313
- taskCount,
1314
- };
1315
- }
1316
-
1317
- function normalizeVerdict(verdict) {
1318
- if (CANONICAL_VERDICTS.includes(verdict)) return { verdict, diagnostic: null };
1319
- if (LEGACY_DIAGNOSTIC_VERDICTS.includes(verdict)) return { verdict: 'BLOCKED', diagnostic: verdict };
1320
- throw new TypeError(`Unsupported canonical verdict: ${String(verdict)}`);
1321
- }
1322
-
1323
- function unfinishedVerdictDecision(verdict, context = {}, diagnostic = null) {
1324
- const action = diagnostic === 'PARTIAL'
1325
- ? 'continue-verification'
1326
- : diagnostic === 'NO_TESTS'
1327
- ? 'configure-or-run-tests'
1328
- : verdict === 'FAIL'
1329
- ? 'fix-and-rerun'
1330
- : 'stop';
1331
- return {
1332
- verdict,
1333
- ...(diagnostic ? { diagnostic } : {}),
1334
- action,
1335
- completion: 'unfinished',
1336
- terminal: verdict !== 'FAIL',
1337
- unfinished: true,
1338
- retry: verdict === 'FAIL',
1339
- blocker: context.blocker || (
1340
- diagnostic === 'PARTIAL'
1341
- ? 'verification is partial; required proof remains unfinished'
1342
- : diagnostic === 'NO_TESTS'
1343
- ? 'no tests were executed'
1344
- : verdict === 'FAIL'
1345
- ? 'verification returned FAIL'
1346
- : 'verification is blocked; resolve the blocker before retrying'
1347
- ),
1348
- };
1349
- }
1350
-
1351
- function adaptVerdict(verdict, context = {}) {
1352
- const normalized = normalizeVerdict(verdict);
1353
- if (normalized.verdict === 'PASS' || normalized.verdict === 'PASS_WITH_WARNINGS') {
1354
- return {
1355
- verdict: normalized.verdict,
1356
- ...(normalized.diagnostic ? { diagnostic: normalized.diagnostic } : {}),
1357
- action: normalized.verdict === 'PASS_WITH_WARNINGS' ? 'review-passed-with-warnings' : 'review-passed',
1358
- completion: 'unfinished',
1359
- terminal: true,
1360
- unfinished: true,
1361
- retry: false,
1362
- blocker: context.blocker || 'completion decision requires canonical execution proof and workflow obligations',
1363
- };
1364
- }
1365
- return unfinishedVerdictDecision(normalized.verdict, context, normalized.diagnostic);
1366
- }
1367
-
1368
- function workflowPolicyObligations(context) {
1369
- if (!context || typeof context !== 'object' || Array.isArray(context)) return null;
1370
- const policy = hasOwn(context, 'workflow_policy')
1371
- ? context.workflow_policy
1372
- : hasOwn(context, 'workflowPolicy')
1373
- ? context.workflowPolicy
1374
- : hasOwn(context, 'policy')
1375
- ? context.policy
1376
- : context.task_context && typeof context.task_context === 'object'
1377
- ? (hasOwn(context.task_context, 'workflow_policy')
1378
- ? context.task_context.workflow_policy
1379
- : hasOwn(context.task_context, 'workflowPolicy')
1380
- ? context.task_context.workflowPolicy
1381
- : null)
1382
- : null;
1383
- if (!validateWorkflowPolicySnapshot(policy).valid) return null;
1384
- return [...readWorkflowPolicySnapshot({ workflow_policy: policy }).proof_obligations];
1385
- }
1386
-
1387
- function executionReceiptFromContext(context) {
1388
- const candidates = [
1389
- context?.execution_receipt,
1390
- context?.executionReceipt,
1391
- context?.canonical_execution_receipt,
1392
- context?.canonicalExecutionReceipt,
1393
- context?.execution?.receipt,
1394
- context?.execution?.body,
1395
- context?.receipt,
1396
- ];
1397
- return candidates.find((value) => typeof value === 'string' && value.trim() !== '') || null;
1398
- }
1399
-
1400
- function proofContainers(context) {
1401
- return [
1402
- context?.proofs,
1403
- context?.workflow_proofs,
1404
- context?.workflowProofs,
1405
- context?.evidence,
1406
- context?.obligation_evidence,
1407
- context?.obligationEvidence,
1408
- ].filter((value) => value && typeof value === 'object' && !Array.isArray(value));
1409
- }
1410
-
1411
- function concreteEvidence(value) {
1412
- if (typeof value === 'string') return value.trim() !== '' && !isPlaceholderToken(value);
1413
- if (Array.isArray(value)) return value.some(concreteEvidence);
1414
- if (!value || typeof value !== 'object') return false;
1415
- return ['evidence', 'proof', 'receipt', 'report', 'summary']
1416
- .some((key) => concreteEvidence(value[key]));
1417
- }
1418
-
1419
- function evidenceForObligation(context, obligation) {
1420
- for (const container of proofContainers(context)) {
1421
- if (Object.prototype.hasOwnProperty.call(container, obligation) && concreteEvidence(container[obligation])) {
1422
- return container[obligation];
1423
- }
1424
- }
1425
- const aliases = {
1426
- needsInspection: ['inspection', 'inspectionEvidence', 'inspection_evidence'],
1427
- needsDurableTaskState: ['durableTaskState', 'durable_task_state', 'taskState', 'task_state'],
1428
- needsResearchGrounding: ['research', 'researchGrounding', 'research_grounding'],
1429
- };
1430
- for (const key of aliases[obligation] || []) {
1431
- if (concreteEvidence(context?.[key])) return context[key];
1432
- }
1433
- return null;
1434
- }
1435
-
1436
- function independentAuditEvidence(context) {
1437
- const runtimeContext = runtimeContextFromContext(context);
1438
- const expectedProvenance = runtimeContext
1439
- ? { base: runtimeContext.base, head: runtimeContext.head }
1440
- : null;
1441
- const candidates = [
1442
- ...proofContainers(context).map((container) => container.needsIndependentAudit),
1443
- context?.independentAudit,
1444
- context?.independent_audit,
1445
- context?.audit,
1446
- ];
1447
- return candidates.find((value) => {
1448
- if (Array.isArray(value)) return false;
1449
- return validateIndependentAuditEvidence(value, expectedProvenance, runtimeContext).valid;
1450
- }) || null;
1451
- }
1452
-
1453
- function validateIndependentAuditEvidence(value, expectedProvenance, runtimeContext) {
1454
- const errors = [];
1455
- if (!value || typeof value !== 'object' || Array.isArray(value)) {
1456
- return { valid: false, errors: ['audit must be an object'] };
1457
- }
1458
- if (JSON.stringify(Object.keys(value).sort()) !== JSON.stringify([...INDEPENDENT_AUDIT_FIELDS].sort())) {
1459
- errors.push(`audit fields must be exactly ${INDEPENDENT_AUDIT_FIELDS.join(', ')}`);
1460
- }
1461
- if (value.schema_version !== INDEPENDENT_AUDIT_SCHEMA_VERSION) {
1462
- errors.push(`audit.schema_version must be "${INDEPENDENT_AUDIT_SCHEMA_VERSION}"`);
1463
- }
1464
- for (const field of ['reviewer_session_id', 'implementation_session_id']) {
1465
- if (typeof value[field] !== 'string' || isPlaceholderToken(value[field])) {
1466
- errors.push(`audit.${field} must be concrete`);
1467
- }
1468
- }
1469
- if (typeof value.reviewer_session_id === 'string'
1470
- && typeof value.implementation_session_id === 'string'
1471
- && value.reviewer_session_id.trim() === value.implementation_session_id.trim()) {
1472
- errors.push('audit reviewer and implementation sessions must be distinct');
1473
- }
1474
- if (typeof value.evidence !== 'string' || isPlaceholderToken(value.evidence)) {
1475
- errors.push('audit.evidence must be concrete');
1476
- }
1477
- if (value.verdict !== 'PASS') errors.push('audit.verdict must be literal PASS');
1478
-
1479
- if (!PROVENANCE.isTrustedRuntimeContext(runtimeContext)) {
1480
- errors.push('audit runtime provenance/session context is unavailable');
1481
- } else if (typeof value.implementation_session_id === 'string'
1482
- && value.implementation_session_id.trim() !== runtimeContext.runtime_session) {
1483
- errors.push('audit implementation session does not match the runtime session');
1484
- }
1485
-
1486
- if (!value.expected_provenance
1487
- || typeof value.expected_provenance !== 'object'
1488
- || Array.isArray(value.expected_provenance)
1489
- || JSON.stringify(Object.keys(value.expected_provenance).sort()) !== JSON.stringify(['base', 'head'])) {
1490
- errors.push('audit.expected_provenance must contain exactly base and head');
1491
- }
1492
- const actualBinding = expectedProvenanceFromSource(value.expected_provenance);
1493
- const runtimeBinding = PROVENANCE.isTrustedRuntimeContext(runtimeContext)
1494
- ? { base: runtimeContext.base, head: runtimeContext.head }
1495
- : null;
1496
- if (!actualBinding
1497
- || !isConcreteProvenanceValue(actualBinding.base)
1498
- || !isConcreteProvenanceValue(actualBinding.head)
1499
- || !expectedProvenance
1500
- || !isConcreteProvenanceValue(expectedProvenance.base)
1501
- || !isConcreteProvenanceValue(expectedProvenance.head)
1502
- || normalizeProvenanceValue(actualBinding.base) !== normalizeProvenanceValue(expectedProvenance.base)
1503
- || normalizeProvenanceValue(actualBinding.head) !== normalizeProvenanceValue(expectedProvenance.head)) {
1504
- errors.push('audit expected Base and Head binding does not match the runtime binding');
1505
- }
1506
- if (runtimeBinding
1507
- && (normalizeProvenanceValue(actualBinding?.base) !== runtimeBinding.base
1508
- || normalizeProvenanceValue(actualBinding?.head) !== runtimeBinding.head)) {
1509
- errors.push('audit expected Base and Head do not match the freshly derived runtime binding');
1510
- }
1511
- return { valid: errors.length === 0, errors };
1512
- }
1513
-
1514
- function completionReceiptOptions(context) {
1515
- const task = context?.task_context || context?.taskContext || context?.task || context;
1516
- const runtimeContext = runtimeContextFromContext(context);
1517
- const derived = receiptValidatorOptions(task && typeof task === 'object' ? task : {}, {
1518
- requireProvenanceBinding: true,
1519
- runtimeContext,
1520
- });
1521
- const supplied = context?.receipt_options || context?.receiptOptions;
1522
- if (!supplied || typeof supplied !== 'object' || Array.isArray(supplied)) return derived;
1523
- return {
1524
- ...derived,
1525
- ...supplied,
1526
- artifactDeclarationValid: derived.artifactDeclarationValid === false
1527
- ? false
1528
- : supplied.artifactDeclarationValid !== false,
1529
- requireArtifactHash: derived.requireArtifactHash === true || supplied.requireArtifactHash === true,
1530
- artifactPaths: derived.artifactPaths.length > 0 ? derived.artifactPaths : (Array.isArray(supplied.artifactPaths) ? supplied.artifactPaths : []),
1531
- artifactRoot: derived.artifactRoot,
1532
- verifyArtifactBytes: derived.verifyArtifactBytes === true || supplied.verifyArtifactBytes === true,
1533
- expectedProvenance: derived.expectedProvenance,
1534
- requireProvenanceBinding: true,
1535
- };
1536
- }
1537
-
1538
- function runtimeBoundProofsFromSpec(spec, runtimeContext) {
1539
- // Spec files are worker-writable: plain proof strings are never execution
1540
- // evidence. Only canonical receipts bound to this fresh runtime may enter
1541
- // the completion decision; independent audit remains a strict object schema.
1542
- const proofSources = [
1543
- 'completion_proofs',
1544
- 'completionProofs',
1545
- 'workflow_proofs',
1546
- 'workflowProofs',
1547
- 'obligation_evidence',
1548
- 'obligationEvidence',
1549
- 'proofs',
1550
- ];
1551
- const sourceKey = proofSources.find((key) => spec && spec[key] && typeof spec[key] === 'object' && !Array.isArray(spec[key]));
1552
- if (!sourceKey) return null;
1553
- const source = spec[sourceKey];
1554
- const trusted = {};
1555
- const receiptOptions = receiptValidatorOptions({}, {
1556
- runtimeContext,
1557
- requireProvenanceBinding: true,
1558
- });
1559
- for (const [obligation, value] of Object.entries(source)) {
1560
- if (obligation === 'needsIndependentAudit') {
1561
- trusted[obligation] = value;
1562
- continue;
1563
- }
1564
- const candidates = value && typeof value === 'object' && !Array.isArray(value)
1565
- ? [value.receipt, value.execution_receipt, value.executionReceipt]
1566
- : [];
1567
- const receipt = candidates.find((candidate) => (
1568
- typeof candidate === 'string'
1569
- && validateCanonicalReceipt(candidate, receiptOptions).length === 0
1570
- ));
1571
- if (receipt) trusted[obligation] = { receipt };
1572
- }
1573
- return trusted;
1574
- }
1575
-
1576
- function completionDecisionForSpec(spec, {
1577
- runtimeContext,
1578
- executionReceipt,
1579
- taskContext,
1580
- } = {}) {
1581
- const policy = spec && typeof spec === 'object' ? spec.workflow_policy : null;
1582
- const policyView = policy ? readWorkflowPolicySnapshot({ workflow_policy: policy }) : null;
1583
- const context = {
1584
- workflow_policy: policy,
1585
- runtime_context: runtimeContext,
1586
- execution_receipt: executionReceipt,
1587
- task_context: taskContext,
1588
- };
1589
- const trustedProofs = runtimeBoundProofsFromSpec(spec, runtimeContext);
1590
- if (trustedProofs) context.proofs = trustedProofs;
1591
- if (policyView && policyView.lane === 'Direct') {
1592
- return {
1593
- completion: 'not_applicable',
1594
- unfinished: false,
1595
- missingProof: [],
1596
- blocker: null,
1597
- };
1598
- }
1599
- return completionDecision('PASS', context);
1600
- }
1601
-
1602
- function completionDecision(verdict, context = {}) {
1603
- if (!context || typeof context !== 'object' || Array.isArray(context)) context = {};
1604
- const normalized = normalizeVerdict(verdict);
1605
- if (normalized.verdict === 'PASS_WITH_WARNINGS') {
1606
- return {
1607
- verdict: normalized.verdict,
1608
- action: 'review-passed-with-warnings',
1609
- completion: 'unfinished',
1610
- terminal: true,
1611
- unfinished: true,
1612
- retry: false,
1613
- blocker: context.blocker || 'PASS_WITH_WARNINGS is not a completion verdict; literal PASS is required',
1614
- };
1615
- }
1616
- if (normalized.verdict !== 'PASS') {
1617
- return unfinishedVerdictDecision(normalized.verdict, context, normalized.diagnostic);
1618
- }
1619
-
1620
- const runtimeContext = runtimeContextFromContext(context);
1621
- if (!runtimeContext) {
1622
- return {
1623
- verdict: normalized.verdict,
1624
- action: 'blocked-runtime-provenance',
1625
- completion: 'unfinished',
1626
- terminal: true,
1627
- unfinished: true,
1628
- retry: false,
1629
- blocker: 'completion blocked: runtime-derived provenance and session context are unavailable or stale',
1630
- missingProof: ['runtime_provenance'],
1631
- };
1632
- }
1633
- const decisionContext = { ...context, runtime_context: runtimeContext };
1634
-
1635
- const missing = [];
1636
- const receipt = executionReceiptFromContext(decisionContext);
1637
- const receiptFailures = receipt
1638
- ? validateCanonicalReceipt(receipt, completionReceiptOptions(decisionContext))
1639
- : ['verification_state'];
1640
- if (receiptFailures.length > 0) missing.push(`execution_receipt:${receiptFailures.join(',')}`);
1641
-
1642
- const obligations = workflowPolicyObligations(decisionContext);
1643
- if (!obligations) {
1644
- missing.push('workflow_policy.proof_obligations');
1645
- } else {
1646
- for (const obligation of obligations) {
1647
- if (obligation === 'needsExecutionProof') continue;
1648
- if (obligation === 'needsIndependentAudit') {
1649
- if (!independentAuditEvidence(decisionContext)) missing.push(obligation);
1650
- continue;
1651
- }
1652
- if (!evidenceForObligation(decisionContext, obligation)) missing.push(obligation);
1653
- }
1654
- }
1655
- if (missing.length > 0) {
1656
- return {
1657
- verdict: normalized.verdict,
1658
- ...(normalized.diagnostic ? { diagnostic: normalized.diagnostic } : {}),
1659
- action: 'await-required-proof',
1660
- completion: 'unfinished',
1661
- terminal: true,
1662
- unfinished: true,
1663
- retry: false,
1664
- blocker: decisionContext.blocker || `completion blocked: missing ${missing.join(', ')}`,
1665
- missingProof: missing,
1666
- };
1667
- }
1668
- return {
1669
- verdict: normalized.verdict,
1670
- action: 'complete',
1671
- completion: 'complete',
1672
- terminal: true,
1673
- unfinished: false,
1674
- retry: false,
1675
- blocker: null,
1676
- };
1677
- }
1678
-
1679
- function consumeReviewVerdict(verdict, context = {}) {
1680
- const result = adaptVerdict(verdict, context);
1681
- return {
1682
- ...result,
1683
- source: 'review',
1684
- completion: 'unfinished',
1685
- unfinished: true,
1686
- createsCompletion: false,
1687
- completionDecisionRequired: true,
1688
- action: result.verdict === 'PASS' || result.verdict === 'PASS_WITH_WARNINGS'
1689
- ? 'review-result-only'
1690
- : result.action,
1691
- blocker: result.verdict === 'PASS' || result.verdict === 'PASS_WITH_WARNINGS'
1692
- ? context.blocker || 'review verdict is not completion proof'
1693
- : result.blocker,
1694
- };
1695
- }
1696
-
1697
- function isFlashUnverified(task) {
1698
- return task?.status === 'in_progress' && task.receipt === 'FLASH_UNVERIFIED';
1699
- }
1700
-
1701
- function isCanonicalFlashUnverified(task) {
1702
- return isFlashUnverified(task)
1703
- && task.dependencyBlocked === true
1704
- && task.unblocks === false
1705
- && typeof task.blocker === 'string'
1706
- && !isPlaceholderToken(task.blocker)
1707
- && !FLASH_PROMOTION_FIELDS.some((field) => hasOwn(task, field));
1708
- }
1709
-
1710
- function isStaleFlashDone(task) {
1711
- return task?.status === 'done' && task.receipt === 'FLASH_UNVERIFIED';
1712
- }
1713
-
1714
- const SYNC_FINALIZE_CAPABILITY = Symbol('sync-finalize-capability');
1715
- const trustedFlashPromotions = new WeakMap();
1716
-
1717
- function flashRuntimeContext(value) {
1718
- if (!PROVENANCE.isTrustedRuntimeContext(value)) return null;
1719
- try { return PROVENANCE.recomputeRuntimeContext(value); } catch (_) { return null; }
1720
- }
1721
-
1722
- function evaluateFlashPromotion(task, verdict, proof = 'Verification: PASS', capability = null, runtimeContext) {
1723
- const trustedRuntime = flashRuntimeContext(runtimeContext);
1724
- if (!trustedRuntime) {
1725
- return blockedFlashSync(task, 'flash promotion blocked: runtime-derived provenance and session context are unavailable or stale');
1726
- }
1727
- const normalized = normalizeVerdict(verdict);
1728
- if (!isFlashUnverified(task)) return task;
1729
-
1730
- if (normalized.verdict !== 'PASS') {
1731
- return {
1732
- ...task,
1733
- status: 'in_progress',
1734
- receipt: 'FLASH_UNVERIFIED',
1735
- blocker: normalized.diagnostic === 'NO_TESTS'
1736
- ? 'awaiting test proof from /cf:test <feature>'
1737
- : normalized.diagnostic === 'PARTIAL'
1738
- ? 'verification is partial; awaiting complete test proof'
1739
- : `verification returned ${normalized.verdict}`,
1740
- dependencyBlocked: true,
1741
- unblocks: false,
1742
- readyForSync: false,
1743
- flashTransition: 'unverified',
1744
- promotionReceipt: null,
1745
- };
1746
- }
1747
-
1748
- if (typeof proof !== 'string') {
1749
- throw new TypeError('PASS promotion requires a concrete Verification: PASS receipt');
1750
- }
1751
- const failures = validateCanonicalReceipt(proof, receiptValidatorOptions(task, {
1752
- requireProvenanceBinding: true,
1753
- runtimeContext: trustedRuntime,
1754
- }));
1755
- if (failures.length > 0) {
1756
- return {
1757
- ...task,
1758
- status: 'in_progress',
1759
- receipt: 'FLASH_UNVERIFIED',
1760
- blocker: `canonical receipt required for promotion: missing ${failures.join(', ')}`,
1761
- dependencyBlocked: true,
1762
- unblocks: false,
1763
- readyForSync: false,
1764
- flashTransition: 'unverified',
1765
- promotionReceipt: null,
1766
- };
1767
- }
1768
-
1769
- const promoted = {
1770
- ...task,
1771
- status: 'in_progress',
1772
- receipt: proof.trim(),
1773
- blocker: null,
1774
- dependencyBlocked: true,
1775
- unblocks: false,
1776
- readyForSync: true,
1777
- flashTransition: 'promoted',
1778
- promotionReceipt: proof.trim(),
1779
- };
1780
- if (capability === SYNC_FINALIZE_CAPABILITY) trustedFlashPromotions.set(promoted, capability);
1781
- return promoted;
1782
- }
1783
-
1784
- function promoteFlashTask(task, verdict, proof = 'Verification: PASS', runtimeContext) {
1785
- const candidate = runtimeContext
1786
- || task?.runtime_context
1787
- || task?.runtimeContext
1788
- || task?.receipt_binding?.runtimeContext
1789
- || task?.receiptBinding?.runtimeContext;
1790
- return evaluateFlashPromotion(task, verdict, proof, null, candidate);
1791
- }
1792
-
1793
- function finalizeFlashTaskInternal(task, operation, capability, runtimeContext) {
1794
- if (operation !== 'sync-finalize' || capability !== SYNC_FINALIZE_CAPABILITY || trustedFlashPromotions.get(task) !== capability) {
1795
- return blockedFlashSync(task, 'sync-finalize blocked: only trusted same-operation promotion may finalize');
1796
- }
1797
- if (
1798
- task?.status !== 'in_progress'
1799
- || task.readyForSync !== true
1800
- ) return blockedFlashSync(task, 'sync-finalize blocked: trusted promotion state is incomplete');
1801
- const receipt = String(task.receipt || '');
1802
- if (task.flashTransition !== 'promoted' || task.promotionReceipt !== receipt) {
1803
- return blockedFlashSync(task, 'sync-finalize blocked: trusted promotion state is incomplete');
1804
- }
1805
- const failures = validateCanonicalReceipt(receipt, receiptValidatorOptions(task, {
1806
- requireProvenanceBinding: true,
1807
- runtimeContext,
1808
- }));
1809
- if (failures.length > 0) {
1810
- return blockedFlashSync(task, `sync-finalize blocked: canonical receipt required (${failures.join(', ')})`);
1811
- }
1812
-
1813
- trustedFlashPromotions.delete(task);
1814
- return {
1815
- ...task,
1816
- status: 'done',
1817
- dependencyBlocked: false,
1818
- unblocks: true,
1819
- readyForSync: false,
1820
- flashTransition: 'finalized',
1821
- };
1822
- }
1823
-
1824
- function finalizeFlashTask(task, operation) {
1825
- if (operation !== 'sync-finalize') return task;
1826
- return blockedFlashSync(task, 'sync-finalize blocked: only syncFinalizeFlashTask owns finalization');
1827
- }
1828
-
1829
- function blockedFlashSync(task, blocker) {
1830
- return {
1831
- ...task,
1832
- status: 'in_progress',
1833
- receipt: 'FLASH_UNVERIFIED',
1834
- dependencyBlocked: true,
1835
- unblocks: false,
1836
- readyForSync: false,
1837
- flashTransition: 'unverified',
1838
- promotionReceipt: null,
1839
- blocker,
1840
- };
1841
- }
1842
-
1843
- function syncFinalizeFlashTask(task, verdict, proof, runtimeContext) {
1844
- if (!isCanonicalFlashUnverified(task)) {
1845
- return blockedFlashSync(
1846
- task,
1847
- 'sync-finalize blocked: current task must be FLASH_UNVERIFIED in the exact canonical stored state; caller promotion state is not trusted',
1848
- );
1849
- }
1850
- if (verdict !== 'PASS') {
1851
- return blockedFlashSync(task, 'sync-finalize blocked: explicit PASS verdict is required');
1852
- }
1853
- if (typeof proof !== 'string' || proof.trim() === '') {
1854
- return blockedFlashSync(task, 'sync-finalize blocked: explicit canonical --proof is required');
1855
- }
1856
-
1857
- // Treat all promotion fields from --task-json as untrusted. Rebuild the
1858
- // unverified state, then derive promotion and finalization in this operation.
1859
- const current = {
1860
- ...task,
1861
- readyForSync: false,
1862
- flashTransition: 'unverified',
1863
- promotionReceipt: null,
1864
- dependencyBlocked: true,
1865
- unblocks: false,
1866
- };
1867
- const candidate = runtimeContext
1868
- || task?.runtime_context
1869
- || task?.runtimeContext
1870
- || task?.receipt_binding?.runtimeContext
1871
- || task?.receiptBinding?.runtimeContext;
1872
- const trustedRuntime = flashRuntimeContext(candidate);
1873
- if (!trustedRuntime) {
1874
- return blockedFlashSync(task, 'sync-finalize blocked: runtime-derived provenance and session context are unavailable or stale');
1875
- }
1876
- const promoted = evaluateFlashPromotion(current, 'PASS', proof, SYNC_FINALIZE_CAPABILITY, trustedRuntime);
1877
- if (promoted.readyForSync !== true) return promoted;
1878
- return finalizeFlashTaskInternal(promoted, 'sync-finalize', SYNC_FINALIZE_CAPABILITY, trustedRuntime);
1879
- }
1880
-
1881
- function flashState(taskRegistry = {}) {
1882
- return Object.entries(taskRegistry)
1883
- .filter(([, task]) => isFlashUnverified(task))
1884
- .map(([taskPath]) => taskPath);
1885
- }
1886
-
1887
293
  // Every other section of a task file writes its fields as list items — the Verification
1888
294
  // Plan's own `- Command:` sits a few lines above the Receipt — so continuing that house
1889
295
  // style inside the Receipt is the natural mistake, and it used to fail five checks at
@@ -2055,91 +461,16 @@ function validateCanonicalReceipt(body, options = {}) {
2055
461
  }
2056
462
 
2057
463
  function parseCliArgs(argv) {
2058
- const options = {
2059
- flash: false,
2060
- parallel: false,
2061
- json: false,
2062
- verdict: null,
2063
- task: null,
2064
- action: null,
2065
- lane: null,
2066
- override: null,
2067
- proof: null,
2068
- userAuthorized: false,
2069
- origin: null,
2070
- projectRoot: null,
2071
- specsRoot: null,
2072
- specFile: null,
2073
- featureName: null,
2074
- runtimeSession: null,
2075
- provenanceMode: null,
2076
- runtimeContextInput: undefined,
2077
- };
2078
- const args = [...argv];
2079
- for (let i = 0; i < args.length; i += 1) {
2080
- const arg = args[i];
464
+ const options = { flash: false, parallel: false, json: false };
465
+ for (const arg of argv) {
2081
466
  if (arg === '--flash') options.flash = true;
2082
467
  else if (arg === '--parallel') options.parallel = true;
2083
468
  else if (arg === '--json') options.json = true;
2084
- else if (arg === '--user-authorized') options.userAuthorized = true;
2085
- else if (arg === '--origin') options.origin = args[++i];
2086
- else if (arg === '--consume-verdict') options.action = 'consume-verdict';
2087
- else if (arg === '--promote-flash') options.action = 'promote-flash';
2088
- else if (arg === '--sync-finalize') options.action = 'sync-finalize';
2089
- else if (arg === '--classify-lane') options.action = 'classify-lane';
2090
- else if (arg === '--lane-policy') options.action = 'lane-policy';
2091
- else if (arg === '--policy-snapshot') options.action = 'policy-snapshot';
2092
- else if (arg === '--validate-policy') options.action = 'validate-policy';
2093
- else if (arg === '--completion-decision') options.action = 'completion-decision';
2094
- else if (arg === '--approval-state') options.action = 'approval-state';
2095
- else if (arg === '--validate-approval') options.action = 'validate-approval';
2096
- else if (arg === '--validate-receipt') options.action = 'validate-receipt';
2097
- else if (arg === '--lane') options.lane = args[++i];
2098
- else if (arg === '--override') options.override = args[++i];
2099
- else if (arg === '--verdict') {
2100
- options.verdict = args[++i];
2101
- if (!options.verdict) throw new Error('--verdict requires a value');
2102
- } else if (arg === '--task-json') {
2103
- const raw = args[++i];
2104
- if (!raw) throw new Error('--task-json requires a JSON value');
2105
- options.task = JSON.parse(raw);
2106
- } else if (arg === '--proof') {
2107
- options.proof = args[++i];
2108
- if (!options.proof) throw new Error('--proof requires a value');
2109
- } else if (arg === '--project-root') options.projectRoot = args[++i];
2110
- else if (arg === '--specs-root') options.specsRoot = args[++i];
2111
- else if (arg === '--spec-file') options.specFile = args[++i];
2112
- else if (arg === '--feature-name') options.featureName = args[++i];
2113
- else if (arg === '--runtime-session' || arg === '--session') options.runtimeSession = args[++i];
2114
- else if (arg === '--provenance-mode') options.provenanceMode = args[++i];
2115
- else if (arg === '--runtime-context') {
2116
- const raw = args[++i];
2117
- if (!raw) throw new Error('--runtime-context requires a JSON value');
2118
- options.runtimeContextInput = JSON.parse(raw);
2119
- } else {
2120
- throw new Error(`Unknown option: ${arg}`);
2121
- }
469
+ else throw new Error(`Unknown option: ${arg}`);
2122
470
  }
2123
471
  return options;
2124
472
  }
2125
473
 
2126
- function cliRuntimeContext(options) {
2127
- if (options.runtimeContextInput !== undefined) {
2128
- throw new Error('--runtime-context JSON is not trusted; provide runtime identity flags');
2129
- }
2130
- const supplied = [options.projectRoot, options.specsRoot, options.specFile, options.featureName, options.runtimeSession]
2131
- .some((value) => value !== null && value !== undefined);
2132
- if (!supplied) return null;
2133
- return PROVENANCE.deriveRuntimeContext({
2134
- projectRoot: options.projectRoot,
2135
- specsRoot: options.specsRoot,
2136
- specFile: options.specFile,
2137
- featureName: options.featureName,
2138
- runtimeSession: options.runtimeSession,
2139
- provenanceMode: options.provenanceMode || undefined,
2140
- });
2141
- }
2142
-
2143
474
  function cliResult(result, json) {
2144
475
  process.stdout.write(json ? `${JSON.stringify(result)}\n` : `${result.message}\n`);
2145
476
  return result.exitCode;
@@ -2159,74 +490,6 @@ function runCli(argv = process.argv.slice(2)) {
2159
490
  }, options.json);
2160
491
  }
2161
492
 
2162
- if (options.action === 'classify-lane') {
2163
- const input = options.task || {};
2164
- // Merge CLI lane/override and auth flags into input
2165
- const merged = { ...input };
2166
- if (options.lane) merged.override = options.lane;
2167
- if (options.override) merged.override = options.override;
2168
- if (options.userAuthorized) merged.userAuthorized = true;
2169
- if (options.origin) merged.origin = options.origin;
2170
- const classification = classifyLane(merged);
2171
- return cliResult({ ok: true, classification, policy: lanePolicy(classification), exitCode: 0, message: `Lane: ${classification.lane}` }, options.json);
2172
- }
2173
- if (options.action === 'lane-policy') {
2174
- const input = options.task || options.lane;
2175
- if (!input) throw new Error('--lane-policy requires --lane or --task-json');
2176
- const policy = lanePolicy(input);
2177
- return cliResult({ ok: true, policy, exitCode: 0, message: `Lane: ${policy.lane}` }, options.json);
2178
- }
2179
- if (options.action === 'policy-snapshot') {
2180
- if (!options.task) throw new Error('--policy-snapshot requires --task-json');
2181
- const snapshot = canonicalWorkflowPolicySnapshot(options.task);
2182
- const view = readWorkflowPolicySnapshot({ workflow_policy: snapshot });
2183
- return cliResult({ ok: true, snapshot, exitCode: 0, message: `Policy snapshot: ${view.lane}` }, options.json);
2184
- }
2185
- if (options.action === 'validate-policy') {
2186
- if (!options.task) throw new Error('--validate-policy requires --task-json');
2187
- const snapshot = hasOwn(options.task, 'workflow_policy') ? options.task.workflow_policy : options.task;
2188
- const result = validateWorkflowPolicySnapshot(snapshot);
2189
- return cliResult({ ok: result.valid, ...result, exitCode: result.valid ? 0 : 2, message: result.valid ? 'Workflow policy snapshot valid' : result.errors.join('; ') }, options.json);
2190
- }
2191
- if (options.action === 'approval-state') {
2192
- if (!options.task) throw new Error('--approval-state requires --task-json');
2193
- const state = approvalState(options.task);
2194
- return cliResult({ ok: true, state, exitCode: 0, message: `Approval ready: ${state.ready}` }, options.json);
2195
- }
2196
- if (options.action === 'validate-approval') {
2197
- if (!options.task) throw new Error('--validate-approval requires --task-json');
2198
- const result = validateApprovalSchema(options.task);
2199
- return cliResult({ ok: result.valid, ...result, exitCode: result.valid ? 0 : 2, message: result.valid ? 'Approval schema valid' : result.error }, options.json);
2200
- }
2201
- if (options.action === 'validate-receipt') {
2202
- if (!options.task) throw new Error('--validate-receipt requires --task-json');
2203
- const body = options.task.body || options.task.receipt || '';
2204
- const failures = validateCanonicalReceipt(body, receiptValidatorOptions(options.task, { runtimeContext: cliRuntimeContext(options) }));
2205
- return cliResult({ ok: failures.length === 0, failures, exitCode: failures.length === 0 ? 0 : 2, message: failures.length === 0 ? 'Receipt valid' : `Receipt missing: ${failures.join(', ')}` }, options.json);
2206
- }
2207
- if (options.action === 'consume-verdict') {
2208
- const result = consumeReviewVerdict(options.verdict, options.task || {});
2209
- return cliResult({ ok: true, ...result, exitCode: 0, message: `Verdict ${options.verdict}: ${result.action}` }, options.json);
2210
- }
2211
- if (options.action === 'completion-decision') {
2212
- const runtimeContext = cliRuntimeContext(options);
2213
- const result = completionDecision(options.verdict, runtimeContext
2214
- ? { ...(options.task || {}), runtime_context: runtimeContext }
2215
- : (options.task || {}));
2216
- return cliResult({ ok: true, ...result, exitCode: 0, message: `Verdict ${options.verdict}: ${result.completion}` }, options.json);
2217
- }
2218
- if (options.action === 'promote-flash') {
2219
- if (!options.task) throw new Error('--promote-flash requires --task-json');
2220
- const result = promoteFlashTask(options.task, options.verdict, options.proof, cliRuntimeContext(options));
2221
- return cliResult({ ok: true, task: result, exitCode: 0, message: 'Flash task promotion evaluated.' }, options.json);
2222
- }
2223
- if (options.action === 'sync-finalize') {
2224
- if (!options.task) throw new Error('--sync-finalize requires --task-json');
2225
- const result = syncFinalizeFlashTask(options.task, options.verdict, options.proof, cliRuntimeContext(options));
2226
- const ok = result.status === 'done';
2227
- return cliResult({ ok, task: result, exitCode: ok ? 0 : 2, message: ok ? 'Flash task sync-finalize evaluated.' : (result.blocker || 'Flash task sync-finalize blocked.') }, options.json);
2228
- }
2229
-
2230
493
  return cliResult({ ok: true, contract: 'execution-policy', ...policy, exitCode: 0, message: `Execution mode: ${policy.mode}` }, options.json);
2231
494
  } catch (error) {
2232
495
  process.stderr.write(`${error.message}\n`);
@@ -2237,70 +500,9 @@ function runCli(argv = process.argv.slice(2)) {
2237
500
  if (require.main === module) process.exitCode = runCli();
2238
501
 
2239
502
  module.exports = {
2240
- REVIEW_VERDICTS,
2241
- CANONICAL_VERDICTS,
2242
- LEGACY_DIAGNOSTIC_VERDICTS,
2243
- EXECUTION_TIERS,
2244
- LANES,
2245
- PLANNING_DEPTHS,
2246
- ASSURANCE_LEVELS,
2247
- ARTIFACT_PROFILES,
2248
- PLANNING_OBLIGATIONS,
2249
- PROOF_OBLIGATIONS,
2250
- WORKFLOW_POLICY_VERSION,
2251
- WORKFLOW_POLICY_FIELDS,
2252
- CANONICAL_WORKFLOW_POLICY_VERSION,
2253
- CANONICAL_WORKFLOW_POLICY_FIELDS,
2254
- RECEIPT_BINDING_FIELDS,
2255
- INDEPENDENT_AUDIT_SCHEMA_VERSION,
2256
- INDEPENDENT_AUDIT_FIELDS,
2257
- APPROVAL_SCHEMA_VERSION,
2258
- LEGACY_APPROVAL_ERROR,
2259
503
  executionPolicy,
2260
- classifyLane,
2261
- compatibilityLane,
2262
- planningObligationsFor,
2263
- obligationsForAssurance,
2264
- obligationsForLane,
2265
- actorNeedsFor,
2266
- isWorkflowPolicySnapshot,
2267
- validateWorkflowPolicySnapshot,
2268
- assertWorkflowPolicySnapshot,
2269
- workflowPolicySnapshot,
2270
- canonicalWorkflowPolicySnapshot,
2271
- isCanonicalTasklessTerminalSpec,
2272
- createWorkflowPolicySnapshot,
2273
- persistWorkflowPolicySnapshot,
2274
- persistWorkflowPolicy,
2275
- readWorkflowPolicySnapshot,
2276
- escalateWorkflowPolicy,
2277
- policyForSpec,
2278
- lanePolicy,
2279
- approvalState,
2280
- validateApprovalSchema,
2281
- isLegacyApprovalObject,
2282
504
  isTapMetadataHeading,
2283
505
  deriveRuntimeContext: PROVENANCE.deriveRuntimeContext,
2284
- recomputeRuntimeContext: PROVENANCE.recomputeRuntimeContext,
2285
- isTrustedRuntimeContext: PROVENANCE.isTrustedRuntimeContext,
2286
- createReceiptBinding,
2287
506
  receiptValidatorOptions,
2288
507
  validateCanonicalReceipt,
2289
- validateIndependentAuditEvidence,
2290
- completionDecisionForSpec,
2291
- delegationPlan,
2292
- assertVerdict,
2293
- normalizeVerdict,
2294
- adaptVerdict,
2295
- completionDecision,
2296
- consumeReviewVerdict,
2297
- isFlashUnverified,
2298
- isCanonicalFlashUnverified,
2299
- isStaleFlashDone,
2300
- promoteFlashTask,
2301
- finalizeFlashTask,
2302
- syncFinalizeFlashTask,
2303
- flashState,
2304
- parseCliArgs,
2305
- runCli,
2306
508
  };