@entrorove/skill 0.1.23 → 0.1.25

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 (167) hide show
  1. package/AGREEMENT.md +6 -6
  2. package/CAPABILITY-MAP.md +6 -0
  3. package/CHANGELOG.md +10 -0
  4. package/SKILL.md +18 -4
  5. package/archguard-runtime-build.mjs +15 -0
  6. package/blueprint-criterion-runner.mjs +110 -0
  7. package/blueprint-criterion-wire.mjs +78 -0
  8. package/calctool-runtime-build.mjs +59 -0
  9. package/calctool-runtime-entry.mjs +92 -0
  10. package/cli.mjs +35 -83
  11. package/local-approved-plan.mjs +74 -15
  12. package/local-archguard-ast.mjs +371 -0
  13. package/local-archguard-checkpoint.mjs +123 -0
  14. package/local-archguard-components.mjs +177 -0
  15. package/local-archguard-contract.mjs +109 -0
  16. package/local-archguard-contracts.mjs +42 -0
  17. package/local-archguard-evaluate.mjs +120 -0
  18. package/local-archguard-language-ast.mjs +165 -0
  19. package/local-archguard-mutation-fence.mjs +28 -0
  20. package/local-archguard-operations.mjs +25 -0
  21. package/local-archguard-scope.mjs +64 -0
  22. package/local-archguard-signing.mjs +52 -0
  23. package/local-archguard-snapshot.mjs +126 -0
  24. package/local-archguard-state.mjs +200 -0
  25. package/local-archguard.mjs +74 -0
  26. package/local-budget-git-attributes.mjs +49 -0
  27. package/local-budget-git-loader.mjs +61 -0
  28. package/local-budget-git-native-attributes.mjs +81 -0
  29. package/local-budget-git-native-darwin.mjs +104 -0
  30. package/local-budget-git-native-linux.mjs +93 -0
  31. package/local-budget-git-native-reads.mjs +88 -0
  32. package/local-budget-git-native-request.mjs +53 -0
  33. package/local-budget-git-native-runtime.mjs +18 -0
  34. package/local-budget-git-native.c +313 -0
  35. package/local-budget-git-native.mjs +109 -0
  36. package/local-budget-git-outcome.mjs +33 -0
  37. package/local-budget-git-scratch-loader.mjs +158 -0
  38. package/local-budget-git-scratch.mjs +112 -0
  39. package/local-budget-git-system-attributes.mjs +44 -0
  40. package/local-budget-git.mjs +92 -0
  41. package/local-budget-grants.mjs +82 -0
  42. package/local-budget-handoff-completion.mjs +70 -0
  43. package/local-budget-handoff-contracts.mjs +53 -0
  44. package/local-budget-handoff.mjs +229 -0
  45. package/local-budget-lock.mjs +55 -0
  46. package/local-budget-reads.mjs +135 -0
  47. package/local-budget-renewal.mjs +86 -34
  48. package/local-budget-source-bindings.mjs +51 -0
  49. package/local-budget-store.mjs +131 -0
  50. package/local-budget-transition.mjs +46 -0
  51. package/local-budget.mjs +185 -0
  52. package/local-calctool-cli.mjs +24 -0
  53. package/local-calctool-contracts.mjs +42 -0
  54. package/local-calctool-runtime.mjs +10 -0
  55. package/local-calctool-storage.mjs +101 -0
  56. package/local-calctool.mjs +103 -0
  57. package/local-chain-bindings.mjs +93 -0
  58. package/local-chain-broker.mjs +61 -0
  59. package/local-chain-contracts.mjs +21 -0
  60. package/local-chain-controller.mjs +321 -0
  61. package/local-chain-human.mjs +48 -0
  62. package/local-chain-lock.mjs +54 -0
  63. package/local-chain-operations.mjs +7 -0
  64. package/local-chain-plan.mjs +232 -0
  65. package/local-chain-release-labels.mjs +12 -0
  66. package/local-chain-results.mjs +75 -0
  67. package/local-cli.mjs +65 -24
  68. package/local-contract-types.mjs +46 -0
  69. package/local-contracts.mjs +126 -0
  70. package/local-credential.mjs +101 -0
  71. package/local-decimal.mjs +85 -0
  72. package/local-error.mjs +23 -0
  73. package/local-evidence-verification.mjs +199 -0
  74. package/local-execution.mjs +115 -304
  75. package/local-final-gate16-bundle.mjs +21 -0
  76. package/local-final-gate16-checks.mjs +88 -0
  77. package/local-final-gate16-contract.mjs +15 -0
  78. package/local-final-gate16-plan.mjs +66 -0
  79. package/local-final-gate16-proof.mjs +75 -0
  80. package/local-final-gate16-release.mjs +17 -0
  81. package/local-final-gate16-state.mjs +70 -0
  82. package/local-final-gate16-worker.mjs +19 -0
  83. package/local-final-gate16.mjs +137 -0
  84. package/local-formula-ast.mjs +106 -0
  85. package/local-formula-engine.mjs +125 -0
  86. package/local-formula-tests.mjs +61 -0
  87. package/local-formula.mjs +25 -125
  88. package/local-integrity.mjs +22 -41
  89. package/local-invocations.mjs +142 -0
  90. package/local-ledger.mjs +15 -9
  91. package/local-merge-archguard-verification.d.mts +44 -0
  92. package/local-merge-archguard-verification.mjs +240 -0
  93. package/local-merge-archguard.mjs +138 -0
  94. package/local-merge-artifacts.mjs +86 -0
  95. package/local-merge-authority.mjs +89 -0
  96. package/local-merge-contracts.mjs +56 -0
  97. package/local-merge-controller.mjs +170 -0
  98. package/local-merge-head.mjs +56 -0
  99. package/local-merge-journal.mjs +77 -0
  100. package/local-merge-mutation.mjs +97 -0
  101. package/local-merge-rollback.mjs +126 -0
  102. package/local-merge-snapshot-rollback.mjs +26 -0
  103. package/local-merge-storage.mjs +103 -0
  104. package/local-merge-targets.mjs +47 -0
  105. package/local-merge-transaction.mjs +133 -0
  106. package/local-mutation-audit.mjs +34 -0
  107. package/local-mutation-authority.mjs +103 -0
  108. package/local-mutation-batch-receipt.mjs +84 -0
  109. package/local-mutation-batch.mjs +143 -0
  110. package/local-mutation-binding.mjs +89 -0
  111. package/local-mutation-files.mjs +270 -0
  112. package/local-mutation-pass-store.mjs +63 -0
  113. package/local-mutation-snapshot.mjs +200 -0
  114. package/local-mutation-wire.mjs +89 -0
  115. package/local-probe.mjs +133 -11
  116. package/local-scope-candidate.mjs +28 -0
  117. package/local-scope-context.mjs +28 -0
  118. package/local-scope-files.mjs +106 -0
  119. package/local-scope-git-attributes.mjs +200 -0
  120. package/local-scope-git-comparison.mjs +8 -0
  121. package/local-scope-git-history.mjs +72 -0
  122. package/local-scope-git-reads.mjs +66 -0
  123. package/local-scope-git.mjs +124 -0
  124. package/local-scope-repository.mjs +249 -0
  125. package/local-scope-signature.mjs +70 -0
  126. package/local-scope-snapshot.mjs +39 -0
  127. package/local-scope-state.mjs +204 -0
  128. package/local-scope-workspaces.mjs +83 -0
  129. package/local-swarm-authority.mjs +143 -0
  130. package/local-swarm-baseline.mjs +32 -0
  131. package/local-swarm-completion.mjs +102 -0
  132. package/local-swarm-contracts.mjs +47 -0
  133. package/local-swarm-mutation.mjs +142 -0
  134. package/local-swarm-priority.mjs +237 -0
  135. package/local-swarm-state.mjs +102 -0
  136. package/local-swarm-storage.mjs +135 -0
  137. package/local-swarm.mjs +179 -0
  138. package/local-task-handoff-completion-wire.mjs +71 -0
  139. package/local-task-handoff-contracts.mjs +66 -0
  140. package/local-task-handoff-fence.mjs +478 -0
  141. package/local-task-handoff-observation.mjs +41 -0
  142. package/local-task-handoff-overlay-wire.mjs +94 -0
  143. package/local-task-handoff-read-authority.mjs +236 -0
  144. package/local-task-handoff-storage.mjs +73 -0
  145. package/local-task-handoff-write-archive.mjs +80 -0
  146. package/local-task-handoff-write-constraints.mjs +66 -0
  147. package/local-task-handoff-write-receipts.mjs +99 -0
  148. package/local-task-handoff-write-wire.mjs +99 -0
  149. package/local-task-routing.mjs +202 -22
  150. package/manifest.json +1 -1
  151. package/package.json +164 -3
  152. package/release-state.json +164 -18
  153. package/release.mjs +242 -4
  154. package/releases.json +12 -0
  155. package/remote-client.mjs +127 -36
  156. package/remote-transport.mjs +80 -0
  157. package/review-client.mjs +38 -0
  158. package/runtime/archguard-runtime-v7.0.44.mjs +314 -0
  159. package/runtime/calctool-runtime-v7.0.43.mjs +1910 -0
  160. package/runtime/git-native-darwin-v1.mjs +5 -0
  161. package/runtime/git-native-linux-v1.mjs +5 -0
  162. package/task-message-cli.mjs +47 -0
  163. package/task-message-entry.mjs +159 -0
  164. package/workflow-local-client.mjs +35 -0
  165. package/workflow-local-contracts.mjs +58 -0
  166. package/workflow-local-request.mjs +53 -0
  167. package/workflow-local-runner.mjs +350 -0
