diff --git a/audit/frontier/ALLOY_STATE_NATIVE_RUNTIME_POSTMERGE_HARDENING_PROOF_2026-08-13.md b/audit/frontier/ALLOY_STATE_NATIVE_RUNTIME_POSTMERGE_HARDENING_PROOF_2026-08-13.md new file mode 100644 index 000000000..805a96e00 --- /dev/null +++ b/audit/frontier/ALLOY_STATE_NATIVE_RUNTIME_POSTMERGE_HARDENING_PROOF_2026-08-13.md @@ -0,0 +1,40 @@ +# Proof Packet - State-Native Runtime Post-Merge Hardening + +**Workcell:** A11OY-STATE-001-C +**Date:** 2026-08-13 +**Repository:** szl-holdings/platform +**Protected base:** `58812fff46f8c5f18979d089fd1b7a059d6892d7` +**Claim level:** source implementation pending exact-head protected qualification + +## Context + +An independent read-only audit of the state-native runtime on protected main found four remaining +mutable-boundary defects after PR #595 merged. Hosted checks on #595 were green, but no independent +Codex review completed because the review service reported a usage-limit block. + +## Patch + +- Read each registered kernel field once, validate those local values, and bind admitted prototype + methods to the original receiver before freezing the definition. +- Read verifier `passed`, `reason`, and `evidenceDigests` once, copy each evidence element once, and + use only that validated snapshot for the mandatory decision and receipt. +- Read each epoch-validation check field once before shape validation, state selection, and storage. +- Give kernel execution and mandatory verification independent deep copies of capsule metadata and + payload bytes. +- Preserve the complete governed-request snapshot, complete cognitive-epoch specification snapshot, + and closed policy-effect set already present on protected main. + +## Adversarial regressions + +The focused tests exercise verifier accessor flips, validation-check accessor flips, class private +receiver state, distinct nested capsule identities, caller mutation across admission and receipt +fields, and one-read coverage for every declared cognitive-epoch field. + +## Truth boundary + +This packet records source changes only. Local validation, hosted CI, independent exact-head review, +merge qualification, deployment, runtime operation, and external witness state remain unverified +until separately observed at the exact successor head. + +No UI route, deployment, database, DNS, secret, branch-protection setting, or external account is +changed by this workcell. No production or customer-runtime claim is made. diff --git a/packages/a11oy-runtime/src/state-native/epoch-manager.ts b/packages/a11oy-runtime/src/state-native/epoch-manager.ts index 9999c84e8..cf724b738 100644 --- a/packages/a11oy-runtime/src/state-native/epoch-manager.ts +++ b/packages/a11oy-runtime/src/state-native/epoch-manager.ts @@ -28,7 +28,9 @@ function specDigest(spec: CognitiveEpochSpec): string { function freezeRecord(record: CognitiveEpochRecord): CognitiveEpochRecord { return Object.freeze({ ...record, - validationChecks: Object.freeze(record.validationChecks.map((check) => Object.freeze({ ...check }))), + validationChecks: Object.freeze( + record.validationChecks.map((check) => Object.freeze({ ...check })), + ), }); } @@ -90,7 +92,11 @@ export class CognitiveEpochManager { } public validate(epochId: string, checks: readonly EpochValidationCheck[]): CognitiveEpochRecord { - assertStateNative(checks.length > 0, 'INVALID_INPUT', 'At least one epoch validation check is required.'); + assertStateNative( + checks.length > 0, + 'INVALID_INPUT', + 'At least one epoch validation check is required.', + ); const current = this.require(epochId); if (current.state !== 'PREPARED') { throw new StateNativeError( @@ -106,22 +112,25 @@ export class CognitiveEpochManager { 'INVALID_INPUT', 'Cognitive epoch validation checks must be objects.', ); + const name = check.name; + const passed = check.passed; + const detail = check.detail; assertStateNative( - typeof check.name === 'string' && check.name.trim().length > 0, + typeof name === 'string' && name.trim().length > 0, 'INVALID_INPUT', 'Cognitive epoch validation check name must be a non-empty string.', ); assertStateNative( - typeof check.passed === 'boolean', + typeof passed === 'boolean', 'INVALID_INPUT', 'Cognitive epoch validation check passed must be a boolean.', ); assertStateNative( - typeof check.detail === 'string' && check.detail.trim().length > 0, + typeof detail === 'string' && detail.trim().length > 0, 'INVALID_INPUT', 'Cognitive epoch validation check detail must be a non-empty string.', ); - return Object.freeze({ name: check.name, passed: check.passed, detail: check.detail }); + return Object.freeze({ name, passed, detail }); }), ); const state = normalizedChecks.every((check) => check.passed) ? 'VALIDATED' : 'REJECTED'; @@ -181,12 +190,23 @@ export class CognitiveEpochManager { return next; } - public rollback(activeEpochId: string, targetEpochId: string, reason: string): CognitiveEpochRecord { - assertStateNative(reason.trim().length > 0, 'INVALID_INPUT', 'Rollback reason must not be empty.'); + public rollback( + activeEpochId: string, + targetEpochId: string, + reason: string, + ): CognitiveEpochRecord { + assertStateNative( + reason.trim().length > 0, + 'INVALID_INPUT', + 'Rollback reason must not be empty.', + ); const active = this.require(activeEpochId); const target = this.require(targetEpochId); if (active.state !== 'ACTIVE') { - throw new StateNativeError('INVALID_TRANSITION', 'Only an active cognitive epoch can be rolled back.'); + throw new StateNativeError( + 'INVALID_TRANSITION', + 'Only an active cognitive epoch can be rolled back.', + ); } if (active.tenantId !== target.tenantId || active.route !== target.route) { throw new StateNativeError( @@ -232,23 +252,33 @@ export class CognitiveEpochManager { const routeKey = this.#routeKey(tenantId, route); const epochId = this.#activeByTenantRoute.get(routeKey); if (!epochId) { - throw new StateNativeError('EPOCH_NOT_ACTIVE', 'No active cognitive epoch exists for this route.', { - tenantId, - route, - }); + throw new StateNativeError( + 'EPOCH_NOT_ACTIVE', + 'No active cognitive epoch exists for this route.', + { + tenantId, + route, + }, + ); } if (expectedEpochId && expectedEpochId !== epochId) { - throw new StateNativeError('EPOCH_NOT_ACTIVE', 'The requested cognitive epoch is not active.', { - tenantId, - route, - expectedEpochId, - activeEpochId: epochId, - }); + throw new StateNativeError( + 'EPOCH_NOT_ACTIVE', + 'The requested cognitive epoch is not active.', + { + tenantId, + route, + expectedEpochId, + activeEpochId: epochId, + }, + ); } const current = this.require(epochId); if (current.state !== 'ACTIVE') { - throw new StateNativeError('EPOCH_NOT_ACTIVE', 'Cognitive epoch is no longer active.', { epochId }); + throw new StateNativeError('EPOCH_NOT_ACTIVE', 'Cognitive epoch is no longer active.', { + epochId, + }); } const pinned = freezeRecord({ ...current, leaseCount: current.leaseCount + 1 }); this.#records.set(epochId, pinned); diff --git a/packages/a11oy-runtime/src/state-native/kernel-runtime.ts b/packages/a11oy-runtime/src/state-native/kernel-runtime.ts index e0b8a173d..09a1263f2 100644 --- a/packages/a11oy-runtime/src/state-native/kernel-runtime.ts +++ b/packages/a11oy-runtime/src/state-native/kernel-runtime.ts @@ -104,10 +104,18 @@ function epochCompatibility( adapterSetDigest: epoch.adapterSetDigest, semanticSpaceDigest: request.inputCompatibility.semanticSpaceDigest ?? - digestObject({ kernelId: definition.kernelId, version: definition.version, semantic: output.stateType }), + digestObject({ + kernelId: definition.kernelId, + version: definition.version, + semantic: output.stateType, + }), schemaDigest: request.inputCompatibility.schemaDigest ?? - digestObject({ kernelId: definition.kernelId, version: definition.version, stateType: output.stateType }), + digestObject({ + kernelId: definition.kernelId, + version: definition.version, + stateType: output.stateType, + }), policyDigest: epoch.policyDigest, cognitiveEpoch: epoch.epochId, providerSessionId: request.inputCompatibility.providerSessionId, @@ -153,7 +161,11 @@ function validateInputCompatibilityAgainstEpoch( cognitiveEpoch: epoch.epochId, }; const mandatory = new Set(['policyDigest', 'cognitiveEpoch']); - const mismatches: Array<{ readonly field: string; readonly expected: string; readonly actual?: string }> = []; + const mismatches: Array<{ + readonly field: string; + readonly expected: string; + readonly actual?: string; + }> = []; for (const [field, expectedValue] of Object.entries(expected) as Array< [keyof typeof expected, string] @@ -179,13 +191,12 @@ function defaultGovernance( ): StateGovernance { const minimumSensitivity = inputCapsules.length > 0 ? highestSensitivity(inputCapsules) : 'public'; - const governance = - output.governance ?? { - sensitivity: inputCapsules.length > 0 ? minimumSensitivity : 'internal', - retentionClass: 'session', - reusePolicy: 'same_session', - evidenceTier: 'MEASURED', - }; + const governance = output.governance ?? { + sensitivity: inputCapsules.length > 0 ? minimumSensitivity : 'internal', + retentionClass: 'session', + reusePolicy: 'same_session', + evidenceTier: 'MEASURED', + }; assertStateNative( SENSITIVITY_RANK[governance.sensitivity] >= SENSITIVITY_RANK[minimumSensitivity], 'REUSE_DENIED', @@ -209,9 +220,7 @@ function producedStateSnapshot( compatibility: produced.compatibility ? Object.freeze({ ...produced.compatibility }) : undefined, - governance: produced.governance - ? Object.freeze({ ...produced.governance }) - : undefined, + governance: produced.governance ? Object.freeze({ ...produced.governance }) : undefined, }), ), ); @@ -255,14 +264,61 @@ function requestSnapshot(request: KernelExecutionRequest): KernelExecutionReques } function definitionSnapshot(definition: KernelDefinition): KernelDefinition { + const kernelId = definition.kernelId; + const version = definition.version; + const kind = definition.kind; + const route = definition.route; + const requiresVerification = definition.requiresVerification; + const execute = definition.execute; + const verify = definition.verify; + + assertStateNative( + typeof kernelId === 'string' && kernelId.trim().length > 0, + 'INVALID_INPUT', + 'kernelId must not be empty.', + ); + assertStateNative( + typeof version === 'string' && version.trim().length > 0, + 'INVALID_INPUT', + 'kernel version must not be empty.', + ); + assertStateNative( + typeof route === 'string' && route.trim().length > 0, + 'INVALID_INPUT', + 'kernel route must not be empty.', + ); + assertStateNative(KERNEL_KINDS.has(kind), 'INVALID_INPUT', 'kernel kind is unsupported.'); + assertStateNative( + typeof requiresVerification === 'boolean', + 'INVALID_INPUT', + 'requiresVerification must be boolean.', + ); + assertStateNative( + typeof execute === 'function', + 'INVALID_INPUT', + 'kernel execute must be callable.', + ); + assertStateNative( + verify === undefined || typeof verify === 'function', + 'INVALID_INPUT', + 'kernel verify must be callable when present.', + ); + if (requiresVerification && !verify) { + throw new StateNativeError( + 'INVALID_INPUT', + 'A verification-required kernel must provide an independent verifier.', + { kernelId }, + ); + } + return Object.freeze({ - kernelId: definition.kernelId, - version: definition.version, - kind: definition.kind, - route: definition.route, - requiresVerification: definition.requiresVerification, - execute: definition.execute, - verify: definition.verify, + kernelId, + version, + kind, + route, + requiresVerification, + execute: execute.bind(definition), + verify: verify?.bind(definition), }); } @@ -270,7 +326,7 @@ function stateReadSnapshot(input: readonly StateReadResult[]): readonly StateRea return Object.freeze( input.map((item) => Object.freeze({ - capsule: item.capsule, + capsule: boundarySnapshot(item.capsule, 'State capsule'), payload: Uint8Array.from(item.payload), }), ), @@ -306,10 +362,25 @@ function kernelContextSnapshot(input: { } function verifierResultSnapshot(result: KernelVerifierResult): KernelVerifierResult { - assertStateNative(typeof result.passed === 'boolean', 'INVALID_INPUT', 'Verifier passed must be boolean.'); - assertStateNative(typeof result.reason === 'string', 'INVALID_INPUT', 'Verifier reason must be a string.'); - assertStateNative(Array.isArray(result.evidenceDigests), 'INVALID_INPUT', 'Verifier evidenceDigests must be an array.'); - const evidenceDigests = Object.freeze([...result.evidenceDigests]); + const passed = result.passed; + const reason = result.reason; + const rawEvidenceDigests = result.evidenceDigests; + assertStateNative( + typeof passed === 'boolean', + 'INVALID_INPUT', + 'Verifier passed must be boolean.', + ); + assertStateNative( + typeof reason === 'string', + 'INVALID_INPUT', + 'Verifier reason must be a string.', + ); + assertStateNative( + Array.isArray(rawEvidenceDigests), + 'INVALID_INPUT', + 'Verifier evidenceDigests must be an array.', + ); + const evidenceDigests = Object.freeze(Array.from(rawEvidenceDigests)); for (const digest of evidenceDigests) { assertStateNative( typeof digest === 'string' && /^[0-9a-f]{64}$/u.test(digest), @@ -318,8 +389,8 @@ function verifierResultSnapshot(result: KernelVerifierResult): KernelVerifierRes ); } return Object.freeze({ - passed: result.passed, - reason: result.reason, + passed, + reason, evidenceDigests, }); } @@ -357,7 +428,11 @@ function validateBudget(request: KernelExecutionRequest): void { ); } -function validateApproval(approval: ApprovalEvidence | undefined, requestDigest: string, actionId: string): void { +function validateApproval( + approval: ApprovalEvidence | undefined, + requestDigest: string, + actionId: string, +): void { if (!approval) { throw new StateNativeError('APPROVAL_REQUIRED', 'Policy requires exact approval evidence.'); } @@ -368,7 +443,10 @@ function validateApproval(approval: ApprovalEvidence | undefined, requestDigest: { approvalId: approval.approvalId }, ); } - if (!Number.isFinite(Date.parse(approval.approvedAt)) || approval.approvedBy.trim().length === 0) { + if ( + !Number.isFinite(Date.parse(approval.approvedAt)) || + approval.approvedBy.trim().length === 0 + ) { throw new StateNativeError('APPROVAL_REQUIRED', 'Approval evidence is malformed.', { approvalId: approval.approvalId, }); @@ -393,41 +471,6 @@ export class AlloyKernelRuntime { } public register(definition: KernelDefinition): void { - assertStateNative( - typeof definition.kernelId === 'string' && definition.kernelId.trim().length > 0, - 'INVALID_INPUT', - 'kernelId must not be empty.', - ); - assertStateNative( - typeof definition.version === 'string' && definition.version.trim().length > 0, - 'INVALID_INPUT', - 'kernel version must not be empty.', - ); - assertStateNative( - typeof definition.route === 'string' && definition.route.trim().length > 0, - 'INVALID_INPUT', - 'kernel route must not be empty.', - ); - assertStateNative(KERNEL_KINDS.has(definition.kind), 'INVALID_INPUT', 'kernel kind is unsupported.'); - assertStateNative( - typeof definition.requiresVerification === 'boolean', - 'INVALID_INPUT', - 'requiresVerification must be boolean.', - ); - assertStateNative(typeof definition.execute === 'function', 'INVALID_INPUT', 'kernel execute must be callable.'); - assertStateNative( - definition.verify === undefined || typeof definition.verify === 'function', - 'INVALID_INPUT', - 'kernel verify must be callable when present.', - ); - if (definition.requiresVerification && !definition.verify) { - throw new StateNativeError( - 'INVALID_INPUT', - 'A verification-required kernel must provide an independent verifier.', - { kernelId: definition.kernelId }, - ); - } - const snapshot = definitionSnapshot(definition); if (this.#kernels.has(snapshot.kernelId)) { throw new StateNativeError('DIVERGENT_REPLAY', 'Kernel identifier is already registered.', { @@ -437,10 +480,15 @@ export class AlloyKernelRuntime { this.#kernels.set(snapshot.kernelId, snapshot); } - public listKernels(): readonly Pick[] { + public listKernels(): readonly Pick< + KernelDefinition, + 'kernelId' | 'version' | 'kind' | 'route' + >[] { return Object.freeze( [...this.#kernels.values()] - .map(({ kernelId, version, kind, route }) => Object.freeze({ kernelId, version, kind, route })) + .map(({ kernelId, version, kind, route }) => + Object.freeze({ kernelId, version, kind, route }), + ) .sort((left, right) => left.kernelId.localeCompare(right.kernelId)), ); } @@ -449,7 +497,9 @@ export class AlloyKernelRuntime { const request = requestSnapshot(inputRequest); const definition = this.#kernels.get(request.kernelId); if (!definition) { - throw new StateNativeError('NOT_FOUND', 'Kernel is not registered.', { kernelId: request.kernelId }); + throw new StateNativeError('NOT_FOUND', 'Kernel is not registered.', { + kernelId: request.kernelId, + }); } validateBudget(request); @@ -502,7 +552,10 @@ export class AlloyKernelRuntime { this.#idempotency.set(idempotencyKey, { requestDigest: replayDigest, status: 'IN_FLIGHT' }); } if (lease.epoch.route !== definition.route) { - throw new StateNativeError('EPOCH_NOT_ACTIVE', 'Kernel route does not match the pinned epoch.'); + throw new StateNativeError( + 'EPOCH_NOT_ACTIVE', + 'Kernel route does not match the pinned epoch.', + ); } validateInputCompatibilityAgainstEpoch(lease.epoch, request.inputCompatibility); @@ -515,7 +568,9 @@ export class AlloyKernelRuntime { outcome: 'blocked', reason: decision.reason, runtimeMs: performance.now() - startedAt, - inputCapsules: request.inputCapsuleIds.map((id) => this.#stateBus.requireMetadata(id, request.tenantId)), + inputCapsules: request.inputCapsuleIds.map((id) => + this.#stateBus.requireMetadata(id, request.tenantId), + ), outputCapsules: [], }); receiptWritten = true; @@ -531,7 +586,8 @@ export class AlloyKernelRuntime { request.authorization.envelope.actionId, ); } catch (error) { - const reason = error instanceof Error ? error.message : 'Approval evidence failed validation.'; + const reason = + error instanceof Error ? error.message : 'Approval evidence failed validation.'; const receipt = await this.#writeTerminalReceipt({ request, definition, @@ -566,10 +622,14 @@ export class AlloyKernelRuntime { inputCapsules = input.map((item) => item.capsule); const inputBytes = input.reduce((total, item) => total + item.payload.byteLength, 0); if (inputBytes > request.budget.maxInputBytes) { - throw new StateNativeError('BUDGET_EXCEEDED', 'Kernel input exceeds the declared byte budget.', { - inputBytes, - maxInputBytes: request.budget.maxInputBytes, - }); + throw new StateNativeError( + 'BUDGET_EXCEEDED', + 'Kernel input exceeds the declared byte budget.', + { + inputBytes, + maxInputBytes: request.budget.maxInputBytes, + }, + ); } const controller = new AbortController(); @@ -585,10 +645,7 @@ export class AlloyKernelRuntime { const rawOutput = await this.#runWithDeadline( () => { executionStarted = true; - return definition.execute( - kernelInputSnapshot(input, request.parameters), - makeContext(), - ); + return definition.execute(kernelInputSnapshot(input, request.parameters), makeContext()); }, controller, deadlineAt, @@ -604,10 +661,14 @@ export class AlloyKernelRuntime { } const outputBytes = output.reduce((total, item) => total + item.payload.byteLength, 0); if (outputBytes > request.budget.maxOutputBytes) { - throw new StateNativeError('BUDGET_EXCEEDED', 'Kernel output exceeds the declared byte budget.', { - outputBytes, - maxOutputBytes: request.budget.maxOutputBytes, - }); + throw new StateNativeError( + 'BUDGET_EXCEEDED', + 'Kernel output exceeds the declared byte budget.', + { + outputBytes, + maxOutputBytes: request.budget.maxOutputBytes, + }, + ); } const verifier = definition.verify diff --git a/packages/a11oy-runtime/test/state-native-runtime-boundary-regressions.test.mjs b/packages/a11oy-runtime/test/state-native-runtime-boundary-regressions.test.mjs index c497621d8..c7c5b1d14 100644 --- a/packages/a11oy-runtime/test/state-native-runtime-boundary-regressions.test.mjs +++ b/packages/a11oy-runtime/test/state-native-runtime-boundary-regressions.test.mjs @@ -143,7 +143,11 @@ test('registered kernel invariants cannot be downgraded through caller mutation' }, verify: async () => { verifierCalls += 1; - return { passed: false, reason: 'Independent verifier rejected output.', evidenceDigests: [] }; + return { + passed: false, + reason: 'Independent verifier rejected output.', + evidenceDigests: [], + }; }, }; runtime.register(definition); @@ -252,6 +256,8 @@ test('kernel input mutation cannot alter the verifier input snapshot', async () const active = prepareEpoch(manager, 'epoch_input_snapshot', 'rev-input'); const ledger = []; const trusted = Buffer.from('trusted-input'); + let executionCapsule; + let verifierCapsule; try { const input = await bus.put({ tenantId: 'tenant_a', @@ -276,14 +282,27 @@ test('kernel input mutation cannot alter the verifier input snapshot', async () route: 'state.test', requiresVerification: true, execute: async ({ capsules }) => { + executionCapsule = capsules[0].capsule; capsules[0].payload.fill(0); + try { + capsules[0].capsule.governance.sensitivity = 'public'; + } catch {} + try { + capsules[0].capsule.provenance.parentCapsuleIds.push('forged-parent'); + } catch {} return [outputState('accepted')]; }, - verify: async (_outputs, { capsules }) => ({ - passed: Buffer.from(capsules[0].payload).equals(trusted), - reason: 'Verifier received an isolated input snapshot.', - evidenceDigests: [], - }), + verify: async (_outputs, { capsules }) => { + verifierCapsule = capsules[0].capsule; + return { + passed: + Buffer.from(capsules[0].payload).equals(trusted) && + capsules[0].capsule.governance.sensitivity === 'confidential' && + !capsules[0].capsule.provenance.parentCapsuleIds.includes('forged-parent'), + reason: 'Verifier received isolated capsule metadata and payload bytes.', + evidenceDigests: [], + }; + }, }); const request = requestFor({ actionId: 'runtime-action-input-snapshot', @@ -295,18 +314,24 @@ test('kernel input mutation cannot alter the verifier input snapshot', async () const result = await runtime.execute(request); assert.equal(result.receipt.outcome, 'success'); assert.equal(result.receipt.verifier.passed, true); + assert.notEqual(executionCapsule, verifierCapsule); + assert.notEqual(executionCapsule, input); + assert.notEqual(verifierCapsule, input); + assert.notEqual(executionCapsule.governance, verifierCapsule.governance); + assert.notEqual(executionCapsule.provenance, verifierCapsule.provenance); } finally { bus.dispose(); stateKey.fill(0); } }); -test('caller mutation after execute starts cannot expand the private request budget', async () => { +test('caller mutation after execute starts cannot alter admission, execution, or receipt fields', async () => { const stateKey = randomBytes(32); const bus = new AlloyStateBus({ masterKey: stateKey }); const manager = new CognitiveEpochManager(); const active = prepareEpoch(manager, 'epoch_request_snapshot', 'rev-request'); const ledger = []; + let observed; try { const runtime = createRuntime(bus, manager, ledger); runtime.register({ @@ -315,8 +340,15 @@ test('caller mutation after execute starts cannot expand the private request bud kind: 'custom', route: 'state.test', requiresVerification: false, - execute: async () => { + execute: async (input, context) => { await new Promise((resolve) => setImmediate(resolve)); + observed = { + actionId: context.actionId, + tenantId: context.tenantId, + sessionId: context.sessionId, + parameterValue: input.parameters.nested.value, + inputCount: input.capsules.length, + }; return [outputState('one'), outputState('two')]; }, }); @@ -327,10 +359,36 @@ test('caller mutation after execute starts cannot expand the private request bud epochId: active.epochId, }); const execution = runtime.execute(request); + request.authorization.decision.effect = 'block'; + request.authorization.decision.reason = 'Caller changed the policy decision.'; + request.authorization.envelope.actionId = 'mutated-action'; + request.authorization.envelope.tenantId = 'mutated-tenant'; + request.authorization.envelope.argsDigest = '0'.repeat(64); + request.authorization.allowedSensitivities.length = 0; + request.tenantId = 'mutated-tenant'; + request.sessionId = 'mutated-session'; + request.epochId = 'mutated-epoch'; + request.parameters.nested.value = 99; + request.inputCapsuleIds.push('forged-capsule'); request.budget.maxStateWrites = 2; request.budget.maxOutputBytes = 8192; await assert.rejects(execution, expectCode('BUDGET_EXCEEDED')); + assert.deepEqual(observed, { + actionId: 'runtime-action-request-snapshot', + tenantId: 'tenant_a', + sessionId: 'session_a', + parameterValue: 1, + inputCount: 0, + }); assert.equal(ledger.length, 1); + assert.equal(ledger[0].outcome, 'error'); + assert.equal(ledger[0].actionId, 'runtime-action-request-snapshot'); + assert.equal(ledger[0].tenantId, 'tenant_a'); + assert.equal(ledger[0].sessionId, 'session_a'); + assert.equal(ledger[0].epochId, active.epochId); + assert.equal(ledger[0].policyEffect, 'allow'); + assert.equal(ledger[0].policyReason, 'Boundary regression policy decision.'); + assert.deepEqual(ledger[0].inputCapsuleIds, []); assert.equal(ledger[0].budget.maxStateWrites, 1); } finally { bus.dispose(); @@ -360,7 +418,11 @@ test('verifier closure mutation cannot change the output snapshot selected for p }, verify: async () => { rawOutput[0].payload.fill(0); - return { passed: true, reason: 'Verifier accepted the immutable output snapshot.', evidenceDigests: [] }; + return { + passed: true, + reason: 'Verifier accepted the immutable output snapshot.', + evidenceDigests: [], + }; }, }); const request = requestFor({ diff --git a/packages/a11oy-runtime/test/state-native-runtime-security.test.mjs b/packages/a11oy-runtime/test/state-native-runtime-security.test.mjs index 80437889d..b64b7e719 100644 --- a/packages/a11oy-runtime/test/state-native-runtime-security.test.mjs +++ b/packages/a11oy-runtime/test/state-native-runtime-security.test.mjs @@ -64,7 +64,14 @@ function bindRequest(provisional) { }; } -function requestFor({ actionId, kernelId, compatibility, epochId, inputCapsuleIds = [], idempotencyKey }) { +function requestFor({ + actionId, + kernelId, + compatibility, + epochId, + inputCapsuleIds = [], + idempotencyKey, +}) { return bindRequest({ authorization: { envelope: { @@ -78,7 +85,10 @@ function requestFor({ actionId, kernelId, compatibility, epochId, inputCapsuleId requestedAt: new Date().toISOString(), argsDigest: '', }, - decision: { effect: 'allow', reason: 'Bounded security conformance policy allows execution.' }, + decision: { + effect: 'allow', + reason: 'Bounded security conformance policy allows execution.', + }, allowedSensitivities: ['confidential'], }, kernelId, @@ -157,7 +167,10 @@ test('a request pinned to a new epoch cannot consume state using an older epoch await assert.rejects(runtime.execute(request), expectCode('COMPATIBILITY_MISMATCH')); assert.equal(executed, false); assert.equal(ledger.length, 2); - assert.equal(ledger.every((receipt) => receipt.outcome === 'error'), true); + assert.equal( + ledger.every((receipt) => receipt.outcome === 'error'), + true, + ); } finally { bus.dispose(); stateKey.fill(0); @@ -243,7 +256,11 @@ test('a verifier cannot mutate the bytes that are persisted after verification', ], verify: async (outputs) => { outputs[0].payload.fill(0); - return { passed: true, reason: 'Verifier accepted its isolated copy.', evidenceDigests: [] }; + return { + passed: true, + reason: 'Verifier accepted its isolated copy.', + evidenceDigests: [], + }; }, }); @@ -268,6 +285,133 @@ test('a verifier cannot mutate the bytes that are persisted after verification', } }); +test('every kernel definition field is read once before validation and storage', async () => { + const stateKey = randomBytes(32); + const bus = new AlloyStateBus({ masterKey: stateKey }); + const manager = new CognitiveEpochManager(); + const active = prepareEpoch(manager, 'epoch_definition_accessors', 'rev-definition-accessors'); + const { privateKey } = generateKeyPairSync('ed25519'); + const ledger = []; + const reads = { + kernelId: 0, + version: 0, + kind: 0, + route: 0, + requiresVerification: 0, + execute: 0, + verify: 0, + }; + const calls = { execute: 0, verify: 0 }; + const admittedExecute = async () => { + calls.execute += 1; + return []; + }; + const admittedVerify = async () => { + calls.verify += 1; + return { + passed: false, + reason: 'The first-read verifier rejects this execution.', + evidenceDigests: [], + }; + }; + try { + const runtime = new AlloyKernelRuntime({ + stateBus: bus, + epochManager: manager, + config: { + receiptSigner: { keyId: 'test-key', privateKey }, + receiptWriter: async (receipt) => ledger.push(receipt), + }, + }); + const definition = {}; + Object.defineProperties(definition, { + kernelId: { + enumerable: true, + get() { + reads.kernelId += 1; + return reads.kernelId === 1 ? 'state.definition-accessors' : ''; + }, + }, + version: { + enumerable: true, + get() { + reads.version += 1; + return reads.version === 1 ? '1.0.0' : ''; + }, + }, + kind: { + enumerable: true, + get() { + reads.kind += 1; + return reads.kind === 1 ? 'custom' : 'forged'; + }, + }, + route: { + enumerable: true, + get() { + reads.route += 1; + return reads.route === 1 ? 'state.test' : ''; + }, + }, + requiresVerification: { + enumerable: true, + get() { + reads.requiresVerification += 1; + return reads.requiresVerification === 1; + }, + }, + execute: { + enumerable: true, + get() { + reads.execute += 1; + return reads.execute === 1 ? admittedExecute : undefined; + }, + }, + verify: { + enumerable: true, + get() { + reads.verify += 1; + return reads.verify === 1 ? admittedVerify : undefined; + }, + }, + }); + + runtime.register(definition); + assert.deepEqual(reads, { + kernelId: 1, + version: 1, + kind: 1, + route: 1, + requiresVerification: 1, + execute: 1, + verify: 1, + }); + + const request = requestFor({ + actionId: 'runtime-action-definition-accessors', + kernelId: 'state.definition-accessors', + compatibility: active.compatibility, + epochId: 'epoch_definition_accessors', + }); + await assert.rejects(runtime.execute(request), expectCode('VERIFICATION_FAILED')); + assert.deepEqual(reads, { + kernelId: 1, + version: 1, + kind: 1, + route: 1, + requiresVerification: 1, + execute: 1, + verify: 1, + }); + assert.deepEqual(calls, { execute: 1, verify: 1 }); + assert.equal(ledger.length, 1); + assert.equal(ledger[0].outcome, 'blocked'); + } finally { + bus.dispose(); + stateKey.fill(0); + } +}); + test('kernel registration snapshots verification policy and implementation', async () => { const stateKey = randomBytes(32); const bus = new AlloyStateBus({ masterKey: stateKey }); @@ -286,7 +430,12 @@ test('kernel registration snapshots verification policy and implementation', asy }, }); class DefinitionFixture { - constructor() { + #stats; + #reason; + + constructor(receiverStats) { + this.#stats = receiverStats; + this.#reason = 'The admitted verifier rejects this execution.'; this.kernelId = 'state.definition-snapshot'; this.version = '1.0.0'; this.kind = 'custom'; @@ -295,20 +444,20 @@ test('kernel registration snapshots verification policy and implementation', asy } async execute() { - stats.executeCalls += 1; + this.#stats.executeCalls += 1; return []; } async verify() { - stats.verifyCalls += 1; + this.#stats.verifyCalls += 1; return { passed: false, - reason: 'The admitted verifier rejects this execution.', + reason: this.#reason, evidenceDigests: [digestObject({ verifier: 'admitted' })], }; } } - const definition = new DefinitionFixture(); + const definition = new DefinitionFixture(stats); runtime.register(definition); definition.requiresVerification = false; definition.verify = undefined; @@ -337,7 +486,8 @@ test('verifier decisions are read once and evidence digests are snapshotted', as const manager = new CognitiveEpochManager(); const active = prepareEpoch(manager, 'epoch_verifier_result', 'rev-verifier-result'); const { privateKey } = generateKeyPairSync('ed25519'); - let passedReads = 0; + const ledger = []; + const reads = { passed: 0, reason: 0, evidence: 0 }; let evidenceReads = 0; try { const runtime = new AlloyKernelRuntime({ @@ -345,7 +495,7 @@ test('verifier decisions are read once and evidence digests are snapshotted', as epochManager: manager, config: { receiptSigner: { keyId: 'test-key', privateKey }, - receiptWriter: async () => {}, + receiptWriter: async (receipt) => ledger.push(receipt), }, }); const stableEvidenceDigest = digestObject({ verifier: 'stable' }); @@ -358,6 +508,32 @@ test('verifier decisions are read once and evidence digests are snapshotted', as }, }); evidenceDigests.length = 1; + const verifierResult = {}; + Object.defineProperties(verifierResult, { + passed: { + enumerable: true, + get() { + reads.passed += 1; + return reads.passed === 1 ? false : true; + }, + }, + reason: { + enumerable: true, + get() { + reads.reason += 1; + return reads.reason === 1 + ? 'Verifier rejected the snapshotted result.' + : 'Accessor changed the rejection reason.'; + }, + }, + evidenceDigests: { + enumerable: true, + get() { + reads.evidence += 1; + return reads.evidence === 1 ? evidenceDigests : ['not-a-sha256-digest']; + }, + }, + }); runtime.register({ kernelId: 'state.verifier-result', version: '1.0.0', @@ -365,14 +541,7 @@ test('verifier decisions are read once and evidence digests are snapshotted', as route: 'state.test', requiresVerification: true, execute: async () => [], - verify: async () => { - passedReads += 1; - return { - passed: true, - reason: 'Verifier result is stable at the trust boundary.', - evidenceDigests, - }; - }, + verify: async () => verifierResult, }); const request = requestFor({ @@ -381,11 +550,14 @@ test('verifier decisions are read once and evidence digests are snapshotted', as compatibility: active.compatibility, epochId: 'epoch_verifier_result', }); - const result = await runtime.execute(request); - assert.equal(passedReads, 1); + await assert.rejects(runtime.execute(request), expectCode('VERIFICATION_FAILED')); + assert.deepEqual(reads, { passed: 1, reason: 1, evidence: 1 }); assert.equal(evidenceReads, 1); - assert.equal(result.receipt.outcome, 'success'); - assert.equal(result.receipt.verifier.evidenceDigests[0], stableEvidenceDigest); + assert.equal(ledger.length, 1); + assert.equal(ledger[0].outcome, 'blocked'); + assert.equal(ledger[0].verifier.passed, false); + assert.equal(ledger[0].verifier.reason, 'Verifier rejected the snapshotted result.'); + assert.equal(ledger[0].verifier.evidenceDigests[0], stableEvidenceDigest); } finally { bus.dispose(); stateKey.fill(0); @@ -466,6 +638,47 @@ test('cognitive epoch validation checks require non-empty name and detail string ); }); +test('cognitive epoch validation check fields are read and stored exactly once', () => { + const manager = new CognitiveEpochManager(); + prepareEpochDraft(manager, 'epoch_validation_accessor', 'rev-validation-accessor'); + const reads = { name: 0, passed: 0, detail: 0 }; + const check = {}; + Object.defineProperties(check, { + name: { + enumerable: true, + get() { + reads.name += 1; + return reads.name === 1 ? 'self-test' : ''; + }, + }, + passed: { + enumerable: true, + get() { + reads.passed += 1; + return reads.passed === 1 ? false : true; + }, + }, + detail: { + enumerable: true, + get() { + reads.detail += 1; + return reads.detail === 1 ? 'Rejected by the immutable check.' : ''; + }, + }, + }); + + const record = manager.validate('epoch_validation_accessor', [check]); + assert.deepEqual(reads, { name: 1, passed: 1, detail: 1 }); + assert.equal(record.state, 'REJECTED'); + assert.deepEqual(record.validationChecks, [ + { + name: 'self-test', + passed: false, + detail: 'Rejected by the immutable check.', + }, + ]); +}); + test('malformed cognitive epoch digests are rejected before storage', () => { const manager = new CognitiveEpochManager(); assert.throws( @@ -492,19 +705,25 @@ test('malformed cognitive epoch digests are rejected before storage', () => { assert.equal(manager.get('epoch_invalid_digest'), undefined); }); -test('cognitive epoch fields are read once before validation and storage', () => { - const manager = new CognitiveEpochManager(); - const stableTokenizerDigest = digestObject({ tokenizer: 'stable' }); - let tokenizerReads = 0; - const spec = { - epochId: 'epoch_accessor_digest', +test('every cognitive epoch field is read once before validation, hashing, lookup, and storage', () => { + const digestFields = new Set([ + 'tokenizerDigest', + 'layoutDigest', + 'adapterSetDigest', + 'verifierSetDigest', + 'promptBundleDigest', + 'policyDigest', + 'toolManifestDigest', + ]); + const baseSpec = { + epochId: 'epoch_accessor_all_fields', tenantId: 'tenant_a', route: 'state.test', modelId: 'model-accessor', modelRevision: 'rev-accessor', engineId: 'engine-a', engineVersion: '1.0.0', - tokenizerDigest: stableTokenizerDigest, + tokenizerDigest: digestObject({ tokenizer: 'accessor' }), layoutDigest: digestObject({ layout: 'accessor' }), adapterSetDigest: digestObject({ adapters: [] }), verifierSetDigest: digestObject({ verifiers: [] }), @@ -513,18 +732,31 @@ test('cognitive epoch fields are read once before validation and storage', () => toolManifestDigest: digestObject({ tools: [] }), createdAt: new Date().toISOString(), }; - Object.defineProperty(spec, 'tokenizerDigest', { - enumerable: true, - get() { - tokenizerReads += 1; - return tokenizerReads === 1 ? stableTokenizerDigest : 'not-a-sha256-digest'; - }, - }); - const prepared = manager.prepare(spec); - assert.equal(tokenizerReads, 1); - assert.equal(prepared.tokenizerDigest, stableTokenizerDigest); - assert.equal(manager.require(spec.epochId).tokenizerDigest, stableTokenizerDigest); + for (const field of Object.keys(baseSpec)) { + const manager = new CognitiveEpochManager(); + const stableSpec = { ...baseSpec, epochId: `${baseSpec.epochId}_${field}` }; + const stableValue = stableSpec[field]; + const changedValue = digestFields.has(field) + ? digestObject({ field, changed: true }) + : field === 'createdAt' + ? new Date(Date.now() + 60_000).toISOString() + : `${stableValue}-changed`; + let reads = 0; + const accessorSpec = { ...stableSpec }; + Object.defineProperty(accessorSpec, field, { + enumerable: true, + get() { + reads += 1; + return reads === 1 ? stableValue : changedValue; + }, + }); + + const prepared = manager.prepare(accessorSpec); + assert.equal(reads, 1, field); + assert.equal(prepared[field], stableValue, field); + assert.equal(manager.require(stableSpec.epochId)[field], stableValue, field); + } }); test('kernel outputs cannot downgrade the highest input sensitivity', async () => {