@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.
Files changed (91) hide show
  1. package/bin/__tests__/codex-hooks.test.js +444 -14
  2. package/bin/__tests__/codex-native.test.js +228 -7
  3. package/bin/__tests__/develop-contract.test.js +615 -185
  4. package/bin/__tests__/installer-safety.test.js +39 -0
  5. package/bin/__tests__/opencode-spec-gate.test.js +24 -16
  6. package/bin/__tests__/package-inventory.test.js +687 -13
  7. package/bin/__tests__/specs-v2-execution-closeout.test.js +421 -0
  8. package/bin/__tests__/specs-v2-policy-and-scaffold.test.js +601 -0
  9. package/bin/__tests__/specs-v2-validator-grounder-contract.test.js +952 -0
  10. package/bin/__tests__/test-runtime-dependency-closure.cjs +182 -0
  11. package/bin/__tests__/validator-grounding.test.js +1914 -221
  12. package/bin/lib/managed-writer.js +12 -3
  13. package/bin/phases/codex-runtime.js +17 -1
  14. package/package.json +1 -1
  15. package/src/claude/CLAUDE.md +6 -0
  16. package/src/claude/agents/code-auditor.md +20 -0
  17. package/src/claude/agents/spec-maker.md +143 -99
  18. package/src/claude/hooks/__tests__/completion-authority.test.js +291 -58
  19. package/src/claude/hooks/__tests__/privacy-block.test.js +172 -0
  20. package/src/claude/hooks/__tests__/semantic-review-authority.test.js +257 -0
  21. package/src/claude/hooks/__tests__/spec-gate.test.js +115 -40
  22. package/src/claude/hooks/completion-authority-check.cjs +37 -288
  23. package/src/claude/hooks/completion-authority-state.cjs +10 -8
  24. package/src/claude/hooks/completion-authority.cjs +13 -7
  25. package/src/claude/hooks/docs-sync.cjs +5 -2
  26. package/src/claude/hooks/lib/privacy-command-analysis.cjs +393 -0
  27. package/src/claude/hooks/lib/runtime-path-safety.cjs +119 -0
  28. package/src/claude/hooks/privacy-block.cjs +77 -30
  29. package/src/claude/hooks/semantic-review-authority.cjs +152 -0
  30. package/src/claude/hooks/spec-gate.cjs +47 -117
  31. package/src/claude/hooks/spec-state.cjs +8 -2
  32. package/src/claude/migration-manifest.json +9 -1
  33. package/src/claude/rules/state-sync.md +52 -21
  34. package/src/claude/rules/workflow.md +17 -9
  35. package/src/claude/scripts/spec-final-state.cjs +244 -0
  36. package/src/claude/scripts/spec-ground.cjs +535 -173
  37. package/src/claude/scripts/spec-readiness.cjs +118 -0
  38. package/src/claude/scripts/spec-receipt.cjs +162 -0
  39. package/src/claude/scripts/spec-resolver.cjs +67 -16
  40. package/src/claude/scripts/spec-scaffold.cjs +1354 -139
  41. package/src/claude/scripts/spec-semantic-model.cjs +396 -0
  42. package/src/claude/scripts/validate-spec-output.cjs +2782 -142
  43. package/src/claude/scripts/workflow-policy.cjs +501 -183
  44. package/src/claude/settings/settings.json +4 -0
  45. package/src/claude/skills/code-review/SKILL.md +12 -3
  46. package/src/claude/skills/code-review/references/spec-compliance-review.md +10 -2
  47. package/src/claude/skills/code-review/references/verification-gate.md +13 -0
  48. package/src/claude/skills/develop/SKILL.md +47 -60
  49. package/src/claude/skills/develop/references/parallel-waves.md +9 -6
  50. package/src/claude/skills/develop/references/quality-gate.md +19 -12
  51. package/src/claude/skills/specs/SKILL.md +183 -177
  52. package/src/claude/skills/specs/references/ask-user-question-gates.md +27 -18
  53. package/src/claude/skills/specs/references/cross-spec-dependency.md +8 -4
  54. package/src/claude/skills/specs/references/research-strategy.md +4 -1
  55. package/src/claude/skills/specs/references/review.md +200 -260
  56. package/src/claude/skills/specs/references/scope-inquiry.md +18 -5
  57. package/src/claude/skills/specs/rules/design-discovery-full.md +33 -81
  58. package/src/claude/skills/specs/rules/design-discovery-light.md +21 -42
  59. package/src/claude/skills/specs/rules/design-principles.md +136 -180
  60. package/src/claude/skills/specs/rules/design-review.md +42 -97
  61. package/src/claude/skills/specs/rules/ears-format.md +42 -50
  62. package/src/claude/skills/specs/rules/phase-decision-matrix.md +42 -41
  63. package/src/claude/skills/specs/rules/task-scoring-rubric.md +2 -1
  64. package/src/claude/skills/specs/rules/tasks-generation.md +189 -300
  65. package/src/claude/skills/specs/templates/design.md +49 -263
  66. package/src/claude/skills/specs/templates/requirements.md +26 -36
  67. package/src/claude/skills/specs/templates/research.md +9 -98
  68. package/src/claude/skills/specs/templates/spec-state.json +23 -85
  69. package/src/claude/skills/specs/templates/task.md +30 -102
  70. package/src/claude/skills/sync/SKILL.md +33 -10
  71. package/src/claude/skills/sync/references/sync-protocols.md +31 -13
  72. package/src/claude/skills/test/SKILL.md +17 -7
  73. package/src/codex/AGENTS.md +6 -0
  74. package/src/codex/hooks/completion-authority-check.cjs +43 -240
  75. package/src/codex/hooks/completion-authority-state.cjs +10 -8
  76. package/src/codex/hooks/completion-authority.cjs +13 -7
  77. package/src/codex/hooks/lib/hook-context.cjs +16 -2
  78. package/src/codex/hooks/lib/privacy-command-analysis.cjs +3 -0
  79. package/src/codex/hooks/lib/privacy-state.cjs +38 -16
  80. package/src/codex/hooks/lib/runtime-path-safety.cjs +3 -0
  81. package/src/codex/hooks/lib/spec-receipt.cjs +72 -134
  82. package/src/codex/hooks/lib/spec-utils.cjs +56 -5
  83. package/src/codex/hooks/privacy-approval.cjs +1 -1
  84. package/src/codex/hooks/privacy-block.cjs +67 -11
  85. package/src/codex/hooks/semantic-review-authority.cjs +130 -0
  86. package/src/codex/hooks/spec-gate.cjs +49 -34
  87. package/src/codex/hooks/spec-state.cjs +10 -3
  88. package/src/codex/hooks/state.cjs +2 -0
  89. package/src/codex/hooks.json +6 -1
  90. package/src/codex/rules/state-sync.md +47 -27
  91. package/src/common/AGENTS.md +1 -1
