mirror of
https://github.com/morluto/rea.git
synced 2026-11-11 01:13:39 +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>
141 lines
4.7 KiB
TypeScript
141 lines
4.7 KiB
TypeScript
import type { JsonValue } from "../jsonValue.js";
|
|
import { isObject, same, type JsonObject } from "./environmentStateRules.js";
|
|
import { readClock, type ClockFormat } from "./environmentStateWriteValues.js";
|
|
import { ENTRY_BYTES, textBytes } from "./webNetworkRouteTemplates.js";
|
|
|
|
/** Retain the bytes a view's learned members keep, as the gate's budget counts them. */
|
|
type Retain = (bytes: number) => void;
|
|
|
|
/** Whether two objects show the same keys, in any order. */
|
|
const sameKeys = (left: readonly string[], shown: JsonObject): boolean =>
|
|
left.length === Object.keys(shown).length &&
|
|
left.every((key) => Object.hasOwn(shown, key));
|
|
|
|
/** The spellings a view always gives clock fields where its recordings spell the stored instant otherwise. */
|
|
export class ClockSpellings {
|
|
/** Each category retains one spelling or a permanent mixed marker, never an expanding format history. */
|
|
readonly #fields = new Map<
|
|
string,
|
|
{
|
|
matched: ClockFormat | null | undefined;
|
|
other: ClockFormat | null | undefined;
|
|
}
|
|
>();
|
|
|
|
/** Per object field, the keys every recording showed in the first one's order, or null once two differ, and whether one showed fewer than stored. */
|
|
readonly #shapes = new Map<
|
|
string,
|
|
{ keys: string[] | null; projects: boolean }
|
|
>();
|
|
|
|
constructor(private readonly retain: Retain) {
|
|
retain(ENTRY_BYTES);
|
|
}
|
|
|
|
#shape(
|
|
field: string,
|
|
shown: JsonObject,
|
|
stored: JsonValue | undefined,
|
|
): void {
|
|
const keys = Object.keys(shown);
|
|
const projects =
|
|
isObject(stored) &&
|
|
Object.keys(stored).some((key) => !Object.hasOwn(shown, key));
|
|
const facts = this.#shapes.get(field);
|
|
if (facts === undefined) {
|
|
this.retain(
|
|
ENTRY_BYTES * 2 +
|
|
textBytes(field.length) +
|
|
keys.reduce(
|
|
(sum, key) => sum + ENTRY_BYTES + textBytes(key.length),
|
|
0,
|
|
),
|
|
);
|
|
this.#shapes.set(field, { keys, projects });
|
|
return;
|
|
}
|
|
if (facts.keys !== null && !sameKeys(facts.keys, shown)) facts.keys = null;
|
|
facts.projects ||= projects;
|
|
}
|
|
|
|
#note(
|
|
field: string,
|
|
category: "matched" | "other",
|
|
format: ClockFormat | null,
|
|
): void {
|
|
let facts = this.#fields.get(field);
|
|
if (facts === undefined) {
|
|
this.retain(ENTRY_BYTES + textBytes(field.length));
|
|
facts = { matched: undefined, other: undefined };
|
|
this.#fields.set(field, facts);
|
|
}
|
|
const held = facts[category];
|
|
if (held === undefined) {
|
|
if (format !== null)
|
|
this.retain(
|
|
ENTRY_BYTES +
|
|
(format.kind === "iso" ? textBytes(format.zone.length) : 0),
|
|
);
|
|
facts[category] = format;
|
|
} else if (held !== null && !same(held, format)) facts[category] = null;
|
|
}
|
|
|
|
note(stored: JsonObject, shown: JsonObject): void {
|
|
for (const [field, value] of Object.entries(shown)) {
|
|
if (isObject(value)) this.#shape(field, value, stored[field]);
|
|
const now = readClock(value);
|
|
if (now === undefined) continue;
|
|
const was = Object.hasOwn(stored, field)
|
|
? readClock(stored[field])
|
|
: undefined;
|
|
if (
|
|
was === undefined ||
|
|
was.ms !== now.ms ||
|
|
was.subMillisecond !== now.subMillisecond
|
|
) {
|
|
this.#note(field, "other", now.format);
|
|
continue;
|
|
}
|
|
const spelled = same(stored[field], value) ? null : now.format;
|
|
this.#note(field, "matched", spelled);
|
|
}
|
|
}
|
|
|
|
/** The fields whose objects every recording showed with the same keys, fewer than one stored. */
|
|
get #nested(): { readonly nested_fields?: Record<string, string[]> } {
|
|
const nested = [...this.#shapes].flatMap(([field, { keys, projects }]) =>
|
|
keys !== null && projects ? [[field, keys] as const] : [],
|
|
);
|
|
return nested.length === 0
|
|
? {}
|
|
: { nested_fields: Object.fromEntries(nested) };
|
|
}
|
|
|
|
/** The view members: the spelling of fields it always spells otherwise, and the keys of objects it shows fewer of. */
|
|
get view(): {
|
|
readonly clock_formats?: Record<string, ClockFormat>;
|
|
readonly nested_fields?: Record<string, string[]>;
|
|
} {
|
|
const formats: [string, ClockFormat][] = [];
|
|
for (const [field, { matched, other }] of this.#fields) {
|
|
if (
|
|
matched === undefined ||
|
|
matched === null ||
|
|
other === null ||
|
|
(other !== undefined && !same(other, matched))
|
|
)
|
|
continue;
|
|
if (formats.length === 0) this.retain(ENTRY_BYTES * 2);
|
|
// Both the temporary entry and its returned object member remain covered.
|
|
this.retain(ENTRY_BYTES * 2 + textBytes(field.length));
|
|
formats.push([field, matched]);
|
|
}
|
|
return {
|
|
...(formats.length === 0
|
|
? {}
|
|
: { clock_formats: Object.fromEntries(formats) }),
|
|
...this.#nested,
|
|
};
|
|
}
|
|
}
|