Files
reverse-engineering-anything/src/domain/analysisView/environmentStateWriteApply.ts
T
Edgar ArakelyanandClaude Opus 5.5 b490c8fd51 fix(environment): answer sent empty members as sent, and keep a placed create's order
- A write answer spells an empty list or map the call did not send as its
  recorded answer did; one the call sent is answered as sent, so a create
  sending `[]` is not answered `null` because the first recorded create was.
- A read listing the whole collection that placed a create leaves the create
  only the sorted inserts whose order that read follows, so a later rename
  cannot teach a sort an earlier read ruled out.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 14:38:55 -07:00

648 lines
23 KiB
TypeScript

import type { JsonValue } from "../jsonValue.js";
import { err, ok, type Result } from "../result.js";
import {
ownMember,
setOwnMember,
at,
isObject,
pointerTo,
pointerTokens,
replaceAt,
same,
scalarText,
type JsonObject,
type StateEntity,
} from "./environmentStateRules.js";
import {
emptySpellings,
type FieldRule,
type GeneratedRule,
type JsonKind,
type StateWritePlan,
type WriteBody,
type WriteCall,
type WriteResponse,
} from "./environmentStateWriteRules.js";
import {
stampProblem,
nestedClockValue,
} from "./environmentStateWriteStamps.js";
import { composedValue } from "./environmentStateWriteComposed.js";
import {
referencedValue,
type ReferencedEntity,
} from "./environmentStateWriteReferences.js";
import {
echoed,
integerOf,
readClock,
respellClock,
isUuid,
replaceDelimited,
slugOf,
spelledLike,
} from "./environmentStateWriteValues.js";
/** The value with each UUID at the pointers (`*` for any array index) replaced by `fresh(uuid)`, others kept. */
export const renewUuids = (
value: JsonValue,
pointers: readonly string[],
fresh: (uuid: string) => string,
): JsonValue => {
const renewed = new Set(pointers);
const renew = (item: JsonValue, pointer: string): JsonValue => {
if (isUuid(item)) return renewed.has(pointer) ? fresh(item) : item;
if (Array.isArray(item))
return item.map((child) => renew(child, `${pointer}/*`));
return isObject(item)
? Object.fromEntries(
Object.entries(item).map(([key, child]) => [
key,
renew(child, `${pointer}${pointerTo(key)}`),
]),
)
: item;
};
return renew(value, "");
};
/** A refusal message naming what the call did wrong; the state is left unchanged. */
type Applied<T> = Result<T, string>;
/** A generated value or a typed refusal when the store cannot safely provide one. */
export type GenerateValue = (
rule: GeneratedRule,
) => Applied<JsonValue | undefined>;
const kindOf = (value: JsonValue | undefined): string =>
value === null ? "null" : Array.isArray(value) ? "array" : typeof value;
/** The JSON types `stored` holds members in, for those `observed` shows no value of. */
export const storedKinds = (
members: readonly string[],
observed: JsonObject,
stored: JsonObject,
): WriteBody["kinds"] =>
members.flatMap((member) => {
const value = ownMember(stored, member);
return (ownMember(observed, member) ?? null) === null &&
value !== undefined &&
value !== null
? [{ member, kind: kindOf(value) as JsonKind }]
: [];
});
/** Members a refusal names, each cut to `LISTED_NAME_CHARS`; the plan in the state Evidence holds every one whole. */
const LISTED_MEMBERS = 12;
const LISTED_NAME_CHARS = 120;
/** Captured member names quoted, so a separator in one cannot read as another, and bounded in count and length. */
const listed = (members: readonly string[]): string => {
if (members.length === 0) return "none";
const shown = members
.slice(0, LISTED_MEMBERS)
.map((member) =>
JSON.stringify(
member.length > LISTED_NAME_CHARS
? `${member.slice(0, LISTED_NAME_CHARS)}…`
: member,
),
)
.join(", ");
return members.length > LISTED_MEMBERS
? `${shown}, … (${String(members.length)} members; all in the state Evidence)`
: shown;
};
/** What a body may hold, for refusal messages. */
const bodyContract = (body: WriteBody): string =>
`accepted members: ${listed(body.accepted)}; members accepted only with their recorded value: ${listed(body.fixed.map(({ member }) => member))}; required: ${listed(body.required)}`;
/** Recorded values up to this many characters are shown where a served write names them; a longer one is only named. */
const SHOWN_VALUE_CHARS = 120;
/** A recorded value as JSON text, when short enough to show. */
export const shownValue = (value: JsonValue): string | undefined => {
const text = JSON.stringify(value);
return text.length <= SHOWN_VALUE_CHARS ? text : undefined;
};
/** Why a call's body does not fit the members the capture showed, if it does not; `shapes` gives each accepted member's JSON type, else the body's recorded kind. */
export const bodyProblem = (
body: WriteBody,
call: WriteCall,
shapes: { readonly entity: JsonObject; readonly id?: string },
): string | undefined => {
const sent = call.body ?? {};
const fixed = new Map(body.fixed.map((entry) => [entry.member, entry.sent]));
const kinds = new Map(body.kinds.map((entry) => [entry.member, entry.kind]));
for (const [member, value] of Object.entries(sent)) {
if (member === body.identity) {
if (scalarText(value) !== shapes.id)
return `Member ${member} must repeat the target's identity ${shapes.id ?? ""}.`;
continue;
}
if (fixed.has(member)) {
if (!same(fixed.get(member), value))
return `Member ${member} is accepted only with the value the capture sent, ${shownValue(fixed.get(member)!) ?? "too long to show here"}, because the application stored it differently or other recorded fields go with it (${bodyContract(body)}).`;
continue;
}
if (!body.accepted.includes(member))
return `Member ${member} is not one the capture showed stored (${bodyContract(body)}).`;
// A recorded call sent this member empty, as a form does for a field left as it was.
if (
body.blank?.some(
(entry) => entry.member === member && same(entry.sent, value),
) === true
)
continue;
const recorded = ownMember(shapes.entity, member);
const kind =
recorded === undefined || recorded === null
? kinds.get(member)
: kindOf(recorded);
if (kind !== undefined && kind !== kindOf(value))
return `Member ${member} must be a JSON ${kind}, as the capture stored it.`;
}
const missing = body.required.filter(
(member) => !Object.hasOwn(sent, member),
);
if (missing.length > 0)
return `The call lacks required members ${listed(missing)} (${bodyContract(body)}).`;
return undefined;
};
/** The value at a JSON pointer inside a body, following array indexes and members. */
export const valueAt = (
body: JsonValue,
pointer: string,
): JsonValue | undefined => {
let value: JsonValue | undefined = body;
for (const token of pointerTokens(pointer)) {
if (Array.isArray(value)) value = value[Number(token)];
else if (isObject(value) && Object.hasOwn(value, token))
value = value[token];
else return undefined;
}
return value;
};
/** A path value spelled as the observed value: a number when the observed value is one. */
const pathValue = (observed: JsonValue | undefined, text: string): JsonValue =>
typeof observed === "number" && integerOf(text) !== undefined
? Number(text)
: text;
/** A generated field from a recorded call, reproduced with the same nested-clock renderer serving uses. */
export const recordedGeneratedValue = (
rule: GeneratedRule,
base: JsonValue | undefined,
value: JsonValue | undefined,
): Applied<JsonValue | undefined> => {
if (rule.rule !== "nested-clock") return ok(value);
const clock = isObject(value)
? ownMember(value, rule.members[0]!)
: undefined;
if (
clock === undefined ||
JSON.stringify(readClock(clock)?.format) !== JSON.stringify(rule.format)
)
return err(`The recorded ${rule.field} has no clock in the plan's format.`);
const rebuilt = nestedClockValue(base, rule, clock);
return rebuilt.ok && same(rebuilt.value, value)
? rebuilt
: err(
`The recorded ${rule.field} differs from its nested-clock rendering.`,
);
};
/** One field's value under a rule that reads the call or generates. */
const ruledValue = (
rule: FieldRule,
observed: JsonObject,
call: WriteCall,
generate: GenerateValue,
referenced?: ReferencedEntity,
): Applied<JsonValue | undefined> => {
const body = call.body ?? {};
switch (rule.rule) {
case "reference":
return referencedValue(rule, body, referenced);
case "identity":
case "sequence":
case "unique":
case "renewed":
case "clock":
case "nested-clock":
case "held-clock":
return generate(rule);
case "echo":
return ok(
Object.hasOwn(body, rule.field)
? echoed(rule, body[rule.field]!)
: undefined,
);
case "copy": {
const value = valueAt(body, rule.from);
return value === undefined
? err(
`The call has no value at ${rule.from} in its body, which ${rule.field} copies.`,
)
: ok(value);
}
case "path": {
const value = call.pathValues.get(rule.from);
return value === undefined
? err(
`The call names no ${rule.from} path value, which ${rule.field} takes.`,
)
: ok(pathValue(ownMember(observed, rule.field), value));
}
default:
return ok(undefined);
}
};
/** A string field rebuilt around the new value of the generated field it embeds. */
const templated = (
observed: JsonObject,
written: JsonObject,
rule: Extract<FieldRule, { rule: "template" }>,
): JsonValue | undefined => {
const text = ownMember(observed, rule.field);
const token = scalarText(ownMember(observed, rule.from));
const replacement = scalarText(ownMember(written, rule.from));
return typeof text === "string" &&
token !== undefined &&
replacement !== undefined
? replaceDelimited(text, token, replacement)
: undefined;
};
/** Apply the rules that derive from fields already written or the call: slugs, templates, stamps and composed strings; why a composed string cannot be rebuilt, if one cannot. */
const derive = (
rules: readonly FieldRule[],
observed: JsonObject,
written: JsonObject,
sent: JsonObject,
): string | undefined => {
for (const rule of rules) {
if (rule.rule === "slug") {
const from = ownMember(sent, rule.from);
if (typeof from === "string")
setOwnMember(written, rule.field, slugOf(from));
} else if (rule.rule === "template") {
const value = templated(observed, written, rule);
if (value !== undefined) setOwnMember(written, rule.field, value);
} else if (rule.rule === "stamp")
setOwnMember(written, rule.field, rule.value);
else if (rule.rule === "derived" && Object.hasOwn(sent, rule.member))
setOwnMember(written, rule.field, rule.value);
}
// Composed strings read the fields the rules above wrote.
for (const rule of rules)
if (rule.rule === "composed") {
const value = composedValue(rule.parts, written);
if (value === undefined) {
const sources = new Set(
rule.parts.flatMap((part) => ("from" in part ? [part.from] : [])),
);
return `The write composes ${JSON.stringify(rule.field)} from ${listed([...sources])}, and the entity holds no value one of them can spell.`;
}
setOwnMember(written, rule.field, value);
}
return undefined;
};
/** Members stored only under another name, with the value of the field each is copied whole into, for type checks. */
const copiedShapes = (
rules: readonly FieldRule[],
observed: JsonObject,
): JsonObject =>
Object.fromEntries(
rules.flatMap((rule): [string, JsonValue][] => {
if (rule.rule !== "copy" || !Object.hasOwn(observed, rule.field))
return [];
const tokens = pointerTokens(rule.from);
return tokens.length === 1 ? [[tokens[0]!, observed[rule.field]!]] : [];
}),
);
/** A create's call with the members it left out that recorded calls sent empty, as the earliest sent them; one a call that left it out showed a default for keeps that default. */
const withBlanks = (body: WriteBody, call: WriteCall): WriteCall => {
const given = call.body ?? {};
const left = (body.blank ?? []).filter(
({ member }, index, blank) =>
!Object.hasOwn(given, member) &&
!(body.defaults ?? []).some((entry) => entry.member === member) &&
blank.findIndex((entry) => entry.member === member) === index,
);
return left.length === 0
? call
: {
...call,
body: {
...Object.fromEntries(left.map(({ member, sent }) => [member, sent])),
...given,
},
};
};
/** The fields of a created entity: the observed entity with every ruled field taken from the call or generated. */
export const createdFields = (
plan: Pick<StateWritePlan, "rules" | "body">,
observed: JsonObject,
sentCall: WriteCall,
generate: GenerateValue,
referenced?: ReferencedEntity,
): Applied<JsonObject> => {
if (plan.body === null) return err("The tool has no modelled body.");
const call = withBlanks(plan.body, sentCall);
const problem = bodyProblem(plan.body, call, {
entity: { ...copiedShapes(plan.rules, observed), ...observed },
});
if (problem !== undefined) return err(problem);
const written: JsonObject = { ...observed };
const defaults = new Map(
(plan.body.defaults ?? []).map(({ member, value }) => [member, value]),
);
for (const rule of plan.rules) {
const value = ruledValue(rule, observed, call, generate, referenced);
if (!value.ok) return value;
if (value.value !== undefined)
setOwnMember(written, rule.field, value.value);
else if (rule.rule === "echo" && defaults.has(rule.field))
setOwnMember(written, rule.field, defaults.get(rule.field)!);
else if (rule.rule === "echo" || rule.rule === "reference")
delete written[rule.field];
}
const unspelled = derive(plan.rules, observed, written, call.body ?? {});
return unspelled === undefined ? ok(written) : err(unspelled);
};
/** Rules an update gives a new value on every call. */
const REGENERATED: ReadonlySet<FieldRule["rule"]> = new Set([
"clock",
"nested-clock",
"held-clock",
"unique",
]);
const regenerated = (
rule: FieldRule,
): rule is Extract<
GeneratedRule,
{ rule: "clock" | "nested-clock" | "held-clock" | "unique" }
> => REGENERATED.has(rule.rule);
/** The value an echo or constant rule writes for a sent member, or a set rule on every call; undefined when it writes none. */
const statedValue = (
rule: FieldRule,
sent: JsonObject,
): JsonValue | undefined => {
if (rule.rule === "set") return rule.value;
if (rule.rule !== "echo" && rule.rule !== "constant") return undefined;
if (!Object.hasOwn(sent, rule.field)) return undefined;
return rule.rule === "echo" ? echoed(rule, sent[rule.field]!) : rule.value;
};
/** The fields of an updated entity: sent echoed members, the constants the plan recorded, generated and derived changes. */
export const updatedFields = (
plan: Pick<StateWritePlan, "rules" | "body" | "address">,
entity: { readonly before: JsonObject; readonly observed: JsonObject },
call: WriteCall & { readonly id: string },
generate: GenerateValue,
referenced?: ReferencedEntity,
): Applied<JsonObject> => {
const { before, observed } = entity;
if (plan.body === null) return err("The tool has no modelled body.");
const sent = call.body ?? {};
const problem =
bodyProblem(plan.body, call, {
entity: { ...observed, ...before },
id: call.id,
}) ?? stampProblem(plan.rules, before, sent);
if (problem !== undefined) return err(problem);
const members = Object.keys(sent).filter(
(member) => member !== plan.body?.identity,
);
// An item action names its change in its path, so it may send nothing.
if (members.length === 0 && plan.address?.by !== "action")
return err(
`The call sends none of the members an update applies (${bodyContract(plan.body)}).`,
);
const written: JsonObject = { ...before };
for (const rule of plan.rules) {
const stated = statedValue(rule, sent);
if (stated !== undefined) setOwnMember(written, rule.field, stated);
else if (rule.rule === "reference" && Object.hasOwn(sent, rule.member)) {
const value = referencedValue(rule, sent, referenced);
if (!value.ok) return value;
if (value.value !== undefined)
setOwnMember(written, rule.field, value.value);
} else if (rule.rule === "increment") {
const was = integerOf(ownMember(before, rule.field));
if (was !== undefined)
setOwnMember(
written,
rule.field,
spelledLike(ownMember(before, rule.field)!, was + 1),
);
} else if (
regenerated(rule) ||
// Child rows are renewed only on an entity that holds them.
(rule.rule === "renewed" && Object.hasOwn(before, rule.field))
) {
const value = generate(rule);
if (!value.ok) return value;
if (value.value !== undefined)
setOwnMember(written, rule.field, value.value);
}
}
const unspelled = derive(
plan.rules.filter(
(rule) => rule.rule !== "slug" || Object.hasOwn(sent, rule.from),
),
observed,
written,
sent,
);
return unspelled === undefined ? ok(written) : err(unspelled);
};
/** Why a document write's body does not fit what the capture showed: members its plan accepts, as the JSON types the document holds, a member a replacing write dropped keeping the type the initial document showed. */
export const documentBodyProblem = (
plan: Pick<StateWritePlan, "body" | "observation">,
call: WriteCall,
documents: {
readonly initial: JsonValue | undefined;
readonly current: JsonValue;
},
): string | undefined => {
if (plan.body === null) return "The tool has no modelled body.";
const pointer = plan.observation?.pointer ?? "";
const members = (value: JsonValue | undefined): JsonObject => {
const object = value === undefined ? undefined : at(value, pointer);
return isObject(object) ? object : {};
};
return bodyProblem(plan.body, call, {
entity: { ...members(documents.initial), ...members(documents.current) },
});
};
/** A document after a write: the body's members merged into, or replacing, the object at the pointer. */
export const writtenDocument = (
plan: Pick<StateWritePlan, "merge" | "observation">,
before: JsonValue,
call: WriteCall,
): Applied<JsonValue> => {
const pointer = plan.observation?.pointer ?? "";
const target = at(before, pointer);
if (!isObject(target) || call.body === undefined)
return err("The call's body does not fit the document.");
const value =
plan.merge === "replace" ? { ...call.body } : { ...target, ...call.body };
return ok(replaceAt(before, pointer, value));
};
/** The fields a create stores only when a call sends their member: references and echoes of members not every call sent, with no default. */
export const optionalFields = (
plan: Pick<StateWritePlan, "effect" | "rules" | "body">,
): string[] => {
if (plan.effect !== "create" || plan.body === null) return [];
const { required, defaults = [] } = plan.body;
return plan.rules.flatMap((rule) =>
(rule.rule === "reference" && !required.includes(rule.member)) ||
(rule.rule === "echo" &&
!required.includes(rule.field) &&
!defaults.some(({ member }) => member === rule.field))
? [rule.field]
: [],
);
};
/** A write's answer body from its recorded response: the entity or identity rendered in, nothing, or the recorded body; optional fields show only when written. */
export const writtenResponse = (
response: WriteResponse,
recorded: JsonValue | undefined,
written: {
readonly fields: JsonObject | undefined;
readonly id: string;
/** Generated clocks keep the response's spelling independently of the stored entity's. */
readonly clocks?: readonly string[];
/** Members the call sent, which the answer shows as stored. */
readonly sent?: readonly string[];
},
optional: readonly string[] = [],
): Applied<JsonValue | undefined> => {
if (response.shape === "empty") return ok(undefined);
if (response.shape === "recorded" || recorded === undefined)
return ok(recorded);
const pointer = response.pointer ?? "";
const shown = at(recorded, pointer);
if (response.shape === "identity")
return ok(
replaceAt(
recorded,
pointer,
typeof shown === "number" && integerOf(written.id) !== undefined
? Number(written.id)
: written.id,
),
);
if (!isObject(shown) || written.fields === undefined)
return err("The recorded response does not carry the entity.");
const held = (field: string) => Object.hasOwn(written.fields!, field);
const missing = Object.keys(shown).filter(
(field) => !held(field) && !optional.includes(field),
);
if (missing.length > 0)
return err(
`The state holds no complete entity for this response: it lacks ${missing.join(", ")}.`,
);
const fields = [
...Object.keys(shown).filter(held),
...optional.filter((field) => !Object.hasOwn(shown, field) && held(field)),
];
return ok(
replaceAt(
recorded,
pointer,
Object.fromEntries(
fields.map((field) => {
const value = ownMember(written.fields!, field)!;
const answered = ownMember(shown, field);
// An answer spells an empty list or map the call did not send as its recording did.
if (
answered !== undefined &&
written.sent?.includes(field) !== true &&
emptySpellings(answered, value)
)
return [field, answered];
const clock =
written.clocks?.includes(field) === true
? readClock(answered)
: undefined;
return [
field,
clock === undefined ? value : respellClock(value, clock.format),
];
}),
),
),
);
};
/** The shared bulk renderer: fixed acknowledgements, or one first-template element per written entity. */
export const writtenBulkResponse = (
response: WriteResponse,
recorded: JsonValue | undefined,
written: readonly {
readonly fields: JsonObject;
readonly id: string;
readonly sent?: readonly string[];
}[],
): Applied<JsonValue | undefined> => {
if (response.shape === "empty") return ok(undefined);
if (response.shape === "recorded") return ok(recorded);
const [template] = Array.isArray(recorded) ? recorded : [];
if (template === undefined)
return err("The recorded response shows no element to render.");
const elements: JsonValue[] = [];
for (const entity of written) {
const element = writtenResponse(
{ ...response, pointer: "" },
template,
entity,
);
if (!element.ok) return element;
elements.push(element.value ?? null);
}
return ok(elements);
};
/** An entity with updated fields: view-specific values of the fields that changed give way to the new values, and it cites no records. */
export const updatedEntity = (
entity: StateEntity,
fields: JsonObject,
): StateEntity => {
const changed = new Set(
Object.keys(fields).filter(
(field) => !same(ownMember(entity.fields, field), fields[field]),
),
);
// Written values are no longer the ones the cited records show.
return {
...entity,
fields,
record_ordinals: [],
view_fields: entity.view_fields
.map((view) => ({
tool: view.tool,
fields: Object.fromEntries(
Object.entries(view.fields).filter(([field]) => !changed.has(field)),
),
}))
.filter((view) => Object.keys(view.fields).length > 0),
};
};