mirror of
https://github.com/morluto/rea
synced 2026-10-25 22:08:11 -04:00
38719a6fa9
* 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>
* 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>
* 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>
* 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): 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
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: morluto <76467478+morluto@users.noreply.github.com>
165 lines
5.0 KiB
TypeScript
165 lines
5.0 KiB
TypeScript
import type { JsonValue } from "../jsonValue.js";
|
|
import {
|
|
readClock,
|
|
zoneMinutesForFormat,
|
|
type ClockFormat,
|
|
type ClockReading,
|
|
} from "./environmentStateWriteValues.js";
|
|
|
|
const MINUTE_MS = 60_000;
|
|
|
|
const pad = (value: number, width: number): string =>
|
|
String(value).padStart(width, "0");
|
|
|
|
/** Whether a parsed clock fits a learned format without widening it or learning a new spelling. */
|
|
export const matchesClockFormat = (
|
|
format: ClockFormat,
|
|
reading: ClockReading | undefined,
|
|
): boolean => {
|
|
if (reading === undefined) return false;
|
|
const spelled = reading.format;
|
|
if (format.kind === "epoch")
|
|
return spelled.kind === "epoch" && spelled.unit === format.unit;
|
|
return (
|
|
spelled.kind === "iso" &&
|
|
spelled.separator === format.separator &&
|
|
spelled.zone === format.zone &&
|
|
(format.trimmed === true
|
|
? !reading.fixedWidth && spelled.fraction <= format.fraction
|
|
: spelled.fraction === format.fraction)
|
|
);
|
|
};
|
|
|
|
/**
|
|
* The format that spells both a format's values and another clock value: the format itself, or a trimmed one when the
|
|
* two differ only in how many fraction digits they show and neither ends a fraction in a zero. `exemplar` is the value
|
|
* the format was learned from, which shows whether its own width is fixed.
|
|
*/
|
|
export const fittedClockFormat = (
|
|
format: ClockFormat,
|
|
value: JsonValue | undefined,
|
|
exemplar: JsonValue | undefined,
|
|
): ClockFormat | undefined => {
|
|
const reading = readClock(value);
|
|
if (reading === undefined) return undefined;
|
|
if (matchesClockFormat(format, reading)) return format;
|
|
const spelled = reading.format;
|
|
if (
|
|
format.kind !== "iso" ||
|
|
spelled.kind !== "iso" ||
|
|
spelled.separator !== format.separator ||
|
|
spelled.zone !== format.zone ||
|
|
reading.fixedWidth
|
|
)
|
|
return undefined;
|
|
if (format.trimmed !== true) {
|
|
const learned = readClock(exemplar);
|
|
return learned === undefined || learned.fixedWidth
|
|
? undefined
|
|
: {
|
|
...format,
|
|
fraction: Math.max(format.fraction, spelled.fraction),
|
|
trimmed: true,
|
|
};
|
|
}
|
|
return { ...format, fraction: spelled.fraction };
|
|
};
|
|
|
|
/** Whether a value spells a clock as an ISO format does with a fraction ending in a zero, which no trimmed spelling shows. */
|
|
export const showsFixedWidth = (
|
|
format: ClockFormat,
|
|
value: JsonValue | undefined,
|
|
): boolean => {
|
|
const reading = readClock(value);
|
|
return (
|
|
reading !== undefined &&
|
|
reading.fixedWidth &&
|
|
format.kind === "iso" &&
|
|
reading.format.kind === "iso" &&
|
|
reading.format.separator === format.separator &&
|
|
reading.format.zone === format.zone
|
|
);
|
|
};
|
|
|
|
/** Keep the first value showing each field as an ISO clock at a fixed width, which no trimmed spelling of the field may contradict. */
|
|
export const noteFixedWidths = (
|
|
seen: Map<string, JsonValue>,
|
|
fields: Readonly<Record<string, JsonValue>>,
|
|
): void => {
|
|
for (const [field, value] of Object.entries(fields)) {
|
|
const reading = readClock(value);
|
|
if (
|
|
reading?.fixedWidth === true &&
|
|
reading.format.kind === "iso" &&
|
|
!seen.has(field)
|
|
)
|
|
seen.set(field, value);
|
|
}
|
|
};
|
|
|
|
/** A clock format trimmed as a sibling's is, when it is spelled alike and its own value does not show a fixed width. */
|
|
export const trimmedLike = (
|
|
format: ClockFormat,
|
|
trimmed: ClockFormat,
|
|
value: JsonValue | undefined,
|
|
): ClockFormat => {
|
|
const reading = readClock(value);
|
|
return format.kind === "iso" &&
|
|
trimmed.kind === "iso" &&
|
|
trimmed.trimmed === true &&
|
|
format.trimmed !== true &&
|
|
format.separator === trimmed.separator &&
|
|
format.zone === trimmed.zone &&
|
|
reading !== undefined &&
|
|
!reading.fixedWidth
|
|
? {
|
|
...format,
|
|
fraction: Math.max(format.fraction, trimmed.fraction),
|
|
trimmed: true,
|
|
}
|
|
: format;
|
|
};
|
|
|
|
/** Calendar digits a composed string spells a clock in: date and time run together, or a date. */
|
|
export type ClockPattern =
|
|
| "YYYYMMDDhhmmss"
|
|
| "YYYYMMDD"
|
|
| "YYYY-MM-DD"
|
|
| "YYYY/MM/DD";
|
|
|
|
export const CLOCK_PATTERNS: readonly ClockPattern[] = [
|
|
"YYYYMMDDhhmmss",
|
|
"YYYYMMDD",
|
|
"YYYY-MM-DD",
|
|
"YYYY/MM/DD",
|
|
];
|
|
|
|
/** A clock value's wall clock in its own zone, in a calendar pattern; undefined when it is not a clock. */
|
|
export const clockDigits = (
|
|
value: JsonValue | undefined,
|
|
pattern: ClockPattern,
|
|
): string | undefined => {
|
|
const clock = readClock(value);
|
|
if (clock === undefined) return undefined;
|
|
const zone =
|
|
clock.format.kind === "iso" ? zoneMinutesForFormat(clock.format.zone) : 0;
|
|
const at = new Date(clock.ms + zone * MINUTE_MS);
|
|
const year = at.getUTCFullYear();
|
|
if (!Number.isInteger(year) || year < 0 || year > 9999) return undefined;
|
|
const [y, m, d] = [
|
|
pad(year, 4),
|
|
pad(at.getUTCMonth() + 1, 2),
|
|
pad(at.getUTCDate(), 2),
|
|
];
|
|
switch (pattern) {
|
|
case "YYYYMMDDhhmmss":
|
|
return `${y}${m}${d}${pad(at.getUTCHours(), 2)}${pad(at.getUTCMinutes(), 2)}${pad(at.getUTCSeconds(), 2)}`;
|
|
case "YYYYMMDD":
|
|
return `${y}${m}${d}`;
|
|
case "YYYY-MM-DD":
|
|
return `${y}-${m}-${d}`;
|
|
case "YYYY/MM/DD":
|
|
return `${y}/${m}/${d}`;
|
|
}
|
|
};
|