Files
rea/tests/boundary/cli/environmentCompareCli.test.ts
T
Edgar Arakelyan 38719a6fa9 feat(environment): explain values an application derives from other fields (#7025)
* 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>
2026-10-25 05:06:21 +08:00

618 lines
18 KiB
TypeScript

import { readFile, writeFile } from "node:fs/promises";
import { join } from "node:path";
import { describe, expect } from "vitest";
import {
analysisViewCaptureEvidence,
environmentManifestCaptureFixture,
timedCaptureEvidence,
type TimedCaptureRow,
} from "../../fixtures/analysisView.js";
import { environmentStateServeFixture } from "../../fixtures/environmentServe.js";
import {
BOOKMARK_ROWS,
bookmark,
TAG_ROWS,
} from "../../fixtures/environmentStateDerivedValues.js";
import {
BOOK_NOT_FOUND,
BOOK_ROWS,
FIRST_BOOK,
SECOND_BOOK,
THIRD_BOOK,
book,
numberedUuid,
renamedBook,
} from "../../fixtures/environmentStateWrites.js";
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
import { cliTest } from "../../support/cli/cliFixture.js";
/** The books environment and a probe of the application re-seeded under other ids, written as Evidence files. */
const writeCompareFiles = async (root: string) => {
const fixture = environmentStateServeFixture(timedCaptureEvidence(BOOK_ROWS));
const first = book(101, "First Book", "2026-10-20T09:00:00Z");
const second = book(102, "Second Book", "2026-10-20T10:00:00Z");
const probe = timedCaptureEvidence([
["GET", "/api/books", [first, second]],
["DELETE", "/api/books/102", "", 204],
["GET", "/api/books/102", BOOK_NOT_FOUND, 404],
]);
const paths = {
manifest: join(root, "manifest.json"),
capture: join(root, "capture.json"),
probe: join(root, "probe.json"),
};
await writeFile(paths.manifest, JSON.stringify(fixture.manifest));
await writeFile(paths.capture, JSON.stringify(fixture.capture));
await writeFile(paths.probe, JSON.stringify(probe));
return paths;
};
/** The exact chronology and clock-format probes share one unchanged source capture. */
const writeExactClockFiles = async (
root: string,
): Promise<Map<string, string>> => {
const capture = timedCaptureEvidence([
["GET", "/api/books", [FIRST_BOOK]],
["GET", "/api/books/1", FIRST_BOOK],
["DELETE", "/api/books/1", "", 204],
["GET", "/api/books/1", BOOK_NOT_FOUND, 404],
]);
const fixture = environmentStateServeFixture(capture);
const chronologyProbe = (later: string, earlier: string) =>
analysisViewCaptureEvidence(
environmentManifestCaptureFixture([
{
startedDateTime: later,
request: { method: "GET", url: "http://app.example/api/books/1" },
response: {
status: 404,
content: {
mimeType: "application/json",
text: JSON.stringify(BOOK_NOT_FOUND),
},
},
},
{
startedDateTime: earlier,
request: {
method: "DELETE",
url: "http://app.example/api/books/1",
},
response: {
status: 204,
content: { mimeType: "application/json", text: "" },
},
},
]),
);
const ordered = chronologyProbe(
"0001-01-01T00:00:00.0000000002Z",
"0001-01-01T01:00:00.0000000001+01:00",
);
const legacyOrdered = chronologyProbe(
"2026-10-20 10:00:00.0002+0100",
"2026-10-20 09:00:00.0001+0000",
);
const clocks = timedCaptureEvidence([
[
"GET",
"/api/books/1",
{ ...FIRST_BOOK, created_at: "0001-01-01 00:00:00.000Z" },
],
]);
const originals = new Map<string, string>();
for (const [name, value] of [
["manifest.json", fixture.manifest],
["capture.json", capture],
["order.json", ordered],
["legacy-order.json", legacyOrdered],
["clocks.json", clocks],
] as const) {
const path = join(root, name);
const text = JSON.stringify(value);
originals.set(path, text);
await writeFile(path, text);
}
return originals;
};
/** Capture-confirmed generated writes and literal/clock probes, retained as unchanged Evidence files. */
const writeValueContextFiles = async (
root: string,
): Promise<Map<string, string>> => {
const recorded = {
samples: [1700000000],
nested_samples: [[1700000000]],
scalar_samples: 1700000000,
sku: "product1",
catalog: ["product1"],
serial: numberedUuid(1),
updated_at: [1700000000],
nested: { updated_at: 1700000000 },
fixed_samples: [1700000000],
};
const clocks = {
...recorded,
updated_at: [1800000000],
nested: { updated_at: 1800000000 },
};
const changed = {
...clocks,
samples: [1800000000],
nested_samples: [[1800000000]],
scalar_samples: 1800000000,
sku: "product2",
catalog: ["product2"],
serial: numberedUuid(2),
};
const first = { ...FIRST_BOOK, ref: numberedUuid(1) };
const second = { ...SECOND_BOOK, ref: numberedUuid(2) };
const third = { ...THIRD_BOOK, ref: numberedUuid(3) };
const renamed = {
...renamedBook(first, "Renamed Book", 6),
ref: numberedUuid(4),
};
const missing = { error: "missing", ref: numberedUuid(99) };
const rows: TimedCaptureRow[] = [
["GET", "/api/config", recorded],
["GET", "/api/books", [first, second]],
["GET", "/api/books/1", first],
["GET", "/api/books/2", second],
["POST", "/api/books", third, 201, { name: "Third Book" }],
["GET", "/api/books/3", third],
["PATCH", "/api/books/1", renamed, 200, { name: "Renamed Book" }],
["GET", "/api/books/1", renamed],
["GET", "/api/books/99", missing, 404],
];
const capture = timedCaptureEvidence(rows);
const fixture = environmentStateServeFixture(capture);
expect(fixture.state.normalized_result).toMatchObject({
writes: expect.arrayContaining([
expect.objectContaining({
tool: "post_books",
served: true,
response: { ordinal: 4, shape: "entity", pointer: "" },
rules: expect.arrayContaining([{ field: "ref", rule: "unique" }]),
}),
expect.objectContaining({
tool: "patch_books_by_id",
served: true,
response: { ordinal: 6, shape: "entity", pointer: "" },
rules: expect.arrayContaining([{ field: "ref", rule: "unique" }]),
}),
]),
});
const generated = {
...book(101, "Third Book", "2026-11-01T12:00:00Z"),
ref: numberedUuid(101),
};
const originals = new Map<string, string>();
for (const [name, value] of [
["manifest.json", fixture.manifest],
["capture.json", capture],
["changed.json", timedCaptureEvidence([["GET", "/api/config", changed]])],
["clocks.json", timedCaptureEvidence([["GET", "/api/config", clocks]])],
[
"generated.json",
timedCaptureEvidence([
["POST", "/api/books", generated, 201, { name: "Third Book" }],
[
"PATCH",
"/api/books/1",
{ ...renamed, ref: numberedUuid(102) },
200,
{ name: "Renamed Book" },
],
]),
],
[
"missing.json",
timedCaptureEvidence([
[
"PATCH",
"/api/books/99",
{ ...missing, ref: numberedUuid(100) },
404,
{ name: "Ghost" },
],
]),
],
] as const) {
const path = join(root, name);
const text = JSON.stringify(value);
originals.set(path, text);
await writeFile(path, text);
}
return originals;
};
describe("rea environment-compare value context", () => {
cliTest(
"keeps business values and numeric measurements literal while normalizing confirmed generation and clocks",
async ({ cli }) => {
const root = await createTestTempDirectory(
"rea-environment-value-context-",
);
const originals = await writeValueContextFiles(root);
const result = await cli.run({
arguments: [
"environment-compare",
join(root, "manifest.json"),
"--evidence",
join(root, "capture.json"),
"--probe",
join(root, "changed.json"),
"--probe",
join(root, "clocks.json"),
"--probe",
join(root, "generated.json"),
"--probe",
join(root, "missing.json"),
"--json",
],
});
expect(result.exitCode, result.stderr).toBe(0);
expect(result.json).toMatchObject({
status: "complete",
summary: {
probes: 4,
steps: 5,
equal: 3,
agreement: 0.6,
normalised: { generated: 2 },
},
probes: [
{
label: "changed.json",
normalised: { generated: 0, clocks: 2, ids: 0 },
results: [
{
equal: false,
categories: ["value"],
differences: [
{
category: "value",
pointer: "/samples/0",
live: "1800000000",
env: "1700000000",
},
{
category: "value",
pointer: "/nested_samples/0/0",
live: "1800000000",
env: "1700000000",
},
{
category: "value",
pointer: "/scalar_samples",
live: "1800000000",
env: "1700000000",
},
{
category: "value",
pointer: "/sku",
live: '"product2"',
env: '"product1"',
},
{
category: "value",
pointer: "/catalog/0",
live: '"product2"',
env: '"product1"',
},
{
category: "value",
pointer: "/serial",
live: JSON.stringify(numberedUuid(2)),
env: JSON.stringify(numberedUuid(1)),
},
],
},
],
},
{
label: "clocks.json",
normalised: { clocks: 2, generated: 0 },
results: [{ equal: true, differences: [] }],
},
{
label: "generated.json",
normalised: { generated: 2, ids: 1 },
results: [
{ equal: true, differences: [] },
{ equal: true, differences: [] },
],
},
{
label: "missing.json",
normalised: { generated: 0 },
results: [
{
tool: "patch_books_by_id",
live_status: 404,
env_status: 404,
equal: false,
differences: [
{
category: "value",
pointer: "/ref",
live: JSON.stringify(numberedUuid(100)),
env: JSON.stringify(numberedUuid(99)),
},
],
},
],
},
],
});
for (const [path, text] of originals)
expect(await readFile(path, "utf8")).toBe(text);
},
);
});
describe("rea environment-compare recorded clock spelling", () => {
for (const scenario of [
{
name: "variable fractional widths under a confirmed trimmed write plan",
rows: TAG_ROWS,
probe: [
[
"POST",
"/api/tags",
{
id: numberedUuid(20),
name: "Spare",
createdAt: "2026-10-20T10:00:00.12345678Z",
updatedAt: "2026-10-20T10:00:00.12345678Z",
},
201,
{ name: "Spare" },
],
] as const,
generated: 0,
},
{
name: "a composed bookmark URL through its creation clock",
rows: BOOKMARK_ROWS,
probe: [
[
"POST",
"/api/bookmarks",
bookmark(
20,
"https://pier.example.test/inspection",
"Pier",
"2026-10-20T10:00:00Z",
),
201,
{ url: "https://pier.example.test/inspection", title: "Pier" },
],
] as const,
generated: 1,
},
]) {
cliTest(`accepts ${scenario.name}`, async ({ cli }) => {
const root = await createTestTempDirectory(
"rea-environment-derived-compare-",
);
const capture = timedCaptureEvidence(scenario.rows);
const fixture = environmentStateServeFixture(capture);
const probe = timedCaptureEvidence(scenario.probe);
const originals = new Map<string, string>();
for (const [name, value] of [
["manifest.json", fixture.manifest],
["capture.json", capture],
["probe.json", probe],
] as const) {
const path = join(root, name);
const text = JSON.stringify(value);
originals.set(path, text);
await writeFile(path, text);
}
const result = await cli.run({
arguments: [
"environment-compare",
join(root, "manifest.json"),
"--evidence",
join(root, "capture.json"),
"--probe",
join(root, "probe.json"),
"--json",
],
});
expect(result.exitCode, result.stderr).toBe(0);
expect(result.json).toMatchObject({
status: "complete",
summary: { probes: 1, steps: 1, equal: 1, agreement: 1 },
probes: [
{
normalised: { clocks: 2, generated: scenario.generated },
results: [{ method: "POST", equal: true, differences: [] }],
},
],
});
for (const [path, text] of originals)
expect(await readFile(path, "utf8")).toBe(text);
});
}
cliTest(
"replays compact-offset submillisecond probe timestamps in exact order",
async ({ cli }) => {
const root = await createTestTempDirectory(
"rea-environment-legacy-chronology-",
);
const originals = await writeExactClockFiles(root);
const result = await cli.run({
arguments: [
"environment-compare",
join(root, "manifest.json"),
"--evidence",
join(root, "capture.json"),
"--probe",
join(root, "legacy-order.json"),
"--json",
],
});
expect(result.exitCode, result.stderr).toBe(0);
expect(result.json).toMatchObject({
status: "complete",
environment: { state: "served" },
summary: { probes: 1, steps: 2, equal: 2, agreement: 1 },
probes: [
{
label: "legacy-order.json",
results: [
{
ordinal: 1,
method: "DELETE",
live_status: 204,
env_status: 204,
equal: true,
},
{
ordinal: 0,
method: "GET",
live_status: 404,
env_status: 404,
equal: true,
},
],
},
],
});
for (const [path, text] of originals)
expect(await readFile(path, "utf8")).toBe(text);
},
);
});
describe("rea environment-compare replay and chronology", () => {
cliTest(
"preserves exact probe chronology and first-century clock-format differences",
async ({ cli }) => {
const root = await createTestTempDirectory(
"rea-environment-exact-clock-",
);
const originals = await writeExactClockFiles(root);
const result = await cli.run({
arguments: [
"environment-compare",
join(root, "manifest.json"),
"--evidence",
join(root, "capture.json"),
"--probe",
join(root, "order.json"),
"--probe",
join(root, "clocks.json"),
"--json",
],
});
expect(result.exitCode, result.stderr).toBe(0);
expect(result.json).toMatchObject({
status: "complete",
environment: { state: "served" },
summary: { probes: 2, steps: 3, equal: 2 },
probes: [
{
label: "order.json",
results: [
{
ordinal: 1,
method: "DELETE",
live_status: 204,
env_status: 204,
equal: true,
},
{
ordinal: 0,
method: "GET",
live_status: 404,
env_status: 404,
equal: true,
},
],
},
{
label: "clocks.json",
results: [
{
categories: ["clock-format"],
differences: [
{
category: "clock-format",
pointer: "/created_at",
live: '"0001-01-01 00:00:00.000Z"',
},
],
},
],
},
],
});
for (const [path, text] of originals)
expect(await readFile(path, "utf8")).toBe(text);
},
);
cliTest(
"compares the served environment with a probe of the re-seeded application and prints the report",
async ({ cli }) => {
const files = await writeCompareFiles(
await createTestTempDirectory("rea-environment-compare-cli-"),
);
const result = await cli.run({
arguments: [
"environment-compare",
files.manifest,
"--evidence",
files.capture,
"--probe",
files.probe,
"--json",
],
timeoutMs: 30_000,
});
expect(result.exitCode).toBe(0);
expect(result.json).toMatchObject({
status: "complete",
environment: { state: "served" },
summary: { probes: 1, steps: 3, equal: 3, agreement: 1 },
probes: [{ label: "probe.json", ids_mapped: 2 }],
});
},
);
cliTest(
"refuses a probe that is not capture Evidence and names it",
async ({ cli }) => {
const files = await writeCompareFiles(
await createTestTempDirectory("rea-environment-compare-refusal-cli-"),
);
const result = await cli.run({
arguments: [
"environment-compare",
files.manifest,
"--evidence",
files.capture,
"--probe",
files.manifest,
"--json",
],
timeoutMs: 30_000,
});
expect(result.exitCode).toBe(1);
expect(result.json).toMatchObject({
code: "invalid_request",
details: {
operation: "environment-compare",
issues: [{ path: ["probe", 0] }],
},
});
},
);
});