@haposoft/cafekit 0.16.0-rc.3 → 0.16.0-rc.4
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/bin/__tests__/codex-hooks.test.js +444 -14
- package/bin/__tests__/codex-native.test.js +228 -7
- package/bin/__tests__/develop-contract.test.js +615 -185
- package/bin/__tests__/installer-safety.test.js +39 -0
- package/bin/__tests__/opencode-spec-gate.test.js +24 -16
- package/bin/__tests__/package-inventory.test.js +687 -13
- package/bin/__tests__/specs-v2-execution-closeout.test.js +421 -0
- package/bin/__tests__/specs-v2-policy-and-scaffold.test.js +601 -0
- package/bin/__tests__/specs-v2-validator-grounder-contract.test.js +952 -0
- package/bin/__tests__/test-runtime-dependency-closure.cjs +182 -0
- package/bin/__tests__/validator-grounding.test.js +1914 -221
- package/bin/lib/managed-writer.js +12 -3
- package/bin/phases/codex-runtime.js +17 -1
- package/package.json +1 -1
- package/src/claude/CLAUDE.md +6 -0
- package/src/claude/agents/code-auditor.md +20 -0
- package/src/claude/agents/spec-maker.md +143 -99
- package/src/claude/hooks/__tests__/completion-authority.test.js +291 -58
- package/src/claude/hooks/__tests__/privacy-block.test.js +172 -0
- package/src/claude/hooks/__tests__/semantic-review-authority.test.js +257 -0
- package/src/claude/hooks/__tests__/spec-gate.test.js +115 -40
- package/src/claude/hooks/completion-authority-check.cjs +37 -288
- package/src/claude/hooks/completion-authority-state.cjs +10 -8
- package/src/claude/hooks/completion-authority.cjs +13 -7
- package/src/claude/hooks/docs-sync.cjs +5 -2
- package/src/claude/hooks/lib/privacy-command-analysis.cjs +393 -0
- package/src/claude/hooks/lib/runtime-path-safety.cjs +119 -0
- package/src/claude/hooks/privacy-block.cjs +77 -30
- package/src/claude/hooks/semantic-review-authority.cjs +152 -0
- package/src/claude/hooks/spec-gate.cjs +47 -117
- package/src/claude/hooks/spec-state.cjs +8 -2
- package/src/claude/migration-manifest.json +9 -1
- package/src/claude/rules/state-sync.md +52 -21
- package/src/claude/rules/workflow.md +17 -9
- package/src/claude/scripts/spec-final-state.cjs +244 -0
- package/src/claude/scripts/spec-ground.cjs +535 -173
- package/src/claude/scripts/spec-readiness.cjs +118 -0
- package/src/claude/scripts/spec-receipt.cjs +162 -0
- package/src/claude/scripts/spec-resolver.cjs +67 -16
- package/src/claude/scripts/spec-scaffold.cjs +1354 -139
- package/src/claude/scripts/spec-semantic-model.cjs +396 -0
- package/src/claude/scripts/validate-spec-output.cjs +2782 -142
- package/src/claude/scripts/workflow-policy.cjs +501 -183
- package/src/claude/settings/settings.json +4 -0
- package/src/claude/skills/code-review/SKILL.md +12 -3
- package/src/claude/skills/code-review/references/spec-compliance-review.md +10 -2
- package/src/claude/skills/code-review/references/verification-gate.md +13 -0
- package/src/claude/skills/develop/SKILL.md +47 -60
- package/src/claude/skills/develop/references/parallel-waves.md +9 -6
- package/src/claude/skills/develop/references/quality-gate.md +19 -12
- package/src/claude/skills/specs/SKILL.md +183 -177
- package/src/claude/skills/specs/references/ask-user-question-gates.md +27 -18
- package/src/claude/skills/specs/references/cross-spec-dependency.md +8 -4
- package/src/claude/skills/specs/references/research-strategy.md +4 -1
- package/src/claude/skills/specs/references/review.md +200 -260
- package/src/claude/skills/specs/references/scope-inquiry.md +18 -5
- package/src/claude/skills/specs/rules/design-discovery-full.md +33 -81
- package/src/claude/skills/specs/rules/design-discovery-light.md +21 -42
- package/src/claude/skills/specs/rules/design-principles.md +136 -180
- package/src/claude/skills/specs/rules/design-review.md +42 -97
- package/src/claude/skills/specs/rules/ears-format.md +42 -50
- package/src/claude/skills/specs/rules/phase-decision-matrix.md +42 -41
- package/src/claude/skills/specs/rules/task-scoring-rubric.md +2 -1
- package/src/claude/skills/specs/rules/tasks-generation.md +189 -300
- package/src/claude/skills/specs/templates/design.md +49 -263
- package/src/claude/skills/specs/templates/requirements.md +26 -36
- package/src/claude/skills/specs/templates/research.md +9 -98
- package/src/claude/skills/specs/templates/spec-state.json +23 -85
- package/src/claude/skills/specs/templates/task.md +30 -102
- package/src/claude/skills/sync/SKILL.md +33 -10
- package/src/claude/skills/sync/references/sync-protocols.md +31 -13
- package/src/claude/skills/test/SKILL.md +17 -7
- package/src/codex/AGENTS.md +6 -0
- package/src/codex/hooks/completion-authority-check.cjs +43 -240
- package/src/codex/hooks/completion-authority-state.cjs +10 -8
- package/src/codex/hooks/completion-authority.cjs +13 -7
- package/src/codex/hooks/lib/hook-context.cjs +16 -2
- package/src/codex/hooks/lib/privacy-command-analysis.cjs +3 -0
- package/src/codex/hooks/lib/privacy-state.cjs +38 -16
- package/src/codex/hooks/lib/runtime-path-safety.cjs +3 -0
- package/src/codex/hooks/lib/spec-receipt.cjs +72 -134
- package/src/codex/hooks/lib/spec-utils.cjs +56 -5
- package/src/codex/hooks/privacy-approval.cjs +1 -1
- package/src/codex/hooks/privacy-block.cjs +67 -11
- package/src/codex/hooks/semantic-review-authority.cjs +130 -0
- package/src/codex/hooks/spec-gate.cjs +49 -34
- package/src/codex/hooks/spec-state.cjs +10 -3
- package/src/codex/hooks/state.cjs +2 -0
- package/src/codex/hooks.json +6 -1
- package/src/codex/rules/state-sync.md +47 -27
- package/src/common/AGENTS.md +1 -1
|
@@ -10,21 +10,43 @@ const LEGACY_DIAGNOSTIC_VERDICTS = Object.freeze(['PARTIAL', 'NO_TESTS']);
|
|
|
10
10
|
const REVIEW_VERDICTS = CANONICAL_VERDICTS;
|
|
11
11
|
const EXECUTION_TIERS = Object.freeze(['Light', 'Standard', 'Deep']);
|
|
12
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']);
|
|
13
15
|
const ARTIFACT_PROFILES = Object.freeze(['targeted', 'bounded', 'strict']);
|
|
16
|
+
const PLANNING_OBLIGATIONS = Object.freeze([
|
|
17
|
+
'needsRequirements',
|
|
18
|
+
'needsDesign',
|
|
19
|
+
]);
|
|
14
20
|
const PROOF_OBLIGATIONS = Object.freeze([
|
|
15
21
|
'needsInspection',
|
|
16
22
|
'needsExecutionProof',
|
|
17
23
|
'needsIndependentAudit',
|
|
18
24
|
'needsDurableTaskState',
|
|
25
|
+
]);
|
|
26
|
+
const READ_COMPAT_PROOF_OBLIGATIONS = Object.freeze([
|
|
27
|
+
...PROOF_OBLIGATIONS,
|
|
19
28
|
'needsResearchGrounding',
|
|
20
29
|
]);
|
|
21
|
-
const WORKFLOW_POLICY_VERSION = '
|
|
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
|
+
]);
|
|
22
39
|
const WORKFLOW_POLICY_FIELDS = Object.freeze([
|
|
23
40
|
'version',
|
|
41
|
+
'planning_depth',
|
|
42
|
+
'automatic_planning_depth',
|
|
43
|
+
'assurance_level',
|
|
44
|
+
'automatic_assurance_level',
|
|
24
45
|
'lane',
|
|
25
46
|
'automatic_lane',
|
|
26
47
|
'risks',
|
|
27
48
|
'artifact_profile',
|
|
49
|
+
'planning_obligations',
|
|
28
50
|
'proof_obligations',
|
|
29
51
|
'actor_needs',
|
|
30
52
|
'override_receipt',
|
|
@@ -41,7 +63,12 @@ const INDEPENDENT_AUDIT_FIELDS = Object.freeze([
|
|
|
41
63
|
]);
|
|
42
64
|
const FLASH_PROMOTION_FIELDS = Object.freeze(['readyForSync', 'flashTransition', 'promotionReceipt']);
|
|
43
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
|
+
]);
|
|
44
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' });
|
|
45
72
|
const ACTOR_NEEDS_BY_OBLIGATION = Object.freeze({
|
|
46
73
|
needsInspection: Object.freeze({ capability: 'inspection', independence: 'same-session' }),
|
|
47
74
|
needsExecutionProof: Object.freeze({ capability: 'execution-proof', independence: 'same-session' }),
|
|
@@ -51,27 +78,13 @@ const ACTOR_NEEDS_BY_OBLIGATION = Object.freeze({
|
|
|
51
78
|
});
|
|
52
79
|
const ACTOR_CAPABILITIES = new Set(Object.values(ACTOR_NEEDS_BY_OBLIGATION).map((need) => need.capability));
|
|
53
80
|
const ACTOR_INDEPENDENCE = new Set(['same-session', 'independent']);
|
|
54
|
-
const CRITICAL_RISKS = new Set([
|
|
55
|
-
'reversibility',
|
|
56
|
-
'destructive',
|
|
57
|
-
'auth',
|
|
58
|
-
'payment',
|
|
59
|
-
'privacy',
|
|
60
|
-
'data',
|
|
61
|
-
'schema',
|
|
62
|
-
'migration',
|
|
63
|
-
'publicContract',
|
|
64
|
-
'crossRuntime',
|
|
65
|
-
'ambiguity',
|
|
66
|
-
'rollback',
|
|
67
|
-
]);
|
|
68
81
|
const LANE_RISK_KEYS = Object.freeze({
|
|
69
82
|
reversibility: ['reversible', 'reversibility', 'irreversible', 'non-reversible'],
|
|
70
83
|
destructive: ['destructive', 'deletion', 'delete', 'destroy'],
|
|
71
84
|
auth: ['auth', 'authentication', 'authorization'],
|
|
72
85
|
payment: ['payment', 'billing', 'charge'],
|
|
73
86
|
privacy: ['privacy', 'pii', 'personal data'],
|
|
74
|
-
data: ['data', '
|
|
87
|
+
data: ['data loss', 'loss of data', 'data corruption', 'corrupt data', 'data integrity'],
|
|
75
88
|
schema: ['schema'],
|
|
76
89
|
migration: ['migration', 'migrate', 'database migration'],
|
|
77
90
|
publicContract: ['publicContract', 'public_contract', 'public contract', 'api contract', 'breaking change', 'backward compatibility'],
|
|
@@ -415,9 +428,16 @@ function hasExplicitFailure(body) {
|
|
|
415
428
|
return false;
|
|
416
429
|
}
|
|
417
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
|
+
}
|
|
418
438
|
|
|
419
439
|
const APPROVAL_SCHEMA_VERSION = '2.0';
|
|
420
|
-
const LEGACY_APPROVAL_ERROR = `Legacy approval state detected (field \`approved\`). Migration required: replace \`approved\` with \`agent_validated\`
|
|
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.`;
|
|
421
441
|
|
|
422
442
|
function assertLane(lane) {
|
|
423
443
|
if (!LANES.includes(lane)) {
|
|
@@ -426,6 +446,35 @@ function assertLane(lane) {
|
|
|
426
446
|
return lane;
|
|
427
447
|
}
|
|
428
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
|
+
|
|
429
478
|
function asTrue(value) {
|
|
430
479
|
return value === true || value === 1 || value === 'true' || value === 'yes';
|
|
431
480
|
}
|
|
@@ -444,11 +493,13 @@ function textValue(input) {
|
|
|
444
493
|
|
|
445
494
|
function classifyRiskSignals(input = {}) {
|
|
446
495
|
const nestedSignals = input.signals && typeof input.signals === 'object' ? input.signals : {};
|
|
447
|
-
const
|
|
496
|
+
const declaredRiskValues = [input.riskSignals, input.risks]
|
|
448
497
|
.flatMap((value) => Array.isArray(value) ? value : [])
|
|
449
|
-
.filter((value) => typeof value === 'string')
|
|
498
|
+
.filter((value) => typeof value === 'string');
|
|
499
|
+
const declaredSignals = declaredRiskValues
|
|
450
500
|
.join(' ')
|
|
451
501
|
.toLowerCase();
|
|
502
|
+
const explicitRisk = (terms) => declaredRiskValues.some((value) => terms.includes(value.trim().toLowerCase()));
|
|
452
503
|
const source = {
|
|
453
504
|
...input,
|
|
454
505
|
...nestedSignals,
|
|
@@ -469,7 +520,9 @@ function classifyRiskSignals(input = {}) {
|
|
|
469
520
|
auth: signalValue(source, LANE_RISK_KEYS.auth) || hasTerm(LANE_RISK_KEYS.auth),
|
|
470
521
|
payment: signalValue(source, LANE_RISK_KEYS.payment) || hasTerm(LANE_RISK_KEYS.payment),
|
|
471
522
|
privacy: signalValue(source, LANE_RISK_KEYS.privacy) || hasTerm(LANE_RISK_KEYS.privacy),
|
|
472
|
-
data: signalValue(source,
|
|
523
|
+
data: signalValue(source, ['data', 'dataset', 'records'])
|
|
524
|
+
|| explicitRisk(['data', 'dataset', 'records'])
|
|
525
|
+
|| hasTerm(LANE_RISK_KEYS.data),
|
|
473
526
|
schema: signalValue(source, LANE_RISK_KEYS.schema) || hasTerm(LANE_RISK_KEYS.schema),
|
|
474
527
|
migration: signalValue(source, LANE_RISK_KEYS.migration) || hasTerm(LANE_RISK_KEYS.migration),
|
|
475
528
|
publicContract: signalValue(source, LANE_RISK_KEYS.publicContract) || hasTerm(LANE_RISK_KEYS.publicContract),
|
|
@@ -499,23 +552,6 @@ function overrideLane(input = {}) {
|
|
|
499
552
|
return input.override ?? input.laneOverride ?? input.requestedLane ?? input.lane ?? null;
|
|
500
553
|
}
|
|
501
554
|
|
|
502
|
-
function isValidOverrideReceipt(receipt, automaticLane, requestedLane, risks = []) {
|
|
503
|
-
// P0.4 fail-closed: no trusted runtime issuer exists yet.
|
|
504
|
-
// Even a correctly shaped object with source:runtime must NOT be considered valid provenance.
|
|
505
|
-
// Downgrade is blocked until a verifiable, runtime-issued override receipt is implemented
|
|
506
|
-
// (bound to automaticLane/requestedLane/risks/session and verifiable by that issuer).
|
|
507
|
-
// Keep this function fail-closed; do not accept plain objects — always return false for P0.
|
|
508
|
-
return false;
|
|
509
|
-
}
|
|
510
|
-
|
|
511
|
-
function isUserAuthorizedForDowngrade(input = {}, automaticLane, requestedLane, risks) {
|
|
512
|
-
// P0.4: downgrade requires structured override receipt with runtime provenance.
|
|
513
|
-
// Boolean flags (userAuthorized, user_approved, etc.) are never trusted.
|
|
514
|
-
// Fail closed — no trusted issuer exists yet, so always false.
|
|
515
|
-
// Explicitly ignore all boolean/string downgrade flags to prevent forged approvals.
|
|
516
|
-
return false;
|
|
517
|
-
}
|
|
518
|
-
|
|
519
555
|
function freezeClassification(obj) {
|
|
520
556
|
Object.freeze(obj);
|
|
521
557
|
if (Array.isArray(obj.warnings)) Object.freeze(obj.warnings);
|
|
@@ -525,63 +561,80 @@ function freezeClassification(obj) {
|
|
|
525
561
|
return obj;
|
|
526
562
|
}
|
|
527
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
|
+
|
|
528
583
|
function classifyLane(input = {}) {
|
|
529
584
|
if (typeof input === 'string') {
|
|
530
|
-
const
|
|
531
|
-
return freezeClassification(
|
|
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
|
+
});
|
|
532
595
|
}
|
|
533
596
|
if (!input || typeof input !== 'object') throw new TypeError('Lane classification input must be an object');
|
|
534
|
-
|
|
535
|
-
const signals = classifyRiskSignals(input);
|
|
536
|
-
const risks = Object.entries(signals).filter(([, active]) => active).map(([name]) => name);
|
|
537
597
|
const taskCount = input.taskCount === undefined ? 1 : input.taskCount;
|
|
538
598
|
if (!Number.isInteger(taskCount) || taskCount < 1) throw new RangeError('taskCount must be a positive integer');
|
|
539
599
|
|
|
540
|
-
const
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
if (
|
|
560
|
-
throw new Error(
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
warnings.push(`Downgrade authorized by user - proceeding with ${requested} despite ${automaticLane} signals.`);
|
|
573
|
-
}
|
|
574
|
-
}
|
|
575
|
-
}
|
|
576
|
-
const c2 = {
|
|
577
|
-
lane: requested,
|
|
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,
|
|
578
632
|
automaticLane,
|
|
579
|
-
overridden:
|
|
580
|
-
warnings,
|
|
633
|
+
overridden: Boolean(requestedPlanning || requestedAssurance || legacyAxes),
|
|
634
|
+
warnings: legacyAxes ? [`Legacy lane ${legacyRequested} adapted to Specs v2 axes.`] : [],
|
|
581
635
|
risks,
|
|
582
636
|
signals,
|
|
583
|
-
};
|
|
584
|
-
return freezeClassification(c2);
|
|
637
|
+
});
|
|
585
638
|
}
|
|
586
639
|
|
|
587
640
|
function uniqueStrings(values, field) {
|
|
@@ -593,67 +646,184 @@ function uniqueStrings(values, field) {
|
|
|
593
646
|
return { valid: true };
|
|
594
647
|
}
|
|
595
648
|
|
|
596
|
-
function
|
|
597
|
-
|
|
598
|
-
|
|
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);
|
|
599
658
|
const obligations = new Set(['needsExecutionProof']);
|
|
600
|
-
if (
|
|
601
|
-
if (
|
|
659
|
+
if (level === 'Elevated') obligations.add('needsInspection');
|
|
660
|
+
if (level === 'Strict') {
|
|
602
661
|
obligations.add('needsInspection');
|
|
603
662
|
obligations.add('needsIndependentAudit');
|
|
604
|
-
if ([
|
|
605
|
-
'destructive', 'reversibility', 'rollback', 'data', 'schema', 'migration',
|
|
606
|
-
'privacy', 'payment', 'crossRuntime',
|
|
607
|
-
].some((risk) => riskSet.has(risk))) {
|
|
608
|
-
obligations.add('needsDurableTaskState');
|
|
609
|
-
}
|
|
610
|
-
if ([
|
|
611
|
-
'auth', 'payment', 'privacy', 'schema', 'migration', 'publicContract',
|
|
612
|
-
'crossRuntime', 'ambiguity',
|
|
613
|
-
].some((risk) => riskSet.has(risk))) {
|
|
614
|
-
obligations.add('needsResearchGrounding');
|
|
615
|
-
}
|
|
616
663
|
}
|
|
617
664
|
return PROOF_OBLIGATIONS.filter((obligation) => obligations.has(obligation));
|
|
618
665
|
}
|
|
619
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
|
+
|
|
620
688
|
function actorNeedsFor(obligations) {
|
|
621
689
|
return obligations.map((obligation) => ({ ...ACTOR_NEEDS_BY_OBLIGATION[obligation] }));
|
|
622
690
|
}
|
|
623
691
|
|
|
624
|
-
function
|
|
625
|
-
const
|
|
626
|
-
|
|
627
|
-
if (Object.entries(classifiedRisks).some(([risk, active]) => active && CRITICAL_RISKS.has(risk))) {
|
|
628
|
-
return 'Critical';
|
|
629
|
-
}
|
|
630
|
-
if (listedRisks.length > 0) return 'Standard';
|
|
631
|
-
return 'Direct';
|
|
692
|
+
function mergeProofObligations(...groups) {
|
|
693
|
+
const selected = new Set(groups.flat());
|
|
694
|
+
return READ_COMPAT_PROOF_OBLIGATIONS.filter((obligation) => selected.has(obligation));
|
|
632
695
|
}
|
|
633
696
|
|
|
634
697
|
function snapshotFromClassification(classification) {
|
|
635
|
-
const
|
|
636
|
-
const
|
|
637
|
-
|
|
638
|
-
|
|
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}`);
|
|
639
707
|
}
|
|
640
708
|
const risks = Array.isArray(classification.risks) ? [...new Set(classification.risks)] : [];
|
|
641
|
-
const
|
|
642
|
-
if (
|
|
643
|
-
throw new Error(`Workflow policy
|
|
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(', ')}`);
|
|
644
712
|
}
|
|
713
|
+
const lane = compatibilityLane(planningDepth, assuranceLevel);
|
|
714
|
+
const automaticLane = compatibilityLane(automaticPlanningDepth, automaticAssuranceLevel);
|
|
715
|
+
const proofObligations = obligationsForAssurance(assuranceLevel, risks);
|
|
645
716
|
return {
|
|
646
717
|
version: WORKFLOW_POLICY_VERSION,
|
|
718
|
+
planning_depth: planningDepth,
|
|
719
|
+
automatic_planning_depth: automaticPlanningDepth,
|
|
720
|
+
assurance_level: assuranceLevel,
|
|
721
|
+
automatic_assurance_level: automaticAssuranceLevel,
|
|
647
722
|
lane,
|
|
648
723
|
automatic_lane: automaticLane,
|
|
649
724
|
risks,
|
|
650
|
-
artifact_profile:
|
|
651
|
-
|
|
652
|
-
|
|
725
|
+
artifact_profile: ARTIFACT_PROFILE_BY_PLANNING[planningDepth],
|
|
726
|
+
planning_obligations: planningObligationsFor(planningDepth),
|
|
727
|
+
proof_obligations: proofObligations,
|
|
728
|
+
actor_needs: actorNeedsFor(proofObligations),
|
|
653
729
|
override_receipt: null,
|
|
654
730
|
};
|
|
655
731
|
}
|
|
656
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
|
+
|
|
657
827
|
function workflowPolicySnapshot(input = {}) {
|
|
658
828
|
if (input && typeof input === 'object' && hasOwn(input, 'workflow_policy')) {
|
|
659
829
|
return readWorkflowPolicySnapshot(input);
|
|
@@ -663,7 +833,8 @@ function workflowPolicySnapshot(input = {}) {
|
|
|
663
833
|
}
|
|
664
834
|
if (input && typeof input === 'object' && isWorkflowPolicySnapshot(input)) {
|
|
665
835
|
assertWorkflowPolicySnapshot(input);
|
|
666
|
-
|
|
836
|
+
if (input.version === WORKFLOW_POLICY_VERSION) return clonePolicySnapshot(input);
|
|
837
|
+
return readWorkflowPolicySnapshot({ workflow_policy: input });
|
|
667
838
|
}
|
|
668
839
|
const classification = trustedClassifications.has(input)
|
|
669
840
|
? input
|
|
@@ -672,9 +843,37 @@ function workflowPolicySnapshot(input = {}) {
|
|
|
672
843
|
}
|
|
673
844
|
|
|
674
845
|
function isWorkflowPolicySnapshot(value) {
|
|
675
|
-
return Boolean(value && typeof value === 'object' && Object.prototype.hasOwnProperty.call(value, 'version')
|
|
676
|
-
|
|
677
|
-
|
|
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 };
|
|
678
877
|
}
|
|
679
878
|
|
|
680
879
|
function validateWorkflowPolicySnapshot(snapshot) {
|
|
@@ -682,43 +881,98 @@ function validateWorkflowPolicySnapshot(snapshot) {
|
|
|
682
881
|
if (!snapshot || typeof snapshot !== 'object' || Array.isArray(snapshot)) {
|
|
683
882
|
return { valid: false, errors: ['workflow_policy must be an object'] };
|
|
684
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);
|
|
685
926
|
const keys = Object.keys(snapshot).sort();
|
|
686
927
|
const expectedKeys = [...WORKFLOW_POLICY_FIELDS].sort();
|
|
687
928
|
if (JSON.stringify(keys) !== JSON.stringify(expectedKeys)) {
|
|
688
929
|
errors.push(`workflow_policy fields must be exactly ${WORKFLOW_POLICY_FIELDS.join(', ')}`);
|
|
689
930
|
}
|
|
690
|
-
if (snapshot.version !== WORKFLOW_POLICY_VERSION) errors.push('workflow_policy.version must be "
|
|
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(', ')}`);
|
|
691
936
|
if (!LANES.includes(snapshot.lane)) errors.push(`workflow_policy.lane must be one of ${LANES.join(', ')}`);
|
|
692
937
|
if (!LANES.includes(snapshot.automatic_lane)) errors.push(`workflow_policy.automatic_lane must be one of ${LANES.join(', ')}`);
|
|
693
|
-
if (
|
|
694
|
-
&&
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
if (
|
|
698
|
-
errors.push(
|
|
699
|
-
|
|
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');
|
|
700
947
|
const risks = uniqueStrings(snapshot.risks, 'workflow_policy.risks');
|
|
701
948
|
if (!risks.valid) errors.push(risks.error);
|
|
702
|
-
if (risks.valid &&
|
|
703
|
-
const
|
|
704
|
-
if (
|
|
705
|
-
errors.push(`workflow_policy.
|
|
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`);
|
|
706
953
|
}
|
|
707
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');
|
|
708
959
|
const obligations = uniqueStrings(snapshot.proof_obligations, 'workflow_policy.proof_obligations');
|
|
709
960
|
if (!obligations.valid) {
|
|
710
961
|
errors.push(obligations.error);
|
|
711
962
|
} else {
|
|
712
963
|
for (const obligation of snapshot.proof_obligations) {
|
|
713
|
-
if (!
|
|
964
|
+
if (!READ_COMPAT_PROOF_OBLIGATIONS.includes(obligation)) errors.push(`workflow_policy.proof_obligations has unknown obligation ${obligation}`);
|
|
714
965
|
}
|
|
715
966
|
if (!snapshot.proof_obligations.includes('needsExecutionProof')) {
|
|
716
967
|
errors.push('workflow_policy.proof_obligations must include needsExecutionProof');
|
|
717
968
|
}
|
|
718
|
-
if (
|
|
719
|
-
const expected =
|
|
720
|
-
|
|
721
|
-
|
|
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');
|
|
722
976
|
}
|
|
723
977
|
}
|
|
724
978
|
}
|
|
@@ -734,14 +988,12 @@ function validateWorkflowPolicySnapshot(snapshot) {
|
|
|
734
988
|
break;
|
|
735
989
|
}
|
|
736
990
|
}
|
|
737
|
-
if (
|
|
991
|
+
if (ASSURANCE_LEVELS.includes(snapshot.assurance_level) && obligations.valid
|
|
738
992
|
&& JSON.stringify(snapshot.actor_needs) !== JSON.stringify(actorNeedsFor(snapshot.proof_obligations))) {
|
|
739
993
|
errors.push('workflow_policy.actor_needs must correspond to proof_obligations');
|
|
740
994
|
}
|
|
741
995
|
}
|
|
742
|
-
if (snapshot.override_receipt !== null)
|
|
743
|
-
errors.push('workflow_policy.override_receipt must be null until a trusted downgrade issuer exists');
|
|
744
|
-
}
|
|
996
|
+
if (snapshot.override_receipt !== null) errors.push('legacy workflow_policy.override_receipt is inert and must be null');
|
|
745
997
|
return { valid: errors.length === 0, errors };
|
|
746
998
|
}
|
|
747
999
|
|
|
@@ -754,8 +1006,36 @@ function assertWorkflowPolicySnapshot(snapshot) {
|
|
|
754
1006
|
function readWorkflowPolicySnapshot(spec = {}) {
|
|
755
1007
|
if (!spec || typeof spec !== 'object') throw new TypeError('Spec state must be an object');
|
|
756
1008
|
if (hasOwn(spec, 'workflow_policy')) {
|
|
757
|
-
|
|
758
|
-
|
|
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;
|
|
759
1039
|
}
|
|
760
1040
|
// Legacy state is read-compatible only. The fallback is deliberately not
|
|
761
1041
|
// written back and cannot authorize a downgrade or override new input.
|
|
@@ -764,7 +1044,10 @@ function readWorkflowPolicySnapshot(spec = {}) {
|
|
|
764
1044
|
? Object.keys(LEGACY_TIER_LANES).find((tier) => tier.toLowerCase() === rawLegacyTier.toLowerCase())
|
|
765
1045
|
: null;
|
|
766
1046
|
const legacyLane = legacyTier ? LEGACY_TIER_LANES[legacyTier] : null;
|
|
767
|
-
if (legacyLane)
|
|
1047
|
+
if (legacyLane) {
|
|
1048
|
+
const axes = v1Axes(legacyLane);
|
|
1049
|
+
return snapshotFromClassification({ ...axes, automaticPlanningDepth: axes.planningDepth, automaticAssuranceLevel: axes.assuranceLevel, risks: [] });
|
|
1050
|
+
}
|
|
768
1051
|
throw new TypeError('workflow_policy snapshot is missing at the spec boundary');
|
|
769
1052
|
}
|
|
770
1053
|
|
|
@@ -773,29 +1056,32 @@ function persistWorkflowPolicySnapshot(spec, input = {}) {
|
|
|
773
1056
|
const next = { ...spec };
|
|
774
1057
|
delete next.override_receipt;
|
|
775
1058
|
if (hasOwn(spec, 'workflow_policy')) {
|
|
776
|
-
|
|
777
|
-
return { ...next, workflow_policy: JSON.parse(JSON.stringify(spec.workflow_policy)) };
|
|
1059
|
+
return { ...next, workflow_policy: canonicalWorkflowPolicySnapshot(spec) };
|
|
778
1060
|
}
|
|
779
|
-
return { ...next, workflow_policy:
|
|
1061
|
+
return { ...next, workflow_policy: canonicalWorkflowPolicySnapshot(input) };
|
|
780
1062
|
}
|
|
781
1063
|
|
|
782
1064
|
const persistWorkflowPolicy = persistWorkflowPolicySnapshot;
|
|
783
1065
|
const createWorkflowPolicySnapshot = workflowPolicySnapshot;
|
|
784
1066
|
|
|
785
|
-
function
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
const
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
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)))];
|
|
796
1079
|
}
|
|
797
1080
|
|
|
798
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);
|
|
799
1085
|
const current = policyOrSpec && typeof policyOrSpec === 'object' && hasOwn(policyOrSpec, 'workflow_policy')
|
|
800
1086
|
? readWorkflowPolicySnapshot(policyOrSpec)
|
|
801
1087
|
: workflowPolicySnapshot(policyOrSpec);
|
|
@@ -808,15 +1094,36 @@ function escalateWorkflowPolicy(policyOrSpec, riskInput = {}) {
|
|
|
808
1094
|
...explicitRisks,
|
|
809
1095
|
])];
|
|
810
1096
|
const risks = [...new Set([...current.risks, ...newRisks])];
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
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,
|
|
818
1124
|
risks,
|
|
819
|
-
}
|
|
1125
|
+
};
|
|
1126
|
+
return canonicalInput ? canonicalSnapshotFromAxes(axes) : snapshotFromClassification(axes);
|
|
820
1127
|
}
|
|
821
1128
|
|
|
822
1129
|
function policyForSpec(spec, riskInput = null) {
|
|
@@ -828,15 +1135,17 @@ function lanePolicy(input = {}) {
|
|
|
828
1135
|
const snapshot = input && typeof input === 'object' && hasOwn(input, 'workflow_policy')
|
|
829
1136
|
? readWorkflowPolicySnapshot(input)
|
|
830
1137
|
: input && typeof input === 'object' && isWorkflowPolicySnapshot(input)
|
|
831
|
-
? (assertWorkflowPolicySnapshot(input),
|
|
1138
|
+
? (assertWorkflowPolicySnapshot(input), clonePolicySnapshot(input))
|
|
832
1139
|
: workflowPolicySnapshot(input);
|
|
833
1140
|
const lane = assertLane(snapshot.lane);
|
|
834
1141
|
return {
|
|
835
1142
|
...snapshot,
|
|
836
1143
|
proof_obligations: [...snapshot.proof_obligations],
|
|
837
1144
|
actor_needs: snapshot.actor_needs.map((need) => ({ ...need })),
|
|
838
|
-
|
|
839
|
-
|
|
1145
|
+
planning_obligations: [...snapshot.planning_obligations],
|
|
1146
|
+
requiresSpec: snapshot.planning_depth !== 'None',
|
|
1147
|
+
requiresState: snapshot.planning_depth === 'Full'
|
|
1148
|
+
|| snapshot.proof_obligations.includes('needsDurableTaskState'),
|
|
840
1149
|
qualityGate: lane === 'Direct' ? 'main-session' : lane === 'Standard' ? 'combined-feature-review' : 'strict-evidence',
|
|
841
1150
|
shipPoint: lane === 'Direct' ? 'task' : 'feature',
|
|
842
1151
|
evidence: snapshot.artifact_profile,
|
|
@@ -873,8 +1182,8 @@ function validateApprovalSchema(spec) {
|
|
|
873
1182
|
return { valid: false, absent: false, legacy: false, error: `approvals.${stage} must be an approval object` };
|
|
874
1183
|
}
|
|
875
1184
|
const keys = Object.keys(val).sort();
|
|
876
|
-
if (
|
|
877
|
-
return { valid: false, absent: false, legacy: false, error: `approvals.${stage} must contain
|
|
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` };
|
|
878
1187
|
}
|
|
879
1188
|
if (keys.some((key) => typeof val[key] !== 'boolean')) {
|
|
880
1189
|
return { valid: false, absent: false, legacy: false, error: `approvals.${stage} fields must be boolean` };
|
|
@@ -908,7 +1217,6 @@ function approvalState(state = {}) {
|
|
|
908
1217
|
return {
|
|
909
1218
|
generated: false,
|
|
910
1219
|
agent_validated: false,
|
|
911
|
-
user_approved: false,
|
|
912
1220
|
ready: false,
|
|
913
1221
|
present: true,
|
|
914
1222
|
schema_version: APPROVAL_SCHEMA_VERSION,
|
|
@@ -936,13 +1244,13 @@ function approvalState(state = {}) {
|
|
|
936
1244
|
const result = {
|
|
937
1245
|
generated: state.generated === true,
|
|
938
1246
|
agent_validated: state.agent_validated === true,
|
|
939
|
-
user_approved: state.user_approved === true,
|
|
940
1247
|
};
|
|
941
1248
|
return {
|
|
942
1249
|
...result,
|
|
943
|
-
ready: result.generated && result.agent_validated
|
|
1250
|
+
ready: result.generated && result.agent_validated,
|
|
944
1251
|
present,
|
|
945
1252
|
schema_version: APPROVAL_SCHEMA_VERSION,
|
|
1253
|
+
legacy_user_approved: state.user_approved === true,
|
|
946
1254
|
};
|
|
947
1255
|
}
|
|
948
1256
|
function assertVerdict(verdict) {
|
|
@@ -1073,7 +1381,7 @@ function workflowPolicyObligations(context) {
|
|
|
1073
1381
|
: null)
|
|
1074
1382
|
: null;
|
|
1075
1383
|
if (!validateWorkflowPolicySnapshot(policy).valid) return null;
|
|
1076
|
-
return [...policy.proof_obligations];
|
|
1384
|
+
return [...readWorkflowPolicySnapshot({ workflow_policy: policy }).proof_obligations];
|
|
1077
1385
|
}
|
|
1078
1386
|
|
|
1079
1387
|
function executionReceiptFromContext(context) {
|
|
@@ -1271,6 +1579,7 @@ function completionDecisionForSpec(spec, {
|
|
|
1271
1579
|
taskContext,
|
|
1272
1580
|
} = {}) {
|
|
1273
1581
|
const policy = spec && typeof spec === 'object' ? spec.workflow_policy : null;
|
|
1582
|
+
const policyView = policy ? readWorkflowPolicySnapshot({ workflow_policy: policy }) : null;
|
|
1274
1583
|
const context = {
|
|
1275
1584
|
workflow_policy: policy,
|
|
1276
1585
|
runtime_context: runtimeContext,
|
|
@@ -1279,7 +1588,7 @@ function completionDecisionForSpec(spec, {
|
|
|
1279
1588
|
};
|
|
1280
1589
|
const trustedProofs = runtimeBoundProofsFromSpec(spec, runtimeContext);
|
|
1281
1590
|
if (trustedProofs) context.proofs = trustedProofs;
|
|
1282
|
-
if (
|
|
1591
|
+
if (policyView && policyView.lane === 'Direct') {
|
|
1283
1592
|
return {
|
|
1284
1593
|
completion: 'not_applicable',
|
|
1285
1594
|
unfinished: false,
|
|
@@ -1857,8 +2166,9 @@ function runCli(argv = process.argv.slice(2)) {
|
|
|
1857
2166
|
}
|
|
1858
2167
|
if (options.action === 'policy-snapshot') {
|
|
1859
2168
|
if (!options.task) throw new Error('--policy-snapshot requires --task-json');
|
|
1860
|
-
const snapshot =
|
|
1861
|
-
|
|
2169
|
+
const snapshot = canonicalWorkflowPolicySnapshot(options.task);
|
|
2170
|
+
const view = readWorkflowPolicySnapshot({ workflow_policy: snapshot });
|
|
2171
|
+
return cliResult({ ok: true, snapshot, exitCode: 0, message: `Policy snapshot: ${view.lane}` }, options.json);
|
|
1862
2172
|
}
|
|
1863
2173
|
if (options.action === 'validate-policy') {
|
|
1864
2174
|
if (!options.task) throw new Error('--validate-policy requires --task-json');
|
|
@@ -1920,10 +2230,15 @@ module.exports = {
|
|
|
1920
2230
|
LEGACY_DIAGNOSTIC_VERDICTS,
|
|
1921
2231
|
EXECUTION_TIERS,
|
|
1922
2232
|
LANES,
|
|
2233
|
+
PLANNING_DEPTHS,
|
|
2234
|
+
ASSURANCE_LEVELS,
|
|
1923
2235
|
ARTIFACT_PROFILES,
|
|
2236
|
+
PLANNING_OBLIGATIONS,
|
|
1924
2237
|
PROOF_OBLIGATIONS,
|
|
1925
2238
|
WORKFLOW_POLICY_VERSION,
|
|
1926
2239
|
WORKFLOW_POLICY_FIELDS,
|
|
2240
|
+
CANONICAL_WORKFLOW_POLICY_VERSION,
|
|
2241
|
+
CANONICAL_WORKFLOW_POLICY_FIELDS,
|
|
1927
2242
|
RECEIPT_BINDING_FIELDS,
|
|
1928
2243
|
INDEPENDENT_AUDIT_SCHEMA_VERSION,
|
|
1929
2244
|
INDEPENDENT_AUDIT_FIELDS,
|
|
@@ -1931,12 +2246,17 @@ module.exports = {
|
|
|
1931
2246
|
LEGACY_APPROVAL_ERROR,
|
|
1932
2247
|
executionPolicy,
|
|
1933
2248
|
classifyLane,
|
|
2249
|
+
compatibilityLane,
|
|
2250
|
+
planningObligationsFor,
|
|
2251
|
+
obligationsForAssurance,
|
|
1934
2252
|
obligationsForLane,
|
|
1935
2253
|
actorNeedsFor,
|
|
1936
2254
|
isWorkflowPolicySnapshot,
|
|
1937
2255
|
validateWorkflowPolicySnapshot,
|
|
1938
2256
|
assertWorkflowPolicySnapshot,
|
|
1939
2257
|
workflowPolicySnapshot,
|
|
2258
|
+
canonicalWorkflowPolicySnapshot,
|
|
2259
|
+
isCanonicalTasklessTerminalSpec,
|
|
1940
2260
|
createWorkflowPolicySnapshot,
|
|
1941
2261
|
persistWorkflowPolicySnapshot,
|
|
1942
2262
|
persistWorkflowPolicy,
|
|
@@ -1947,8 +2267,6 @@ module.exports = {
|
|
|
1947
2267
|
approvalState,
|
|
1948
2268
|
validateApprovalSchema,
|
|
1949
2269
|
isLegacyApprovalObject,
|
|
1950
|
-
isUserAuthorizedForDowngrade,
|
|
1951
|
-
isValidOverrideReceipt,
|
|
1952
2270
|
isTapMetadataHeading,
|
|
1953
2271
|
deriveRuntimeContext: PROVENANCE.deriveRuntimeContext,
|
|
1954
2272
|
recomputeRuntimeContext: PROVENANCE.recomputeRuntimeContext,
|