mirror of
https://github.com/morluto/rea.git
synced 2026-11-06 14:56:32 +00:00
* chore: restack environment fidelity onto main Keep probe comparison, paging, and tied-create behavior on main after #5600. Capture parsing keeps field-path errors and records whether a probe kept request and response bodies. Co-authored-by: morluto <morluto@users.noreply.github.com> * fix(environment): keep served collection order with the fidelity checks The tied-create collection order lives with the checks that match encoded parents. The write simulation reads field checks from that attribution, and taint uses the same segment match. Co-authored-by: morluto <morluto@users.noreply.github.com> * fix(environment): translate an object's keys only when it is keyed by learned ids Differ.object mapped every key through the probe's id map, so a field named like a learned id was compared with another field, and a real rename could pass as a mapped key. Keys now translate only for an object keyed by learned ids as learning reads one: a key both sides share needs every key evidently an id, and otherwise each entry is an entity or each key evidently an id. Any other object compares field by field. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: morluto <morluto@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: morluto <76467478+morluto@users.noreply.github.com>
210 lines
7.5 KiB
TypeScript
210 lines
7.5 KiB
TypeScript
import {
|
|
compareUnicodeCodePoints as compare,
|
|
compositeKey,
|
|
} from "../unicodeCodePointOrder.js";
|
|
import {
|
|
iterateUrlQueryParameters,
|
|
singleQueryValues,
|
|
} from "../recordedTransportUrl.js";
|
|
import {
|
|
allLines,
|
|
pathSegmentMatches,
|
|
isParam,
|
|
listsAll,
|
|
segmentsOf,
|
|
type StateRecordFacts,
|
|
type StateToolFacts,
|
|
type StateView,
|
|
} from "./environmentStateRules.js";
|
|
import { pagesOnly } from "./environmentStatePaging.js";
|
|
|
|
/** A query as replay compares a call's: decoded values by name, in any order; undefined when it repeats a name with another value, which no call's arguments can carry. */
|
|
const queryKey = (query: string | undefined): string | undefined => {
|
|
const values = singleQueryValues(query);
|
|
if (values === undefined) return undefined;
|
|
return compositeKey(
|
|
[...values].sort(([left], [right]) => compare(left, right)),
|
|
);
|
|
};
|
|
|
|
interface CheckedLine {
|
|
readonly ordinal: number;
|
|
readonly segments: readonly string[];
|
|
readonly query: string | undefined;
|
|
}
|
|
|
|
/** A list or document view's checked lines, by query, in the tool's recorded order, and which path segments are the tool's parameters. */
|
|
export interface CheckedLines {
|
|
readonly params: ReadonlySet<number>;
|
|
readonly byQuery: ReadonlyMap<string, readonly CheckedLine[]>;
|
|
/** The lines listing their whole collection that only page it, which serving answers a call supplying no query from. */
|
|
readonly unfiltered: readonly CheckedLine[];
|
|
/** For a list with a query: the names with a learned role, and the query and whole lines serving evaluates new values on. */
|
|
readonly query?: {
|
|
readonly roles: ReadonlySet<string>;
|
|
readonly lines: readonly CheckedLine[];
|
|
};
|
|
}
|
|
|
|
/** The line a read's call is answered from, and the collection serving lists for it. */
|
|
export interface ServedLine {
|
|
readonly ordinal: number;
|
|
readonly collection: string;
|
|
/** Whether the read's query names the line; serving answers a read on any other line in that line's envelope. */
|
|
readonly named: boolean;
|
|
/** Set when serving evaluates the read's own query values on a line it did not name. */
|
|
readonly queried?: boolean;
|
|
}
|
|
|
|
type LineMatch = Pick<ServedLine, "ordinal" | "collection">;
|
|
|
|
const lineOf = (record: StateRecordFacts): CheckedLine => ({
|
|
ordinal: record.ordinal,
|
|
segments: segmentsOf(record.path),
|
|
query: record.query,
|
|
});
|
|
|
|
/** A query list's roles, and its query and whole lines in the tool's recorded order, which replay keeps among equal matches. */
|
|
const queryLines = (
|
|
view: StateView,
|
|
tool: StateToolFacts,
|
|
byOrdinal: ReadonlyMap<number, StateRecordFacts>,
|
|
): CheckedLines["query"] => {
|
|
if (view.shape !== "list" || view.query === undefined) return undefined;
|
|
const answered = new Set([...view.query.record_ordinals, ...allLines(view)]);
|
|
return {
|
|
roles: new Set(Object.keys(view.query.parameters)),
|
|
lines: tool.record_ordinals.flatMap((ordinal) => {
|
|
const record = byOrdinal.get(ordinal);
|
|
return record === undefined || !answered.has(ordinal)
|
|
? []
|
|
: [lineOf(record)];
|
|
}),
|
|
};
|
|
};
|
|
|
|
/** Each list or document view's checked lines. */
|
|
export const checkedLines = (input: {
|
|
readonly tools: readonly StateToolFacts[];
|
|
readonly records: readonly StateRecordFacts[];
|
|
readonly views: ReadonlyMap<string, StateView>;
|
|
}): Map<string, CheckedLines> => {
|
|
const byOrdinal = new Map(
|
|
input.records.map((record) => [record.ordinal, record]),
|
|
);
|
|
const tools = new Map(input.tools.map((tool) => [tool.name, tool]));
|
|
const byView = new Map<string, CheckedLines>();
|
|
for (const view of input.views.values()) {
|
|
const tool = tools.get(view.tool);
|
|
if (view.shape === "recorded" || view.shape === "item" || !tool) continue;
|
|
const checked = new Set(view.checked_record_ordinals);
|
|
const byQuery = new Map<string, CheckedLine[]>();
|
|
const unfiltered: CheckedLine[] = [];
|
|
for (const ordinal of tool.record_ordinals) {
|
|
const record = byOrdinal.get(ordinal);
|
|
const key = record === undefined ? undefined : queryKey(record.query);
|
|
if (!checked.has(ordinal) || key === undefined) continue;
|
|
const line = lineOf(record!);
|
|
byQuery.set(key, [...(byQuery.get(key) ?? []), line]);
|
|
if (
|
|
view.shape === "list" &&
|
|
listsAll(view, ordinal) &&
|
|
pagesOnly(record!.query)
|
|
)
|
|
unfiltered.push(line);
|
|
}
|
|
const params = new Set(
|
|
segmentsOf(tool.path_template).flatMap((segment, index) =>
|
|
isParam(segment) ? [index] : [],
|
|
),
|
|
);
|
|
const query = queryLines(view, tool, byOrdinal);
|
|
byView.set(view.tool, {
|
|
params,
|
|
byQuery,
|
|
unfiltered,
|
|
...(query === undefined ? {} : { query }),
|
|
});
|
|
}
|
|
return byView;
|
|
};
|
|
|
|
/**
|
|
* The checked line serving answers a read's call from, ranked as replay ranks lines: every
|
|
* parameter the read's value verbatim or once percent-decoded and the query the read's, then the
|
|
* most verbatim parameters, then the most query values the read shares, then recorded order. A
|
|
* read with no query falls back, as serving does, on a whole list's lines that only page it.
|
|
*/
|
|
export const servedLine = (
|
|
lines: CheckedLines | undefined,
|
|
record: StateRecordFacts,
|
|
): ServedLine | undefined => {
|
|
const key = queryKey(record.query);
|
|
if (lines === undefined || key === undefined) return undefined;
|
|
const names = [...iterateUrlQueryParameters(record.query ?? "")].map(
|
|
([name]) => name,
|
|
);
|
|
const named = bestLine(lines, lines.byQuery.get(key) ?? [], record);
|
|
if (named !== undefined) return { ...named, named: true };
|
|
const unfiltered =
|
|
names.length === 0 ? bestLine(lines, lines.unfiltered, record) : undefined;
|
|
if (unfiltered !== undefined) return { ...unfiltered, named: false };
|
|
if (lines.query === undefined) return undefined;
|
|
// As serving does, a read whose every query name has a role answers on a query or whole line, on its own values.
|
|
const { roles } = lines.query;
|
|
if (!names.every((name) => roles.has(name))) return undefined;
|
|
const line = bestLine(lines, lines.query.lines, record);
|
|
return line === undefined
|
|
? undefined
|
|
: { ...line, named: false, queried: names.length > 0 };
|
|
};
|
|
|
|
/** How many of the read's query names a line recorded with the read's value, as replay counts them. */
|
|
const queryMatches = (
|
|
read: ReadonlyMap<string, string>,
|
|
query: string | undefined,
|
|
): number => {
|
|
const matched = new Set<string>();
|
|
for (const [name, value] of iterateUrlQueryParameters(query ?? ""))
|
|
if (read.get(name) === value) matched.add(name);
|
|
return matched.size;
|
|
};
|
|
|
|
const bestLine = (
|
|
lines: CheckedLines,
|
|
candidates: readonly CheckedLine[],
|
|
record: StateRecordFacts,
|
|
): LineMatch | undefined => {
|
|
const read = segmentsOf(record.path);
|
|
const values = new Map(iterateUrlQueryParameters(record.query ?? ""));
|
|
let best: { line: LineMatch; verbatim: number; query: number } | undefined;
|
|
for (const { ordinal, segments, query } of candidates) {
|
|
if (segments.length !== read.length) continue;
|
|
let verbatim = 0;
|
|
const matches = segments.every((segment, index) => {
|
|
if (segment === read[index]) {
|
|
if (lines.params.has(index)) verbatim += 1;
|
|
return true;
|
|
}
|
|
return (
|
|
lines.params.has(index) &&
|
|
read[index] !== undefined &&
|
|
pathSegmentMatches(segment, read[index])
|
|
);
|
|
});
|
|
if (!matches) continue;
|
|
const matched = queryMatches(values, query);
|
|
if (
|
|
best === undefined ||
|
|
verbatim > best.verbatim ||
|
|
(verbatim === best.verbatim && matched > best.query)
|
|
)
|
|
best = {
|
|
line: { ordinal, collection: `/${segments.join("/")}` },
|
|
verbatim,
|
|
query: matched,
|
|
};
|
|
}
|
|
return best?.line;
|
|
};
|