Files
rea/src/domain/analysisView/environmentStateWriteDocument.ts
T
Edgar ArakelyanandClaude Opus 5.5 466ed8b433 fix(environment): name entities and weigh interference per write as serving and the recording show
Greptile raised these on this branch, the fidelity branch and the list-lines branch; five follow from the previous
round's changes.
- An entity field whose name looks like a pointer is matched by name; only a document's pointers cover the members under them.
- A failed read contradicts each exercised write that no unmodelled write came after, instead of skipping them all when the oldest pending write preceded one; the shared pending `since` time goes.
- An item read, update or delete names its entity as serving's namedEntity does: held collections its path matches, verbatim spellings first, the id decoded once, then as written; `book%2531` no longer reaches `book1`.
- A plan whose calls use a member both as the identity and as a fixed member, or fix it at different values, is refused, as serving could not take every recorded call.
- A size of zero bounds nothing only on a line whose recording listed items under it (pageBound), in serving and in the gate.
- An update whose observation drops a field the entity held is refused, as serving keeps every field it does not write.
- A later create whose clock takes another format, or whose unique value is not a UUID, is refused.
- A derived state data/get would write past the 32 MiB intake limit is not served (admitServedState); the environment serves replay.

The verdicts over the 50 captures are unchanged plan by plan, and the smoke applies all 31 served write tools.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 11:53:26 -07:00

140 lines
4.4 KiB
TypeScript

import type { JsonValue } from "../jsonValue.js";
import {
decodedSegment,
pointerTo,
same,
segmentsOf,
type JsonObject,
} from "./environmentStateRules.js";
import {
documentBodyProblem,
writtenDocument,
} from "./environmentStateWriteApply.js";
import { documentTarget } from "./environmentStateWriteChecks.js";
import {
built,
callOf,
fail,
refuse,
requestObject,
UNKNOWN,
type EffectContext,
type EffectResult,
type WriteDraft,
type WriteStep,
} from "./environmentStateWriteEffects.js";
import { documentPointerOf } from "./environmentStateWriteExplain.js";
/** Whether a write's path names the held document's, a segment verbatim or once percent-decoded, as serving matches a call. */
const namesDocument = (path: string, held: string): boolean => {
const written = segmentsOf(path);
const recorded = segmentsOf(held);
return (
written.length === recorded.length &&
recorded.every(
(segment, index) =>
segment === written[index] ||
decodedSegment(segment) === written[index],
)
);
};
/** Where a write goes: a built plan writes where serving does, at its own object; an exemplar at the object holding most of its members. */
const writePointer = (
draft: WriteDraft,
value: JsonValue,
body: JsonObject,
): string | undefined =>
built(draft)
? draft.plan.observation?.pointer
: documentPointerOf(value, body);
/** The fields a document write changed, as pointers: a merge the members it sent at its object, a replacement all under it (undefined: the whole document). */
const writtenPointers = (
draft: WriteDraft,
body: JsonObject,
): readonly string[] | undefined => {
const at = draft.plan.observation?.pointer ?? "";
if (draft.plan.merge === "merge")
return Object.keys(body).map((member) => `${at}${pointerTo(member)}`);
return at === "" ? undefined : [at];
};
/** Build a document plan from its exemplar: its object, merge or replacement by method, and the members it sent. */
const buildDocument = (
draft: WriteDraft,
step: WriteStep,
evidence: { readonly pointer: string; readonly body: JsonObject },
): void => {
const { ordinal } = step.record;
Object.assign(draft.plan, {
exemplar_ordinal: ordinal,
observation: { ordinal, pointer: evidence.pointer },
merge: step.tool.method === "PUT" ? "replace" : "merge",
body: {
accepted: Object.keys(evidence.body),
fixed: [],
required: [],
identity: null,
},
});
};
/** Apply a recorded document write: the body merged into, or replacing, the object holding most of its members. */
export const applyDocument = (
context: EffectContext,
draft: WriteDraft,
step: WriteStep,
): EffectResult => {
const tool = draft.plan.target!;
const initial = context.initialDocument(tool);
// Serving refuses a write at another path, so one here changes no document the state holds.
if (initial !== undefined && !namesDocument(step.record.path, initial.path))
return UNKNOWN;
const value = context.state.document(tool);
const body = requestObject(context, step.record);
const pointer =
value === undefined || body === undefined
? undefined
: writePointer(draft, value, body);
if (step.observeOnly || pointer === undefined) {
fail(draft, step, body === undefined ? "body-unusable" : "no-observation");
return UNKNOWN;
}
if (!built(draft)) buildDocument(draft, step, { pointer, body: body! });
const call = callOf(step, body!);
// Serving refuses a body its check refuses, so such a call cannot confirm the plan.
if (
documentBodyProblem(draft.plan, call, {
initial: initial?.value,
current: value!,
}) !== undefined
) {
refuse(draft, step, "unexplained-change");
return UNKNOWN;
}
const written = writtenDocument(draft.plan, value!, call);
if (!written.ok) {
refuse(draft, step, "no-observation");
return UNKNOWN;
}
const { ordinal } = step.record;
draft.plan.response ??= context.io.withBody(ordinal, (response) => ({
ordinal,
shape: context.io.emptyResponse(ordinal)
? "empty"
: response !== undefined && same(response, written.value)
? "entity"
: "recorded",
pointer: null,
}));
context.io.retain(64 + JSON.stringify(written.value).length * 2);
context.state.setDocument(tool, written.value);
const fields = writtenPointers(draft, body!);
return {
kind: "applied",
targets: [documentTarget(tool)],
...(fields === undefined ? {} : { fields, pointers: true }),
};
};