@entrorove/skill 0.1.37 → 0.1.38

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 (57) hide show
  1. package/CAPABILITY-MAP.md +11 -0
  2. package/CHANGELOG.md +5 -0
  3. package/IDE-EXECUTION.md +93 -0
  4. package/REUSE-DISCIPLINE.md +18 -0
  5. package/SKILL.md +22 -93
  6. package/cli.mjs +11 -7
  7. package/cloud-task-client.mjs +3 -1
  8. package/ide-code-mode.mjs +38 -0
  9. package/ide-command.mjs +76 -0
  10. package/ide-hook.mjs +167 -0
  11. package/ide-install.mjs +59 -0
  12. package/ide-presentation.mjs +6 -0
  13. package/ide-results.mjs +59 -0
  14. package/ide-session.mjs +86 -0
  15. package/local-approved-plan.mjs +21 -6
  16. package/local-archguard-ast.mjs +6 -1
  17. package/local-archguard-language-ast.mjs +7 -0
  18. package/local-cli.mjs +6 -1
  19. package/local-contracts.mjs +4 -1
  20. package/local-discipline-actions.mjs +71 -0
  21. package/local-discipline-analysis.mjs +153 -0
  22. package/local-discipline-batch.mjs +32 -0
  23. package/local-discipline-contracts.d.mts +6 -0
  24. package/local-discipline-contracts.mjs +62 -0
  25. package/local-discipline-gate.mjs +85 -0
  26. package/local-discipline-native.mjs +33 -0
  27. package/local-discipline-operations.mjs +39 -0
  28. package/local-discipline-repository.mjs +87 -0
  29. package/local-discipline-snapshot.mjs +29 -0
  30. package/local-equivalence-runner.mjs +33 -0
  31. package/local-error.mjs +10 -0
  32. package/local-execution.mjs +1 -0
  33. package/local-integrity.d.mts +5 -0
  34. package/local-integrity.mjs +4 -0
  35. package/local-merge-controller.mjs +1 -0
  36. package/local-merge-rollback.mjs +2 -1
  37. package/local-mutation-authority.mjs +16 -7
  38. package/local-mutation-batch.mjs +7 -3
  39. package/local-mutation-files.mjs +22 -0
  40. package/local-mutation-snapshot.mjs +6 -3
  41. package/local-mutation-wire.mjs +6 -1
  42. package/local-runner-discipline.mjs +23 -0
  43. package/local-scope-repository.mjs +16 -0
  44. package/local-task-handoff-write-receipts.mjs +11 -0
  45. package/manifest.json +1 -1
  46. package/package.json +33 -4
  47. package/release-state.json +55 -28
  48. package/release.mjs +3 -1
  49. package/releases.json +6 -0
  50. package/remote-client.mjs +8 -0
  51. package/reuse-discipline-release.mjs +18 -0
  52. package/runtime/validator-runtime-v7.0.44.d.mts +3 -0
  53. package/runtime/validator-runtime-v7.0.44.mjs +1362 -0
  54. package/snapshot-cleanup-journal.mjs +32 -0
  55. package/snapshot-retention.mjs +95 -0
  56. package/task-entry-client.mjs +2 -2
  57. package/validator-runtime-build.mjs +16 -0
