mirror of
https://github.com/morluto/rea
synced 2026-10-25 22:08:11 -04:00
fix(evidence): preserve JSON ownership across metadata rebinding (#7036)
This commit is contained in:
@@ -13,8 +13,15 @@ import {
|
||||
parseEvidence,
|
||||
immutableEvidence,
|
||||
withEvidenceCallerInputPath,
|
||||
rebindEvidenceToTargetPath,
|
||||
} from "./evidence.js";
|
||||
import { MAX_JSON_DEPTH, type JsonValue } from "./jsonValue.js";
|
||||
import {
|
||||
MAX_JSON_DEPTH,
|
||||
jsonValueSchema,
|
||||
type JsonValue,
|
||||
} from "./jsonValue.js";
|
||||
import { AnalysisResourceConstraintError } from "./analysisErrorCore.js";
|
||||
import { projectAnalysisError } from "./analysisErrorProjection.js";
|
||||
import { createEvidenceBundle } from "./evidenceBundle.js";
|
||||
import { freezeJsonSnapshot } from "./immutableJson.js";
|
||||
|
||||
@@ -183,6 +190,49 @@ it("retains verbatim caller input without changing semantic identity or copying
|
||||
expect(() => withEvidenceCallerInputPath(second, "invalid\0path")).toThrow();
|
||||
});
|
||||
|
||||
it.each(["caller selection", "subject display"])(
|
||||
"preserves sealed JSON ownership when rebinding %s metadata",
|
||||
(metadata) => {
|
||||
const original = createEvidence(
|
||||
TARGET,
|
||||
PROVIDER,
|
||||
{
|
||||
operation: "inspect",
|
||||
parameters: {},
|
||||
result: { rows: [{ value: "observed" }] },
|
||||
rawResult: { source: ["provider"] },
|
||||
},
|
||||
{ immutable: true },
|
||||
);
|
||||
const rebound =
|
||||
metadata === "caller selection"
|
||||
? withEvidenceCallerInputPath(original, "./selected/../alias")
|
||||
: rebindEvidenceToTargetPath(original, {
|
||||
...TARGET,
|
||||
path: "/tmp/selected-alias",
|
||||
});
|
||||
const projected = projectAnalysisError(
|
||||
new AnalysisResourceConstraintError(
|
||||
"inspect",
|
||||
"memory",
|
||||
"low heap",
|
||||
null,
|
||||
{
|
||||
partialObservation: rebound,
|
||||
},
|
||||
),
|
||||
);
|
||||
expect(rebound).not.toBe(original);
|
||||
expect(rebound.evidence_id).toBe(original.evidence_id);
|
||||
expect(rebound.normalized_result).toBe(original.normalized_result);
|
||||
expect(rebound.raw_result).toBe(original.raw_result);
|
||||
expect(projected.details?.partial_observation).toBe(rebound);
|
||||
expect(jsonValueSchema.parse(rebound)).toBe(rebound);
|
||||
expect(original.caller_input_path).toBeUndefined();
|
||||
expect(original.subject?.local_path).toBe(TARGET.path);
|
||||
},
|
||||
);
|
||||
|
||||
it("applies the JSON depth boundary to owned immutable results", () => {
|
||||
let result: JsonValue = 1;
|
||||
for (let index = 0; index <= MAX_JSON_DEPTH; index += 1)
|
||||
|
||||
+17
-9
@@ -26,6 +26,7 @@ import {
|
||||
freezeJsonSnapshot,
|
||||
freezeOwnedJsonTreeSnapshot,
|
||||
canBorrowParsedJsonSnapshot,
|
||||
immutableJsonSnapshotMember,
|
||||
isImmutableJsonSnapshot,
|
||||
} from "./immutableJson.js";
|
||||
import { digestSchema } from "./../domain/digests.js";
|
||||
@@ -355,11 +356,7 @@ export const rebindEvidenceToTargetPath = (
|
||||
// The original authenticated Evidence owns immutable payloads. Only the
|
||||
// small display subject and envelope are new, so preserve those payloads
|
||||
// without reparsing a potentially large analysis result.
|
||||
Object.freeze(reboundSubject.digest);
|
||||
Object.freeze(reboundSubject);
|
||||
Object.freeze(reboundEvidence);
|
||||
immutableEvidenceSnapshots.set(reboundEvidence, reboundEvidence);
|
||||
return reboundEvidence;
|
||||
return rememberReboundEvidence(evidence, reboundEvidence);
|
||||
};
|
||||
|
||||
/** Bind the exact selected request path without changing semantic identity or stored history. */
|
||||
@@ -372,12 +369,23 @@ export const withEvidenceCallerInputPath = (
|
||||
if (owned.caller_input_path === callerInputPath) return owned;
|
||||
// The authenticated source owns its immutable payloads. Only this small
|
||||
// envelope changes, so replay does not copy a document-sized result.
|
||||
const rebound = Object.freeze({
|
||||
const rebound = {
|
||||
...owned,
|
||||
caller_input_path: callerInputPath,
|
||||
});
|
||||
immutableEvidenceSnapshots.set(rebound, rebound);
|
||||
return rebound;
|
||||
};
|
||||
return rememberReboundEvidence(owned, rebound);
|
||||
};
|
||||
|
||||
/** Carry the source's sealing proof before sealing only the changed envelope. */
|
||||
const rememberReboundEvidence = (
|
||||
source: Evidence,
|
||||
rebound: Evidence,
|
||||
): Evidence => {
|
||||
// Each own member belongs to an authenticated immutable source. Register
|
||||
// these shared roots so sealing the envelope never walks their payloads.
|
||||
for (const key of Object.keys(source))
|
||||
immutableJsonSnapshotMember(source, key);
|
||||
return rememberImmutableEvidence(rebound);
|
||||
};
|
||||
|
||||
/** Recognize authenticated Evidence whose complete reachable JSON data is immutable. */
|
||||
|
||||
Reference in New Issue
Block a user