From 58b04f685a9b7487ac60e1b0754c9db40a2b8480 Mon Sep 17 00:00:00 2001 From: Forge Date: Tue, 22 Sep 2026 17:11:21 +0000 Subject: [PATCH] test: retain auditable shadow identity and query observations --- docs/design/semantic-dependency-shadow.md | 4 +++- docs/validation/dependency-shadow/recorder.cjs | 6 +++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/docs/design/semantic-dependency-shadow.md b/docs/design/semantic-dependency-shadow.md index 312c53aac..6120eb432 100644 --- a/docs/design/semantic-dependency-shadow.md +++ b/docs/design/semantic-dependency-shadow.md @@ -52,7 +52,9 @@ properties, preserve all remaining values and array order. This is a new interna not transparent interception of arbitrary v1 callbacks. The evaluator cannot read the omitted fields. Returned arrays/objects are detached copies. All empty, false and null results are recorded, not just successful symbols. Each observation stores operation, -arguments, complete result SHA256, serialized value byte count and empty-result marker. +arguments, complete result value and SHA256, serialized value byte count and empty-result marker. +Diagnostic rows retain invocation identity, premerge owned output and the certified +SQLite edge set so the receipt hashes and comparisons can be independently recomputed. No previous returned member list substitutes for reexecuting a query predicate. Identity binds canonical project path, loaded adapter/observer/engine bytes, engine diff --git a/docs/validation/dependency-shadow/recorder.cjs b/docs/validation/dependency-shadow/recorder.cjs index 870c7efbd..2af08304c 100644 --- a/docs/validation/dependency-shadow/recorder.cjs +++ b/docs/validation/dependency-shadow/recorder.cjs @@ -83,7 +83,7 @@ class Session { const finalizeStart = clock(); // Never throw an observer failure into the successful engine operation. const record = { name, success: !error, certified: false, phases: op.phases, ...op.pending?.metrics, - authoritativeInvocations: op.invocations, graphMarker: null, trace: op.pending?.trace || [], + authoritativeInvocations: op.invocations, graphMarker: null, identity: op.pending?.identity || null, trace: op.pending?.trace || [], owned: op.pending?.owned || [], reason: error ? 'operation-failed' : op.reason || 'not-observed' }; try { const state = dbState(this.root); record.graphMarker = state.marker; @@ -98,7 +98,7 @@ class Session { const sort = es => normalize(es).sort((a, b) => JSON.stringify(a).localeCompare(JSON.stringify(b))); if (hash(sort(actual)) !== hash(sort(expected))) throw Error('committed SQLite/owned merge mismatch'); this.committed = { identity: p.identity, trace: p.trace, owned: copy(p.owned), marker: state.marker, bytes: p.metrics.traceBytes }; - record.certified = true; record.reason = 'committed'; record.sqliteHash = hash(sort(actual)); + record.certified = true; record.reason = 'committed'; record.sqlite = sort(actual); record.sqliteHash = hash(record.sqlite); } else if (error || op.reason || !op.graphCommitted) this.committed = null; } catch (e) { record.reason = 'observer:' + e.message; this.committed = null; this.disabled = true; } record.finalizeMs = clock() - finalizeStart; @@ -141,7 +141,7 @@ function invoke(root, ctx, options) { const t = clock(); if (op.fault === 'capture-error') throw Error('injected observer failure'); if (methods.has(method) && !reason) { - const entry = { method, args: copy(args), hash: hash(value), valueBytes: Buffer.byteLength(JSON.stringify(value)), + const entry = { method, args: copy(args), value: copy(value), hash: hash(value), valueBytes: Buffer.byteLength(JSON.stringify(value)), empty: value === null || value === false || (Array.isArray(value) && value.length === 0) }; traceBytes += Buffer.byteLength(JSON.stringify(entry)); if (trace.length >= session.options.maxReads || traceBytes > session.options.maxBytes) reason = 'observation-budget';