@@ -0,0 +1,1362 @@
1
+ // Generated by scripts/build-validator-domain.mjs from the pinned Validator base and first-party logic.
2
+ // Generated by scripts/build-validator-runtime.mjs from first-party Validator source modules.
3
+ const PUBLIC_BIND_ADDRESSES = new Set(['0.0.0.0', '::', '[::]', '0:0:0:0:0:0:0:0'])
4
+ const BIND_OPTION = /^(?:host|hostname|bind|bindhost|bindaddress|listenhost|listenaddress)$/i
5
+ const BIND_METHODS = new Set(['listen', 'bind'])
6
+ const SCRIPT_EXTENSION = /\.(?:[cm]?[jt]sx?)$/i
7
+
8
+ function decodeHostLiteral(value) {
9
+ return value.replace(/\\(?:u\{([0-9a-f]+)\}|u([0-9a-f]{4})|x([0-9a-f]{2})|(.))/gi,
10
+ (match, point, unicode, byte, character) => {
11
+ const digits = point ?? unicode ?? byte
12
+ if (digits === undefined) return character
13
+ const code = parseInt(digits, 16)
14
+ return code <= 0x10ffff ? String.fromCodePoint(code) : match
15
+ })
16
+ }
17
+
18
+ function quotedToken(source, start) {
19
+ const quote = source[start]
20
+ let end = start + 1
21
+ while (end < source.length) {
22
+ if (source[end] === '\\') { end += 2; continue }
23
+ if (source[end] === quote) {
24
+ return { kind: 'literal', value: decodeHostLiteral(source.slice(start + 1, end)), start, end: end + 1 }
25
+ }
26
+ end += 1
27
+ }
28
+ return { kind: 'invalid', value: source.slice(start), start, end: source.length }
29
+ }
30
+
31
+ function templateTokens(source, start) {
32
+ const tokens = []
33
+ let index = start + 1
34
+ while (index < source.length) {
35
+ if (source[index] === '\\') { index += 2; continue }
36
+ if (source[index] === '`') {
37
+ if (tokens.length === 0) tokens.push(quotedToken(source, start))
38
+ return { tokens, end: index + 1 }
39
+ }
40
+ if (source[index] === '$' && source[index + 1] === '{') {
41
+ const expression = codeTokens(source, index + 2, true)
42
+ tokens.push(...expression.tokens)
43
+ index = expression.end
44
+ continue
45
+ }
46
+ index += 1
47
+ }
48
+ return { tokens, end: source.length }
49
+ }
50
+
51
+ function codeTokens(source, start = 0, templateExpression = false) {
52
+ const tokens = []
53
+ let depth = 0
54
+ for (let index = start; index < source.length;) {
55
+ const tail = source.slice(index)
56
+ const ignored = tail.match(/^(?:\s+|\/\/[^\n]*|\/\*[\s\S]*?(?:\*\/|$))/)
57
+ if (ignored) { index += ignored[0].length; continue }
58
+ if (source[index] === '`') {
59
+ const template = templateTokens(source, index)
60
+ tokens.push(...template.tokens)
61
+ index = template.end
62
+ continue
63
+ }
64
+ if (['"', "'"].includes(source[index])) {
65
+ const token = quotedToken(source, index)
66
+ tokens.push(token)
67
+ index = token.end
68
+ continue
69
+ }
70
+ if (source[index] === '}' && templateExpression && depth === 0) return { tokens, end: index + 1 }
71
+ if (source[index] === '{') depth += 1
72
+ if (source[index] === '}') depth -= 1
73
+ const word = tail.match(/^[A-Za-z_$][\w$]*/)
74
+ const value = word === null ? source[index] : word[0]
75
+ tokens.push({ kind: word === null ? 'symbol' : 'word', value, start: index, end: index + value.length })
76
+ index += value.length
77
+ }
78
+ return { tokens, end: source.length }
79
+ }
80
+
81
+ function bindHost(token, aliases) {
82
+ if (token === undefined) return false
83
+ return token.kind === 'literal' ? PUBLIC_BIND_ADDRESSES.has(token.value)
84
+ : token.kind === 'word' && aliases.has(token.value)
85
+ }
86
+
87
+ function publicAliases(tokens) {
88
+ const aliases = new Set()
89
+ let changed = true
90
+ while (changed) {
91
+ changed = false
92
+ for (let index = 0; index < tokens.length - 2; index += 1) {
93
+ const token = tokens[index]
94
+ if (token.kind === 'word' && tokens[index + 1].value === '='
95
+ && bindHost(tokens[index + 2], aliases) && !aliases.has(token.value)) {
96
+ aliases.add(token.value)
97
+ changed = true
98
+ }
99
+ }
100
+ }
101
+ return aliases
102
+ }
103
+
104
+ function callArguments(tokens, opening) {
105
+ const args = [[]]
106
+ let depth = 0
107
+ for (let index = opening + 1; index < tokens.length; index += 1) {
108
+ const token = tokens[index]
109
+ if (token.value === ')' && depth === 0) return args
110
+ if (token.value === ',' && depth === 0) { args.push([]); continue }
111
+ args[args.length - 1].push(token)
112
+ if (['(', '[', '{'].includes(token.value)) depth += 1
113
+ if ([')', ']', '}'].includes(token.value)) depth -= 1
114
+ }
115
+ return []
116
+ }
117
+
118
+ function scriptBindSites(source) {
119
+ const { tokens } = codeTokens(source)
120
+ const aliases = publicAliases(tokens)
121
+ const sites = []
122
+ for (let index = 0; index < tokens.length; index += 1) {
123
+ const token = tokens[index]
124
+ const normalized = token.value.replaceAll('_', '').replaceAll('-', '')
125
+ if (BIND_OPTION.test(normalized) && [':', '='].includes(tokens[index + 1]?.value)
126
+ && bindHost(tokens[index + 2], aliases)) {
127
+ sites.push({ offset: token.start, context: 'bind-option', address: tokens[index + 2].value })
128
+ }
129
+ if (!BIND_METHODS.has(token.value)) continue
130
+ let opening = index + 1
131
+ if (tokens[index - 1]?.value === '[' && tokens[opening]?.value === ']') opening += 1
132
+ if (tokens[opening]?.value === '?' && tokens[opening + 1]?.value === '.') opening += 2
133
+ if (tokens[opening]?.value !== '(') continue
134
+ const args = callArguments(tokens, opening)
135
+ const hosts = token.value === 'bind' ? args.slice(0, 2).flat() : args[1]
136
+ if (hosts === undefined) continue
137
+ const publicHost = hosts.find((candidate) => bindHost(candidate, aliases))
138
+ if (publicHost) sites.push({ offset: token.start, context: `${token.value}-argument`, address: publicHost.value })
139
+ }
140
+ return sites
141
+ }
142
+
143
+ function configurationBindSites(source) {
144
+ const sites = []
145
+ const pattern = /(?:\b(?:host|hostname|bind(?:[_-]?(?:host|address))?|listen(?:[_-]?(?:host|address))?)\b["']?\s*(?:[:=]\s*|\s+)|--(?:host|bind|listen)(?:=|\s+))["']?(0\.0\.0\.0|\[::\]|::)(?=["'\s:;,]|$)/gi
146
+ for (const match of source.matchAll(pattern)) {
147
+ const lineStart = source.lastIndexOf('\n', match.index) + 1
148
+ if (/^\s*(?:#|\/\/)/.test(source.slice(lineStart, match.index))) continue
149
+ sites.push({ offset: match.index, context: 'bind-configuration', address: match[1] })
150
+ }
151
+ return sites
152
+ }
153
+
154
+ /** Inspect bind sinks and host configuration; address lists and outbound targets are not listeners. */
155
+ function publicBindSites(path, source) {
156
+ const sites = SCRIPT_EXTENSION.test(path) ? scriptBindSites(source) : configurationBindSites(source)
157
+ return sites.map((site) => ({
158
+ ...site, line: source.slice(0, site.offset).split('\n').length,
159
+ }))
160
+ }
161
+
162
+ const { brainRunnerContractSchema, validateBridgeContracts } = (() => {
163
+ const SHA_PATTERN = '^[0-9a-f]{64}$'
164
+ const UUID_PATTERN = '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$'
165
+ const SHA = new RegExp(SHA_PATTERN)
166
+ const UUID = new RegExp(UUID_PATTERN)
167
+ const FIELDS = ['schemaVersion', 'planId', 'planSha256', 'reportId', 'reportSha256',
168
+ 'checksSha256', 'scopeContractSha256', 'snapshotSha256', 'adapterSha256']
169
+
170
+ const brainRunnerContractSchema = {
171
+ type: 'object', additionalProperties: false, required: FIELDS,
172
+ properties: {
173
+ schemaVersion: { const: 'brain.frozen-runner/1.0' },
174
+ planId: { type: 'string', pattern: UUID_PATTERN }, reportId: { type: 'string', pattern: UUID_PATTERN },
175
+ ...Object.fromEntries(FIELDS.filter((field) => field.endsWith('Sha256'))
176
+ .map((field) => [field, { type: 'string', pattern: SHA_PATTERN }])),
177
+ },
178
+ }
179
+
180
+ function validBrainRunnerContract(value, subject) {
181
+ if (!value || typeof value !== 'object' || Array.isArray(value)
182
+ || Object.keys(value).sort().join('|') !== [...FIELDS].sort().join('|')
183
+ || value.schemaVersion !== 'brain.frozen-runner/1.0'
184
+ || !UUID.test(value.planId) || !UUID.test(value.reportId)
185
+ || value.planId !== subject.planId || subject.chainId !== 'chn-' + value.reportId
186
+ || subject.validationRunId !== 'brain-' + value.reportId) return false
187
+ return FIELDS.filter((field) => field.endsWith('Sha256')).every((field) => typeof value[field] === 'string' && SHA.test(value[field]))
188
+ }
189
+
190
+ function validateBridgeContracts(value, entityRef, hardened, utilities) {
191
+ const { isObj, text, idRegex, shaRegex, finding } = utilities
192
+ const findings = []
193
+ if (hardened && (!isObj(value.contracts) || (!isObj(value.contracts.aimlock)
194
+ && !validBrainRunnerContract(value.contracts.brain, value)))) findings.push(finding("P0", "VALIDATION-SUBJECT-SNAPSHOT", `${entityRef}.contracts`, "Hardened validation requires an Aimlock snapshot or server frozen Brain contract binding"));
195
+ if (value.contracts?.brain !== undefined && !validBrainRunnerContract(value.contracts.brain, value)) findings.push(finding("P0", "VALIDATION-SUBJECT-BRAIN", `${entityRef}.contracts.brain`, "Brain runner contract is invalid or bound to another execution"));
196
+ if (value.contracts !== undefined && (!isObj(value.contracts)
197
+ || (value.contracts.aimlock !== undefined && (!isObj(value.contracts.aimlock)
198
+ || !idRegex.test(text(value.contracts.aimlock.goalId))
199
+ || !shaRegex.test(text(value.contracts.aimlock.scopeContractSha256))
200
+ || !shaRegex.test(text(value.contracts.aimlock.snapshotSha256))))
201
+ || (value.contracts.blueprint !== undefined && (!isObj(value.contracts.blueprint)
202
+ || !idRegex.test(text(value.contracts.blueprint.blueprintId))
203
+ || !shaRegex.test(text(value.contracts.blueprint.acceptanceReportSha256))))
204
+ || (value.contracts.archguard !== undefined && (!isObj(value.contracts.archguard)
205
+ || !shaRegex.test(text(value.contracts.archguard.contractSha256))
206
+ || !shaRegex.test(text(value.contracts.archguard.ledgerSha256))
207
+ || !["green", "yellow", "red"].includes(value.contracts.archguard.driftStatus))))) findings.push(finding("P0", "VALIDATION-SUBJECT-CONTRACTS", `${entityRef}.contracts`, "Aimlock, Blueprint, and ArchGuard bridge contracts require stable ids, SHA-256 digests, and a valid drift status"));
208
+ return findings
209
+ }
210
+
211
+ return { brainRunnerContractSchema, validateBridgeContracts }; })();
212
+
213
+
214
+ import { createHash, createPublicKey, verify as verifySignature } from "node:crypto";
215
+
216
+ const REQ = "validator.skill.request/1.0";
217
+ const RES = "validator.skill.response/1.0";
218
+ const ERR = "validator.skill.error/1.0";
219
+ const NAME = "validator";
220
+ const COMPILER_VERSION = "v7.0.43";
221
+ const CATALOG_SCHEMA = "entrorove.skill-catalog/1.0";
222
+ const RECEIPT_SCHEMA = "validator.execution-receipt/1.0";
223
+ const VALIDATION_SUBJECT_SCHEMA = "validator.validation-subject/1.0";
224
+ const GOLDEN_BASELINE_SCHEMA = "validator.golden-baseline/1.0";
225
+ const TEST_EVIDENCE_SCHEMA = "entrorove.test-evidence/2.0";
226
+ const RECEIPT_PUBLIC_KEY_ENV = "ENTROROVE_VALIDATOR_RECEIPT_PUBLIC_KEY";
227
+ const MAX_RECEIPT_LIFETIME_MS = 10 * 60 * 1000;
228
+ const ID_PATTERN = "^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$";
229
+ const MEMBER_PATTERN = "^[A-Za-z0-9][A-Za-z0-9_-]{0,31}$";
230
+ const CHAIN_PATTERN = "^chn-[0-9a-f-]{36}$";
231
+ const SHA_PATTERN = "^[0-9a-f]{64}$";
232
+ const idRegex = new RegExp(ID_PATTERN);
233
+ const memberRegex = new RegExp(MEMBER_PATTERN);
234
+ const chainRegex = new RegExp(CHAIN_PATTERN);
235
+ const shaRegex = new RegExp(SHA_PATTERN);
236
+
237
+ const OPS = ["capabilities","help","intake","plan","validate-structure","security-scan","compliance-audit","functional-verify","sandbox-run","fuzz-input","perf-benchmark","intrusive-test","verdict"];
238
+ const PURE = new Set(["capabilities","help","intake","plan","validate-structure","security-scan","compliance-audit","functional-verify","verdict"]);
239
+ const LOCAL_ONLY = new Set(["sandbox-run","fuzz-input","perf-benchmark","intrusive-test"]);
240
+ const CATALOG = OPS.map((operation) => ({ operation, summary: operation }));
241
+
242
+ const stringSchema = (extra = {}) => ({ type: "string", ...extra });
243
+ const arraySchema = (items, extra = {}) => ({ type: "array", items, ...extra });
244
+ const objectSchema = (properties, required = [], extra = {}) => ({
245
+ type: "object", properties, required, additionalProperties: false, ...extra,
246
+ });
247
+ const anyObjectSchema = { type: "object" };
248
+ const findingSchema = objectSchema({
249
+ severity: { enum: ["P0", "P1", "P2"] }, ruleId: stringSchema({ minLength: 1 }),
250
+ entityRef: stringSchema({ minLength: 1 }), message: stringSchema({ minLength: 1 }),
251
+ evidence: anyObjectSchema,
252
+ }, ["severity", "ruleId", "entityRef", "message", "evidence"]);
253
+ const receiptSchema = objectSchema({
254
+ schemaVersion: { const: RECEIPT_SCHEMA }, keyId: stringSchema({ pattern: SHA_PATTERN }),
255
+ nonce: stringSchema({ pattern: ID_PATTERN, minLength: 16, maxLength: 128 }), subjectDigest: stringSchema({ pattern: SHA_PATTERN }),
256
+ issuedAt: stringSchema({ format: "date-time" }), expiresAt: stringSchema({ format: "date-time" }),
257
+ result: objectSchema({ runner: { const: "trusted-runner" }, passed: { type: "boolean" },
258
+ exitCode: { type: "integer" }, durationMs: { type: "number", minimum: 0 },
259
+ summary: stringSchema({ minLength: 1 }) }, ["runner", "passed", "exitCode", "durationMs", "summary"]),
260
+ signature: stringSchema({ minLength: 1 }),
261
+ }, ["schemaVersion", "keyId", "nonce", "subjectDigest", "issuedAt", "expiresAt", "result", "signature"]);
262
+ const baselineSchema = objectSchema({
263
+ schemaVersion: { const: GOLDEN_BASELINE_SCHEMA }, baselineId: stringSchema({ pattern: ID_PATTERN }),
264
+ source: objectSchema({ kind: { enum: ["repository-commit", "artifact", "approved-record"] },
265
+ locator: stringSchema({ minLength: 1 }), digestSha256: stringSchema({ pattern: SHA_PATTERN }) },
266
+ ["kind", "locator", "digestSha256"]),
267
+ version: stringSchema({ pattern: ID_PATTERN }), frozen: { const: true },
268
+ frozenAt: stringSchema({ format: "date-time" }), frozenBy: stringSchema({ pattern: ID_PATTERN }),
269
+ testsSha256: stringSchema({ pattern: SHA_PATTERN }),
270
+ }, ["schemaVersion", "baselineId", "source", "version", "frozen", "frozenAt", "frozenBy", "testsSha256"]);
271
+ const contractsSchema = objectSchema({
272
+ brain: brainRunnerContractSchema,
273
+ aimlock: objectSchema({ goalId: stringSchema({ pattern: ID_PATTERN }),
274
+ scopeContractSha256: stringSchema({ pattern: SHA_PATTERN }), snapshotSha256: stringSchema({ pattern: SHA_PATTERN }) },
275
+ ["goalId", "scopeContractSha256", "snapshotSha256"]),
276
+ blueprint: objectSchema({ blueprintId: stringSchema({ pattern: ID_PATTERN }),
277
+ acceptanceReportSha256: stringSchema({ pattern: SHA_PATTERN }) }, ["blueprintId", "acceptanceReportSha256"]),
278
+ archguard: objectSchema({ contractSha256: stringSchema({ pattern: SHA_PATTERN }),
279
+ ledgerSha256: stringSchema({ pattern: SHA_PATTERN }),
280
+ driftStatus: { enum: ["green", "yellow", "red"] } },
281
+ ["contractSha256", "ledgerSha256", "driftStatus"]),
282
+ });
283
+ const validationFileSchema = objectSchema({
284
+ path: stringSchema({ minLength: 1, maxLength: 500 }),
285
+ sha256: stringSchema({ pattern: SHA_PATTERN }),
286
+ }, ["path", "sha256"]);
287
+ const subjectSchema = objectSchema({
288
+ schemaVersion: { const: VALIDATION_SUBJECT_SCHEMA }, artifactSha256: stringSchema({ pattern: SHA_PATTERN }),
289
+ memberId: stringSchema({ pattern: MEMBER_PATTERN }), chainId: stringSchema({ pattern: CHAIN_PATTERN }),
290
+ executedAt: stringSchema({ format: "date-time" }), files: arraySchema(validationFileSchema, { minItems: 1 }),
291
+ validationRunId: stringSchema({ pattern: ID_PATTERN }), planId: stringSchema({ pattern: ID_PATTERN }),
292
+ tests: arraySchema(anyObjectSchema, { minItems: 1 }), policy: anyObjectSchema,
293
+ goldenBaseline: baselineSchema, contracts: contractsSchema,
294
+ }, ["schemaVersion", "artifactSha256", "validationRunId", "planId", "tests", "policy", "goldenBaseline"]);
295
+ const testEvidenceProperties = {
296
+ schemaVersion: { const: TEST_EVIDENCE_SCHEMA }, evidenceId: stringSchema({ pattern: ID_PATTERN }),
297
+ kind: { enum: ["test", "build", "lint", "security", "benchmark"] }, command: stringSchema({ minLength: 1 }),
298
+ exitCode: { type: "integer" }, durationMs: { type: "number", minimum: 0 },
299
+ summary: stringSchema({ minLength: 1 }), artifactSha256: stringSchema({ pattern: SHA_PATTERN }),
300
+ subject: subjectSchema, subjectDigest: stringSchema({ pattern: SHA_PATTERN }), receipt: receiptSchema,
301
+ };
302
+ const testEvidenceRequired = ["schemaVersion", "evidenceId", "kind", "runner", "command", "exitCode", "durationMs", "summary"];
303
+ const testEvidenceSchema = {
304
+ oneOf: [
305
+ objectSchema({ ...testEvidenceProperties, runner: { const: "local" } }, testEvidenceRequired),
306
+ objectSchema({ ...testEvidenceProperties, runner: { const: "trusted-runner" } },
307
+ [...testEvidenceRequired, "artifactSha256", "subject", "subjectDigest", "receipt"]),
308
+ ],
309
+ };
310
+ const riskEntrySchema = objectSchema({
311
+ riskId: stringSchema({ pattern: ID_PATTERN }), findingRuleId: stringSchema({ minLength: 1 }),
312
+ findingEntityRef: stringSchema({ minLength: 1 }), owner: stringSchema({ pattern: ID_PATTERN }),
313
+ mitigation: stringSchema({ minLength: 1 }), acceptedBy: stringSchema({ pattern: ID_PATTERN }),
314
+ acceptedAt: stringSchema({ format: "date-time" }),
315
+ }, ["riskId", "findingRuleId", "findingEntityRef", "owner", "mitigation", "acceptedBy", "acceptedAt"]);
316
+ const repairCategorySchema = {
317
+ enum: ["structure-schema", "formula-calculation", "scope-drift", "execution-dispatch", "validator-self"],
318
+ };
319
+ const fileSchema = objectSchema({ path: stringSchema({ minLength: 1 }), content: stringSchema(), schema: anyObjectSchema }, ["path", "content"]);
320
+ const nextSchema = objectSchema({ operation: { type: ["string", "null"] }, instruction: stringSchema() }, ["operation", "instruction"]);
321
+ const responseSchema = (properties, required) => objectSchema({
322
+ schemaVersion: { const: RES }, requestId: stringSchema({ minLength: 1 }), status: { enum: ["succeeded", "blocked", "failed"] }, ...properties,
323
+ }, ["schemaVersion", "requestId", "status", ...required]);
324
+ const operationSchema = (input, inputRequired, output, outputRequired) => ({
325
+ input: objectSchema(input, inputRequired), output: responseSchema(output, outputRequired),
326
+ });
327
+ const LEGACY_VALIDATOR_SCHEMAS = Object.freeze({
328
+ capabilities: operationSchema({}, [], { capabilities: anyObjectSchema, skill: anyObjectSchema, operationSchemas: anyObjectSchema, nextStep: nextSchema }, ["capabilities", "skill", "operationSchemas", "nextStep"]),
329
+ help: operationSchema({}, [], { help: anyObjectSchema, operationSchemas: anyObjectSchema, nextStep: nextSchema }, ["help", "operationSchemas", "nextStep"]),
330
+ intake: operationSchema({ goal: stringSchema({ minLength: 1 }), riskLevel: { enum: ["low", "medium", "high"] }, complianceReqs: arraySchema(stringSchema()), targetFiles: arraySchema(stringSchema()) }, ["goal", "riskLevel"], { intake: anyObjectSchema, nextStep: nextSchema }, ["intake", "nextStep"]),
331
+ plan: operationSchema({ findings: arraySchema(findingSchema), findingCategories: arraySchema(repairCategorySchema), intakeResult: anyObjectSchema, availableSkills: arraySchema(stringSchema()) }, ["intakeResult"], { plan: anyObjectSchema, nextStep: nextSchema }, ["plan", "nextStep"]),
332
+ "validate-structure": operationSchema({ files: arraySchema(fileSchema, { minItems: 1 }), rules: arraySchema(anyObjectSchema) }, ["files"], { findings: arraySchema(findingSchema), summary: anyObjectSchema, nextStep: nextSchema }, ["findings", "summary", "nextStep"]),
333
+ "security-scan": operationSchema({ files: arraySchema(fileSchema, { minItems: 1 }), rules: arraySchema(anyObjectSchema) }, ["files"], { findings: arraySchema(findingSchema), summary: anyObjectSchema, nextStep: nextSchema }, ["findings", "summary", "nextStep"]),
334
+ "compliance-audit": operationSchema({ files: arraySchema(fileSchema, { minItems: 1 }), template: stringSchema({ minLength: 1 }), requirements: arraySchema(stringSchema()) }, ["files", "template"], { findings: arraySchema(findingSchema), template: stringSchema(), nextStep: nextSchema }, ["findings", "template", "nextStep"]),
335
+ "functional-verify": operationSchema({ validationContext: subjectSchema, receipts: arraySchema(receiptSchema, { minItems: 1 }) }, ["validationContext", "receipts"], { subject: subjectSchema, subjectDigest: stringSchema({ pattern: SHA_PATTERN }), results: arraySchema(anyObjectSchema), summary: anyObjectSchema, findings: arraySchema(findingSchema), evidence: arraySchema(testEvidenceSchema), nextStep: nextSchema }, ["subject", "subjectDigest", "results", "summary", "findings", "evidence", "nextStep"]),
336
+ "sandbox-run": operationSchema({ command: stringSchema({ minLength: 1 }), files: arraySchema(fileSchema), timeout: { type: "number", minimum: 0 }, networkPolicy: { enum: ["block", "allow"] } }, ["command", "networkPolicy"], { sandbox: anyObjectSchema, runner: { const: "local-only" }, evidence: arraySchema(testEvidenceSchema), nextStep: nextSchema }, ["sandbox", "runner", "evidence", "nextStep"]),
337
+ "fuzz-input": operationSchema({ targetFile: stringSchema({ minLength: 1 }), cases: arraySchema(anyObjectSchema), maxCases: { type: "integer", minimum: 1 } }, ["targetFile", "maxCases"], { fuzz: anyObjectSchema, runner: { const: "local-only" }, evidence: arraySchema(testEvidenceSchema), nextStep: nextSchema }, ["fuzz", "runner", "evidence", "nextStep"]),
338
+ "perf-benchmark": operationSchema({ command: stringSchema({ minLength: 1 }), baseline: baselineSchema, threshold: { type: "number", minimum: 0 } }, ["command", "baseline", "threshold"], { benchmark: anyObjectSchema, runner: { const: "local-only" }, evidence: arraySchema(testEvidenceSchema), nextStep: nextSchema }, ["benchmark", "runner", "evidence", "nextStep"]),
339
+ "intrusive-test": operationSchema({ authorization: { type: "boolean" }, tests: arraySchema(anyObjectSchema, { minItems: 1 }), sandbox: { const: true } }, ["authorization", "tests", "sandbox"], { intrusive: anyObjectSchema, runner: { const: "local-only" }, evidence: arraySchema(testEvidenceSchema), nextStep: nextSchema }, ["intrusive", "runner", "evidence", "nextStep"]),
340
+ verdict: operationSchema({ expectedSubject: subjectSchema, validationContext: subjectSchema, findings: arraySchema(findingSchema), evidence: arraySchema(testEvidenceSchema), riskLedger: arraySchema(riskEntrySchema) }, [], { report: anyObjectSchema, findings: arraySchema(findingSchema), evidence: arraySchema(testEvidenceSchema), nextStep: nextSchema }, ["report", "findings", "evidence", "nextStep"]),
341
+ });
342
+
343
+ function text(value) { return String(value ?? ""); }
344
+ function isObj(value) { return value !== null && typeof value === "object" && !Array.isArray(value); }
345
+ function finding(severity, ruleId, entityRef, message, evidence) {
346
+ return { severity, ruleId, entityRef, message, evidence: evidence === undefined ? {} : { example: evidence } };
347
+ }
348
+ function ok(requestId, payload) { return { schemaVersion: RES, requestId, status: "succeeded", ...payload }; }
349
+ function blocked(requestId, findings) { return { schemaVersion: RES, requestId, status: "blocked", validation: { valid: false, guarantee: "blocked", findings } }; }
350
+ function failed(requestId, code, message) { return { schemaVersion: RES, requestId, status: "failed", errorSchema: ERR, error: { code, message } }; }
351
+ function requiredText(input, key) {
352
+ const value = text(input[key]).trim();
353
+ return value ? { value } : { error: finding("P0", "REQUIRED", `input.${key}`, `${key} is required`) };
354
+ }
355
+ function requiredArray(input, key, minimum = 0) {
356
+ if (!Array.isArray(input[key]) || input[key].length < minimum) return { error: finding("P0", "REQUIRED", `input.${key}`, `${key} must contain at least ${minimum} item(s)`) };
357
+ return { value: input[key] };
358
+ }
359
+ function canonicalJson(value) {
360
+ if (value === null || typeof value === "string" || typeof value === "boolean") return JSON.stringify(value);
361
+ if (typeof value === "number") {
362
+ if (!Number.isFinite(value)) throw new TypeError("Evidence must contain finite numbers");
363
+ return JSON.stringify(value);
364
+ }
365
+ if (Array.isArray(value)) return `[${value.map(canonicalJson).join(",")}]`;
366
+ if (!isObj(value)) throw new TypeError("Evidence must be JSON serializable");
367
+ return `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${canonicalJson(value[key])}`).join(",")}}`;
368
+ }
369
+ export function validatorReceiptSubject(value) { return createHash("sha256").update(canonicalJson(value)).digest("hex"); }
370
+ export function validatorArtifactSubject(files) {
371
+ const manifest = files.map((file) => `${file.path}\0${file.sha256}`).join("\n");
372
+ return createHash("sha256").update(`validator.file-manifest/1.0\n${manifest}`).digest("hex");
373
+ }
374
+ export function validatorReceiptPayload(receipt) {
375
+ if (!isObj(receipt)) throw new TypeError("Execution receipt must be an object");
376
+ const { signature: _signature, ...payload } = receipt;
377
+ return canonicalJson(payload);
378
+ }
379
+
380
+ function validateGoldenBaseline(value, entityRef, tests) {
381
+ const findings = [];
382
+ if (!isObj(value)) return [finding("P0", "GOLDEN-BASELINE-REQUIRED", entityRef, "A frozen golden baseline is required")];
383
+ if (value.schemaVersion !== GOLDEN_BASELINE_SCHEMA) findings.push(finding("P0", "GOLDEN-BASELINE-SCHEMA", `${entityRef}.schemaVersion`, `Expected ${GOLDEN_BASELINE_SCHEMA}`));
384
+ if (!idRegex.test(text(value.baselineId)) || !idRegex.test(text(value.version)) || !idRegex.test(text(value.frozenBy))) findings.push(finding("P0", "GOLDEN-BASELINE-ID", entityRef, "baselineId, version, and frozenBy must be stable identifiers"));
385
+ if (value.frozen !== true || !Number.isFinite(Date.parse(text(value.frozenAt)))) findings.push(finding("P0", "GOLDEN-BASELINE-FROZEN", entityRef, "Baseline must be frozen with a valid timestamp"));
386
+ if (!isObj(value.source) || !["repository-commit", "artifact", "approved-record"].includes(value.source.kind)
387
+ || !text(value.source.locator).trim() || !shaRegex.test(text(value.source.digestSha256))) findings.push(finding("P0", "GOLDEN-BASELINE-SOURCE", `${entityRef}.source`, "Baseline requires traceable source, locator, and SHA-256"));
388
+ if (!shaRegex.test(text(value.testsSha256)) || value.testsSha256 !== validatorReceiptSubject(tests)) findings.push(finding("P0", "GOLDEN-BASELINE-TESTS", `${entityRef}.testsSha256`, "Frozen tests digest does not match subject tests"));
389
+ return findings;
390
+ }
391
+ function legacyValidatorreadValidationSubject(value, entityRef) {
392
+ if (!isObj(value)) return { findings: [finding("P0", "VALIDATION-SUBJECT-REQUIRED", entityRef, "A validation subject is required")] };
393
+ const findings = [];
394
+ const allowed = new Set(["schemaVersion", "memberId", "chainId", "executedAt", "files", "artifactSha256", "validationRunId", "planId", "tests", "policy", "goldenBaseline", "contracts"]);
395
+ const unknown = Object.keys(value).filter((key) => !allowed.has(key));
396
+ if (unknown.length) findings.push(finding("P0", "VALIDATION-SUBJECT-FIELDS", entityRef, `Unknown fields: ${unknown.join(", ")}`));
397
+ if (value.schemaVersion !== VALIDATION_SUBJECT_SCHEMA) findings.push(finding("P0", "VALIDATION-SUBJECT-SCHEMA", `${entityRef}.schemaVersion`, `Expected ${VALIDATION_SUBJECT_SCHEMA}`));
398
+ if (!shaRegex.test(text(value.artifactSha256))) findings.push(finding("P0", "VALIDATION-SUBJECT-ARTIFACT", `${entityRef}.artifactSha256`, "artifactSha256 must be lowercase SHA-256"));
399
+ for (const key of ["validationRunId", "planId"]) if (!idRegex.test(text(value[key]))) findings.push(finding("P0", "VALIDATION-SUBJECT-ID", `${entityRef}.${key}`, `${key} must be stable`));
400
+ if (!Array.isArray(value.tests) || value.tests.length === 0) findings.push(finding("P0", "VALIDATION-SUBJECT-TESTS", `${entityRef}.tests`, "tests must be non-empty"));
401
+ if (!isObj(value.policy) || !text(value.policy.command).trim() || !Number.isInteger(value.policy.requiredExitCode)) findings.push(finding("P0", "VALIDATION-SUBJECT-POLICY", `${entityRef}.policy`, "policy requires command and requiredExitCode"));
402
+ const hardenedFields = ["memberId", "chainId", "executedAt", "files"];
403
+ const hardened = hardenedFields.some((key) => value[key] !== undefined);
404
+ if (hardened) {
405
+ if (!memberRegex.test(text(value.memberId))) findings.push(finding("P0", "VALIDATION-SUBJECT-MEMBER", `${entityRef}.memberId`, "memberId must be a stable member identifier"));
406
+ if (!chainRegex.test(text(value.chainId))) findings.push(finding("P0", "VALIDATION-SUBJECT-CHAIN", `${entityRef}.chainId`, "chainId must be a valid member chain identifier"));
407
+ if (!Number.isFinite(Date.parse(text(value.executedAt)))) findings.push(finding("P0", "VALIDATION-SUBJECT-EXECUTED-AT", `${entityRef}.executedAt`, "executedAt must be a valid timestamp"));
408
+ if (!Array.isArray(value.files) || value.files.length === 0) findings.push(finding("P0", "VALIDATION-SUBJECT-FILES", `${entityRef}.files`, "files must be a non-empty normalized manifest"));
409
+ else {
410
+ for (const [index, file] of value.files.entries()) {
411
+ const path = text(file?.path);
412
+ const normalized = path === path.normalize("NFC") && !/[\u0000-\u001f\u007f]/.test(path)
413
+ && !path.startsWith("/") && !path.includes("\\")
414
+ && path.split("/").every((segment) => segment && segment !== "." && segment !== "..");
415
+ if (!isObj(file) || !normalized || !shaRegex.test(text(file.sha256))) findings.push(finding("P0", "VALIDATION-SUBJECT-FILE", `${entityRef}.files[${index}]`, "Each file needs a normalized relative path and SHA-256"));
416
+ if (index > 0 && text(value.files[index - 1]?.path) >= path) findings.push(finding("P0", "VALIDATION-SUBJECT-FILE-ORDER", `${entityRef}.files[${index}].path`, "File paths must be unique and sorted"));
417
+ }
418
+ if (!findings.some((item) => item.ruleId.startsWith("VALIDATION-SUBJECT-FILE"))
419
+ && value.artifactSha256 !== validatorArtifactSubject(value.files)) findings.push(finding("P0", "VALIDATION-SUBJECT-ARTIFACT-MANIFEST", `${entityRef}.artifactSha256`, "Artifact digest does not match the file manifest"));
420
+ }
421
+ }
422
+ findings.push(...validateBridgeContracts(value, entityRef, hardened, { isObj, text, idRegex, shaRegex, finding }));
423
+ if (Array.isArray(value.tests)) findings.push(...validateGoldenBaseline(value.goldenBaseline, `${entityRef}.goldenBaseline`, value.tests));
424
+ try { canonicalJson(value); } catch (error) { findings.push(finding("P0", "VALIDATION-SUBJECT-JSON", entityRef, error instanceof Error ? error.message : "Invalid JSON")); }
425
+ return findings.length ? { findings } : { value };
426
+ }
427
+ function expectedValidationSubject(input) {
428
+ if (input.expectedSubject === undefined && input.validationContext === undefined) return readValidationSubject(undefined, "input.expectedSubject");
429
+ const expected = input.expectedSubject === undefined ? null : readValidationSubject(input.expectedSubject, "input.expectedSubject");
430
+ const context = input.validationContext === undefined ? null : readValidationSubject(input.validationContext, "input.validationContext");
431
+ const findings = [...(expected?.findings ?? []), ...(context?.findings ?? [])];
432
+ if (findings.length) return { findings };
433
+ const value = expected?.value ?? context.value;
434
+ const digest = validatorReceiptSubject(value);
435
+ if (expected?.value && context?.value && digest !== validatorReceiptSubject(context.value)) return { findings: [finding("P0", "VALIDATION-SUBJECT-CONFLICT", "input.validationContext", "Targets differ")] };
436
+ return { value, digest };
437
+ }
438
+
439
+ function configuredReceiptKey(input = {}) {
440
+ const encoded = text(input.receiptPublicKey).trim() || text(process.env[RECEIPT_PUBLIC_KEY_ENV]).trim();
441
+ if (!/^[A-Za-z0-9+/]+={0,2}$/.test(encoded)) return null;
442
+ try {
443
+ const der = Buffer.from(encoded, "base64");
444
+ const key = createPublicKey({ key: der, format: "der", type: "spki" });
445
+ return key.asymmetricKeyType === "ed25519" ? { key, keyId: createHash("sha256").update(der).digest("hex") } : null;
446
+ } catch { return null; }
447
+ }
448
+ function verifiedReceipt(receipt, subjectDigest, subject, input = {}) {
449
+ const configured = configuredReceiptKey(input);
450
+ if (!configured || !isObj(receipt) || receipt.schemaVersion !== RECEIPT_SCHEMA) return null;
451
+ const result = receipt.result;
452
+ const issuedAt = Date.parse(text(receipt.issuedAt));
453
+ const expiresAt = Date.parse(text(receipt.expiresAt));
454
+ const executedAt = subject.executedAt === undefined ? null : Date.parse(text(subject.executedAt));
455
+ const now = Date.now();
456
+ if (receipt.keyId !== configured.keyId || receipt.subjectDigest !== subjectDigest || !idRegex.test(text(receipt.nonce))
457
+ || !Number.isFinite(issuedAt) || !Number.isFinite(expiresAt) || issuedAt > now + 60_000 || expiresAt <= now
458
+ || (executedAt !== null && (!Number.isFinite(executedAt) || executedAt > issuedAt
459
+ || issuedAt - executedAt > MAX_RECEIPT_LIFETIME_MS))
460
+ || expiresAt <= issuedAt || expiresAt - issuedAt > MAX_RECEIPT_LIFETIME_MS || !isObj(result)
461
+ || result.runner !== "trusted-runner" || typeof result.passed !== "boolean" || !Number.isInteger(result.exitCode)
462
+ || typeof result.durationMs !== "number" || result.durationMs < 0 || !text(result.summary).trim()) return null;
463
+ try {
464
+ const signature = Buffer.from(text(receipt.signature), "base64url");
465
+ return verifySignature(null, Buffer.from(validatorReceiptPayload(receipt)), configured.key, signature) ? receipt : null;
466
+ } catch { return null; }
467
+ }
468
+ function createTestEvidence(receipt, subject, subjectDigest, index) {
469
+ return { schemaVersion: TEST_EVIDENCE_SCHEMA, evidenceId: `${subject.validationRunId}:${index}`, kind: "test",
470
+ runner: "trusted-runner", command: subject.policy.command, exitCode: receipt.result.exitCode,
471
+ durationMs: receipt.result.durationMs, summary: receipt.result.summary, artifactSha256: subject.artifactSha256,
472
+ subject, subjectDigest, receipt };
473
+ }
474
+ function legacyValidatorevidenceState(evidence, subject, subjectDigest, input = {}) {
475
+ if (!isObj(evidence) || evidence.schemaVersion !== TEST_EVIDENCE_SCHEMA || evidence.runner === "local") return "unverifiable";
476
+ let evidenceDigest;
477
+ try { evidenceDigest = validatorReceiptSubject(evidence.subject); } catch { return "unverifiable"; }
478
+ if (!idRegex.test(text(evidence.evidenceId)) || !["test", "build", "lint", "security", "benchmark"].includes(evidence.kind)
479
+ || evidence.runner !== "trusted-runner" || evidence.command !== subject.policy.command
480
+ || evidence.artifactSha256 !== subject.artifactSha256 || evidence.subjectDigest !== subjectDigest
481
+ || !isObj(evidence.subject) || evidenceDigest !== subjectDigest) return "unverifiable";
482
+ const receipt = verifiedReceipt(evidence.receipt, subjectDigest, subject, input);
483
+ if (!receipt || evidence.exitCode !== receipt.result.exitCode || evidence.durationMs !== receipt.result.durationMs
484
+ || evidence.summary !== receipt.result.summary) return "unverifiable";
485
+ return receipt.result.passed && receipt.result.exitCode === subject.policy.requiredExitCode ? "valid" : "failed";
486
+ }
487
+
488
+ const DEFAULT_SECURITY_RULES = [
489
+ { id: "SEC-EVAL", pattern: /\beval\s*\(|new\s+Function\b|\bFunction\s*\(/g, severity: "P0", fix: "Use controlled AST evaluation", executableOnly: true },
490
+ { id: "SEC-SECRETS", pattern: /(?:api[_-]?key|token|password|secret)\s*[:=]\s*['"][A-Za-z0-9_\-]{8,}/gi, severity: "P0", fix: "Remove hardcoded credentials" },
491
+ { id: "SEC-SQLI", pattern: /(?:SELECT|INSERT|UPDATE|DELETE)\s+.*\+\s*(?:req\.|input\.|params\.)/gi, severity: "P0", fix: "Use parameterized queries" },
492
+ { id: "SEC-XSS", pattern: /innerHTML\s*=\s*(?!\s*['"`]\s*['"`])/g, severity: "P1", fix: "Use sanitized output" },
493
+ ];
494
+ const DEFAULT_STRUCTURE_RULES = ["references-closed", "required-fields", "type-correct", "no-cycle"];
495
+ function executableJavaScript(path) { return /\.(?:[cm]?[jt]sx?)$/i.test(path); }
496
+ function stripJavaScriptInertText(source) {
497
+ let output = "";
498
+ let state = "code";
499
+ let quote = "";
500
+ for (let index = 0; index < source.length; index += 1) {
501
+ const character = source[index];
502
+ const next = source[index + 1];
503
+ if (state === "line") {
504
+ if (character === "\n") { state = "code"; output += "\n"; }
505
+ continue;
506
+ }
507
+ if (state === "block") {
508
+ if (character === "*" && next === "/") { state = "code"; index += 1; }
509
+ else if (character === "\n") output += "\n";
510
+ continue;
511
+ }
512
+ if (state === "string") {
513
+ if (character === "\\") { index += 1; continue; }
514
+ if (character === quote) { state = "code"; quote = ""; }
515
+ else if (character === "\n") output += "\n";
516
+ continue;
517
+ }
518
+ if (character === "/" && next === "/") { state = "line"; index += 1; continue; }
519
+ if (character === "/" && next === "*") { state = "block"; index += 1; continue; }
520
+ if (["'", '"', "`"].includes(character)) { state = "string"; quote = character; continue; }
521
+ output += character;
522
+ }
523
+ return output;
524
+ }
525
+ function runSecurityScan(files, rules = DEFAULT_SECURITY_RULES) {
526
+ const findings = [];
527
+ for (const file of files) for (const rule of rules) {
528
+ const path = text(file.path);
529
+ const content = rule.executableOnly
530
+ ? executableJavaScript(path) ? stripJavaScriptInertText(text(file.content)) : ""
531
+ : text(file.content);
532
+ const matches = content.match(rule.pattern instanceof RegExp ? rule.pattern : new RegExp(rule.pattern, "g"));
533
+ if (matches) findings.push(finding(rule.severity, rule.id, text(file.path), `${matches.length} match(es): ${rule.fix}`, { sample: matches[0] }));
534
+ }
535
+ return findings;
536
+ }
537
+ function runStructureValidation(files) {
538
+ const findings = [];
539
+ for (const file of files) {
540
+ const path = text(file.path);
541
+ if (path.endsWith(".json")) try { JSON.parse(text(file.content)); } catch (error) { findings.push(finding("P0", "STR-JSON", path, error.message)); }
542
+ if (file.schema && path.endsWith(".json")) try {
543
+ const data = JSON.parse(text(file.content));
544
+ for (const field of file.schema.required ?? []) if (data[field] === undefined) findings.push(finding("P0", "STR-REQ", `${path}.${field}`, "Required field is missing"));
545
+ } catch { /* invalid JSON is reported above */ }
546
+ }
547
+ return findings;
548
+ }
549
+ function validateReq(request) {
550
+ const findings = [];
551
+ if (!isObj(request)) return [finding("P0", "REQ_OBJECT", "request", "request must be an object")];
552
+ if (request.schemaVersion !== REQ) findings.push(finding("P0", "REQ_SCHEMA", "request.schemaVersion", `Expected ${REQ}`));
553
+ if (!text(request.requestId).trim()) findings.push(finding("P0", "REQ_FIELD", "request.requestId", "requestId is required"));
554
+ if (!OPS.includes(request.operation)) findings.push(finding("P0", "REQ_OPERATION", "request.operation", "operation is unsupported"));
555
+ if (!isObj(request.input)) findings.push(finding("P0", "REQ_INPUT", "request.input", "input must be an object"));
556
+ return findings;
557
+ }
558
+ function severitySummary(findings) {
559
+ return { total: findings.length, p0: findings.filter((item) => item.severity === "P0").length,
560
+ p1: findings.filter((item) => item.severity === "P1").length,
561
+ p2: findings.filter((item) => item.severity === "P2").length };
562
+ }
563
+ const REPAIR_ROUTES = Object.freeze([
564
+ { category: "structure-schema", skill: "blueprint", action: "recompile-contract", trigger: "structure or schema mismatch", requiresHumanConfirmation: false },
565
+ { category: "formula-calculation", skill: "calctool", action: "repair-formula-engine", trigger: "formula or calculation mismatch", requiresHumanConfirmation: false },
566
+ { category: "scope-drift", skill: "aimlock", action: "relock-scope", trigger: "scope contract violation", requiresHumanConfirmation: false },
567
+ { category: "execution-dispatch", skill: "swarm", action: "repair-dispatch", trigger: "execution or dispatch failure", requiresHumanConfirmation: false },
568
+ { category: "validator-self", skill: "validator", action: "propose-validator-patch", trigger: "Validator rule or engine defect", requiresHumanConfirmation: true },
569
+ ]);
570
+
571
+ function runMeta(requestId, operation) {
572
+ const operationStatus = { implementedPure: [...PURE], localRunnerRequired: [...LOCAL_ONLY], planned: ["mutation-testing"] };
573
+ const goldenPathExample = { operation: "functional-verify", input: { validationContext: "frozen ValidationSubject", receipts: "signed trusted-runner receipt[]" }, next: "verdict with entrorove.test-evidence/2.0" };
574
+ if (operation === "capabilities") return ok(requestId, { capabilities: { pure: false, stateless: true, operationStatus,
575
+ verdictLevels: ["pass", "pass-with-risk", "blocked", "incomplete"], testEvidenceSchema: TEST_EVIDENCE_SCHEMA,
576
+ goldenBaselineSchema: GOLDEN_BASELINE_SCHEMA, catalogSchema: CATALOG_SCHEMA }, operationSchemas: SCHEMAS,
577
+ goldenPathExample, skill: { name: NAME, version: COMPILER_VERSION }, nextStep: { operation: "intake", instruction: "Collect validation requirements." } });
578
+ return ok(requestId, { help: { name: NAME, version: COMPILER_VERSION, operations: CATALOG, operationStatus, goldenPathExample },
579
+ operationSchemas: SCHEMAS, nextStep: { operation: "intake", instruction: "Collect validation requirements." } });
580
+ }
581
+ function runLegacyValidatorPlanning(requestId, operation, input) {
582
+ if (operation === "intake") {
583
+ const goal = requiredText(input, "goal");
584
+ const risk = requiredText(input, "riskLevel");
585
+ if (goal.error || risk.error) return blocked(requestId, [goal.error, risk.error].filter(Boolean));
586
+ if (!["low", "medium", "high"].includes(risk.value)) return blocked(requestId, [finding("P0", "RISK-LEVEL", "input.riskLevel", "riskLevel must be low, medium, or high")]);
587
+ return ok(requestId, { intake: { goal: goal.value, riskLevel: risk.value, complianceReqs: input.complianceReqs, targetFiles: input.targetFiles }, nextStep: { operation: "plan", instruction: "Generate validation plan." } });
588
+ }
589
+ const risk = text(input.intakeResult?.riskLevel);
590
+ if (!["low", "medium", "high"].includes(risk)) return blocked(requestId, [finding("P0", "INTAKE-RESULT", "input.intakeResult", "A valid intakeResult is required")]);
591
+ const modules = ["validate-structure", "security-scan", "functional-verify"];
592
+ if (risk === "high") modules.push("compliance-audit", "sandbox-run", "intrusive-test");
593
+ else modules.push("fuzz-input");
594
+ modules.push("perf-benchmark", "verdict");
595
+ const available = Array.isArray(input.availableSkills) ? input.availableSkills : [];
596
+ const requestedCategories = Array.isArray(input.findingCategories) ? input.findingCategories : [];
597
+ const invalidCategory = requestedCategories.find((category) => !REPAIR_ROUTES.some((route) => route.category === category));
598
+ if (invalidCategory) return blocked(requestId, [finding("P0", "REPAIR-CATEGORY", "input.findingCategories", `Unsupported repair category: ${invalidCategory}`)]);
599
+ const selectedRoutes = requestedCategories.length
600
+ ? REPAIR_ROUTES.filter((route) => requestedCategories.includes(route.category)) : REPAIR_ROUTES;
601
+ const routing = selectedRoutes.map((route) => ({
602
+ ...route,
603
+ available: route.skill === "validator" || available.includes(route.skill),
604
+ invoke: route.skill !== "validator" && available.includes(route.skill),
605
+ }));
606
+ return ok(requestId, { plan: { modules, routing, routingPolicy: "deterministic-category-map",
607
+ riskLevel: risk, totalSteps: modules.length }, nextStep: { operation: modules[0], instruction: `Execute ${modules[0]}.` } });
608
+ }
609
+ function runStatic(requestId, operation, input) {
610
+ const files = requiredArray(input, "files", 1);
611
+ if (files.error) return blocked(requestId, [files.error]);
612
+ if (operation === "validate-structure") {
613
+ const findings = runStructureValidation(files.value);
614
+ return ok(requestId, { findings, summary: severitySummary(findings), line: "static", nextStep: { operation: "security-scan", instruction: "Run security scan." } });
615
+ }
616
+ if (operation === "security-scan") {
617
+ const findings = runSecurityScan(files.value, input.rules);
618
+ return ok(requestId, { findings, summary: severitySummary(findings), line: "static", nextStep: { operation: "functional-verify", instruction: "Run frozen golden baseline." } });
619
+ }
620
+ const template = requiredText(input, "template");
621
+ if (template.error) return blocked(requestId, [template.error]);
622
+ const findings = files.value.flatMap((file) => publicBindSites(text(file.path), text(file.content)).map((site) => finding("P1", "COMP-PORT", text(file.path), "Public bind requires review", site)));
623
+ return ok(requestId, { findings, template: template.value, nextStep: { operation: "functional-verify", instruction: "Run frozen golden baseline." } });
624
+ }
625
+ function runLegacyValidatorFunctional(requestId, input) {
626
+ const subject = readValidationSubject(input.validationContext, "input.validationContext");
627
+ if (subject.findings) return blocked(requestId, subject.findings);
628
+ const receipts = requiredArray(input, "receipts", 1);
629
+ if (receipts.error) return blocked(requestId, [finding("P0", "TRUSTED-RECEIPT-REQUIRED", "input.receipts", "Signed trusted-runner receipts are required")]);
630
+ const subjectDigest = validatorReceiptSubject(subject.value);
631
+ const verified = receipts.value.map((receipt) => verifiedReceipt(receipt, subjectDigest, subject.value, input));
632
+ if (verified.some((receipt) => receipt === null)) return blocked(requestId, [finding("P0", "INVALID-EXECUTION-RECEIPT", "input.receipts", "Receipt signature, lifetime, result, or subject is invalid")]);
633
+ const results = verified.map((receipt) => receipt.result);
634
+ const findings = results.flatMap((result, index) => result.passed && result.exitCode === subject.value.policy.requiredExitCode ? [] : [finding("P1", "GOLDEN-FAIL", `receipt:${index}`, "Frozen golden baseline failed", result)]);
635
+ const evidence = verified.map((receipt, index) => createTestEvidence(receipt, subject.value, subjectDigest, index));
636
+ return ok(requestId, { subject: subject.value, subjectDigest, results,
637
+ summary: { total: results.length, passed: results.length - findings.length, failed: findings.length },
638
+ runner: "trusted-runner", findings, evidence, nextStep: { operation: "verdict", instruction: "Render verdict from TestEvidence." } });
639
+ }
640
+
641
+ function runLegacyValidatorLocalProtocol(requestId, operation, input) {
642
+ if (operation === "intrusive-test" && input.authorization !== true) return blocked(requestId, [finding("P0", "INTRUSIVE-NO-AUTH", "input.authorization", "Explicit authorization is required")]);
643
+ const descriptor = operation === "fuzz-input" ? requiredText(input, "targetFile") : requiredText(input, "command");
644
+ if (descriptor.error && operation !== "intrusive-test") return blocked(requestId, [descriptor.error]);
645
+ const pending = { operation, status: "pending-execution", requestedTarget: descriptor.value };
646
+ const key = operation === "sandbox-run" ? "sandbox" : operation === "fuzz-input" ? "fuzz"
647
+ : operation === "perf-benchmark" ? "benchmark" : "intrusive";
648
+ return ok(requestId, { [key]: pending, runner: "local-only", evidence: [],
649
+ pendingEvidenceRequirements: { schemaVersion: TEST_EVIDENCE_SCHEMA, trustedReceiptRequiredForFinalPass: true },
650
+ nextStep: { operation: "verdict", instruction: "Local runner must return signed TestEvidence; pending is incomplete." } });
651
+ }
652
+ function validRiskLedger(findings, ledger) {
653
+ if (!Array.isArray(ledger)) return false;
654
+ return findings.filter((item) => item.severity === "P1").every((item) => ledger.some((entry) => isObj(entry)
655
+ && idRegex.test(text(entry.riskId)) && entry.findingRuleId === item.ruleId
656
+ && entry.findingEntityRef === item.entityRef
657
+ && idRegex.test(text(entry.owner)) && text(entry.mitigation).trim()
658
+ && idRegex.test(text(entry.acceptedBy)) && Number.isFinite(Date.parse(text(entry.acceptedAt)))));
659
+ }
660
+ function runLegacyValidatorVerdict(requestId, input) {
661
+ const expected = expectedValidationSubject(input);
662
+ if (expected.findings) return blocked(requestId, expected.findings);
663
+ const findings = Array.isArray(input.findings) ? input.findings : [];
664
+ const evidence = Array.isArray(input.evidence) ? input.evidence : [];
665
+ const states = evidence.map((item) => evidenceState(item, expected.value, expected.digest, input));
666
+ const failedCount = states.filter((state) => state === "failed").length;
667
+ const p0 = findings.filter((item) => item.severity === "P0").length;
668
+ const p1 = findings.filter((item) => item.severity === "P1").length;
669
+ const allValid = states.length > 0 && states.every((state) => state === "valid");
670
+ const riskLedgerValid = p1 === 0 || validRiskLedger(findings, input.riskLedger);
671
+ const level = p0 > 0 || failedCount > 0 ? "blocked"
672
+ : !allValid || !riskLedgerValid ? "incomplete" : p1 > 0 ? "pass-with-risk" : "pass";
673
+ const report = { verdict: level, subjectDigest: expected.digest, findings: severitySummary(findings),
674
+ evidenceCount: evidence.length, evidenceValid: allValid,
675
+ evidenceSummary: { valid: states.filter((state) => state === "valid").length, failed: failedCount,
676
+ pending: 0, unverifiable: states.filter((state) => state === "unverifiable").length },
677
+ riskLedgerValid, riskLedger: input.riskLedger };
678
+ return ok(requestId, { report, findings, evidence,
679
+ nextStep: level === "blocked" ? { operation: "plan", instruction: "Repair blocking failures." }
680
+ : level === "incomplete" ? { operation: "verdict", instruction: "Provide trusted evidence and complete P1 risk ledger." }
681
+ : { operation: null, instruction: `Verdict: ${level}.` } });
682
+ }
683
+
684
+ export async function run(request) {
685
+ const validationFindings = validateReq(request);
686
+ if (validationFindings.length) return { ...blocked(request?.requestId ?? "unknown", validationFindings), errorSchema: ERR };
687
+ const { requestId, operation, input } = request;
688
+ if (operation === "capabilities" || operation === "help") return runMeta(requestId, operation);
689
+ if (operation === "intake" || operation === "plan") return runPlanning(requestId, operation, input);
690
+ if (["validate-structure", "security-scan", "compliance-audit"].includes(operation)) return runStatic(requestId, operation, input);
691
+ if (operation === "functional-verify") return runFunctional(requestId, input);
692
+ if (LOCAL_ONLY.has(operation)) return runLocalProtocol(requestId, operation, input);
693
+ if (operation === "verdict") return runVerdict(requestId, input);
694
+ return failed(requestId, "UNSUPPORTED_OPERATION", `Unsupported operation: ${operation}`);
695
+ }
696
+
697
+ export { COMPILER_VERSION, NAME, OPS, PURE, CATALOG, SCHEMAS, GOLDEN_BASELINE_SCHEMA,
698
+ TEST_EVIDENCE_SCHEMA, DEFAULT_SECURITY_RULES, DEFAULT_STRUCTURE_RULES,
699
+ runSecurityScan, runStructureValidation };
700
+
701
+ // First-party Validator logic: value.mjs
702
+ import { createHash as validatorHash } from 'node:crypto';
703
+
704
+ const VALIDATOR_SHA = /^[0-9a-f]{64}$/;
705
+ const VALIDATOR_ID = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/;
706
+ const VALIDATOR_KINDS = ['test', 'build', 'lint', 'security', 'benchmark'];
707
+ const VALIDATOR_RUNNERS = ['local', 'trusted-runner', 'sandbox'];
708
+ const validatorObject = value => value !== null && typeof value === 'object' && !Array.isArray(value)
709
+ && [Object.prototype, null].includes(Object.getPrototypeOf(value));
710
+ const validatorText = value => typeof value === 'string' && value.trim().length > 0;
711
+ function validatorDate(value) {
712
+ if (typeof value !== 'string') return NaN;
713
+ const match = value.match(/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.\d+)?(?:Z|([+-])(\d{2}):(\d{2}))$/);
714
+ if (!match) return NaN;
715
+ const [, year, month, day, hour, minute, second, , offsetHour = '0', offsetMinute = '0'] = match;
716
+ const days = new Date(Date.UTC(Number(year), Number(month), 0)).getUTCDate();
717
+ if (Number(month) < 1 || Number(month) > 12 || Number(day) < 1 || Number(day) > days
718
+ || Number(hour) > 23 || Number(minute) > 59 || Number(second) > 59 || Number(offsetHour) > 23 || Number(offsetMinute) > 59) return NaN;
719
+ const instant = Date.parse(value);
720
+ return Number.isFinite(instant) && instant >= 0 ? instant : NaN;
721
+ }
722
+
723
+ function validatorCanonical(value) {
724
+ if (value === null || typeof value === 'string' || typeof value === 'boolean') return JSON.stringify(value);
725
+ if (typeof value === 'number' && Number.isFinite(value)) return JSON.stringify(value);
726
+ if (Array.isArray(value)) return `[${value.map(validatorCanonical).join(',')}]`;
727
+ if (!validatorObject(value)) throw new Error('Validator data must contain finite JSON values');
728
+ return `{${Object.keys(value).sort().map(key => `${JSON.stringify(key)}:${validatorCanonical(value[key])}`).join(',')}}`;
729
+ }
730
+ const validatorDigest = value => validatorHash('sha256').update(validatorCanonical(value)).digest('hex');
731
+ const validatorHashBytes = value => validatorHash('sha256').update(value).digest('hex');
732
+ const validatorDiagnostic = (ruleId, entityRef, message, severity = 'P0') =>
733
+ ({ severity, ruleId, entityRef, message, evidence: {} });
734
+
735
+
736
+ // First-party Validator logic: subject.mjs
737
+
738
+ function validatorBaselineFindings(baseline, tests, now) {
739
+ if (!validatorObject(baseline)) return [validatorDiagnostic('GOLDEN-BASELINE-REQUIRED', 'subject.goldenBaseline', 'Frozen baseline is missing', 'P2')];
740
+ const findings = [];
741
+ if (Object.keys(baseline).some(key => !['schemaVersion', 'baselineId', 'source', 'version', 'frozen', 'frozenAt', 'frozenBy', 'testsSha256'].includes(key))) {
742
+ findings.push(validatorDiagnostic('GOLDEN-BASELINE-FROZEN', 'subject.goldenBaseline', 'Frozen baseline has unknown fields'));
743
+ }
744
+ if (baseline.schemaVersion !== 'validator.golden-baseline/1.0' || baseline.frozen !== true
745
+ || !['baselineId', 'version', 'frozenBy'].every(key => VALIDATOR_ID.test(baseline[key] ?? ''))) {
746
+ findings.push(validatorDiagnostic('GOLDEN-BASELINE-FROZEN', 'subject.goldenBaseline', 'Frozen baseline identity is invalid'));
747
+ }
748
+ const frozenAt = validatorDate(baseline.frozenAt);
749
+ if (!Number.isFinite(frozenAt) || frozenAt > now) findings.push(validatorDiagnostic('GOLDEN-BASELINE-TIME', 'subject.goldenBaseline.frozenAt', 'Baseline must already be frozen'));
750
+ if (!validatorObject(baseline.source) || !['repository-commit', 'artifact', 'approved-record'].includes(baseline.source.kind)
751
+ || Object.keys(baseline.source).some(key => !['kind', 'locator', 'digestSha256'].includes(key))
752
+ || !validatorText(baseline.source.locator) || baseline.source.locator.length > 2000 || !VALIDATOR_SHA.test(baseline.source.digestSha256 ?? '')) {
753
+ findings.push(validatorDiagnostic('GOLDEN-BASELINE-SOURCE', 'subject.goldenBaseline.source', 'Frozen baseline source is invalid'));
754
+ }
755
+ let testsDigest;
756
+ try { testsDigest = validatorDigest(tests); } catch { testsDigest = null; }
757
+ if (baseline.testsSha256 !== testsDigest) findings.push(validatorDiagnostic('GOLDEN-BASELINE-TESTS', 'subject.goldenBaseline.testsSha256', 'Frozen tests changed or are not finite JSON'));
758
+ return findings;
759
+ }
760
+
761
+ function validatorManifestFindings(subject) {
762
+ const findings = [];
763
+ if (!Array.isArray(subject.files) || subject.files.length === 0) {
764
+ return [validatorDiagnostic('VALIDATION-MANIFEST-MISSING', 'subject.files', 'Independent evidence requires an actual file manifest', 'P2')];
765
+ }
766
+ if (subject.files.length > 10_000) return [validatorDiagnostic('VALIDATION-SUBJECT-FILE', 'subject.files', 'Manifest exceeds its bounded file count')];
767
+ for (const [index, file] of subject.files.entries()) {
768
+ const name = file?.path;
769
+ if (!validatorObject(file) || typeof name !== 'string' || name !== name.normalize('NFC') || name !== name.trim()
770
+ || /[\u0000-\u001f\u007f\\:]/.test(name) || /^[~\/]/.test(name)
771
+ || name.split('/').some(part => !part || part === '.' || part === '..')
772
+ || Object.keys(file).sort().join('|') !== 'path|sha256'
773
+ || !VALIDATOR_SHA.test(file.sha256 ?? '') || (index > 0 && String(subject.files[index - 1]?.path ?? '') >= name)) {
774
+ findings.push(validatorDiagnostic('VALIDATION-SUBJECT-FILE', `subject.files.${index}`, 'Manifest paths and hashes must be normalized, sorted and unique'));
775
+ }
776
+ }
777
+ if (!findings.length) {
778
+ const manifest = subject.files.map(file => `${file.path}\0${file.sha256}`).join('\n');
779
+ const artifact = validatorHashBytes(Buffer.from(`validator.file-manifest/1.0\n${manifest}`));
780
+ if (subject.artifactSha256 !== artifact) findings.push(validatorDiagnostic('VALIDATION-SUBJECT-ARTIFACT-MANIFEST', 'subject.artifactSha256', 'Frozen artifact manifest changed'));
781
+ }
782
+ return findings;
783
+ }
784
+
785
+ function validatorContractFindings(subject) {
786
+ const contracts = subject.contracts;
787
+ if (!validatorObject(contracts) || (!validatorObject(contracts.aimlock) && !validatorObject(contracts.brain))) {
788
+ return [validatorDiagnostic('VALIDATION-SUBJECT-SNAPSHOT', 'subject.contracts', 'A frozen scope or Brain binding is required')];
789
+ }
790
+ const findings = [];
791
+ const specs = { aimlock: ['scopeContractSha256', 'snapshotSha256'], blueprint: ['acceptanceReportSha256'], archguard: ['contractSha256', 'ledgerSha256'] };
792
+ for (const [name, fields] of Object.entries(specs)) if (contracts[name] !== undefined) {
793
+ if (!validatorObject(contracts[name]) || !fields.every(key => VALIDATOR_SHA.test(contracts[name][key] ?? ''))) {
794
+ findings.push(validatorDiagnostic('VALIDATION-SUBJECT-CONTRACTS', `subject.contracts.${name}`, 'Frozen contract digests are invalid'));
795
+ }
796
+ }
797
+ if (contracts.aimlock && !VALIDATOR_ID.test(contracts.aimlock.goalId ?? '')) findings.push(validatorDiagnostic('VALIDATION-SUBJECT-CONTRACTS', 'subject.contracts.aimlock', 'Scope goal is invalid'));
798
+ if (contracts.blueprint && !VALIDATOR_ID.test(contracts.blueprint.blueprintId ?? '')) findings.push(validatorDiagnostic('VALIDATION-SUBJECT-CONTRACTS', 'subject.contracts.blueprint', 'Blueprint identity is invalid'));
799
+ if (contracts.archguard && !['green', 'yellow', 'red'].includes(contracts.archguard.driftStatus)) findings.push(validatorDiagnostic('VALIDATION-SUBJECT-CONTRACTS', 'subject.contracts.archguard', 'Architecture drift status is invalid'));
800
+ if (contracts.archguard?.driftStatus === 'red') findings.push(validatorDiagnostic('SCOPE-DRIFT', 'subject.contracts.archguard', 'Frozen architecture reports red drift'));
801
+ if (contracts.brain) {
802
+ const brain = contracts.brain;
803
+ const fields = ['planSha256', 'reportSha256', 'checksSha256', 'scopeContractSha256', 'snapshotSha256', 'adapterSha256'];
804
+ if (brain.schemaVersion !== 'brain.frozen-runner/1.0' || brain.planId !== subject.planId
805
+ || subject.chainId !== `chn-${brain.reportId}` || subject.validationRunId !== `brain-${brain.reportId}`
806
+ || !fields.every(key => VALIDATOR_SHA.test(brain[key] ?? ''))) findings.push(validatorDiagnostic('VALIDATION-SUBJECT-BRAIN', 'subject.contracts.brain', 'Frozen Brain binding changed'));
807
+ }
808
+ return findings;
809
+ }
810
+
811
+ function validateValidatorSubject(subject, { now = Date.now() } = {}) {
812
+ if (!validatorObject(subject)) return { subject: null, digest: null, findings: [validatorDiagnostic('VALIDATION-SUBJECT-MISSING', 'subject', 'No validation subject is available', 'P2')], valid: false };
813
+ const findings = [];
814
+ const allowed = ['schemaVersion', 'memberId', 'chainId', 'executedAt', 'files', 'artifactSha256', 'validationRunId', 'planId', 'tests', 'policy', 'goldenBaseline', 'contracts', 'execution', 'equivalence', 'disciplineComparison'];
815
+ if (Object.keys(subject).some(key => !allowed.includes(key)) || subject.schemaVersion !== 'validator.validation-subject/1.0') findings.push(validatorDiagnostic('VALIDATION-SUBJECT-SCHEMA', 'subject', 'Validation subject schema is invalid'));
816
+ if (!['planId', 'validationRunId'].every(key => VALIDATOR_ID.test(subject[key] ?? ''))) findings.push(validatorDiagnostic('VALIDATION-SUBJECT-ID', 'subject', 'Frozen execution identity is invalid'));
817
+ if (!validatorObject(subject.policy) || !validatorText(subject.policy.command) || subject.policy.requiredExitCode !== 0) findings.push(validatorDiagnostic('VALIDATION-SUBJECT-POLICY', 'subject.policy', 'Frozen policy requires a command and zero success exit code'));
818
+ if (!Array.isArray(subject.tests) || subject.tests.length === 0) findings.push(validatorDiagnostic('VALIDATION-SUBJECT-TESTS', 'subject.tests', 'Frozen tests must be nonempty'));
819
+ else findings.push(...validatorBaselineFindings(subject.goldenBaseline, subject.tests, now));
820
+ findings.push(...validatorManifestFindings(subject));
821
+ findings.push(...validatorEquivalenceFindings(subject), ...validatorDisciplineFindings(subject));
822
+ if (Array.isArray(subject.files) && subject.files.length > 0) {
823
+ if (!/^[A-Za-z0-9][A-Za-z0-9_-]{0,31}$/.test(subject.memberId ?? '') || !/^chn-[0-9a-f-]{36}$/.test(subject.chainId ?? '')) findings.push(validatorDiagnostic('VALIDATION-SUBJECT-SCOPE', 'subject', 'Frozen member or chain binding is invalid'));
824
+ if (!Number.isFinite(validatorDate(subject.executedAt)) || validatorDate(subject.executedAt) > now + 60_000) findings.push(validatorDiagnostic('VALIDATION-SUBJECT-EXECUTED-AT', 'subject.executedAt', 'Execution timestamp is invalid'));
825
+ findings.push(...validatorContractFindings(subject));
826
+ if (Array.isArray(subject.tests) && subject.tests.some(test => !validatorObject(test)
827
+ || !VALIDATOR_ID.test(test.testId ?? '') || !subject.files.some(file => file?.path === test.path))) {
828
+ findings.push(validatorDiagnostic('GOLDEN-BASELINE-TESTS', 'subject.tests', 'Frozen tests must bind actual manifest files'));
829
+ }
830
+ const source = subject.goldenBaseline?.source;
831
+ if (subject.goldenBaseline && Date.parse(subject.goldenBaseline.frozenAt) > Date.parse(subject.executedAt)) {
832
+ findings.push(validatorDiagnostic('GOLDEN-BASELINE-TIME', 'subject.goldenBaseline.frozenAt', 'Baseline must be frozen before execution'));
833
+ }
834
+ if (source?.kind === 'artifact' && subject.files.some(file => file?.path === source.locator && file?.sha256 !== source.digestSha256)) {
835
+ findings.push(validatorDiagnostic('GOLDEN-BASELINE-SOURCE', 'subject.goldenBaseline.source', 'Frozen baseline source differs from manifest'));
836
+ }
837
+ }
838
+ if (subject.execution !== undefined && (!validatorObject(subject.execution)
839
+ || Object.keys(subject.execution).some(key => !['exitCode', 'signal', 'timedOut', 'outputLimitExceeded'].includes(key))
840
+ || (subject.execution.exitCode !== null && !Number.isInteger(subject.execution.exitCode))
841
+ || (subject.execution.signal !== null && !validatorText(subject.execution.signal))
842
+ || typeof subject.execution.timedOut !== 'boolean' || typeof subject.execution.outputLimitExceeded !== 'boolean')) {
843
+ findings.push(validatorDiagnostic('VALIDATION-SUBJECT-EXECUTION', 'subject.execution', 'Signed execution result is invalid'));
844
+ }
845
+ try { return { subject, digest: validatorDigest(subject), findings, valid: findings.length === 0 }; }
846
+ catch { return { subject, digest: null, findings: [...findings, validatorDiagnostic('VALIDATION-SUBJECT-JSON', 'subject', 'Subject must contain finite JSON values')], valid: false }; }
847
+ }
848
+
849
+ // First-party Validator logic: equivalence.mjs
850
+
851
+ const equivalenceText = { type: 'string', minLength: 1 };
852
+ const VALIDATOR_PARALLEL_PATHS_SCHEMA = { type: 'array', minItems: 1, items: { type: 'object', additionalProperties: false,
853
+ properties: { comparisonId: { type: 'string', pattern: VALIDATOR_ID.source }, originalPath: equivalenceText,
854
+ candidatePath: equivalenceText, baselinePath: equivalenceText, baselineSha256: { type: 'string', pattern: VALIDATOR_SHA.source } },
855
+ required: ['comparisonId', 'originalPath', 'candidatePath', 'baselinePath', 'baselineSha256'] } };
856
+ function validatorParallelDeclarationsValid(pairs) {
857
+ return Array.isArray(pairs) && pairs.length > 0 && pairs.every(declarationValid)
858
+ && new Set(pairs.map(pair => pair.comparisonId)).size === pairs.length;
859
+ }
860
+
861
+ // The declaration is frozen with the original execution policy; the results
862
+ // belong to the existing independently signed test subject.
863
+ function validatorFieldDifferences(original, candidate, prefix = '$') {
864
+ if (validatorDigest(original) === validatorDigest(candidate)) return [];
865
+ if (Array.isArray(original) && Array.isArray(candidate)) {
866
+ const paths = [];
867
+ for (let index = 0; index < Math.max(original.length, candidate.length); index++) {
868
+ const path = `${prefix}[${index}]`;
869
+ if (index >= original.length || index >= candidate.length) paths.push(path);
870
+ else paths.push(...validatorFieldDifferences(original[index], candidate[index], path));
871
+ }
872
+ return paths;
873
+ }
874
+ if (validatorObject(original) && validatorObject(candidate)) {
875
+ const paths = [];
876
+ for (const key of [...new Set([...Object.keys(original), ...Object.keys(candidate)])].sort()) {
877
+ const path = `${prefix}[${JSON.stringify(key)}]`;
878
+ if (!Object.hasOwn(original, key) || !Object.hasOwn(candidate, key)) paths.push(path);
879
+ else paths.push(...validatorFieldDifferences(original[key], candidate[key], path));
880
+ }
881
+ return paths;
882
+ }
883
+ return [prefix];
884
+ }
885
+
886
+ function exact(value, names) {
887
+ return validatorObject(value) && Object.keys(value).sort().join('|') === [...names].sort().join('|');
888
+ }
889
+ function path(value) {
890
+ return typeof value === 'string' && value === value.normalize('NFC') && value === value.trim()
891
+ && value.length > 0 && !/^[~\/]|[\\:\u0000-\u001f\u007f]/.test(value)
892
+ && value.split('/').every(part => part && part !== '.' && part !== '..');
893
+ }
894
+ function declarationValid(pair) {
895
+ return exact(pair, ['comparisonId', 'originalPath', 'candidatePath', 'baselinePath', 'baselineSha256'])
896
+ && VALIDATOR_ID.test(pair.comparisonId ?? '')
897
+ && ['originalPath', 'candidatePath', 'baselinePath'].every(key => path(pair[key]))
898
+ && pair.originalPath !== pair.candidatePath && VALIDATOR_SHA.test(pair.baselineSha256 ?? '');
899
+ }
900
+ function validatorReadEquivalenceBaseline(bytes, pair) {
901
+ if (!declarationValid(pair) || !Buffer.isBuffer(bytes) || bytes.length > 1_048_576
902
+ || validatorHashBytes(bytes) !== pair.baselineSha256) throw new Error('Original baseline bytes changed');
903
+ const baseline = JSON.parse(bytes.toString('utf8'));
904
+ if (!exact(baseline, ['schemaVersion', 'source', 'cases'])
905
+ || baseline.schemaVersion !== 'validator.equivalence-baseline/1.0'
906
+ || !exact(baseline.source, ['path', 'sha256']) || baseline.source.path !== pair.originalPath
907
+ || !VALIDATOR_SHA.test(baseline.source.sha256 ?? '')
908
+ || !Array.isArray(baseline.cases) || baseline.cases.length === 0
909
+ || !baseline.cases.every(item => exact(item, ['caseId', 'input', 'output']) && VALIDATOR_ID.test(item.caseId ?? ''))
910
+ || new Set(baseline.cases.map(item => item.caseId)).size !== baseline.cases.length) {
911
+ throw new Error('Original baseline schema is invalid');
912
+ }
913
+ validatorDigest(baseline);
914
+ return baseline;
915
+ }
916
+ function validatorBindEquivalenceResult(pair, baselineBytes, candidates, frozenAt) {
917
+ const baseline = validatorReadEquivalenceBaseline(baselineBytes, pair);
918
+ if (!Number.isFinite(validatorDate(frozenAt)) || !Array.isArray(candidates)
919
+ || candidates.length !== baseline.cases.length
920
+ || !candidates.every(item => exact(item, ['caseId', 'input', 'output']) && VALIDATOR_ID.test(item.caseId ?? ''))
921
+ || new Set(candidates.map(item => item.caseId)).size !== candidates.length) {
922
+ throw new Error('Candidate comparison cases must match the fixed baseline');
923
+ }
924
+ const cases = baseline.cases.map(original => {
925
+ const candidate = candidates.find(item => item.caseId === original.caseId);
926
+ if (!candidate) throw new Error('Candidate omitted a fixed baseline case');
927
+ return { caseId: original.caseId, originalInput: original.input, candidateInput: candidate.input,
928
+ originalOutput: original.output, candidateOutput: candidate.output };
929
+ });
930
+ const result = { comparisonId: pair.comparisonId, baselineSha256: pair.baselineSha256,
931
+ baselineContentBase64: baselineBytes.toString('base64'), frozenAt, cases };
932
+ return { ...result, comparisonArtifactDigest: validatorDigest(result) };
933
+ }
934
+ function resultValid(result) {
935
+ return exact(result, ['comparisonId', 'baselineSha256', 'baselineContentBase64', 'frozenAt', 'cases', 'comparisonArtifactDigest'])
936
+ && VALIDATOR_ID.test(result.comparisonId ?? '') && VALIDATOR_SHA.test(result.baselineSha256 ?? '')
937
+ && VALIDATOR_SHA.test(result.comparisonArtifactDigest ?? '')
938
+ && typeof result.baselineContentBase64 === 'string' && result.baselineContentBase64.length <= 1_398_104
939
+ && Array.isArray(result.cases) && result.cases.length > 0
940
+ && result.cases.every(item => exact(item, ['caseId', 'originalInput', 'candidateInput', 'originalOutput', 'candidateOutput'])
941
+ && VALIDATOR_ID.test(item.caseId ?? ''))
942
+ && new Set(result.cases.map(item => item.caseId)).size === result.cases.length;
943
+ }
944
+ function manifestBound(subject, pair) {
945
+ const files = subject.files ?? [];
946
+ return files.some(file => file.path === pair.baselinePath && file.sha256 === pair.baselineSha256)
947
+ && [pair.originalPath, pair.candidatePath].every(name => files.some(file => file.path === name))
948
+ && subject.goldenBaseline?.source?.kind === 'artifact'
949
+ && subject.goldenBaseline.source.locator === pair.baselinePath
950
+ && subject.goldenBaseline.source.digestSha256 === pair.baselineSha256;
951
+ }
952
+ function comparisonFindings(subject, pair, result) {
953
+ const at = `equivalence:${pair.comparisonId}`;
954
+ if (!result) return [validatorDiagnostic('EQUIVALENCE-REQUIRED', at,
955
+ 'Parallel paths require independently signed same-input field comparisons', 'P2')];
956
+ if (!resultValid(result)) return [validatorDiagnostic('EQUIVALENCE-INVALID', at, 'Comparison result schema is invalid')];
957
+ const { comparisonArtifactDigest, ...payload } = result;
958
+ if (validatorDigest(payload) !== comparisonArtifactDigest || result.baselineSha256 !== pair.baselineSha256
959
+ || result.frozenAt !== subject.goldenBaseline.frozenAt || !Number.isFinite(validatorDate(result.frozenAt))
960
+ || validatorDate(result.frozenAt) > validatorDate(subject.executedAt)) {
961
+ return [validatorDiagnostic('EQUIVALENCE-BASELINE', at, 'Comparison changed its original frozen baseline')];
962
+ }
963
+ const baselineBytes = Buffer.from(result.baselineContentBase64, 'base64');
964
+ const baseline = validatorReadEquivalenceBaseline(baselineBytes, pair);
965
+ if (baselineBytes.toString('base64') !== result.baselineContentBase64
966
+ || baseline.source.sha256 !== subject.files.find(file => file.path === pair.originalPath)?.sha256
967
+ || result.cases.length !== baseline.cases.length || baseline.cases.some(original => {
968
+ const reported = result.cases.find(item => item.caseId === original.caseId);
969
+ return !reported || validatorDigest(reported.originalInput) !== validatorDigest(original.input)
970
+ || validatorDigest(reported.originalOutput) !== validatorDigest(original.output);
971
+ })) return [validatorDiagnostic('EQUIVALENCE-BASELINE', at, 'Original outputs must come from the actual frozen baseline bytes')];
972
+ const findings = [];
973
+ for (const item of result.cases) {
974
+ if (validatorDigest(item.originalInput) !== validatorDigest(item.candidateInput)) {
975
+ findings.push(validatorDiagnostic('EQUIVALENCE-INPUT', `${at}:${item.caseId}`, 'Original and candidate inputs differ'));
976
+ continue;
977
+ }
978
+ for (const field of validatorFieldDifferences(item.originalOutput, item.candidateOutput)) {
979
+ findings.push(validatorDiagnostic('EQUIVALENCE-FIELD', `${at}:${item.caseId}:${field}`, `Field differs: ${field}`));
980
+ }
981
+ }
982
+ return findings;
983
+ }
984
+ function validatorEquivalenceFindings(subject) {
985
+ const declared = subject?.policy?.parallelPaths;
986
+ const results = subject?.equivalence;
987
+ if (declared === undefined && results === undefined) return [];
988
+ if (!validatorParallelDeclarationsValid(declared)) {
989
+ return [validatorDiagnostic('EQUIVALENCE-DECLARATION', 'subject.policy.parallelPaths', 'Parallel paths need a frozen declaration')];
990
+ }
991
+ if (declared.some(pair => !manifestBound(subject, pair))) {
992
+ return [validatorDiagnostic('EQUIVALENCE-MANIFEST', 'subject.policy.parallelPaths', 'Prior baseline and both paths must bind actual frozen files')];
993
+ }
994
+ if (results === undefined) return declared.flatMap(pair => comparisonFindings(subject, pair, null));
995
+ if (!Array.isArray(results) || results.some(result => !resultValid(result))
996
+ || new Set(results.map(result => result.comparisonId)).size !== results.length
997
+ || results.some(result => !declared.some(pair => pair.comparisonId === result.comparisonId))) {
998
+ return [validatorDiagnostic('EQUIVALENCE-INVALID', 'subject.equivalence', 'Comparison results contain undeclared or duplicate identities')];
999
+ }
1000
+ try { return declared.flatMap(pair => comparisonFindings(subject, pair, results.find(result => result.comparisonId === pair.comparisonId))); }
1001
+ catch { return [validatorDiagnostic('EQUIVALENCE-INVALID', 'subject.equivalence', 'Comparison must contain finite JSON values')]; }
1002
+ }
1003
+
1004
+ // First-party Validator logic: discipline.mjs
1005
+
1006
+ function validatorDisciplineFindings(subject) {
1007
+ if (subject?.policy?.disciplineReviewRequired !== true) return [];
1008
+ const report = subject.disciplineComparison;
1009
+ if (!validatorObject(report)) return [validatorDiagnostic('DISCIPLINE-DIFF-MISSING', 'subject.disciplineComparison', 'Actual whole-repository comparison evidence is required', 'P2')];
1010
+ const { reportDigest, ...core } = report;
1011
+ if (report.schemaVersion !== 'entrorove.discipline-runner-diff/1.0' || report.planId !== subject.planId
1012
+ || report.comparisonScope !== 'whole-repository-including-ignored-files' || !VALIDATOR_SHA.test(report.originalManifestDigest ?? '')
1013
+ || !VALIDATOR_SHA.test(report.actualManifestDigest ?? '') || reportDigest !== validatorDigest(core)
1014
+ || !Number.isSafeInteger(report.changedCount) || report.changedCount < 0 || !Array.isArray(report.changedPaths)
1015
+ || report.changedPaths.length !== Math.min(report.changedCount, 256) || !validatorObject(report.gates)
1016
+ || report.gates.repositoryUnchanged !== (report.changedCount === 0)) {
1017
+ return [validatorDiagnostic('DISCIPLINE-DIFF-BINDING', 'subject.disciplineComparison', 'Original deterministic comparison binding is invalid')];
1018
+ }
1019
+ if (report.changedCount || report.originalManifestDigest !== report.actualManifestDigest) return [validatorDiagnostic('DISCIPLINE-REPOSITORY-DRIFT', 'subject.disciplineComparison', 'Execution changed files outside its guarded write history', 'P0', { changedPaths: report.changedPaths, changedCount: report.changedCount })];
1020
+ return [];
1021
+ }
1022
+
1023
+ // First-party Validator logic: evidence.mjs
1024
+ import { createPublicKey as validatorPublicKey, verify as validatorVerify } from 'node:crypto';
1025
+
1026
+ function validatorReceiptState(receipt, subjectState, { receiptPublicKey, now = Date.now() } = {}) {
1027
+ const reject = reason => ({ state: 'unverifiable', reason });
1028
+ if (!subjectState.valid) return reject('Frozen subject is incomplete or invalid');
1029
+ if (!validatorEvidenceDeclaration(subjectState.subject).valid) return reject('Signed policy declarations are invalid');
1030
+ if (!validatorText(receiptPublicKey)) return reject('Independent receipt key is not configured');
1031
+ let key; let keyId;
1032
+ try {
1033
+ const der = Buffer.from(receiptPublicKey, 'base64');
1034
+ if (der.toString('base64') !== receiptPublicKey) return reject('Independent key encoding is not canonical');
1035
+ key = validatorPublicKey({ key: der, format: 'der', type: 'spki' });
1036
+ if (key.asymmetricKeyType !== 'ed25519' || !key.export({ format: 'der', type: 'spki' }).equals(der)) return reject('Independent key must be canonical Ed25519');
1037
+ keyId = validatorHashBytes(der);
1038
+ } catch { return reject('Independent key is invalid'); }
1039
+ if (!validatorObject(receipt) || receipt.schemaVersion !== 'validator.execution-receipt/1.0'
1040
+ || receipt.keyId !== keyId || receipt.subjectDigest !== subjectState.digest
1041
+ || !VALIDATOR_ID.test(receipt.nonce ?? '') || receipt.nonce.length < 16) return reject('Receipt identity or frozen subject binding is invalid');
1042
+ if (typeof receipt.signature !== 'string' || !/^[A-Za-z0-9_-]{86}$/.test(receipt.signature)) return reject('Ed25519 signature encoding is not canonical');
1043
+ const signature = Buffer.from(receipt.signature, 'base64url');
1044
+ if (signature.length !== 64 || signature.toString('base64url') !== receipt.signature) return reject('Ed25519 signature encoding is not canonical');
1045
+ const { signature: _signature, ...payload } = receipt;
1046
+ try { if (!validatorVerify(null, Buffer.from(validatorCanonical(payload)), key, signature)) return reject('Receipt signature is invalid'); }
1047
+ catch { return reject('Receipt signature is invalid'); }
1048
+ const issuedAt = validatorDate(receipt.issuedAt); const expiresAt = validatorDate(receipt.expiresAt);
1049
+ const executedAt = validatorDate(subjectState.subject.executedAt);
1050
+ if (!Number.isFinite(issuedAt) || !Number.isFinite(expiresAt) || issuedAt > now + 60_000 || expiresAt <= now
1051
+ || expiresAt <= issuedAt || expiresAt - issuedAt > 600_000 || executedAt > issuedAt || issuedAt - executedAt > 600_000) return reject('Receipt lifetime or execution instant is invalid');
1052
+ const result = receipt.result;
1053
+ if (!validatorObject(result) || result.runner !== 'trusted-runner' || typeof result.passed !== 'boolean'
1054
+ || !Number.isInteger(result.exitCode) || result.exitCode < -128 || result.exitCode > 255
1055
+ || !Number.isFinite(result.durationMs) || result.durationMs < 0 || result.durationMs > 86_400_000
1056
+ || !validatorText(result.summary) || result.summary.length > 4000) return reject('Signed result fields are invalid');
1057
+ const execution = subjectState.subject.execution;
1058
+ if (execution && ((execution.exitCode === null ? 1 : execution.exitCode) !== result.exitCode
1059
+ || (result.passed && (execution.signal !== null || execution.timedOut || execution.outputLimitExceeded)))) return reject('Signed result contradicts its signed process outcome');
1060
+ return { state: result.passed && result.exitCode === 0 ? 'valid' : 'failed', receipt };
1061
+ }
1062
+
1063
+ function validatorEvidenceDeclaration(subject) {
1064
+ const kind = subject?.policy?.evidenceKind ?? 'test';
1065
+ const runner = subject?.policy?.evidenceRunner ?? 'trusted-runner';
1066
+ const sandboxDeclared = runner !== 'sandbox' || (subject?.policy?.evidenceKind !== undefined && subject?.policy?.evidenceRunner === 'sandbox');
1067
+ const binding = subject?.policy?.workflowBinding;
1068
+ const bindingDeclared = !Object.hasOwn(subject?.policy ?? {}, 'workflowBinding') || (validatorObject(binding)
1069
+ && ['flowId', 'stepId'].every(key => Object.hasOwn(binding, key) && VALIDATOR_ID.test(binding[key] ?? ''))
1070
+ && (!Object.hasOwn(binding, 'ownerId') || VALIDATOR_ID.test(binding.ownerId ?? '')));
1071
+ const local = subject?.policy?.localVerification;
1072
+ const localDeclared = !Object.hasOwn(subject?.policy ?? {}, 'localVerification') || (validatorObject(local)
1073
+ && Object.keys(local).sort().join('|') === 'operation|requestedTarget|requirementsSha256'
1074
+ && ['sandbox-run', 'fuzz-input', 'perf-benchmark', 'intrusive-test'].includes(local.operation)
1075
+ && validatorText(local.requestedTarget) && VALIDATOR_SHA.test(local.requirementsSha256 ?? ''));
1076
+ return { kind, runner, valid: VALIDATOR_KINDS.includes(kind) && VALIDATOR_RUNNERS.includes(runner) && sandboxDeclared && bindingDeclared && localDeclared };
1077
+ }
1078
+
1079
+ function classifyValidatorEvidence(evidence, subjectState, options = {}) {
1080
+ const reject = reason => ({ state: 'unverifiable', reason });
1081
+ if (!validatorObject(evidence) || evidence.schemaVersion !== 'entrorove.test-evidence/2.0') return reject('Evidence schema is invalid');
1082
+ const fields = ['schemaVersion', 'evidenceId', 'kind', 'runner', 'command', 'exitCode', 'durationMs', 'summary',
1083
+ 'artifactSha256', 'subject', 'subjectDigest', 'receipt', 'flowId', 'stepId', 'status'];
1084
+ if (Object.keys(evidence).some(key => !fields.includes(key))) return reject('Evidence has unknown fields');
1085
+ if (evidence.runner === 'local') return reject('Local evidence cannot become independent evidence');
1086
+ if (['pending', 'pending-execution'].includes(evidence.status)) return { state: 'pending', reason: 'Execution is still pending' };
1087
+ const declaration = validatorEvidenceDeclaration(subjectState.subject);
1088
+ if (!declaration.valid || evidence.kind !== declaration.kind || evidence.runner !== declaration.runner) return reject('Evidence kind or runner differs from its signed policy');
1089
+ let evidenceDigest;
1090
+ try { evidenceDigest = validatorDigest(evidence.subject); } catch { return reject('Evidence subject is invalid'); }
1091
+ const declaredId = subjectState.subject?.policy?.evidenceId;
1092
+ const runId = subjectState.subject?.validationRunId;
1093
+ const idBound = declaredId !== undefined ? evidence.evidenceId === declaredId : evidence.evidenceId === `${runId}:process`
1094
+ || (typeof evidence.evidenceId === 'string' && evidence.evidenceId.startsWith(`${runId}:`) && /^(?:0|[1-9][0-9]*)$/.test(evidence.evidenceId.slice(String(runId).length + 1)));
1095
+ if (!validatorText(evidence.evidenceId) || evidence.evidenceId.length > 200 || !idBound
1096
+ || !validatorText(evidence.command) || evidence.command.length > 2000
1097
+ || evidenceDigest !== subjectState.digest
1098
+ || evidence.subjectDigest !== subjectState.digest || evidence.artifactSha256 !== subjectState.subject?.artifactSha256
1099
+ || evidence.command !== subjectState.subject?.policy?.command) return reject('Evidence does not bind the frozen subject, manifest and command');
1100
+ const binding = subjectState.subject?.policy?.workflowBinding;
1101
+ for (const name of ['flowId', 'stepId']) {
1102
+ const signed = binding?.[name] ?? subjectState.subject?.[name === 'flowId' ? 'chainId' : 'validationRunId'];
1103
+ const expected = options[name] ?? signed;
1104
+ if ((evidence[name] !== undefined && evidence[name] !== expected) || (options[name] !== undefined && signed !== options[name])) return reject(`Evidence ${name} is not signed for its actual authority`);
1105
+ }
1106
+ const verified = validatorReceiptState(evidence.receipt, subjectState, options);
1107
+ if (verified.state === 'unverifiable') return verified;
1108
+ if (evidence.exitCode !== verified.receipt.result.exitCode || evidence.durationMs !== verified.receipt.result.durationMs
1109
+ || evidence.summary !== verified.receipt.result.summary) return reject('Evidence fields differ from the signed result');
1110
+ return verified;
1111
+ }
1112
+
1113
+ function validatorEvidenceFromReceipt(receipt, subjectState, index) {
1114
+ const declaration = validatorEvidenceDeclaration(subjectState.subject);
1115
+ const binding = subjectState.subject.policy.workflowBinding;
1116
+ return { schemaVersion: 'entrorove.test-evidence/2.0', evidenceId: subjectState.subject.policy.evidenceId ?? `${subjectState.subject.validationRunId}:${index}`,
1117
+ kind: declaration.kind, runner: declaration.runner, command: subjectState.subject.policy.command,
1118
+ exitCode: receipt.result.exitCode, durationMs: receipt.result.durationMs, summary: receipt.result.summary,
1119
+ artifactSha256: subjectState.subject.artifactSha256, subject: subjectState.subject, subjectDigest: subjectState.digest, receipt,
1120
+ ...(binding?.flowId === undefined ? {} : { flowId: binding.flowId }), ...(binding?.stepId === undefined ? {} : { stepId: binding.stepId }) };
1121
+ }
1122
+
1123
+ // First-party Validator logic: rework.mjs
1124
+
1125
+ const VALIDATOR_REWORK = Object.freeze([
1126
+ { category: 'structure-schema', skill: 'blueprint', operation: 'validate', inputRequirements: ['flowId', 'expectedRevision', 'blueprint'] },
1127
+ { category: 'formula-calculation', skill: 'calctool', operation: 'compile-inline', inputRequirements: ['requirements.goal', 'requirements.engineId', 'requirements.inputs', 'requirements.formulas'] },
1128
+ { category: 'scope-drift', skill: 'aimlock', operation: 'scope-contract', inputRequirements: ['contract.allowedPaths', 'contract.maxChangedLines', 'contract.allowNewFiles', 'contract.allowDeleteFiles'] },
1129
+ { category: 'execution-dispatch', skill: 'swarm', operation: 'dispatch', inputRequirements: ['tasks', 'taskId', 'workerId', 'actorRole', 'facts'],
1130
+ preconditions: ['Original failed execution is recorded and reconciled by its owner', 'Current dispatcher authorization and dependency checks'] },
1131
+ { category: 'validator-self', skill: 'validator', operation: null, inputRequirements: ['human reviewed Validator correction'] },
1132
+ ]);
1133
+
1134
+ function validatorFindingCategory(finding) {
1135
+ const rule = String(finding?.ruleId ?? '').toUpperCase();
1136
+ if (/^(STR[-_]|SCHEMA[-_]|STRUCTURE[-_]|REF[-_]|TYPE[-_]|CYCLE[-_])/.test(rule)) return 'structure-schema';
1137
+ if (/^(FORMULA[-_]|CALC[-_]|ARITHMETIC[-_])/.test(rule)) return 'formula-calculation';
1138
+ if (/^(SCOPE[-_]|AIMLOCK[-_]|ARCH[-_]|VALIDATION-SUBJECT-(SCOPE|SNAPSHOT|CONTRACTS|BRAIN|ARTIFACT-MANIFEST|POLICY))/.test(rule)) return 'scope-drift';
1139
+ if (/^(GOLDEN-FAIL|EXECUTION[-_]|DISPATCH[-_]|SWARM[-_])/.test(rule)) return 'execution-dispatch';
1140
+ return 'validator-self';
1141
+ }
1142
+
1143
+ function validatorRepairRoutes(findings) {
1144
+ const values = Array.isArray(findings) ? findings.filter(item => validatorObject(item) && validatorText(item.ruleId)) : [];
1145
+ return VALIDATOR_REWORK.flatMap(route => {
1146
+ const matching = values.filter(item => validatorFindingCategory(item) === route.category);
1147
+ if (!matching.length) return [];
1148
+ const triggers = [...new Set(matching.map(item => `${item.ruleId}:${item.entityRef}`))].sort();
1149
+ return [{ ...route, triggers, requiresHumanConfirmation: route.category === 'validator-self',
1150
+ invoke: false, authorityRequired: route.skill !== 'validator' }];
1151
+ });
1152
+ }
1153
+
1154
+ // First-party Validator logic: judgement.mjs
1155
+
1156
+ function validatorRiskLedgerValid(findings, ledger, now = Date.now()) {
1157
+ const risks = findings.filter(item => item.severity === 'P1');
1158
+ if (!risks.length) return true;
1159
+ if (!Array.isArray(ledger)) return false;
1160
+ const entries = ledger.filter(entry => validatorObject(entry)
1161
+ && ['riskId', 'owner', 'acceptedBy'].every(key => VALIDATOR_ID.test(entry[key] ?? ''))
1162
+ && validatorText(entry.mitigation) && validatorText(entry.findingRuleId) && validatorText(entry.findingEntityRef)
1163
+ && Number.isFinite(validatorDate(entry.acceptedAt)) && validatorDate(entry.acceptedAt) <= now);
1164
+ if (new Set(entries.map(entry => entry.riskId)).size !== entries.length) return false;
1165
+ const used = new Set();
1166
+ return risks.every(finding => {
1167
+ const index = entries.findIndex((entry, i) => !used.has(i) && entry.findingRuleId === finding.ruleId && entry.findingEntityRef === finding.entityRef);
1168
+ if (index < 0) return false;
1169
+ used.add(index); return true;
1170
+ });
1171
+ }
1172
+
1173
+ function validatorDuplicateNonces(evidence) {
1174
+ const counts = new Map();
1175
+ for (const value of evidence) if (validatorText(value?.receipt?.nonce)) counts.set(value.receipt.nonce, (counts.get(value.receipt.nonce) ?? 0) + 1);
1176
+ return new Set([...counts].filter(([, count]) => count > 1).map(([nonce]) => nonce));
1177
+ }
1178
+
1179
+ function validatorPendingState(pending, evidence, states) {
1180
+ const required = { 'sandbox-run': 'test', 'fuzz-input': 'test', 'intrusive-test': 'security', 'perf-benchmark': 'benchmark' };
1181
+ const used = new Set();
1182
+ return pending.map(item => {
1183
+ if (!validatorObject(item) || item.status !== 'pending-execution' || !required[item.operation]) return { descriptor: item, resolved: false };
1184
+ const index = evidence.findIndex((value, i) => {
1185
+ if (used.has(i) || states[i].state !== 'valid' || value.kind !== required[item.operation]) return false;
1186
+ if (item.operation === 'sandbox-run' && value.runner !== 'sandbox') return false;
1187
+ if (item.operation === 'intrusive-test' && value.runner !== 'sandbox') return false;
1188
+ const target = item.requestedTarget;
1189
+ const verification = value.subject?.policy?.localVerification;
1190
+ if (!validatorText(target) || !VALIDATOR_SHA.test(item.requirementsSha256 ?? '') || !validatorObject(verification)
1191
+ || Object.keys(verification).sort().join('|') !== 'operation|requestedTarget|requirementsSha256') return false;
1192
+ const targetBound = value.command === target || value.subject?.tests?.some(test => test.path === target || test.testId === target);
1193
+ return targetBound && verification.operation === item.operation && verification.requestedTarget === target
1194
+ && verification.requirementsSha256 === item.requirementsSha256;
1195
+ });
1196
+ if (index >= 0) used.add(index);
1197
+ return { descriptor: item, resolved: index >= 0 };
1198
+ });
1199
+ }
1200
+
1201
+ function evaluateValidatorVerdict(input = {}, options = {}) {
1202
+ const now = options.now ?? Date.now();
1203
+ const subjectState = validateValidatorSubject(input.expectedSubject ?? input.validationContext, { now });
1204
+ const findings = [...(Array.isArray(input.findings) ? input.findings : []), ...subjectState.findings];
1205
+ if (input.expectedSubject && input.validationContext) {
1206
+ try { if (validatorDigest(input.expectedSubject) !== validatorDigest(input.validationContext)) findings.push(validatorDiagnostic('VALIDATION-SUBJECT-SCOPE', 'subject', 'Two frozen subject bindings disagree')); }
1207
+ catch { findings.push(validatorDiagnostic('VALIDATION-SUBJECT-JSON', 'subject', 'Frozen subject is invalid')); }
1208
+ }
1209
+ const evidence = Array.isArray(input.evidence) ? input.evidence : [];
1210
+ const duplicates = validatorDuplicateNonces(evidence);
1211
+ const states = evidence.map(value => duplicates.has(value?.receipt?.nonce)
1212
+ ? { state: 'unverifiable', reason: 'Duplicate receipt nonce is not independent evidence' }
1213
+ : classifyValidatorEvidence(value, subjectState, { ...options, now }));
1214
+ const pending = validatorPendingState(Array.isArray(input.pending) ? input.pending : [], evidence, states);
1215
+ const summary = { valid: states.filter(value => value.state === 'valid').length, failed: states.filter(value => value.state === 'failed').length,
1216
+ pending: states.filter(value => value.state === 'pending').length + pending.filter(value => !value.resolved).length,
1217
+ unverifiable: states.filter(value => value.state === 'unverifiable').length };
1218
+ const p0 = findings.filter(value => value.severity === 'P0').length; const p1 = findings.filter(value => value.severity === 'P1').length;
1219
+ const allValid = subjectState.valid && summary.valid > 0 && summary.failed + summary.pending + summary.unverifiable === 0;
1220
+ const riskLedgerValid = validatorRiskLedgerValid(findings, input.riskLedger, now);
1221
+ const verdict = p0 > 0 || summary.failed > 0 ? 'blocked' : !allValid || !riskLedgerValid ? 'incomplete' : p1 > 0 ? 'pass-with-risk' : 'pass';
1222
+ return { report: { verdict, subjectDigest: subjectState.digest, findings: { total: findings.length, p0, p1, p2: findings.filter(value => value.severity === 'P2').length },
1223
+ evidenceCount: evidence.length, evidenceValid: allValid, evidenceSummary: summary, riskLedgerValid, riskLedger: input.riskLedger ?? [],
1224
+ pending, evidenceDiagnostics: states.map((value, index) => ({ index, state: value.state, reason: value.reason ?? null })),
1225
+ routing: validatorRepairRoutes(findings) }, findings, evidence };
1226
+ }
1227
+
1228
+ function functionalVerifyValidator(input = {}, options = {}) {
1229
+ const subjectState = validateValidatorSubject(input.validationContext, options);
1230
+ const receipts = Array.isArray(input.receipts) ? input.receipts : [];
1231
+ const duplicates = validatorDuplicateNonces(receipts.map(receipt => ({ receipt })));
1232
+ const states = receipts.map(receipt => duplicates.has(receipt?.nonce) ? { state: 'unverifiable', reason: 'Duplicate receipt nonce' }
1233
+ : validatorReceiptState(receipt, subjectState, options));
1234
+ const declaration = validatorEvidenceDeclaration(subjectState.subject);
1235
+ if (!declaration.valid || declaration.runner === 'local') states.fill({ state: 'unverifiable', reason: 'Signed policy does not declare independent evidence' });
1236
+ states.forEach((value, index) => {
1237
+ if (value.receipt) states[index] = classifyValidatorEvidence(validatorEvidenceFromReceipt(value.receipt, subjectState, index), subjectState, options);
1238
+ });
1239
+ const evidence = states.flatMap((value, index) => value.state === 'unverifiable' ? [] : [validatorEvidenceFromReceipt(value.receipt, subjectState, index)]);
1240
+ const findings = [...subjectState.findings, ...states.flatMap((value, index) => value.state === 'failed'
1241
+ ? [validatorDiagnostic('GOLDEN-FAIL', `receipt:${index}`, 'Authentic frozen golden baseline failed', 'P1')] : [])];
1242
+ const failed = states.filter(value => value.state === 'failed').length;
1243
+ const incomplete = !subjectState.valid || !receipts.length || states.some(value => value.state === 'unverifiable');
1244
+ const status = findings.some(value => value.severity === 'P0') ? 'blocked' : incomplete ? 'incomplete' : 'succeeded';
1245
+ return { status, subject: subjectState.subject, subjectDigest: subjectState.digest, evidence, findings,
1246
+ results: states.flatMap(value => value.receipt ? [value.receipt.result] : []),
1247
+ summary: { total: receipts.length, passed: states.filter(value => value.state === 'valid').length, failed,
1248
+ pending: 0, unverifiable: states.filter(value => value.state === 'unverifiable').length },
1249
+ receiptDiagnostics: states.map((value, index) => ({ index, state: value.state, reason: value.reason ?? null })) };
1250
+ }
1251
+
1252
+ // First-party Validator logic: schema.mjs
1253
+
1254
+ function augmentValidatorSchemas(original) {
1255
+ const schemas = structuredClone(original);
1256
+ const execution = { type: 'object', additionalProperties: false, properties: {
1257
+ exitCode: { type: ['integer', 'null'] }, signal: { type: ['string', 'null'] },
1258
+ timedOut: { type: 'boolean' }, outputLimitExceeded: { type: 'boolean' },
1259
+ }, required: ['exitCode', 'signal', 'timedOut', 'outputLimitExceeded'] };
1260
+ function visit(value) {
1261
+ if (!value || typeof value !== 'object') return;
1262
+ if (value.properties?.schemaVersion?.const === 'validator.validation-subject/1.0') {
1263
+ value.properties.execution = execution;
1264
+ value.properties.equivalence = { type: 'array', items: { type: 'object' } };
1265
+ value.properties.disciplineComparison = { type: 'object' };
1266
+ value.properties.policy.properties ??= {};
1267
+ value.properties.policy.properties.parallelPaths = VALIDATOR_PARALLEL_PATHS_SCHEMA;
1268
+ value.properties.policy.properties.disciplineReviewRequired = { type: 'boolean' };
1269
+ const archguard = value.properties.contracts?.properties?.archguard;
1270
+ if (archguard) archguard.properties.contractId = { type: 'string', minLength: 1 };
1271
+ }
1272
+ if (value.properties?.schemaVersion?.const === 'entrorove.test-evidence/2.0') {
1273
+ value.properties.flowId = { type: 'string', minLength: 1 };
1274
+ value.properties.stepId = { type: 'string', minLength: 1 };
1275
+ value.properties.status = { enum: ['pending', 'pending-execution'] };
1276
+ value.properties.evidenceId = { type: 'string', minLength: 1, maxLength: 200 };
1277
+ value.properties.command.maxLength = 2000;
1278
+ value.properties.summary.maxLength = 4000;
1279
+ value.properties.durationMs.maximum = 86_400_000;
1280
+ if (value.properties.runner?.const === 'trusted-runner') value.properties.runner = { enum: ['trusted-runner', 'sandbox'] };
1281
+ }
1282
+ if (value.properties?.schemaVersion?.const === 'validator.execution-receipt/1.0') {
1283
+ value.properties.result.properties.summary.maxLength = 4000;
1284
+ value.properties.result.properties.durationMs.maximum = 86_400_000;
1285
+ value.properties.result.properties.exitCode.minimum = -128;
1286
+ value.properties.result.properties.exitCode.maximum = 255;
1287
+ }
1288
+ for (const item of Object.values(value)) {
1289
+ if (Array.isArray(item)) item.forEach(visit);
1290
+ else if (item && typeof item === 'object') visit(item);
1291
+ }
1292
+ }
1293
+ visit(schemas);
1294
+ const pending = { type: 'object', additionalProperties: false, properties: {
1295
+ operation: { enum: ['sandbox-run', 'fuzz-input', 'perf-benchmark', 'intrusive-test'] },
1296
+ status: { const: 'pending-execution' }, requestedTarget: { type: 'string', minLength: 1 },
1297
+ requirementsSha256: { type: 'string', pattern: '^[0-9a-f]{64}$' },
1298
+ }, required: ['operation', 'status'] };
1299
+ schemas.verdict.input.properties.pending = { type: 'array', items: pending };
1300
+ for (const operation of ['sandbox-run', 'fuzz-input', 'perf-benchmark', 'intrusive-test']) {
1301
+ schemas[operation].output.properties.pendingEvidenceRequirements = { type: 'object' };
1302
+ }
1303
+ for (const key of ['functional-verify', 'verdict']) schemas[key].output.properties.status.enum.push('incomplete');
1304
+ schemas['functional-verify'].input.required = [];
1305
+ schemas['functional-verify'].input.properties.receipts.minItems = 0;
1306
+ schemas['functional-verify'].output.properties.subject.anyOf = [structuredClone(schemas['functional-verify'].output.properties.subject), { type: 'null' }];
1307
+ delete schemas['functional-verify'].output.properties.subject.type;
1308
+ schemas['functional-verify'].output.properties.subjectDigest = { type: ['string', 'null'] };
1309
+ schemas['functional-verify'].output.properties.receiptDiagnostics = { type: 'array', items: { type: 'object' } };
1310
+ return Object.freeze(schemas);
1311
+ }
1312
+
1313
+ // First-party Validator logic: bridge.mjs
1314
+
1315
+ function readValidationSubject(value, entityRef = 'subject') {
1316
+ const state = validateValidatorSubject(value);
1317
+ return state.valid ? { value: state.subject } : { findings: state.findings.map(finding => ({ ...finding, entityRef: finding.entityRef.replace(/^subject/, entityRef) })) };
1318
+ }
1319
+
1320
+ function evidenceState(evidence, subject, subjectDigest, input = {}) {
1321
+ const state = validateValidatorSubject(subject);
1322
+ if (subjectDigest !== state.digest) return 'unverifiable';
1323
+ return classifyValidatorEvidence(evidence, state, validatorRuntimeOptions(input)).state;
1324
+ }
1325
+
1326
+ function validatorRuntimeOptions(input) {
1327
+ return { receiptPublicKey: input.receiptPublicKey ?? process.env.ENTROROVE_VALIDATOR_RECEIPT_PUBLIC_KEY };
1328
+ }
1329
+
1330
+ function runFunctional(requestId, input) {
1331
+ const output = functionalVerifyValidator(input, validatorRuntimeOptions(input));
1332
+ return { schemaVersion: RES, requestId, ...output,
1333
+ nextStep: { operation: output.status === 'blocked' ? 'plan' : 'verdict',
1334
+ instruction: output.status === 'incomplete' ? 'Provide independently verifiable evidence for the frozen subject.' : 'Render verdict from signed TestEvidence.' } };
1335
+ }
1336
+
1337
+ function runVerdict(requestId, input) {
1338
+ const output = evaluateValidatorVerdict(input, validatorRuntimeOptions(input));
1339
+ const level = output.report.verdict;
1340
+ return { schemaVersion: RES, requestId, status: level === 'blocked' || level === 'incomplete' ? level : 'succeeded', ...output,
1341
+ nextStep: { operation: level === 'blocked' ? 'plan' : level === 'incomplete' ? 'verdict' : null,
1342
+ instruction: level === 'blocked' ? 'Repair failures through the finding-derived routes.'
1343
+ : level === 'incomplete' ? 'Provide independent evidence, resolve actual pending executions and complete the P1 risk ledger.' : `Verdict: ${level}.` } };
1344
+ }
1345
+
1346
+ function runPlanning(requestId, operation, input) {
1347
+ const result = runLegacyValidatorPlanning(requestId, operation, { ...input, findingCategories: [] });
1348
+ if (operation !== 'plan' || result.status !== 'succeeded') return result;
1349
+ const routing = validatorRepairRoutes(input.findings);
1350
+ return { ...result, plan: { ...result.plan, routing, routingPolicy: 'deterministic-finding-map' } };
1351
+ }
1352
+
1353
+ function runLocalProtocol(requestId, operation, input) {
1354
+ const output = runLegacyValidatorLocalProtocol(requestId, operation, input);
1355
+ if (output.status !== 'succeeded') return output;
1356
+ const key = { 'sandbox-run': 'sandbox', 'fuzz-input': 'fuzz', 'perf-benchmark': 'benchmark', 'intrusive-test': 'intrusive' }[operation];
1357
+ const { receiptPublicKey: _hostKey, ...originalInput } = input;
1358
+ return { ...output, [key]: { ...output[key], requirementsSha256: validatorDigest(originalInput) } };
1359
+ }
1360
+
1361
+ const SCHEMAS = augmentValidatorSchemas(LEGACY_VALIDATOR_SCHEMAS);
1362
+ export { readValidationSubject, evidenceState, validatorBindEquivalenceResult, validatorReadEquivalenceBaseline, validatorFieldDifferences, validatorEquivalenceFindings, validatorParallelDeclarationsValid, VALIDATOR_PARALLEL_PATHS_SCHEMA };