@@ -0,0 +1,126 @@
1
+ import { FINAL_GATE16_INPUT_SCHEMA, FINAL_GATE16_READ_INPUT_SCHEMA } from './local-final-gate16-contract.mjs';
2
+ import { SWARM_LOCAL_OPERATION_SCHEMAS } from './local-swarm-contracts.mjs';
3
+ import Ajv from 'ajv';
4
+ import { CALCTOOL_LOCAL_OPERATION_SCHEMAS } from './local-calctool-contracts.mjs';
5
+ import { HANDOFF_BUDGET_INIT_PROPERTIES, HANDOFF_BUDGET_INIT_CONDITION, handoffBudgetOperationSchema } from './local-budget-handoff-contracts.mjs';
6
+ import { ARCHGUARD_LOCAL_OPERATION_SCHEMAS } from './local-archguard-contracts.mjs';
7
+ import { MERGE_LOCAL_OPERATION_SCHEMAS } from './local-merge-contracts.mjs';
8
+ import { WORKFLOW_LOCAL_OPERATION_SCHEMA } from './workflow-local-contracts.mjs';
9
+ import { HANDOFF_WRITE_PASS_SCHEMA, HANDOFF_PROVISIONAL_LEASE_SCHEMA, HANDOFF_SNAPSHOT_CANDIDATE_SCHEMA } from './local-task-handoff-contracts.mjs';
10
+ import { LOCAL_CHAIN_OPERATION_SCHEMAS } from './local-chain-contracts.mjs';
11
+ import { array, binding, change, confirmation, enumeration, evidence, evidenceArray, executionContext, executionPlan,
12
+ formula, formulaDefinitions, formulaEntry, decimalValues, identifier, integer, ledgerIdentifier, modes, nullable, object, paths, record, relativePath, sha256, text } from './local-contract-types.mjs';
13
+
14
+ const schemas = {};
15
+ const base = { repositoryRoot: text, home: text };
16
+ const flow = { ...base, flowId: identifier };
17
+ const step = { ...flow, stepId: identifier };
18
+ function add(names, properties, required = [], alternatives) {
19
+ const schema = object(properties, required);
20
+ if (JSON.stringify(properties).includes('#/$defs/formula')) schema.$defs = formulaDefinitions;
21
+ if (alternatives) schema.anyOf = alternatives.map(field => ({ required: [field] }));
22
+ for (const name of names.split('|')) schemas[name] = schema;
23
+ }
24
+ add('capabilities|help', {});
25
+ add('snapshot-plan', { ...step, paths, snapshotId: identifier, contractDigest: nullable(sha256), handoffCandidate: HANDOFF_SNAPSHOT_CANDIDATE_SCHEMA }, ['repositoryRoot', 'flowId', 'stepId', 'paths']);
26
+ add('snapshot-verify', { ...step, snapshotId: identifier }, ['repositoryRoot', 'flowId', 'stepId', 'snapshotId']);
27
+ const gate = { ...step, taskId: identifier, mutationPass: record };
28
+ add('mutate-gate|gate-issue', gate, ['repositoryRoot', 'flowId', 'stepId', 'mutationPass']);
29
+ add('mutation-snapshot-attest', { ...step, taskId: identifier, snapshotId: identifier, challenge: record, signerConfigPath: text },
30
+ ['repositoryRoot', 'flowId', 'stepId', 'snapshotId', 'challenge', 'signerConfigPath']);
31
+ const write = { ...step, taskId: identifier, generation: integer(1), executionAttemptNonce: identifier, gatePassPath: text, targetPath: relativePath, workerId: identifier, leaseId: identifier };
32
+ add('gate-verify', write, ['repositoryRoot', 'flowId', 'stepId', 'gatePassPath', 'targetPath']);
33
+ add('guarded-write', { ...write, content: {}, expectedSha256: nullable(sha256) }, ['repositoryRoot', 'flowId', 'stepId', 'gatePassPath', 'targetPath', 'content']);
34
+ schemas['guarded-write'] = { oneOf: [schemas['guarded-write'], object({ ...step, targetPath: relativePath, content: {},
35
+ expectedSha256: nullable(sha256), handoffId: ledgerIdentifier, epoch: integer(1, 2147483647),
36
+ handoffPass: HANDOFF_WRITE_PASS_SCHEMA, provisionalLease: HANDOFF_PROVISIONAL_LEASE_SCHEMA },
37
+ ['repositoryRoot', 'flowId', 'stepId', 'targetPath', 'content', 'handoffId', 'epoch', 'handoffPass', 'provisionalLease'])] };
38
+ add('budget-init', { ...flow, mode: modes, executionContext, grant: record, ...HANDOFF_BUDGET_INIT_PROPERTIES }, ['repositoryRoot', 'flowId', 'mode', 'executionContext']);
39
+ schemas['budget-init'].allOf = [{ if: { type: 'object', properties: { executionContext: { type: 'object', properties: { executionIsolation: { const: 'sandbox' } }, required: ['executionIsolation'] } }, required: ['executionContext'] }, then: { type: 'object', required: ['grant'] } }];
40
+ schemas['budget-init'].allOf.push(HANDOFF_BUDGET_INIT_CONDITION);
41
+ schemas['budget-handoff'] = handoffBudgetOperationSchema(flow, modes, executionContext);
42
+ add('budget-context', { ...flow, executionContext }, ['repositoryRoot', 'flowId', 'executionContext']);
43
+ add('budget-read', { ...flow, path: relativePath }, ['repositoryRoot', 'flowId', 'path']);
44
+ add('budget-status', flow, ['repositoryRoot', 'flowId']);
45
+ add('budget-extend', { ...flow, confirmation, grant: record, additions: object({ files: integer(0), tokenEstimate: integer(0), durationMs: integer(0) }, ['files', 'tokenEstimate', 'durationMs']) }, ['repositoryRoot', 'flowId', 'additions'], ['grant', 'confirmation']);
46
+ add('budget-transition', { ...flow, grant: record }, ['repositoryRoot', 'flowId', 'grant']);
47
+ schemas['budget-extend'].properties.additions.anyOf = ['files', 'tokenEstimate', 'durationMs'].map(field => ({ type: 'object', properties: { [field]: integer(1) }, required: [field] }));
48
+ Object.assign(schemas, SWARM_LOCAL_OPERATION_SCHEMAS);
49
+ const signableEvidence = { ...evidence, required: evidence.required.filter(field => !['schemaVersion', 'runner'].includes(field)) };
50
+ add('evidence-sign', { ...base, evidence: signableEvidence }, ['repositoryRoot', 'evidence']);
51
+ add('evidence-verify', { ...base, evidence, receiptPublicKey: text }, ['evidence']);
52
+ add('merge-verified', { ...step, sourceRef: text, targetRef: text, snapshotReceipt: record, evidence: evidenceArray, preflight: record, receiptPublicKey: text, changes: array(change) }, ['repositoryRoot', 'flowId', 'stepId', 'sourceRef', 'targetRef', 'snapshotReceipt', 'evidence', 'preflight']);
53
+ add('rollback', { ...step, snapshotId: identifier }, ['repositoryRoot', 'flowId', 'snapshotId']);
54
+ const check = { anyOf: [{ type: 'boolean' }, { type: 'object', properties: { status: text }, required: ['status'] }] };
55
+ add('final-gate', { ...base, ...binding, audit: check, test: check, operations: check, evidence: array(evidence), preflight: record }, ['audit', 'test', 'operations', 'evidence']);
56
+ schemas['final-gate'] = { oneOf: [schemas['final-gate'], FINAL_GATE16_INPUT_SCHEMA, FINAL_GATE16_READ_INPUT_SCHEMA] };
57
+ add('formula-evaluate', { formula, values: decimalValues }, ['formula']);
58
+ add('formula-graph', { formulas: array(formulaEntry), inputs: decimalValues }, ['formulas']);
59
+ add('formula-final-gate', { engine: record, flowId: identifier, stepId: identifier }, ['engine']);
60
+ add('calculation-final-gate', { ...step, enginePath: relativePath }, ['repositoryRoot', 'enginePath']);
61
+ add('run-approved-plan', { repositoryRoot: text, planPath: relativePath, approvalPath: text, signerConfigPath: nullable(text) }, ['repositoryRoot', 'planPath', 'approvalPath', 'signerConfigPath']);
62
+ schemas['run-approved-plan'] = { oneOf: [schemas['run-approved-plan'], object({ ...flow, planPath: relativePath, approvalPath: text, signerConfigPath: nullable(text) },
63
+ ['repositoryRoot', 'home', 'flowId', 'planPath', 'approvalPath', 'signerConfigPath'])] };
64
+ schemas['workflow-run-step'] = WORKFLOW_LOCAL_OPERATION_SCHEMA;
65
+ add('branch-create|branch-create-virtual', { ...base, name: ledgerIdentifier, branchName: ledgerIdentifier, fromRef: text, description: { type: 'string' }, snapshotId: nullable(ledgerIdentifier) }, ['repositoryRoot'], ['name', 'branchName']);
66
+ add('branch-list', base, ['repositoryRoot']);
67
+ add('branch-switch', { ...base, branchName: ledgerIdentifier, name: ledgerIdentifier }, ['repositoryRoot'], ['branchName', 'name']);
68
+ add('diff-report', { ...base, baseRef: text, targetRef: nullable(text), limit: integer(1, 500) }, ['repositoryRoot']);
69
+ add('preflight', { ...base, ...binding, archguard: record, preflight: record, evidence: array(evidence) }, ['repositoryRoot']);
70
+ add('ledger-query|chain-ledger-query', { ...base, limit: integer(1, 500) }, ['repositoryRoot']);
71
+ add('chain-init|chain-ledger-init', { ...base, plan: executionPlan, executionPlan }, ['repositoryRoot'], ['plan', 'executionPlan']);
72
+ const chain = { ...base, chainId: ledgerIdentifier };
73
+ add('chain-resume', { ...chain, stepId: ledgerIdentifier, status: enumeration(['running', 'waiting', 'reported', 'verified', 'blocked', 'failed']), output: {}, error: {} }, ['repositoryRoot', 'chainId']);
74
+ add('chain-status|chain-ledger-status', chain, ['repositoryRoot', 'chainId']);
75
+ add('chain-answer', { ...chain, stepId: ledgerIdentifier, answer: { anyOf: [text, object({ id: text }, ['id'])] }, accepted: { type: 'boolean' }, actorId: nullable(text) }, ['repositoryRoot', 'chainId', 'answer']);
76
+ Object.assign(schemas, LOCAL_CHAIN_OPERATION_SCHEMAS);
77
+ const renewal = { ...flow, requestId: identifier, scope: object({ goal: text, allowedPaths: paths, forbiddenPaths: paths }, ['goal', 'allowedPaths']), policy: object({ intervalMs: integer(1), maxRenewals: integer(1, 100) }, ['intervalMs', 'maxRenewals']) };
78
+ add('budget-auto-renew-request', renewal, ['repositoryRoot', 'flowId', 'requestId', 'scope', 'policy']);
79
+ add('budget-auto-renew', { ...renewal, confirmation, grant: record }, ['repositoryRoot', 'flowId', 'requestId', 'scope', 'policy'], ['grant', 'confirmation']);
80
+ add('budget-auto-renew-stop', { ...flow, reason: enumeration(['revoked', 'completed']) }, ['repositoryRoot', 'flowId', 'reason']);
81
+ const probePaths = { anyOf: [paths, { const: ['.'] }] };
82
+ const scopeRequirement = object({ id: ledgerIdentifier, description: { ...text, pattern: '\\S', maxLength: 12000 } }, ['id', 'description']);
83
+ const scopeInteraction = object({ type: enumeration(['confirm', 'choice', 'multi', 'input']), question: text,
84
+ options: array(object({ id: ledgerIdentifier, label: text, hint: { type: 'string' } }, ['id', 'label'])), riskDescription: { type: 'string' },
85
+ callback: object({ operation: ledgerIdentifier, payload: record }, ['operation', 'payload']) }, ['type', 'question', 'options', 'callback']);
86
+ const scopeTask = object({ goal: { ...text, pattern: '\\S', maxLength: 12000 }, workType: enumeration(['implementation', 'documentation', 'calculation', 'merge', 'review', 'decision', 'diagnosis']),
87
+ difficulty: enumeration(['low', 'medium', 'high']), risk: enumeration(['low', 'medium', 'high']), crossModule: { type: 'boolean' }, parallel: { type: 'boolean' }, needParallel: { type: 'boolean' },
88
+ unclear: { type: 'boolean' }, architectureContract: { type: 'boolean' }, explicitAimlockRequested: { type: 'boolean' }, explicitEntroroveRequested: { type: 'boolean' }, userSpecifiedSkills: array(text),
89
+ requirements: { ...array(scopeRequirement, 1), maxItems: 500 }, phase: enumeration(['plan', 'coordinate', 'architecture', 'verify', 'merge', 'decision']), baselineRef: text, interaction: scopeInteraction }, ['goal', 'workType', 'difficulty', 'risk']);
90
+ const probe = { ...step, paths: probePaths, targetPaths: probePaths, task: scopeTask, baselineRef: text, snapshotId: identifier,
91
+ targetSymbols: array(text, 1), contextMapPath: relativePath, signerConfigPath: nullable(text), taskDigest: sha256, ownerId: ledgerIdentifier, chainDigest: sha256, repositoryId: identifier, challenge: ledgerIdentifier };
92
+ add('probe', { ...probe, handoffCandidate: HANDOFF_SNAPSHOT_CANDIDATE_SCHEMA }, ['repositoryRoot']);
93
+ schemas.probe.allOf = [{ if: { type: 'object', anyOf: ['flowId', 'stepId', 'snapshotId'].map(field => ({ required: [field] })) },
94
+ then: { type: 'object', required: ['flowId', 'stepId', 'snapshotId'], anyOf: ['paths', 'targetPaths', 'targetSymbols'].map(field => ({ required: [field] })) } },
95
+ { if: { type: 'object', properties: { signerConfigPath: text }, required: ['signerConfigPath'] },
96
+ then: { type: 'object', required: ['flowId', 'stepId', 'snapshotId', 'taskDigest', 'ownerId', 'chainDigest', 'repositoryId', 'challenge'] } },
97
+ { if: { type: 'object', required: ['handoffCandidate'] }, then: { type: 'object', required: ['flowId', 'stepId', 'snapshotId', 'paths', 'taskDigest', 'ownerId', 'chainDigest', 'repositoryId', 'challenge', 'signerConfigPath'],
98
+ properties: { signerConfigPath: text } } },
99
+ { if: { type: 'object', required: ['targetSymbols'] }, then: { type: 'object', required: ['contextMapPath'] } }];
100
+ add('reassess', { ...probe, snapshotId: nullable(identifier), currentMode: modes, expectedRevision: integer(1) }, ['repositoryRoot']);
101
+ schemas.reassess.allOf = [{ if: { type: 'object', anyOf: [
102
+ ...['flowId', 'stepId', 'expectedRevision', 'taskDigest', 'ownerId', 'chainDigest', 'repositoryId', 'challenge'].map(field => ({ required: [field] })),
103
+ ...['snapshotId', 'signerConfigPath'].map(field => ({ properties: { [field]: text }, required: [field] }))] },
104
+ then: { type: 'object', required: ['flowId', 'stepId', 'snapshotId', 'expectedRevision'], properties: { snapshotId: identifier },
105
+ anyOf: ['paths', 'targetPaths', 'targetSymbols'].map(field => ({ required: [field] })) } },
106
+ { if: { type: 'object', required: ['targetSymbols'] }, then: { type: 'object', required: ['contextMapPath'] } }];
107
+ const task = { ...base, taskId: ledgerIdentifier };
108
+ add('task-describe', { ...task, goal: { ...text, pattern: '\\S' }, ownerId: nullable(text), checkpoint: {}, createdAt: text }, ['repositoryRoot', 'goal']);
109
+ add('message-route', { ...task, messageId: ledgerIdentifier, body: {}, destination: nullable(text) }, ['repositoryRoot', 'taskId']);
110
+ add('message-accept', { ...task, messageId: ledgerIdentifier, ownerId: nullable(text) }, ['repositoryRoot', 'taskId', 'messageId']);
111
+ add('task-resume', task, ['repositoryRoot', 'taskId']);
112
+
113
+ Object.assign(schemas, ARCHGUARD_LOCAL_OPERATION_SCHEMAS, CALCTOOL_LOCAL_OPERATION_SCHEMAS, MERGE_LOCAL_OPERATION_SCHEMAS);
114
+ export const LOCAL_OPERATION_SCHEMAS = Object.freeze(schemas);
115
+ const ajv = new Ajv({ strict: true, strictRequired: false, allErrors: true });
116
+ const validators = new Map(Object.entries(schemas).map(([operation, schema]) => [operation, ajv.compile(schema)]));
117
+ export function validateLocalInput(operation, input) {
118
+ const validate = validators.get(operation);
119
+ if (!validate) throw Object.assign(new Error(`Unsupported local operation: ${operation}`), { code: 'ENTROROVE_OPERATION_UNKNOWN' });
120
+ if (!validate(input)) {
121
+ const details = validate.errors.map(error => `${error.instancePath || '/'} ${error.message}`).join('; ');
122
+ throw Object.assign(new Error(`Invalid ${operation} input: ${details}`), { code: 'ENTROROVE_INPUT_INVALID' });
123
+ }
124
+ return input;
125
+ }
126
+ export const operationSchemasFor = operations => Object.freeze(Object.fromEntries(Object.keys(operations).map(name => [name, schemas[name]])));
@@ -0,0 +1,101 @@
1
+ import { constants, promises as fs } from 'node:fs';
2
+ import { randomUUID } from 'node:crypto';
3
+ import os from 'node:os';
4
+ import { withStateLock } from './local-state-lock.mjs';
5
+ import path from 'node:path';
6
+
7
+ const SCHEMA = 'entrorove.credential/1.0';
8
+ const TOKEN = /^ent_[A-Za-z0-9_-]{43}$/;
9
+ const error = (code, message) => Object.assign(new Error(message), { code });
10
+
11
+ export function credentialPath(home = os.homedir()) {
12
+ if (typeof home !== 'string' || !path.isAbsolute(home)) throw error('ENTROROVE_CREDENTIAL_HOME', 'Credential home must be absolute');
13
+ if (process.platform === 'win32') {
14
+ const appData = process.env.LOCALAPPDATA;
15
+ if (!appData || !path.isAbsolute(appData)) throw error('ENTROROVE_CREDENTIAL_HOME', 'LOCALAPPDATA must be absolute');
16
+ return path.join(appData, 'Entrorove', 'credential.json');
17
+ }
18
+ const parts = process.platform === 'darwin' ? ['Library', 'Application Support', 'Entrorove']
19
+ : process.platform === 'win32' ? ['AppData', 'Local', 'Entrorove'] : ['.local', 'share', 'entrorove'];
20
+ return path.join(home, ...parts, 'credential.json');
21
+ }
22
+
23
+ function owned(stat) {
24
+ return process.platform === 'win32' || stat.uid === process.getuid();
25
+ }
26
+
27
+ export async function credentialDirectory(home = os.homedir(), create = false) {
28
+ const homeStat = await fs.lstat(home).catch(() => { throw error('ENTROROVE_CREDENTIAL_HOME', 'Credential home is unavailable'); });
29
+ if (!homeStat.isDirectory() || homeStat.isSymbolicLink() || !owned(homeStat)) {
30
+ throw error('ENTROROVE_CREDENTIAL_DIRECTORY', 'Credential home must be an owned real directory');
31
+ }
32
+ const root = process.platform === 'win32' ? path.parse(credentialPath(home)).root : await fs.realpath(home);
33
+ let current = root;
34
+ const relative = path.relative(process.platform === 'win32' ? root : home, path.dirname(credentialPath(home)));
35
+ for (const part of relative.split(path.sep)) {
36
+ current = path.join(current, part);
37
+ if (create) await fs.mkdir(current, { mode: 0o700 }).catch(cause => { if (cause.code !== 'EEXIST') throw cause; });
38
+ const stat = await fs.lstat(current).catch(() => { throw error('ENTROROVE_CREDENTIAL_MISSING', 'Stored credential is unavailable; configure the account key privately'); });
39
+ if (!stat.isDirectory() || stat.isSymbolicLink() || !owned(stat)) {
40
+ throw error('ENTROROVE_CREDENTIAL_DIRECTORY', 'Credential directory must be an owned real directory');
41
+ }
42
+ }
43
+ if (create && process.platform !== 'win32') await fs.chmod(current, 0o700);
44
+ const stat = await fs.lstat(current);
45
+ if (process.platform !== 'win32' && (stat.mode & 0o077)) throw error('ENTROROVE_CREDENTIAL_PERMISSIONS', 'Credential directory permissions must be 0700');
46
+ return current;
47
+ }
48
+
49
+ function privateFile(stat) {
50
+ if (!stat.isFile() || stat.nlink !== 1 || !owned(stat)) throw error('ENTROROVE_CREDENTIAL_FILE', 'Credential path must be an owned private regular file');
51
+ if (process.platform !== 'win32' && (stat.mode & 0o777) !== 0o600) throw error('ENTROROVE_CREDENTIAL_PERMISSIONS', 'Credential permissions must be 0600');
52
+ }
53
+
54
+ export async function readCredential(home = os.homedir()) {
55
+ const directory = await credentialDirectory(home);
56
+ let handle;
57
+ try {
58
+ handle = await fs.open(path.join(directory, 'credential.json'), constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0));
59
+ const stat = await handle.stat(); privateFile(stat);
60
+ if (stat.size > 4096) throw error('ENTROROVE_CREDENTIAL_INVALID', 'Invalid credential file');
61
+ let value;
62
+ try { value = JSON.parse(await handle.readFile('utf8')); }
63
+ catch { throw error('ENTROROVE_CREDENTIAL_INVALID', 'Invalid credential file'); }
64
+ if (!value || value.schemaVersion !== SCHEMA || !TOKEN.test(value.token)
65
+ || Object.keys(value).sort().join(',') !== 'schemaVersion,token') throw error('ENTROROVE_CREDENTIAL_INVALID', 'Invalid credential file');
66
+ return value.token;
67
+ } catch (cause) {
68
+ if (cause.code?.startsWith('ENTROROVE_CREDENTIAL_')) throw cause;
69
+ throw error('ENTROROVE_CREDENTIAL_MISSING', 'Stored credential is unavailable; configure the account key privately');
70
+ } finally { await handle?.close(); }
71
+ }
72
+
73
+ export async function writeCredential(token, home = os.homedir()) {
74
+ if (typeof token !== 'string' || !TOKEN.test(token)) throw error('ENTROROVE_CREDENTIAL_INVALID', 'Invalid entrorove key');
75
+ const directory = await credentialDirectory(home, true);
76
+ const file = path.join(directory, 'credential.json');
77
+ const prior = await fs.lstat(file).catch(cause => { if (cause.code !== 'ENOENT') throw cause; return null; });
78
+ if (prior) privateFile(prior);
79
+ return withStateLock(file, async () => {
80
+ const locked = await fs.lstat(file).catch(cause => { if (cause.code !== 'ENOENT') throw cause; return null; });
81
+ if (locked) privateFile(locked);
82
+ if (Boolean(locked) !== Boolean(prior) || locked && (locked.ino !== prior.ino || locked.dev !== prior.dev)) {
83
+ throw error('ENTROROVE_CREDENTIAL_CHANGED', 'Credential changed before the private rotation lock was acquired');
84
+ }
85
+ const stage = path.join(directory, `.credential-${randomUUID()}.tmp`);
86
+ let handle;
87
+ try {
88
+ handle = await fs.open(stage, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | (constants.O_NOFOLLOW ?? 0), 0o600);
89
+ await handle.writeFile(`${JSON.stringify({ schemaVersion: SCHEMA, token })}\n`);
90
+ await handle.sync(); await handle.close(); handle = undefined;
91
+ const current = await fs.lstat(file).catch(cause => { if (cause.code !== 'ENOENT') throw cause; return null; });
92
+ if (current) privateFile(current);
93
+ if (Boolean(current) !== Boolean(prior) || current && (current.ino !== prior.ino || current.dev !== prior.dev)) {
94
+ throw error('ENTROROVE_CREDENTIAL_CHANGED', 'Credential changed during configuration; read the current credential before replacing it');
95
+ }
96
+ await fs.rename(stage, file);
97
+ const parent = await fs.open(directory, constants.O_RDONLY);
98
+ try { await parent.sync(); } finally { await parent.close(); }
99
+ } finally { await handle?.close(); await fs.rm(stage, { force: true }); }
100
+ });
101
+ }
@@ -0,0 +1,85 @@
1
+ export const DECIMAL_SCALE = 28;
2
+ export const DECIMAL_POLICY = Object.freeze({ precision: 28, rounding: 'half-away-from-zero', representation: 'decimal-string' });
3
+ const LITERAL = /^[+-]?(?:\d+(?:\.\d*)?|\.\d+)$/;
4
+ const MAX_DIGITS = 4096;
5
+
6
+ export function formulaError(code, message, details = {}) {
7
+ return Object.assign(new Error(message), { code, ...details });
8
+ }
9
+ function scale(value, maximum = DECIMAL_SCALE) {
10
+ if (!Number.isSafeInteger(value) || value < 0 || value > maximum) throw formulaError('DECIMAL_SCALE_INVALID', `decimal places must be 0..${maximum}`);
11
+ return value;
12
+ }
13
+ function ten(power) { return 10n ** BigInt(scale(power, MAX_DIGITS * 2)); }
14
+ function normalize(coefficient, places) {
15
+ while (places > 0 && coefficient % 10n === 0n) { coefficient /= 10n; places -= 1; }
16
+ return { coefficient, scale: places };
17
+ }
18
+ function rounded(coefficient, fromScale, toScale) {
19
+ if (fromScale <= toScale) return coefficient * ten(toScale - fromScale);
20
+ const divisor = ten(fromScale - toScale);
21
+ let quotient = coefficient / divisor;
22
+ const remainder = coefficient % divisor;
23
+ if ((remainder < 0n ? -remainder : remainder) * 2n >= divisor) quotient += coefficient < 0n ? -1n : 1n;
24
+ return quotient;
25
+ }
26
+ function requireCoefficient(value) {
27
+ if (typeof value !== 'bigint') throw formulaError('INVALID_DECIMAL', 'decimal coefficient must be a bigint');
28
+ if (value.toString().replace('-', '').length > MAX_DIGITS) throw formulaError('DECIMAL_LIMIT', 'decimal exceeds the supported digit limit');
29
+ }
30
+
31
+ export class Decimal {
32
+ constructor(value) {
33
+ if (value instanceof Decimal) { this.coefficient = value.coefficient; this.scale = value.scale; }
34
+ else {
35
+ if (!['string', 'number', 'bigint'].includes(typeof value) || (typeof value === 'number' && (!Number.isFinite(value) || (Number.isInteger(value) && !Number.isSafeInteger(value))))) {
36
+ throw formulaError('INVALID_DECIMAL', 'decimal must be a finite, lossless number or decimal string');
37
+ }
38
+ const source = String(value).trim();
39
+ if (source.length > MAX_DIGITS || !LITERAL.test(source)) throw formulaError('INVALID_DECIMAL', 'invalid decimal literal');
40
+ const negative = source[0] === '-';
41
+ const [integer = '0', fraction = ''] = source.replace(/^[+-]/, '').split('.');
42
+ const coefficient = BigInt(`${integer || '0'}${fraction}`) * (negative ? -1n : 1n);
43
+ Object.assign(this, normalize(rounded(coefficient, fraction.length, Math.min(fraction.length, DECIMAL_SCALE)), Math.min(fraction.length, DECIMAL_SCALE)));
44
+ }
45
+ this.value = Decimal.format(this.coefficient, this.scale);
46
+ this.s = this.value;
47
+ Object.freeze(this);
48
+ }
49
+ static from(value) { return value instanceof Decimal ? value : new Decimal(value); }
50
+ static fromParts(coefficient, places) {
51
+ requireCoefficient(coefficient); scale(places, MAX_DIGITS);
52
+ const boundedScale = Math.min(places, DECIMAL_SCALE);
53
+ const parts = normalize(rounded(coefficient, places, boundedScale), boundedScale);
54
+ return new Decimal(Decimal.format(parts.coefficient, parts.scale));
55
+ }
56
+ static format(coefficient, places) {
57
+ requireCoefficient(coefficient); scale(places, MAX_DIGITS);
58
+ const digits = (coefficient < 0n ? -coefficient : coefficient).toString().padStart(places + 1, '0');
59
+ const value = places === 0 ? digits : `${digits.slice(0, -places)}.${digits.slice(-places)}`;
60
+ return coefficient < 0n ? `-${value}` : value;
61
+ }
62
+ plus(other) {
63
+ const right = Decimal.from(other); const places = Math.max(this.scale, right.scale);
64
+ return Decimal.fromParts(this.coefficient * ten(places - this.scale) + right.coefficient * ten(places - right.scale), places);
65
+ }
66
+ minus(other) { return this.plus(Decimal.from(other).negate()); }
67
+ negate() { return Decimal.fromParts(-this.coefficient, this.scale); }
68
+ times(other) { const right = Decimal.from(other); return Decimal.fromParts(this.coefficient * right.coefficient, this.scale + right.scale); }
69
+ div(other) {
70
+ const right = Decimal.from(other);
71
+ if (right.isZero()) throw formulaError('DIV_ZERO', 'division by zero', { nodeId: 'div' });
72
+ const numerator = this.coefficient * ten(right.scale + DECIMAL_SCALE);
73
+ const denominator = right.coefficient * ten(this.scale);
74
+ let quotient = numerator / denominator;
75
+ const remainder = numerator % denominator;
76
+ if ((remainder < 0n ? -remainder : remainder) * 2n >= (denominator < 0n ? -denominator : denominator)) quotient += (numerator < 0n) !== (denominator < 0n) ? -1n : 1n;
77
+ return Decimal.fromParts(quotient, DECIMAL_SCALE);
78
+ }
79
+ rounded(dp) { scale(dp); return this.scale <= dp ? this : Decimal.fromParts(rounded(this.coefficient, this.scale, dp), dp); }
80
+ toDecimalPlaces(dp) { return this.rounded(dp); }
81
+ isZero() { return this.coefficient === 0n; }
82
+ isNegative() { return this.coefficient < 0n; }
83
+ toString() { return this.value; }
84
+ }
85
+ export const Decimal28 = Decimal;
@@ -0,0 +1,23 @@
1
+ export const LOCAL_ERROR_SCHEMA = 'entrorove.local-error/1.0';
2
+
3
+ const BLOCKED_CODES = new Set([
4
+ 'ENTROROVE_MUTATE_NOT_ACCEPTED', 'ENTROROVE_MUTATION_AUTHORITY_REQUIRED',
5
+ 'ENTROROVE_MUTATION_PASS_INVALID', 'ENTROROVE_MUTATION_PASS_EXPIRED',
6
+ 'ENTROROVE_BUDGET_LEASE_EXPIRED', 'ENTROROVE_BUDGET_AUTHORITY_REQUIRED',
7
+ 'ENTROROVE_SWARM_AUTHORITY_INVALID', 'ENTROROVE_SWARM_CLOUD_AUTHORITY_REQUIRED',
8
+ 'ENTROROVE_HANDOFF_FENCE', 'ENTROROVE_HANDOFF_WRITE_REVOKED',
9
+ 'ENTROROVE_HANDOFF_WRITE_BINDING', 'ENTROROVE_HANDOFF_WRITE_SNAPSHOT',
10
+ 'ENTROROVE_SNAPSHOT_CORRUPT', 'ENTROROVE_SNAPSHOT_INVALID',
11
+ ]);
12
+
13
+ export function localErrorStatus(error) {
14
+ const code = typeof error?.code === 'string' ? error.code : '';
15
+ return BLOCKED_CODES.has(code) || /(?:AUTHORITY_REQUIRED|LEASE_EXPIRED|NOT_ACCEPTED|REVOKED)$/.test(code)
16
+ ? 'blocked' : 'failed';
17
+ }
18
+
19
+ export function localErrorEnvelope(error, fallbackCode = 'ENTROROVE_LOCAL_FAILURE') {
20
+ const code = typeof error?.code === 'string' && error.code ? error.code : fallbackCode;
21
+ const message = typeof error?.message === 'string' && error.message ? error.message : 'Local operation failed';
22
+ return { schemaVersion: LOCAL_ERROR_SCHEMA, status: localErrorStatus({ ...error, code }), code, message };
23
+ }
@@ -0,0 +1,199 @@
1
+ import { createHash, createPublicKey, verify } from 'node:crypto';
2
+
3
+ const EVIDENCE_SCHEMA = 'entrorove.test-evidence/2.0';
4
+ const EXECUTION_SCHEMA = 'validator.execution-receipt/1.0';
5
+ const GENERIC_SCHEMA = 'entrorove.runner-receipt/1.0';
6
+ const KINDS = new Set(['test', 'build', 'lint', 'security', 'benchmark']);
7
+ const RUNNERS = new Set(['local', 'trusted-runner', 'sandbox']);
8
+ const SHA = /^[0-9a-f]{64}$/;
9
+ const ID = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/;
10
+ const FIELDS = new Set(['schemaVersion', 'evidenceId', 'kind', 'runner', 'command', 'exitCode', 'durationMs',
11
+ 'summary', 'artifactSha256', 'subject', 'subjectDigest', 'receipt', 'flowId', 'stepId']);
12
+ const object = value => value !== null && typeof value === 'object' && !Array.isArray(value);
13
+ const digest = value => createHash('sha256').update(value).digest('hex');
14
+ const text = (value, max) => typeof value === 'string' && value.trim().length > 0 && value.length <= max;
15
+ function fail(code, message) { throw Object.assign(new Error(message), { code }); }
16
+
17
+ function canonical(value) {
18
+ if (value === null || typeof value === 'boolean' || typeof value === 'string') return JSON.stringify(value);
19
+ if (typeof value === 'number' && Number.isFinite(value)) return JSON.stringify(value);
20
+ if (Array.isArray(value)) return `[${value.map(canonical).join(',')}]`;
21
+ if (!object(value)) fail('ENTROROVE_EVIDENCE_JSON', 'Evidence must contain finite JSON values');
22
+ return `{${Object.keys(value).sort().map(key => `${JSON.stringify(key)}:${canonical(value[key])}`).join(',')}}`;
23
+ }
24
+
25
+ function state(status, details = {}) {
26
+ return { verified: false, authentic: false, successful: false, status, evidenceState: status, ...details };
27
+ }
28
+
29
+ function validateEvidence(evidence) {
30
+ if (!object(evidence) || Object.keys(evidence).some(key => !FIELDS.has(key)) || evidence.schemaVersion !== EVIDENCE_SCHEMA
31
+ || !text(evidence.evidenceId, 200) || !KINDS.has(evidence.kind) || !RUNNERS.has(evidence.runner)
32
+ || !text(evidence.command, 2000) || !Number.isInteger(evidence.exitCode) || evidence.exitCode < -128 || evidence.exitCode > 255
33
+ || !Number.isSafeInteger(evidence.durationMs) || evidence.durationMs < 0 || evidence.durationMs > 86_400_000
34
+ || !text(evidence.summary, 4000) || !SHA.test(String(evidence.artifactSha256 ?? ''))) {
35
+ fail('ENTROROVE_EVIDENCE_SCHEMA', 'Evidence schema is invalid');
36
+ }
37
+ canonical(evidence);
38
+ }
39
+
40
+ function publicKey(encoded) {
41
+ if (typeof encoded !== 'string' || !/^[A-Za-z0-9+/]+={0,2}$/.test(encoded)) fail('ENTROROVE_EVIDENCE_KEY', 'Independent receipt public key encoding is invalid');
42
+ const bytes = Buffer.from(encoded, 'base64');
43
+ if (bytes.toString('base64') !== encoded) fail('ENTROROVE_EVIDENCE_KEY', 'Receipt public key must use canonical base64');
44
+ let key; try { key = createPublicKey({ key: bytes, format: 'der', type: 'spki' }); }
45
+ catch { fail('ENTROROVE_EVIDENCE_KEY', 'Receipt public key is invalid'); }
46
+ if (key.asymmetricKeyType !== 'ed25519' || !key.export({ format: 'der', type: 'spki' }).equals(bytes)) {
47
+ fail('ENTROROVE_EVIDENCE_KEY', 'Receipt public key must be canonical Ed25519 SPKI');
48
+ }
49
+ return { key, keyId: digest(bytes) };
50
+ }
51
+
52
+ function verifySignature(receipt, payload, encoded) {
53
+ if (!object(receipt) || !SHA.test(String(receipt.keyId ?? '')) || typeof receipt.signature !== 'string'
54
+ || !/^[A-Za-z0-9_-]{86}$/.test(receipt.signature)) fail('ENTROROVE_EVIDENCE_SIGNATURE', 'Receipt signature fields are invalid');
55
+ const trusted = publicKey(encoded); const signature = Buffer.from(receipt.signature, 'base64url');
56
+ if (signature.length !== 64 || signature.toString('base64url') !== receipt.signature || trusted.keyId !== receipt.keyId
57
+ || (receipt.publicKey !== undefined && receipt.publicKey !== encoded)
58
+ || !verify(null, Buffer.from(canonical(payload)), trusted.key, signature)) {
59
+ fail('ENTROROVE_EVIDENCE_SIGNATURE', 'Receipt key identity or signature is invalid');
60
+ }
61
+ return trusted.keyId;
62
+ }
63
+
64
+ function receiptPayload(evidence) {
65
+ const receipt = evidence.receipt;
66
+ if (receipt?.schemaVersion === GENERIC_SCHEMA) return Object.fromEntries(Object.entries(evidence).filter(([key]) => key !== 'receipt'));
67
+ if (receipt?.schemaVersion !== EXECUTION_SCHEMA) fail('ENTROROVE_EVIDENCE_RECEIPT', 'Signed receipt schema is invalid');
68
+ if (!object(evidence.subject) || evidence.subjectDigest !== digest(canonical(evidence.subject))
69
+ || receipt.subjectDigest !== evidence.subjectDigest) fail('ENTROROVE_EVIDENCE_SUBJECT', 'Execution receipt subject digest is not bound');
70
+ return Object.fromEntries(Object.entries(receipt).filter(([key]) => key !== 'signature'));
71
+ }
72
+
73
+ function manifest(subject) {
74
+ if (!Array.isArray(subject.files) || !subject.files.length || subject.files.length > 512) fail('ENTROROVE_EVIDENCE_MANIFEST', 'Subject requires a nonempty bounded file manifest');
75
+ for (const [index, file] of subject.files.entries()) {
76
+ if (!object(file) || Object.keys(file).sort().join('|') !== 'path|sha256' || !text(file.path, 4096)
77
+ || file.path !== file.path.normalize('NFC') || file.path.startsWith('/') || /^[A-Za-z]:/.test(file.path)
78
+ || /[\\\u0000-\u001f\u007f]/.test(file.path) || file.path.split('/').some(part => !part || part === '.' || part === '..')
79
+ || !SHA.test(String(file.sha256 ?? '')) || (index > 0 && subject.files[index - 1].path >= file.path)) {
80
+ fail('ENTROROVE_EVIDENCE_MANIFEST', 'Subject manifest paths must be normalized, sorted and unique');
81
+ }
82
+ }
83
+ const actual = digest(`validator.file-manifest/1.0\n${subject.files.map(file => `${file.path}\0${file.sha256}`).join('\n')}`);
84
+ if (subject.artifactSha256 !== actual) fail('ENTROROVE_EVIDENCE_ARTIFACT', 'Subject artifact does not match its actual file manifest');
85
+ }
86
+
87
+ function validateSubject(evidence, options) {
88
+ const subject = evidence.subject; const policy = subject?.policy;
89
+ if (!object(subject) || subject.schemaVersion !== 'validator.validation-subject/1.0' || !object(policy)
90
+ || !ID.test(String(subject.planId ?? '')) || !ID.test(String(subject.validationRunId ?? ''))
91
+ || !text(subject.memberId, 128) || !ID.test(String(subject.chainId ?? '')) || !Number.isFinite(Date.parse(subject.executedAt))
92
+ || !SHA.test(String(evidence.subjectDigest ?? '')) || evidence.subjectDigest !== digest(canonical(subject))
93
+ || policy.command !== evidence.command || policy.requiredExitCode !== 0 || subject.artifactSha256 !== evidence.artifactSha256) {
94
+ fail('ENTROROVE_EVIDENCE_SUBJECT', 'Evidence command, artifact or validation subject is not bound');
95
+ }
96
+ manifest(subject);
97
+ const kind = policy.evidenceKind ?? 'test'; const runner = policy.evidenceRunner ?? 'trusted-runner';
98
+ if (!KINDS.has(kind) || !RUNNERS.has(runner) || evidence.kind !== kind || evidence.runner !== runner) {
99
+ fail('ENTROROVE_EVIDENCE_SEMANTICS', 'Evidence kind and runner must match signed policy');
100
+ }
101
+ if (!Array.isArray(subject.tests) || !subject.tests.length || subject.tests.some(item => !object(item)
102
+ || !ID.test(String(item.testId ?? '')) || !subject.files.some(file => file.path === item.path))) {
103
+ fail('ENTROROVE_EVIDENCE_TESTS', 'Subject tests must be bound to manifest files');
104
+ }
105
+ validateBaseline(subject);
106
+ validateBinding(evidence, subject, options);
107
+ if (options.expectedSubject !== undefined && canonical(options.expectedSubject) !== canonical(subject)) {
108
+ fail('ENTROROVE_EVIDENCE_SUBJECT', 'Evidence belongs to a different expected subject');
109
+ }
110
+ return subject;
111
+ }
112
+
113
+ function validateBaseline(subject) {
114
+ const baseline = subject.goldenBaseline; const source = baseline?.source;
115
+ if (!object(baseline) || baseline.schemaVersion !== 'validator.golden-baseline/1.0' || baseline.frozen !== true
116
+ || !ID.test(String(baseline.baselineId ?? '')) || !ID.test(String(baseline.version ?? ''))
117
+ || !ID.test(String(baseline.frozenBy ?? '')) || !Number.isFinite(Date.parse(baseline.frozenAt))
118
+ || !object(source) || !['repository-commit', 'artifact', 'approved-record'].includes(source.kind)
119
+ || !text(source.locator, 4096) || !SHA.test(String(source.digestSha256 ?? ''))
120
+ || baseline.testsSha256 !== digest(canonical(subject.tests))) fail('ENTROROVE_EVIDENCE_BASELINE', 'A frozen traceable baseline must bind the actual subject tests');
121
+ }
122
+
123
+ function validateBinding(evidence, subject, options) {
124
+ const workflow = subject.policy.workflowBinding;
125
+ if (workflow !== undefined && !object(workflow)) fail('ENTROROVE_EVIDENCE_BINDING', 'Signed workflow binding is invalid');
126
+ const values = { flowId: workflow?.flowId ?? subject.chainId, stepId: workflow?.stepId ?? subject.validationRunId,
127
+ snapshotDigest: subject.contracts?.aimlock?.snapshotSha256 ?? subject.snapshotDigest,
128
+ contractDigest: subject.contracts?.archguard?.contractSha256 ?? subject.contractDigest };
129
+ if (!ID.test(String(values.flowId ?? '')) || !ID.test(String(values.stepId ?? ''))) fail('ENTROROVE_EVIDENCE_BINDING', 'Signed flow and step are required');
130
+ for (const field of ['flowId', 'stepId']) {
131
+ if (evidence[field] !== undefined && evidence[field] !== values[field]) fail('ENTROROVE_EVIDENCE_BINDING', 'Outer evidence flow or step differs from signed subject');
132
+ }
133
+ for (const field of ['snapshotDigest', 'contractDigest']) {
134
+ if (subject[field] !== undefined && subject[field] !== values[field]) fail('ENTROROVE_EVIDENCE_BINDING', 'Subject has conflicting contract digest bindings');
135
+ }
136
+ const binding = options.binding ?? {};
137
+ for (const field of ['flowId', 'stepId', 'snapshotDigest', 'contractDigest']) {
138
+ if ((options.requireBinding || binding[field] !== undefined) && (binding[field] === undefined || values[field] !== binding[field]
139
+ || (field.endsWith('Digest') && !SHA.test(String(values[field] ?? ''))))) {
140
+ fail('ENTROROVE_EVIDENCE_BINDING', `Evidence is not bound to requested ${field}`);
141
+ }
142
+ }
143
+ for (const field of ['artifactSha256', 'subjectDigest']) {
144
+ if (binding[field] !== undefined && evidence[field] !== binding[field]) fail('ENTROROVE_EVIDENCE_BINDING', `Evidence ${field} does not match requested subject`);
145
+ }
146
+ }
147
+
148
+ function executionResult(evidence, subject, now) {
149
+ const receipt = evidence.receipt; const result = receipt.result;
150
+ const issued = Date.parse(receipt.issuedAt); const expires = Date.parse(receipt.expiresAt);
151
+ if (!ID.test(String(receipt.nonce ?? '')) || !Number.isFinite(issued) || !Number.isFinite(expires)
152
+ || issued > now + 60_000 || expires <= now || expires <= issued || expires - issued > 600_000) {
153
+ fail('ENTROROVE_EVIDENCE_LIFETIME', 'Execution receipt lifetime or nonce is invalid');
154
+ }
155
+ if (!object(result) || result.runner !== 'trusted-runner' || typeof result.passed !== 'boolean'
156
+ || result.exitCode !== evidence.exitCode || result.durationMs !== evidence.durationMs || result.summary !== evidence.summary) {
157
+ fail('ENTROROVE_EVIDENCE_RESULT', 'Execution receipt result is not bound to evidence');
158
+ }
159
+ if (subject.execution !== undefined) {
160
+ const execution = subject.execution;
161
+ if (!object(execution) || (execution.exitCode === null ? 1 : execution.exitCode) !== evidence.exitCode || typeof execution.timedOut !== 'boolean'
162
+ || typeof execution.outputLimitExceeded !== 'boolean' || (execution.signal !== null && !text(execution.signal, 64))) {
163
+ fail('ENTROROVE_EVIDENCE_RESULT', 'Signed process result is invalid');
164
+ }
165
+ const passed = execution.exitCode === 0 && execution.signal === null && !execution.timedOut && !execution.outputLimitExceeded;
166
+ if (passed !== result.passed) fail('ENTROROVE_EVIDENCE_RESULT', 'Receipt passed flag differs from signed process result');
167
+ }
168
+ if (result.passed && evidence.exitCode !== 0) fail('ENTROROVE_EVIDENCE_RESULT', 'Successful receipt must exit zero');
169
+ return result.passed && evidence.exitCode === 0;
170
+ }
171
+
172
+ export function verifyEvidenceIntegrity(evidence, options = {}) {
173
+ try {
174
+ validateEvidence(evidence); const receipt = evidence.receipt; const payload = receiptPayload(evidence);
175
+ const keyId = verifySignature(receipt, payload, options.publicKey ?? receipt?.publicKey);
176
+ if (receipt.schemaVersion === EXECUTION_SCHEMA) {
177
+ const subject = validateSubject(evidence, options);
178
+ executionResult(evidence, subject, options.now ?? Date.now());
179
+ }
180
+ return state('incomplete', { authentic: true, integrity: 'verified', keyId, evidence,
181
+ reason: 'Signature integrity alone does not establish independent successful execution' });
182
+ } catch (error) { return state('blocked', { integrity: 'invalid', code: error.code ?? 'ENTROROVE_EVIDENCE_INVALID', reason: error.message }); }
183
+ }
184
+
185
+ export function verifyTestEvidence(evidence, options = {}) {
186
+ try {
187
+ validateEvidence(evidence);
188
+ if (evidence.runner === 'local' || !options.publicKey) return state('incomplete', { evidence,
189
+ reason: 'Local evidence or an unconfigured independent verifier cannot establish trusted execution' });
190
+ const now = options.now ?? Date.now();
191
+ if (!Number.isSafeInteger(now) || now < 0) fail('ENTROROVE_EVIDENCE_TIME', 'Trusted verification time is invalid');
192
+ const payload = receiptPayload(evidence); const keyId = verifySignature(evidence.receipt, payload, options.publicKey);
193
+ if (evidence.receipt.schemaVersion === GENERIC_SCHEMA) return state('incomplete', { authentic: true, integrity: 'verified', keyId, evidence,
194
+ reason: 'Generic signatures have no signed execution lifetime or result authority' });
195
+ const subject = validateSubject(evidence, options); const successful = executionResult(evidence, subject, now);
196
+ return state(successful ? 'verified' : 'blocked', { verified: true, authentic: true, successful, keyId, evidence,
197
+ integrity: 'verified', reason: successful ? 'Independent signed execution passed' : 'Independent signed execution failed' });
198
+ } catch (error) { return state('blocked', { code: error.code ?? 'ENTROROVE_EVIDENCE_INVALID', reason: error.message }); }
199
+ }