@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.
- package/README.md +1 -1
- package/bin/__tests__/codex-hooks.test.js +169 -236
- package/bin/__tests__/codex-native.test.js +54 -114
- package/bin/__tests__/develop-contract.test.js +101 -1361
- package/bin/__tests__/legacy-authority-decoupled.test.js +14 -19
- package/bin/__tests__/package-inventory.test.js +1 -395
- package/bin/__tests__/spec-narrowing.test.js +30 -134
- package/bin/__tests__/specs-v2-execution-closeout.test.js +0 -372
- package/bin/__tests__/test-runtime-dependency-closure.cjs +1 -2
- package/bin/__tests__/validator-grounding.test.js +17 -2929
- package/bin/lib/managed-writer.js +2 -3
- package/bin/phases/codex-runtime.js +6 -3
- package/bin/phases/copy-payload.js +13 -28
- package/bin/phases/omp-runtime.js +6 -3
- package/package.json +2 -3
- package/src/claude/agents/code-auditor.md +9 -6
- package/src/claude/agents/debugger.md +3 -9
- package/src/claude/agents/docs-keeper.md +4 -7
- package/src/claude/agents/git-ops.md +2 -4
- package/src/claude/agents/implementer.md +6 -7
- package/src/claude/agents/inspector.md +2 -0
- package/src/claude/agents/project-manager.md +2 -2
- package/src/claude/agents/spec-maker.md +4 -4
- package/src/claude/agents/test-runner.md +8 -11
- package/src/claude/agents/ui-ux-designer.md +0 -1
- package/src/claude/hooks/__tests__/spec-gate.test.js +170 -330
- package/src/claude/hooks/__tests__/spec-state-touched.test.js +26 -0
- package/src/claude/hooks/spec-gate.cjs +8 -63
- package/src/claude/hooks/spec-state.cjs +47 -38
- package/src/claude/migration-manifest.json +12 -7
- package/src/claude/output-styles/coding-level-0-eli5.md +9 -9
- package/src/claude/output-styles/coding-level-1-junior.md +3 -3
- package/src/claude/output-styles/coding-level-2-mid.md +3 -3
- package/src/claude/output-styles/coding-level-3-senior.md +2 -2
- package/src/claude/output-styles/coding-level-4-lead.md +5 -5
- package/src/claude/output-styles/coding-level-5-god.md +6 -6
- package/src/claude/rules/ai-dev-rules.md +1 -2
- package/src/claude/rules/workflow.md +1 -1
- package/src/claude/scripts/spec-receipt.cjs +0 -114
- package/src/claude/scripts/spec-resolver.cjs +41 -457
- package/src/claude/scripts/workflow-policy.cjs +3 -1801
- package/src/claude/skills/agent-browser/SKILL.md +1 -1
- package/src/claude/skills/chrome-devtools/SKILL.md +14 -31
- package/src/claude/skills/code-review/SKILL.md +25 -108
- package/src/claude/skills/code-review/references/spec-compliance-review.md +4 -7
- package/src/claude/skills/develop/SKILL.md +22 -70
- package/src/claude/skills/develop/references/subagent-patterns.md +0 -6
- package/src/claude/skills/research/SKILL.md +24 -55
- package/src/claude/skills/sync/SKILL.md +12 -34
- package/src/claude/skills/sync/references/rebind-and-audit.md +2 -2
- package/src/claude/skills/test/SKILL.md +39 -123
- package/src/claude/skills/test/references/execution-strategy.md +11 -22
- package/src/claude/skills/ui-ux/SKILL.md +1 -1
- package/src/claude/skills/ui-ux-pro-max/SKILL.md +24 -79
- package/src/claude/skills/web-testing/SKILL.md +2 -20
- package/src/codex/hooks/lib/spec-receipt.cjs +2 -30
- package/src/codex/hooks/lib/spec-utils.cjs +6 -45
- package/src/codex/hooks/spec-gate.cjs +11 -68
- package/src/codex/hooks/spec-state.cjs +37 -41
- package/src/common/AGENTS.md +4 -10
- package/bin/__tests__/change-firewall.semantic-firewall.test.js +0 -2133
- package/bin/__tests__/finalizer-pass-gate.semantic-firewall.test.js +0 -342
- package/bin/__tests__/release-preflight.semantic-firewall.test.js +0 -586
- package/bin/__tests__/review-receipt-c2.semantic-firewall.test.js +0 -378
- package/bin/__tests__/specs-v2-policy-and-scaffold.test.js +0 -665
- package/bin/__tests__/specs-v2-validator-grounder-contract.test.js +0 -1132
- package/src/claude/hooks/lib/context.cjs +0 -527
- package/src/claude/hooks/lib/detect.cjs +0 -474
- package/src/claude/scripts/change-firewall.cjs +0 -1748
- package/src/claude/scripts/spec-authoring-digest.cjs +0 -151
- package/src/claude/scripts/spec-authoring-validation.cjs +0 -126
- package/src/claude/scripts/spec-final-state.cjs +0 -290
- package/src/claude/scripts/spec-ground.cjs +0 -650
- package/src/claude/scripts/spec-readiness.cjs +0 -425
- package/src/claude/scripts/spec-scaffold.cjs +0 -2317
- package/src/claude/scripts/spec-semantic-model.cjs +0 -396
- package/src/claude/scripts/validate-spec-output.cjs +0 -3914
- package/src/claude/skills/code-review/references/verification-gate.md +0 -41
- package/src/claude/skills/specs/templates/design.md +0 -82
- package/src/claude/skills/specs/templates/requirements-init.md +0 -8
- package/src/claude/skills/specs/templates/requirements.md +0 -42
- package/src/claude/skills/specs/templates/spec-state.json +0 -56
- 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
|
-
|
|
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
|
|
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
|
};
|