mirror of
https://github.com/morluto/rea.git
synced 2026-11-06 14:56:32 +00:00
* feat(environment): explain values an application derives from other fields
Prompt-built environments withheld creates whose responses carry values the
application derives deterministically, which the write model kept as
constants or could not match across calls:
- A composed string is literal text plus parts read from the fields other
rules write: a field's value, its slug, or a clock's calendar digits. A
create composes a string only when another entity of the type spells it
the same way and none spells it otherwise, and never from two sources
serving could tell apart; an update composes an unsent change from members
stored as sent and unchanged fields that also spell its value before the
call. A composed field no longer pins the members it reads.
- A trimmed clock format drops trailing fraction zeros, as Go spells
nanoseconds. It is learned from the type's values, a later create or a
later update, and spreads to the plan's other clocks unless a value shows
a fixed width.
- A large integer no entity of the type holds, among distinct ones of the
same length and kind, is a generated unique value, served as a seeded
integer.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* docs(environment): record the derived-values verification
Gates, tests, write plans over 69 recorded captures and served write tasks on the baseline and on this branch.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* feat(environment): learn a create plan from every call
A later create the plan does not take is explained on its own and merged when both calls show the same fields, every field both rule they rule alike, and a field neither rules holds one value: members either call sent are accepted, only those every call sent are required, and an echoed member a call left out takes the value that call stored, kept in the body's defaults.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* feat(environment): sorted arrays, referenced entities and nested view keys
- An echo is `sorted` when the application stored the sent strings in
code-point order; serving stores them sorted, and a plain echo and a
sorted one agree in update and create plans.
- A `reference` is an object field holding fields of the entity a sent
member names, as the state holds it. The member is accepted with any
value; replay and serving look the entity up, and serving refuses a call
naming one the environment does not hold.
- A view names in `nested_fields` the keys it always shows of objects it
shows with fewer keys than an entity stores; a stored object it never
recorded renders with them. The view spelling learner moves to its own
module.
- Every later create, not only one the plan refuses, is merged into its
plan when it agrees, keeping the plan's rule order.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(environment): keep clock spellings, linked ids and word boundaries honest
- A later update's clocks take the plan's spelling, sibling-trimmed
clocks included, wherever the call's values fit it, so an update whose
later call trims only one of two clocks is served.
- A held value that ends its fraction in a zero keeps a create's clock
at a fixed width, whatever order the held spellings come in.
- A large integer that another field of the created entity also holds
is linked, as a repeated token is: it is never unique, and the create
is withheld as unexplained-change.
- Composed parts are delimited by whole characters through the shared
joinsBefore/joinsAfter, so an astral letter joins a match to its word.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* test(environment): keep a same-width fixed clock out of trimming
A held .123456780 beside a held .12345678 keeps the create's clock at
nine digits in either order; the cases fail on 8ce1127b5.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* docs(environment): record the create-calls verification
Gates, tests, write plans over 69 recorded captures and served write tasks on the derived-values baseline and on this branch.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* docs(environment): note the create-calls census on current main
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(environment): check composed parts and clock fits before they shape a plan
- A composed string whose part has nothing to spell refuses the create
or update through the apply Result instead of keeping a stale value.
- A composition learned on a later call is rebuilt from each earlier
call's fields, so an earlier call that left the value otherwise
contradicts it.
- An update adopts its fitted clock spellings only after the
unknown-entity and contradiction checks, so a call on an entity an
unmodelled write left unknown no longer trims a served plan's clock.
- Sibling trimming on a later create also checks that call's fields,
so a sibling clock it shows with a trailing zero stays fixed-width.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* test(environment): split the derived-values view tests by rule
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(environment): keep fixed-width, zone and generated-integer evidence through a plan
- A plan keeps the first value that showed each clock at a fixed width,
from held entities and every accepted create or update. No later fit
or sibling trim widens that field, and a later call that shows it
shorter is withheld.
- Two stamped clocks are one composed source only when they share a
zone offset. A held entity that holds the target field as anything
the parts do not spell, a non-string included, vetoes the
composition. The token index is built once, and only when a string is
left to compose.
- A generated integer that another field repeats as a number at any
depth is linked. Compare treats two integers of one JSON kind and
length under a unique rule as generated values.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(environment): keep optional references across create calls and in answers
- A reference a later create added stays while further creates leave its
member out, and agrees with a later call that shows it again.
- A create's answer shows optional reference and echo fields only when the
call wrote them, whichever recorded call the answer came from.
- An update that re-sends the id of the entity a field already repeats keeps
the plan's reference instead of pinning the member.
- An id naming entities of one type in several collections teaches no
reference, and serving or replay refuses a call that names one.
- An echo is not promoted to sorted once a call stored its strings out of
code-point order as sent.
- Nested view keys compare as sets and keep the first recording's order.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* test(setup): share registration prewarm host preparation
* fix(environment): retain write evidence and match trimmed response clocks
* fix(environment): withhold creates whose generated integer another string repeats
A unique integer whose digits show in another top-level string that no
template or composed rule rebuilds from it (an echoed serialText, or a
token like folder-<id>) withholds the create as unexplained-change,
since serving would seed the integer and keep the string. A templated
copy is still served.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(environment): respect earlier clock widths and left-out members in later creates
- The create merge widens a clock to trimmed only against the value that
showed its width: a held entity or accepted call at a fixed width keeps it
untrimmed.
- A later create that left out a member the plan fills, with a default or a
reference's absence, while its observation shows that field otherwise is
unexplained-change, so the plan cannot serve the stale value.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(environment): hold later creates and compare to the composed and integer forms
- A later create whose response spells a composed field otherwise than
the plan rebuilds it is withheld as unexplained-change.
- A unique field learned from a generated integer accepts only an
integer of the same length and kind from a later create, as serving
seeds one.
- Compare counts a composed field as equal when each side's response
spells it from its own fields, which compare under their own rules.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(environment): preserve composed parts and unique generator families
* fix(environment): reject contradictory create references
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: morluto <76467478+morluto@users.noreply.github.com>
424 lines
14 KiB
TypeScript
424 lines
14 KiB
TypeScript
import { identifierWords } from "./environmentStateWords.js";
|
|
import type { JsonValue } from "../jsonValue.js";
|
|
import { compareUnicodeCodePoints as compare } from "../unicodeCodePointOrder.js";
|
|
import {
|
|
at,
|
|
collectionPathOf,
|
|
isObject,
|
|
lastSegmentValue,
|
|
listItems,
|
|
renderStateEntity,
|
|
renderStateList,
|
|
same,
|
|
scalarText,
|
|
type JsonObject,
|
|
type RecordedViewReason,
|
|
type ReplayKey,
|
|
type StateEntity,
|
|
type StateItemView,
|
|
type StateRecordFacts,
|
|
type StateToolFacts,
|
|
type StateView,
|
|
} from "./environmentStateRules.js";
|
|
import { counterPath } from "./environmentStateCounters.js";
|
|
import {
|
|
evaluateQuery,
|
|
learnQuery,
|
|
lineChoices,
|
|
queryTotalCount,
|
|
renderQueryList,
|
|
type ListQuery,
|
|
type QueryLine,
|
|
} from "./environmentStateListQuery.js";
|
|
import { singleQueryValues } from "../recordedTransportUrl.js";
|
|
import { ClockSpellings } from "./environmentStateViewSpellings.js";
|
|
import {
|
|
declaredMembers,
|
|
mintedEnvelope,
|
|
mintedFor,
|
|
type MintedMembers,
|
|
} from "./environmentStateResponseMembers.js";
|
|
import { itemTotals, listFacts } from "./environmentStateListFacts.js";
|
|
import { ENTRY_BYTES, textBytes } from "./webNetworkRouteTemplates.js";
|
|
|
|
/** A successful item read first recorded after the initial reads, with the members its object carries. */
|
|
export interface LateLine {
|
|
readonly record: StateRecordFacts;
|
|
readonly members: ReadonlySet<string>;
|
|
/** Whether it asks for some fields (`fields`, `select`, …). */
|
|
readonly projecting: boolean;
|
|
}
|
|
|
|
/** A read tool whose recordings locate entities of one type at one pointer. */
|
|
export interface StatePlan {
|
|
readonly tool: StateToolFacts;
|
|
readonly item: boolean;
|
|
readonly type: string;
|
|
readonly pointer: string;
|
|
/** Successful init recordings in timeline order. */
|
|
readonly recordings: readonly StateRecordFacts[];
|
|
/** The latest of them per request line, in timeline order. */
|
|
readonly latest: readonly StateRecordFacts[];
|
|
/** Reads left to replay: item reads that asked for some fields only, and representation variants. */
|
|
readonly projected?: readonly number[];
|
|
/** Item lines first recorded after the initial reads whose identity they show, decided against the lines left once variants are dropped. */
|
|
readonly late?: readonly LateLine[];
|
|
/** The query keys that pick calls of the replayed reads out. */
|
|
readonly projectedKeys?: readonly ReplayKey[];
|
|
}
|
|
|
|
/** A view's replayed reads and their keys, when it has any. */
|
|
const replayedReads = (
|
|
plan: StatePlan,
|
|
): Pick<StateItemView, "projected_record_ordinals" | "projected_query_keys"> =>
|
|
plan.projected === undefined || plan.projected.length === 0
|
|
? {}
|
|
: {
|
|
projected_record_ordinals: [...plan.projected],
|
|
...(plan.projectedKeys === undefined || plan.projectedKeys.length === 0
|
|
? {}
|
|
: { projected_query_keys: [...plan.projectedKeys] }),
|
|
};
|
|
|
|
/** What the gate reads from an extraction; `withBody` drops each body's temporary work after `use`. */
|
|
export interface StateGateContext {
|
|
readonly withBody: <T>(
|
|
ordinal: number,
|
|
use: (body: JsonValue | undefined) => T,
|
|
) => T;
|
|
readonly retain: (bytes: number) => void;
|
|
readonly entity: (
|
|
type: string,
|
|
collection: string,
|
|
id: string,
|
|
) => StateEntity | undefined;
|
|
readonly inCollection: (type: string, collection: string) => StateEntity[];
|
|
readonly volatileOf: (type: string) => ReadonlySet<string>;
|
|
readonly idField: (type: string) => string;
|
|
/** By path, the top-level members each response there mints; views of other paths keep them. */
|
|
readonly minted: MintedMembers;
|
|
}
|
|
|
|
/** Ordered key union and always-present keys, grown one object at a time. */
|
|
class FieldUnion {
|
|
readonly fields: string[] = [];
|
|
/** Membership beside the ordered list, so a wide object is not scanned per key. */
|
|
readonly #seen = new Set<string>();
|
|
#required: Set<string> | undefined;
|
|
|
|
add(object: JsonObject): void {
|
|
for (const key of Object.keys(object))
|
|
if (!this.#seen.has(key)) {
|
|
this.#seen.add(key);
|
|
this.fields.push(key);
|
|
}
|
|
const keys = new Set(Object.keys(object));
|
|
this.#required =
|
|
this.#required === undefined
|
|
? keys
|
|
: new Set([...this.#required].filter((key) => keys.has(key)));
|
|
}
|
|
|
|
get required(): string[] {
|
|
return this.fields.filter((key) => this.#required?.has(key) === true);
|
|
}
|
|
|
|
/** Retained bytes of the field lists a plan keeps. */
|
|
get bytes(): number {
|
|
return this.fields.reduce(
|
|
(sum, name) => sum + ENTRY_BYTES * 2 + textBytes(name.length) * 2,
|
|
ENTRY_BYTES * 2,
|
|
);
|
|
}
|
|
}
|
|
|
|
const recorded = (plan: StatePlan, reason: RecordedViewReason): StateView => ({
|
|
tool: plan.tool.name,
|
|
shape: "recorded",
|
|
reason,
|
|
});
|
|
|
|
/** Why one latest item recording does not render from the shared envelope and the entity's state, if it does not. */
|
|
const itemMismatch = (
|
|
context: StateGateContext,
|
|
plan: StatePlan,
|
|
check: {
|
|
readonly view: { readonly tool: string; readonly fields: string[] };
|
|
readonly envelope: { skeleton?: string; members?: string[] };
|
|
readonly spellings: ClockSpellings;
|
|
readonly minted: ReadonlySet<string>;
|
|
},
|
|
record: StateRecordFacts,
|
|
): RecordedViewReason | undefined => {
|
|
const { view, envelope } = check;
|
|
const entity = context.entity(
|
|
plan.type,
|
|
collectionPathOf(record, true),
|
|
lastSegmentValue(record.path),
|
|
);
|
|
return context.withBody(record.ordinal, (body) => {
|
|
const object = body === undefined ? undefined : at(body, plan.pointer);
|
|
if (entity === undefined || body === undefined || !isObject(object))
|
|
return "fields-differ";
|
|
const { skeleton, members } = mintedEnvelope(
|
|
body,
|
|
plan.pointer,
|
|
check.minted,
|
|
);
|
|
if (envelope.skeleton === undefined) {
|
|
context.retain(textBytes(skeleton.length));
|
|
envelope.skeleton = skeleton;
|
|
envelope.members = members;
|
|
}
|
|
if (skeleton !== envelope.skeleton) return "envelope-varies";
|
|
check.spellings.note(entity.fields, object);
|
|
const shown = renderStateEntity(
|
|
entity,
|
|
view,
|
|
context.volatileOf(plan.type),
|
|
object,
|
|
);
|
|
return same(shown, object) ? undefined : "fields-differ";
|
|
});
|
|
};
|
|
|
|
/** An item plan must reproduce each latest recording from the first one's envelope and the entity's state. */
|
|
export const checkItemPlan = (
|
|
context: StateGateContext,
|
|
plan: StatePlan,
|
|
): StateView => {
|
|
const union = new FieldUnion();
|
|
for (const record of plan.latest)
|
|
context.withBody(record.ordinal, (body) => {
|
|
const object = body === undefined ? undefined : at(body, plan.pointer);
|
|
if (isObject(object)) union.add(object);
|
|
});
|
|
context.retain(union.bytes);
|
|
const view = { tool: plan.tool.name, fields: union.fields };
|
|
const envelope: { skeleton?: string; members?: string[] } = {};
|
|
const spellings = new ClockSpellings(context.retain);
|
|
const minted = mintedFor(context.minted, plan.latest);
|
|
for (const record of plan.latest) {
|
|
const reason = itemMismatch(
|
|
context,
|
|
plan,
|
|
{ view, envelope, spellings, minted },
|
|
record,
|
|
);
|
|
if (reason !== undefined) return recorded(plan, reason);
|
|
}
|
|
return {
|
|
tool: plan.tool.name,
|
|
shape: "item",
|
|
type: plan.type,
|
|
pointer: plan.pointer,
|
|
fields: union.fields,
|
|
required_fields: union.required,
|
|
envelope_ordinal: plan.latest[0]!.ordinal,
|
|
...declaredMembers(envelope.members, context.retain),
|
|
checked_record_ordinals: plan.latest.map((record) => record.ordinal),
|
|
...replayedReads(plan),
|
|
...spellings.view,
|
|
};
|
|
};
|
|
|
|
/** The query that lines which are not whole follow, with the item totals that count its matches before paging. */
|
|
const listQuery = (
|
|
context: StateGateContext,
|
|
plan: StatePlan,
|
|
whole: ReadonlySet<number>,
|
|
): ListQuery | undefined => {
|
|
const field = context.idField(plan.type);
|
|
const lines: QueryLine[] = [];
|
|
const counts = new Map<number, [string, number][]>();
|
|
for (const record of plan.latest) {
|
|
const values = whole.has(record.ordinal)
|
|
? undefined
|
|
: singleQueryValues(record.query);
|
|
if (values === undefined || values.size === 0) continue;
|
|
context.withBody(record.ordinal, (body) => {
|
|
const items =
|
|
body === undefined ? undefined : listItems(body, plan.pointer);
|
|
if (body === undefined || items === undefined) return;
|
|
const choices = lineChoices(values, items);
|
|
context.retain(ENTRY_BYTES * (items.length + choices.length * 4 + 2));
|
|
lines.push({
|
|
ordinal: record.ordinal,
|
|
values,
|
|
choices,
|
|
listed: items.map((item) => scalarText(item[field]) ?? ""),
|
|
entities: context.inCollection(
|
|
plan.type,
|
|
collectionPathOf(record, false),
|
|
),
|
|
});
|
|
const parent = plan.pointer === "" || !isObject(body) ? {} : body;
|
|
counts.set(
|
|
record.ordinal,
|
|
itemTotals(parent, plan.pointer).flatMap(
|
|
([name, value]): [string, number][] => {
|
|
const count = queryTotalCount(value);
|
|
return count === undefined ? [] : [[name, count]];
|
|
},
|
|
),
|
|
);
|
|
});
|
|
}
|
|
const learned = learnQuery(lines);
|
|
if (learned === undefined) return undefined;
|
|
// Totals count every match on each line the query answers; a `number`-of-items member counts the page.
|
|
const totals = new Set<string>();
|
|
const contradicted = new Set<string>();
|
|
const answered = new Set(learned.record_ordinals);
|
|
for (const line of lines) {
|
|
if (!answered.has(line.ordinal)) continue;
|
|
const { total } = evaluateQuery(
|
|
learned.parameters,
|
|
line.values,
|
|
line.entities,
|
|
)!;
|
|
// Absence says nothing about another envelope's counter. Only a present
|
|
// count that disagrees invalidates the shared total interpretation.
|
|
for (const [name, count] of counts.get(line.ordinal) ?? []) {
|
|
if (identifierWords(counterPath(name).at(-1)!).includes("number"))
|
|
continue;
|
|
if (count === total) totals.add(name);
|
|
else contradicted.add(name);
|
|
}
|
|
}
|
|
return {
|
|
...learned,
|
|
totals: [...totals].filter((name) => !contradicted.has(name)).sort(compare),
|
|
};
|
|
};
|
|
|
|
/** Whether one latest list recording renders from its own envelope and the state: the whole collection, the query's matches, or the listed entities. */
|
|
const listReproduces = (
|
|
context: StateGateContext,
|
|
plan: StatePlan,
|
|
check: {
|
|
readonly view: {
|
|
readonly tool: string;
|
|
readonly fields: string[];
|
|
readonly pointer: string;
|
|
readonly counters: string[];
|
|
readonly whole: ReadonlySet<number>;
|
|
readonly query: ListQuery | undefined;
|
|
};
|
|
readonly spellings?: ClockSpellings;
|
|
},
|
|
record: StateRecordFacts,
|
|
): boolean => {
|
|
const { view, spellings } = check;
|
|
const field = context.idField(plan.type);
|
|
const collection = collectionPathOf(record, false);
|
|
return context.withBody(record.ordinal, (body) => {
|
|
const items =
|
|
body === undefined ? undefined : listItems(body, plan.pointer);
|
|
if (body === undefined || items === undefined) return false;
|
|
const observed = new Map(
|
|
items.map((item) => [scalarText(item[field]) ?? "", item]),
|
|
);
|
|
const computed = view.query?.record_ordinals.includes(record.ordinal)
|
|
? evaluateQuery(
|
|
view.query.parameters,
|
|
singleQueryValues(record.query) ?? new Map(),
|
|
context.inCollection(plan.type, collection),
|
|
)
|
|
: undefined;
|
|
const members = view.whole.has(record.ordinal)
|
|
? context.inCollection(plan.type, collection)
|
|
: (computed?.page ??
|
|
items.flatMap((item) => {
|
|
const entity = context.entity(
|
|
plan.type,
|
|
collection,
|
|
scalarText(item[field]) ?? "",
|
|
);
|
|
return entity === undefined ? [] : [entity];
|
|
}));
|
|
const rendered = members.map((entity) => {
|
|
const item = observed.get(entity.id);
|
|
if (item !== undefined) spellings?.note(entity.fields, item);
|
|
return renderStateEntity(
|
|
entity,
|
|
view,
|
|
context.volatileOf(plan.type),
|
|
item,
|
|
);
|
|
});
|
|
const answer =
|
|
view.query === undefined ||
|
|
(computed === undefined && !view.whole.has(record.ordinal))
|
|
? renderStateList(view, body, rendered)
|
|
: renderQueryList(
|
|
{ ...view, query: view.query },
|
|
body,
|
|
rendered,
|
|
computed?.total ?? rendered.length,
|
|
);
|
|
return same(answer, body);
|
|
});
|
|
};
|
|
|
|
/** A list plan must reproduce each latest recording from its own envelope and the listed entities' state. */
|
|
export const checkListPlan = (
|
|
context: StateGateContext,
|
|
plan: StatePlan,
|
|
): StateView => {
|
|
const union = new FieldUnion();
|
|
const { whole, counters, minted } = listFacts(context, plan, union);
|
|
context.retain(union.bytes);
|
|
const base = {
|
|
tool: plan.tool.name,
|
|
fields: union.fields,
|
|
pointer: plan.pointer,
|
|
counters,
|
|
whole,
|
|
};
|
|
let query = listQuery(context, plan, whole);
|
|
// A line the query explains but whose envelope or fields the state does not reproduce keeps its recorded members.
|
|
if (query !== undefined) {
|
|
const failing = plan.latest.filter(
|
|
(record) =>
|
|
query!.record_ordinals.includes(record.ordinal) &&
|
|
!listReproduces(context, plan, { view: { ...base, query } }, record),
|
|
);
|
|
const kept = query.record_ordinals.filter(
|
|
(ordinal) => !failing.some((record) => record.ordinal === ordinal),
|
|
);
|
|
query = kept.length === 0 ? undefined : { ...query, record_ordinals: kept };
|
|
}
|
|
const view = { ...base, query };
|
|
const all = plan.latest.every((record) => whole.has(record.ordinal));
|
|
const spellings = new ClockSpellings(context.retain);
|
|
if (
|
|
!plan.latest.every((record) =>
|
|
listReproduces(context, plan, { view, spellings }, record),
|
|
)
|
|
)
|
|
return recorded(plan, "list-differs");
|
|
return {
|
|
tool: plan.tool.name,
|
|
shape: "list",
|
|
type: plan.type,
|
|
pointer: plan.pointer,
|
|
fields: union.fields,
|
|
required_fields: union.required,
|
|
membership: all ? "all" : "recorded",
|
|
...(all || whole.size === 0
|
|
? {}
|
|
: {
|
|
all_record_ordinals: plan.latest
|
|
.map((record) => record.ordinal)
|
|
.filter((ordinal) => whole.has(ordinal)),
|
|
}),
|
|
counters,
|
|
...(query === undefined ? {} : { query }),
|
|
...declaredMembers(minted, context.retain),
|
|
checked_record_ordinals: plan.latest.map((record) => record.ordinal),
|
|
...replayedReads(plan),
|
|
...spellings.view,
|
|
};
|
|
};
|