@@ -0,0 +1,244 @@
1
+ 'use strict';
2
+
3
+ const crypto = require('crypto');
4
+ const fs = require('fs');
5
+ const path = require('path');
6
+
7
+ const SHA256_RE = /^sha256:[a-f0-9]{64}$/;
8
+
9
+ function plain(value) {
10
+ return Boolean(value && typeof value === 'object' && !Array.isArray(value));
11
+ }
12
+
13
+ function inside(root, target) {
14
+ const relative = path.relative(path.resolve(root), path.resolve(target));
15
+ return relative === '' || (relative !== '..' && !relative.startsWith(`..${path.sep}`) && !path.isAbsolute(relative));
16
+ }
17
+
18
+ function readBoundFile(root, target) {
19
+ const requested = path.isAbsolute(target) ? target : path.resolve(root, target);
20
+ if (!inside(root, requested)) return null;
21
+ try {
22
+ const canonicalRoot = fs.realpathSync(root);
23
+ const canonical = fs.realpathSync(requested);
24
+ if (!inside(canonicalRoot, canonical) || !fs.statSync(canonical).isFile()) return null;
25
+ return fs.readFileSync(canonical);
26
+ } catch { return null; }
27
+ }
28
+
29
+ function artifactReferences(body) {
30
+ if (typeof body !== 'string') return [];
31
+ const references = [];
32
+ for (const line of body.split('\n')) {
33
+ if (!/^\s*Artifacts?\s*:/i.test(line) && !/^\s*Artifact\s+produced\b/i.test(line)) continue;
34
+ const hashes = [...line.matchAll(/\b(?:artifact[_-])?sha-?256\s*:\s*([^\s),;`]+)/gi)].map((match) => match[1]);
35
+ const raw = line.replace(/^\s*Artifacts?\s*:\s*/i, '').replace(/^\s*Artifact\s+produced\s*:?[ \t]*/i, '').replace(/\b(?:artifact[_-])?sha-?256\s*:\s*[^\s),;`]+/gi, '').replace(/[()[\]{}]/g, ' ').trim();
36
+ raw.split(/\s*(?:\+|,|;|\band\b)\s*/i).map((value) => value.trim()).filter(Boolean)
37
+ .forEach((artifactPath, index) => references.push({ path: artifactPath, hash: hashes[index] || null }));
38
+ }
39
+ return references;
40
+ }
41
+
42
+ function validatorApi(validatorPath) {
43
+ let imported;
44
+ try { imported = require(validatorPath); }
45
+ catch (error) { throw new Error(`canonical validator CommonJS API could not be loaded (${error.message})`); }
46
+ if (typeof imported.validateSpec !== 'function' || typeof imported.computeSemanticDigest21 !== 'function') {
47
+ throw new Error('canonical validator must export validateSpec and computeSemanticDigest21 CommonJS APIs');
48
+ }
49
+ return imported;
50
+ }
51
+
52
+ function isDurableCloseout(spec) {
53
+ if (!plain(spec)) return false;
54
+ if (spec.schema_version === '2.1') return spec.status === 'done';
55
+ const phase = spec.current_phase || spec.phase;
56
+ return ['done', 'completed', 'complete'].includes(spec.status)
57
+ || ['closeout', 'completion', 'completed', 'complete'].includes(phase);
58
+ }
59
+
60
+ function isValidLifecycleStatus(spec) {
61
+ if (!plain(spec)) return false;
62
+ if (spec.schema_version === '2.1') return ['in_progress', 'paused', 'blocked', 'done'].includes(spec.status);
63
+ return ['in_progress', 'in-progress', 'paused', 'blocked', 'done', 'completed', 'complete'].includes(spec.status);
64
+ }
65
+
66
+ function validateCanonicalFinalState({ policy, projectRoot, candidate, dependencies }) {
67
+ if (candidate.spec.schema_version !== '2.1') return { ok: true, legacy: true, semanticDigest: null };
68
+ const featureDir = candidate.featureDir || path.dirname(candidate.specFile);
69
+ const review = candidate.spec.validation?.semantic_review;
70
+ if (candidate.spec.ready_for_implementation !== true
71
+ || candidate.spec.validation?.status !== 'completed'
72
+ || review?.status !== 'completed'
73
+ || !SHA256_RE.test(review?.semantic_digest || '')) {
74
+ return { ok: false, reason: 'canonical schema 2.1 terminal state requires completed readiness and a bound semantic digest' };
75
+ }
76
+ let apis;
77
+ try { apis = dependencies(); }
78
+ catch (error) { return { ok: false, reason: error.message }; }
79
+ let validation;
80
+ try { validation = apis.validator.validateSpec(featureDir); }
81
+ catch (error) { return { ok: false, reason: `canonical schema 2.1 validation failed safely (${error.message})` }; }
82
+ if (!validation || !Array.isArray(validation.errors) || validation.errors.length > 0) {
83
+ return { ok: false, reason: `canonical schema 2.1 validation failed (${(validation?.errors || ['invalid validator result']).slice(0, 4).join('; ')})` };
84
+ }
85
+ let grounding;
86
+ try { grounding = apis.grounder.groundSpec({ specDir: featureDir, root: projectRoot, spec: candidate.spec }); }
87
+ catch (error) { return { ok: false, reason: `canonical schema 2.1 grounding failed safely (${error.message})` }; }
88
+ if (!grounding || !Array.isArray(grounding.errors) || grounding.errors.length > 0) {
89
+ return { ok: false, reason: `canonical schema 2.1 grounding failed (${(grounding?.errors || ['invalid grounder result']).slice(0, 4).join('; ')})` };
90
+ }
91
+ let digest;
92
+ try {
93
+ const result = apis.validator.computeSemanticDigest21(featureDir, candidate.spec);
94
+ if (!result || !Array.isArray(result.errors) || result.errors.length > 0 || !SHA256_RE.test(result.digest || '')) {
95
+ return { ok: false, reason: `canonical semantic digest could not be recomputed (${(result?.errors || ['invalid digest result']).slice(0, 4).join('; ')})` };
96
+ }
97
+ digest = result.digest;
98
+ } catch (error) {
99
+ return { ok: false, reason: `canonical semantic digest could not be recomputed (${error.message})` };
100
+ }
101
+ if (digest !== review.semantic_digest) return { ok: false, reason: 'canonical semantic digest is stale for the current final-state artifacts' };
102
+ if (policy.readWorkflowPolicySnapshot(candidate.spec).assurance_level === 'Strict') {
103
+ const observed = apis.semanticAuthority.verifyAttestation(projectRoot, candidate.specFile, candidate.featureName, digest);
104
+ if (!observed?.ok) return { ok: false, reason: `Strict semantic authority requires an allowlisted host-hook-observed reviewer PASS event (${observed?.reason || 'observation unavailable'})` };
105
+ }
106
+ return { ok: true, legacy: false, semanticDigest: digest };
107
+ }
108
+
109
+ function configuredProjectMinimum(policy, runtime = {}) {
110
+ const configured = runtime.spec?.policy_minimum;
111
+ if (configured === undefined) return policy.canonicalWorkflowPolicySnapshot({ planning_depth: 'Compact', assurance_level: 'Routine' });
112
+ if (!plain(configured)) throw new Error('runtime.spec.policy_minimum must be a canonical policy object');
113
+ return policy.canonicalWorkflowPolicySnapshot(Object.prototype.hasOwnProperty.call(configured, 'version') ? { workflow_policy: configured } : configured);
114
+ }
115
+
116
+ function axisRank(values, value) { return Array.isArray(values) ? values.indexOf(value) : -1; }
117
+ function canonicalPolicy(policy, snapshot) { return policy.canonicalWorkflowPolicySnapshot({ workflow_policy: snapshot }); }
118
+ function policyView(policy, snapshot) { return policy.readWorkflowPolicySnapshot({ workflow_policy: canonicalPolicy(policy, snapshot) }); }
119
+
120
+ function policyMonotonicFailures(policy, baseline, current) {
121
+ const failures = [];
122
+ const before = policyView(policy, baseline);
123
+ const after = policyView(policy, current);
124
+ for (const [field, values] of [['planning_depth', policy.PLANNING_DEPTHS], ['automatic_planning_depth', policy.PLANNING_DEPTHS], ['assurance_level', policy.ASSURANCE_LEVELS], ['automatic_assurance_level', policy.ASSURANCE_LEVELS]]) {
125
+ if (axisRank(values, after[field]) < axisRank(values, before[field])) failures.push(`workflow_policy.${field === 'automatic_planning_depth' ? 'classified_minimum.planning_depth' : field === 'automatic_assurance_level' ? 'classified_minimum.assurance_level' : field} downgraded from ${before[field]} to ${after[field]}`);
126
+ }
127
+ const risks = new Set(after.risks);
128
+ for (const risk of before.risks) if (!risks.has(risk)) failures.push(`workflow_policy risk ${risk} was removed`);
129
+ const obligations = new Set(after.proof_obligations);
130
+ for (const obligation of before.proof_obligations) if (!obligations.has(obligation)) failures.push(`workflow_policy obligation ${obligation} was removed`);
131
+ return failures;
132
+ }
133
+
134
+ function mergePolicyFloor(policy, floor, current) {
135
+ const merged = { ...current, risks: [...new Set([...(current.risks || []), ...(floor.risks || [])])] };
136
+ for (const [field, values] of [['planning_depth', policy.PLANNING_DEPTHS], ['automatic_planning_depth', policy.PLANNING_DEPTHS], ['assurance_level', policy.ASSURANCE_LEVELS], ['automatic_assurance_level', policy.ASSURANCE_LEVELS]]) {
137
+ if (axisRank(values, floor[field]) > axisRank(values, current[field])) merged[field] = floor[field];
138
+ }
139
+ const riskFloor = policy.classifyLane({ risks: merged.risks, taskCount: 1 }).automaticAssuranceLevel;
140
+ if (axisRank(policy.ASSURANCE_LEVELS, riskFloor) > axisRank(policy.ASSURANCE_LEVELS, merged.automatic_assurance_level)) merged.automatic_assurance_level = riskFloor;
141
+ if (axisRank(policy.ASSURANCE_LEVELS, merged.automatic_assurance_level) > axisRank(policy.ASSURANCE_LEVELS, merged.assurance_level)) merged.assurance_level = merged.automatic_assurance_level;
142
+ merged.lane = policy.compatibilityLane(merged.planning_depth, merged.assurance_level);
143
+ merged.automatic_lane = policy.compatibilityLane(merged.automatic_planning_depth, merged.automatic_assurance_level);
144
+ merged.artifact_profile = { None: 'targeted', Compact: 'bounded', Full: 'strict' }[merged.planning_depth];
145
+ merged.planning_obligations = policy.planningObligationsFor(merged.planning_depth);
146
+ merged.proof_obligations = policy.obligationsForAssurance(merged.assurance_level, merged.risks);
147
+ merged.actor_needs = policy.actorNeedsFor(merged.proof_obligations);
148
+ return merged;
149
+ }
150
+
151
+ function observePolicyBaseline({ state, policy, projectRoot, candidate, runtime }) {
152
+ if (!Object.prototype.hasOwnProperty.call(candidate.spec, 'workflow_policy')) return { ok: false, reason: 'workflow_policy is missing for persisted spec' };
153
+ const validation = policy.validateWorkflowPolicySnapshot(candidate.spec.workflow_policy);
154
+ if (!validation.valid) return { ok: false, reason: `workflow_policy is malformed: ${validation.errors.join('; ')}` };
155
+ const current = canonicalPolicy(policy, candidate.spec.workflow_policy);
156
+ try {
157
+ const identity = { project_root: fs.realpathSync(projectRoot), spec_file: fs.realpathSync(candidate.specFile), feature_name: candidate.featureName };
158
+ state.ensureKey();
159
+ let authorityState = state.readState(projectRoot);
160
+ if (authorityState.malformed || authorityState.keyError || authorityState.keyMissing) return { ok: false, reason: authorityState.reason || 'completion authority state is unavailable' };
161
+ const projectMinimum = configuredProjectMinimum(policy, runtime);
162
+ const minimumFailures = policyMonotonicFailures(policy, projectMinimum, current);
163
+ if (minimumFailures.length) return { ok: false, reason: `workflow_policy is below the configured project minimum (${minimumFailures.join('; ')})` };
164
+ if (!authorityState.floor || state.digest(authorityState.floor.policy) !== state.digest(projectMinimum)) {
165
+ state.writePolicyFloor(projectRoot, projectMinimum);
166
+ authorityState = state.readState(projectRoot);
167
+ if (authorityState.malformed || authorityState.keyError || authorityState.keyMissing || !authorityState.floor) return { ok: false, reason: authorityState.reason || 'configured project policy minimum could not be persisted' };
168
+ }
169
+ const baseline = state.readBaseline(projectRoot, identity);
170
+ if (!baseline) { state.writeBaseline(projectRoot, identity, current); return { ok: true }; }
171
+ const baselineValidation = policy.validateWorkflowPolicySnapshot(baseline.policy);
172
+ if (!baselineValidation.valid) return { ok: false, reason: `persisted workflow policy baseline is malformed: ${baselineValidation.errors.join('; ')}` };
173
+ const failures = policyMonotonicFailures(policy, baseline.policy, current);
174
+ if (failures.length) return { ok: false, reason: `workflow_policy baseline is monotonic and cannot be downgraded (${failures.join('; ')})` };
175
+ if (state.digest(baseline.policy) !== state.digest(current)) state.writeBaseline(projectRoot, identity, current);
176
+ return { ok: true };
177
+ } catch (error) { return { ok: false, reason: `workflow_policy baseline unavailable: ${error.message}` }; }
178
+ }
179
+
180
+ function createAuthority(adapter) {
181
+ if (!adapter || typeof adapter.resolveCandidate !== 'function' || !adapter.state || !adapter.receipts || typeof adapter.dependencies !== 'function') {
182
+ throw new Error('final-state runtime adapter is incomplete');
183
+ }
184
+ const finalStateDependencies = (overrides = {}) => {
185
+ if (overrides.validator && overrides.grounder && overrides.semanticAuthority) return overrides;
186
+ const loaded = adapter.dependencies(overrides);
187
+ if (typeof loaded.validator?.validateSpec !== 'function' || typeof loaded.validator?.computeSemanticDigest21 !== 'function' || typeof loaded.grounder?.groundSpec !== 'function' || typeof loaded.semanticAuthority?.verifyAttestation !== 'function') throw new Error('canonical final-state validator, grounder, or semantic authority API is unavailable');
188
+ return loaded;
189
+ };
190
+ const resolveCandidate = (input) => adapter.resolveCandidate(input);
191
+ const validate = (input) => validateCanonicalFinalState({ ...input, dependencies: () => finalStateDependencies(input.dependencies || {}) });
192
+
193
+ function evaluateCloseout({ policy, projectRoot, runtime = {}, payload = {}, finalState = {}, resolver }) {
194
+ if (payload.stop_hook_active === true) return { ok: true, active: false, candidate: null };
195
+ const candidate = resolveCandidate({ resolver, projectRoot, runtime, payload });
196
+ if (!candidate) return { ok: true, candidate: null };
197
+ if (candidate.error) return { ok: false, candidate, reason: `${candidate.error}: ${Array.isArray(candidate.candidates) ? candidate.candidates.join(', ') : (candidate.reason || candidate.error)}` };
198
+ if (!isValidLifecycleStatus(candidate.spec)) return { ok: false, candidate, reason: `spec status is invalid: ${String(candidate.spec.status)}` };
199
+ if (!isDurableCloseout(candidate.spec)) return { ok: true, active: false, candidate };
200
+ const canonical = validate({ policy, projectRoot, candidate, dependencies: finalState });
201
+ if (!canonical.ok) return { ok: false, candidate, reason: canonical.reason };
202
+ const sessionId = payload.session_id;
203
+ if (typeof sessionId !== 'string' || !sessionId.trim()) return { ok: false, candidate, reason: 'trusted host session ID is unavailable' };
204
+ const baseline = observePolicyBaseline({ state: adapter.state, policy, projectRoot, candidate, runtime });
205
+ if (!baseline.ok) return { ok: false, candidate, reason: baseline.reason };
206
+ if (runtime.spec?.completion_gate !== undefined && runtime.spec.completion_gate !== true) return { ok: false, candidate, reason: 'runtime.spec.completion_gate is worker-writable and cannot authorize completion' };
207
+ const registry = plain(candidate.spec.task_registry) ? candidate.spec.task_registry : {};
208
+ const featureDir = path.join(candidate.specsDir, candidate.featureName);
209
+ if (Object.keys(registry).length === 0 && !policy.isCanonicalTasklessTerminalSpec(candidate.spec, featureDir)) return { ok: false, candidate, reason: 'task_registry is missing or empty outside canonical schema 2.1 taskless topology' };
210
+ let runtimeContext;
211
+ try { runtimeContext = policy.deriveRuntimeContext({ projectRoot, specsRoot: candidate.specsDir, specFile: candidate.specFile, featureName: candidate.featureName, runtimeSession: sessionId }); }
212
+ catch (error) { return { ok: false, candidate, reason: `runtime provenance unavailable: ${error.message}` }; }
213
+ if (!policy.isTrustedRuntimeContext(runtimeContext)) return { ok: false, candidate, reason: 'fresh trusted runtime Base/Head/context is unavailable' };
214
+ const receiptBodies = [];
215
+ const taskFiles = [];
216
+ const failures = [];
217
+ for (const [taskPath, task] of Object.entries(registry)) {
218
+ if (!plain(task) || task.status !== 'done') { failures.push(`${taskPath}: status is not done`); continue; }
219
+ if (policy.isStaleFlashDone(task)) failures.push(`${taskPath}: FLASH_UNVERIFIED`);
220
+ const receipt = adapter.receipts.task(featureDir, taskPath, task, runtimeContext, policy);
221
+ if (receipt.failures.length) failures.push(`${taskPath}: checks ${receipt.failures.join(',')}`);
222
+ if (receipt.body) receiptBodies.push(receipt.body);
223
+ if (receipt.taskFile && receipt.receiptBytes) taskFiles.push({ path: taskPath, task_digest: crypto.createHash('sha256').update(receipt.taskFile.bytes).digest('hex'), receipt_digest: crypto.createHash('sha256').update(receipt.receiptBytes).digest('hex'), receipt_source: receipt.source });
224
+ }
225
+ const featureReceipt = adapter.receipts.feature(featureDir, runtimeContext, policy);
226
+ if (featureReceipt.failures.length) failures.push(`feature-receipt.md: checks ${featureReceipt.failures.join(',')}`);
227
+ if (failures.length) return { ok: false, candidate, reason: `technical completion proof is incomplete (${failures.slice(0, 4).join('; ')})` };
228
+ let completion;
229
+ try { completion = policy.completionDecisionForSpec(candidate.spec, { runtimeContext, executionReceipt: featureReceipt.body, taskContext: {} }); }
230
+ catch (error) { return { ok: false, candidate, reason: `workflow completion proof could not be evaluated: ${error.message}` }; }
231
+ if (!['complete', 'not_applicable'].includes(completion.completion)) return { ok: false, candidate, reason: completion.blocker || 'workflow completion proof is incomplete' };
232
+ if (featureReceipt.body) receiptBodies.push(featureReceipt.body);
233
+ const artifactFiles = receiptBodies.flatMap(artifactReferences).sort((a, b) => `${a.path}:${a.hash}`.localeCompare(`${b.path}:${b.hash}`)).map((reference) => ({ ...reference, digest: crypto.createHash('sha256').update(readBoundFile(projectRoot, reference.path) || Buffer.from('<missing>')).digest('hex') }));
234
+ const digest = adapter.state.digest;
235
+ const specBytes = readBoundFile(projectRoot, candidate.specFile);
236
+ const binding = { project_root: runtimeContext.project_root, platform: process.platform, session_id: sessionId, feature_name: candidate.featureName, spec_file: candidate.specFile, runtime: { base: runtimeContext.base, head: runtimeContext.head, context_id: runtimeContext.context_id }, spec_digest: digest(candidate.spec), spec_bytes_digest: crypto.createHash('sha256').update(specBytes || Buffer.from('<missing>')).digest('hex'), policy_digest: digest(candidate.spec.workflow_policy ?? null), task_registry_digest: digest(registry), task_digest: digest(taskFiles), semantic_digest: canonical.semanticDigest, feature_receipt_digest: crypto.createHash('sha256').update(featureReceipt.receiptBytes).digest('hex'), artifact_digest: digest(artifactFiles), provenance_digest: digest({ runtime: runtimeContext, receipts: receiptBodies.map((body) => body.match(/^\s*(?:Base|Head|base_sha|head_sha)\s*:.*$/gim) || []) }) };
237
+ binding.binding_digest = digest(binding);
238
+ return { ok: true, active: true, candidate, runtimeContext, binding };
239
+ }
240
+
241
+ return { configuredProjectMinimum, evaluateCloseout, finalStateDependencies, isDurableCloseout, isValidLifecycleStatus, mergePolicyFloor, observePolicyBaseline: (input) => observePolicyBaseline({ ...input, state: input.STATE || adapter.state }), policyMonotonicFailures, resolveCandidate, validateCanonicalFinalState: validate, validatorApi };
242
+ }
243
+
244
+ module.exports = { createAuthority, isDurableCloseout, isValidLifecycleStatus, validateCanonicalFinalState, validatorApi };