mirror of
https://github.com/morluto/rea
synced 2026-10-25 22:08:11 -04:00
aba487eec0
* 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>
242 lines
8.7 KiB
TypeScript
242 lines
8.7 KiB
TypeScript
import type { JsonValue } from "../jsonValue.js";
|
|
import { same, type JsonObject } from "./environmentStateRules.js";
|
|
import { storedKinds } from "./environmentStateWriteApply.js";
|
|
import { fittedClockFormat } from "./environmentStateWriteClockFormats.js";
|
|
import {
|
|
alikeRules,
|
|
type FieldRule,
|
|
type WriteBody,
|
|
} from "./environmentStateWriteRules.js";
|
|
|
|
type Default = { readonly member: string; readonly value: JsonValue };
|
|
|
|
/** What earlier calls showed that a merge must respect: members stored unsorted as sent, and the value showing each clock's width. */
|
|
export interface EarlierEvidence {
|
|
readonly kept: ReadonlySet<string>;
|
|
readonly clockOf: (field: string) => JsonValue | undefined;
|
|
}
|
|
|
|
/** The plan's rule for a field and a later call's agree: equal, or clocks one spelling covers given what earlier calls showed. */
|
|
const agreeing = (
|
|
held: FieldRule,
|
|
later: FieldRule,
|
|
values: {
|
|
readonly field: string;
|
|
readonly later: JsonValue | undefined;
|
|
readonly earlier: EarlierEvidence;
|
|
},
|
|
): FieldRule | undefined => {
|
|
const alike = alikeRules(held, later, values.earlier.kept);
|
|
if (alike !== undefined) return alike;
|
|
if (held.rule !== "clock" || later.rule !== "clock") return undefined;
|
|
const format = fittedClockFormat(
|
|
held.format,
|
|
values.later,
|
|
values.earlier.clockOf(values.field),
|
|
);
|
|
return format === undefined ? undefined : { ...held, format };
|
|
};
|
|
|
|
/** Defaults joined, undefined when two calls showed one member left out at different values. */
|
|
const joinedDefaults = (
|
|
held: readonly Default[],
|
|
added: readonly Default[],
|
|
): Default[] | undefined => {
|
|
const joined = new Map(held.map((entry) => [entry.member, entry.value]));
|
|
for (const { member, value } of added) {
|
|
if (joined.has(member) && !same(joined.get(member), value))
|
|
return undefined;
|
|
joined.set(member, value);
|
|
}
|
|
return [...joined].map(([member, value]) => ({ member, value }));
|
|
};
|
|
|
|
/** What two calls show of one field. */
|
|
interface FieldCalls {
|
|
readonly before: FieldRule | undefined;
|
|
readonly after: FieldRule | undefined;
|
|
readonly exemplar: JsonObject;
|
|
readonly later: { readonly entity: JsonObject; readonly body: JsonObject };
|
|
readonly sentBefore: ReadonlySet<string>;
|
|
readonly required: readonly string[];
|
|
readonly earlier: EarlierEvidence;
|
|
}
|
|
|
|
/** A reference an earlier later call added: it stays while a call leaves its member out, and agrees with this call's when it shows it. */
|
|
const addedReference = (
|
|
field: string,
|
|
added: Extract<FieldRule, { rule: "reference" }>,
|
|
calls: FieldCalls,
|
|
): { readonly rule: FieldRule } | undefined => {
|
|
const { after, later } = calls;
|
|
if (!Object.hasOwn(later.entity, field))
|
|
return Object.hasOwn(later.body, added.member)
|
|
? undefined
|
|
: { rule: added };
|
|
const rule =
|
|
after === undefined
|
|
? undefined
|
|
: agreeing(added, after, {
|
|
field,
|
|
later: later.entity[field],
|
|
earlier: calls.earlier,
|
|
});
|
|
return rule === undefined ? undefined : { rule };
|
|
};
|
|
|
|
/** A field one call shows alone, which must be a reference the other call did not send the member of. */
|
|
const shownAlone = (
|
|
inExemplar: boolean,
|
|
calls: FieldCalls,
|
|
): { readonly rule: FieldRule } | undefined => {
|
|
const own = inExemplar ? calls.before : calls.after;
|
|
if (own?.rule !== "reference") return undefined;
|
|
const sent = inExemplar
|
|
? Object.hasOwn(calls.later.body, own.member)
|
|
: calls.sentBefore.has(own.member);
|
|
return sent ? undefined : { rule: own };
|
|
};
|
|
|
|
/** One field's rule in the merged plan, and the default it gives a member; undefined when the calls disagree on it. */
|
|
const mergedField = (
|
|
field: string,
|
|
calls: FieldCalls,
|
|
): { readonly rule?: FieldRule; readonly default?: Default } | undefined => {
|
|
const { before, after, exemplar, later } = calls;
|
|
const inExemplar = Object.hasOwn(exemplar, field);
|
|
if (!inExemplar && before?.rule === "reference")
|
|
return addedReference(field, before, calls);
|
|
if (!inExemplar || !Object.hasOwn(later.entity, field))
|
|
return shownAlone(inExemplar, calls);
|
|
if (before !== undefined && after !== undefined) {
|
|
const rule = agreeing(before, after, {
|
|
field,
|
|
later: later.entity[field],
|
|
earlier: calls.earlier,
|
|
});
|
|
return rule === undefined ? undefined : { rule };
|
|
}
|
|
// The plan echoes a member this call left out: what it stored there is the member's default.
|
|
if (before !== undefined)
|
|
return before.rule === "echo" && !Object.hasOwn(later.body, field)
|
|
? {
|
|
rule: before,
|
|
default: { member: field, value: later.entity[field]! },
|
|
}
|
|
: undefined;
|
|
// This call echoes a member the plan's calls left out: what they stored there is its default.
|
|
if (after !== undefined)
|
|
return after.rule === "echo" && !calls.required.includes(field)
|
|
? { rule: after, default: { member: field, value: exemplar[field]! } }
|
|
: undefined;
|
|
return same(exemplar[field], later.entity[field]) ? {} : undefined;
|
|
};
|
|
|
|
/**
|
|
* A create plan widened by a later call, explained on its own: both calls show the same fields, but for a reference
|
|
* whose member a call without it did not send (one an earlier call added stays), every field both rule they rule alike
|
|
* as far as earlier calls allow, a field neither rules holds one value, and where only one echoes a member the other
|
|
* left out, the member becomes optional with the value the call that left it out stored. Members either call sent are
|
|
* accepted, and only those every call sent are required. Undefined when the calls disagree otherwise, so the later call
|
|
* stays unexplained.
|
|
*/
|
|
export const mergedCreatePlan = (
|
|
plan: { readonly rules: readonly FieldRule[]; readonly body: WriteBody },
|
|
exemplar: JsonObject,
|
|
later: {
|
|
readonly entity: JsonObject;
|
|
readonly body: JsonObject;
|
|
readonly rules: readonly FieldRule[];
|
|
readonly contract: WriteBody;
|
|
},
|
|
earlier: EarlierEvidence = {
|
|
kept: new Set(),
|
|
clockOf: (field) => exemplar[field],
|
|
},
|
|
): { readonly rules: FieldRule[]; readonly body: WriteBody } | undefined => {
|
|
const held = new Map(plan.rules.map((rule) => [rule.field, rule]));
|
|
const shown = new Map(later.rules.map((rule) => [rule.field, rule]));
|
|
const sentBefore = new Set([
|
|
...plan.body.accepted,
|
|
...plan.body.fixed.map(({ member }) => member),
|
|
...plan.body.required,
|
|
]);
|
|
const merged = new Map<string, FieldRule>();
|
|
const defaults: Default[] = [];
|
|
for (const field of new Set([
|
|
...Object.keys(exemplar),
|
|
...Object.keys(later.entity),
|
|
...held.keys(),
|
|
])) {
|
|
const outcome = mergedField(field, {
|
|
before: held.get(field),
|
|
after: shown.get(field),
|
|
exemplar,
|
|
later,
|
|
sentBefore,
|
|
required: plan.body.required,
|
|
earlier,
|
|
});
|
|
if (outcome === undefined) return undefined;
|
|
if (outcome.rule !== undefined) merged.set(field, outcome.rule);
|
|
if (outcome.default !== undefined) defaults.push(outcome.default);
|
|
}
|
|
const body = mergedBody(plan.body, later, exemplar);
|
|
const joined =
|
|
body === undefined
|
|
? undefined
|
|
: joinedDefaults(plan.body.defaults ?? [], defaults);
|
|
if (body === undefined || joined === undefined) return undefined;
|
|
// The plan's rules keep their order; rules the later call adds follow.
|
|
return {
|
|
rules: [
|
|
...plan.rules.flatMap((rule) => {
|
|
const kept = merged.get(rule.field);
|
|
return kept === undefined ? [] : [kept];
|
|
}),
|
|
...[...merged.values()].filter((rule) => !held.has(rule.field)),
|
|
],
|
|
body: joined.length === 0 ? body : { ...body, defaults: joined },
|
|
};
|
|
};
|
|
|
|
/** Two calls' body contracts joined, undefined when a member one accepts with any value the other fixes, or both fix it at different values. */
|
|
const mergedBody = (
|
|
body: WriteBody,
|
|
later: {
|
|
readonly body: JsonObject;
|
|
readonly contract: WriteBody;
|
|
readonly entity: JsonObject;
|
|
},
|
|
exemplar: JsonObject,
|
|
): WriteBody | undefined => {
|
|
const fixed = new Map(body.fixed.map((entry) => [entry.member, entry.sent]));
|
|
const { contract } = later;
|
|
if (
|
|
contract.accepted.some((member) => fixed.has(member)) ||
|
|
contract.fixed.some(
|
|
(entry) =>
|
|
body.accepted.includes(entry.member) ||
|
|
(fixed.has(entry.member) && !same(fixed.get(entry.member), entry.sent)),
|
|
) ||
|
|
contract.envelope !== body.envelope
|
|
)
|
|
return undefined;
|
|
const added = contract.accepted.filter(
|
|
(member) => !body.accepted.includes(member),
|
|
);
|
|
return {
|
|
...body,
|
|
accepted: [...body.accepted, ...added],
|
|
fixed: [
|
|
...body.fixed,
|
|
...contract.fixed.filter((entry) => !fixed.has(entry.member)),
|
|
],
|
|
required: body.required.filter((member) =>
|
|
Object.hasOwn(later.body, member),
|
|
),
|
|
identity: body.identity ?? contract.identity,
|
|
kinds: [...body.kinds, ...storedKinds(added, exemplar, later.entity)],
|
|
};
|
|
};
|