Files
538017d1d2 feat(environment): apply evidence-gated write effects to the served state (#5600)
* feat(environment): render more read tools from the state and describe responses

Keep main's environment serving and fold in coverage for non-entity reads,
response summaries, and MCP tool annotations.

Squashed rebase of #5352 onto main.

* fix(environment): keep rendered documents and quoted samples inside their budgets

A recorded body of exponential numbers can render past 256 KiB and turn a
working read into a state size error, so that document stays a recorded replay.
Description samples are measured after JSON quoting, and the replay-fallback
catalog check uses a tool count the longer response descriptions still separate.

Co-authored-by: morluto <morluto@users.noreply.github.com>

* feat(environment): apply evidence-gated write effects to the served state

Serve write tools whose later reads confirm the effect, and keep main's
state-file ownership and part-wise reply measurement.

Squashed rebase of #5600 onto the rebased #5352 tip.

* fix(environment): bound and validate write state history

* feat(environment): render more read tools from the state and describe responses

Keep main's environment serving and fold in coverage for non-entity reads,
response summaries, and MCP tool annotations.

Squashed rebase of #5352 onto main.

* fix(environment): keep rendered documents and quoted samples inside their budgets

A recorded body of exponential numbers can render past 256 KiB and turn a
working read into a state size error, so that document stays a recorded replay.
Description samples are measured after JSON quoting, and the replay-fallback
catalog check uses a tool count the longer response descriptions still separate.

Co-authored-by: morluto <morluto@users.noreply.github.com>

* feat(environment): apply evidence-gated write effects to the served state

Serve write tools whose later reads confirm the effect, and keep main's
state-file ownership and part-wise reply measurement.

Squashed rebase of #5600 onto the rebased #5352 tip.

* fix(environment): bound and validate write state history

* fix(environment): bound additional collection history

* fix(environment): credit sent fields, negative sequences, and encoded targets

A later overwrite copied surviving derived fields into the sent-member
claim, so a members-only check rejected an update whose sent field still
matched. Serving floored sequences at zero and stored missing history as
zero, so a collection sequence learned from negative ranks skipped the
next value. An absence after a delete forgot the resolved collection, and
an encoded read could not observe an update spelled with another
collection encoding.

Regression tests cover each case.

Co-authored-by: morluto <morluto@users.noreply.github.com>

* test(environment): cover resolved encoded update identities

* fix: validate timed update observations

* fix(environment): share write overlap and check clocks on earlier calls

Observation search skips a read begun before the write ended. That
comparison now uses the same overlap as the check path.

A clock learned on a later call was not checked against earlier calls
that kept the field. Serving would stamp those calls too, so the plan
stays unserved. A clock learned earlier is still refused when a later
call does not advance it.

Regression tests cover an overlapping empty update and a published_at
clock that one recorded call leaves unchanged.

Co-authored-by: morluto <morluto@users.noreply.github.com>

* fix: preserve partial update evidence

* fix(environment): let an unmodelled write explain only the list members that differ

A failed whole-list read now reports which members alone account for the
difference. An unmodelled write excuses the read's writes only when it came
after each such member's last check and pending writes, so an older change
elsewhere no longer hides a newer write the list shows undone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 7b6aab9d7191e600051693f2c192afcfc743d083)

* fix(environment): explain added fields, tie creates to serving's collection, and bound write descriptions

An update explains a field the entity lacked before when the observing
view had shown the entity, so a server-filled field is not dropped. A
create is tied to the held collection serving picks for its path. Write
descriptions and body refusals quote member names and sample them within
a byte bound. The request body is indexed once per explanation, so copied
and slugged fields no longer rescan it.

Replays 07aca54e onto the restacked branch. Its rule that a read begun
before a write ended teaches nothing is already here (fb6785dc), so only
its test is kept.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(environment): state how list reads weigh unmodelled writes, added update fields and quoted members

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* refactor(environment): keep the observing view helpers with the observations

shownBefore and the item-or-list fields check move to the observe module,
keeping the write effects within the line limit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(environment): resolve unmodelled writes onto the held collection

An unmodelled path kept the request's collection spelling. An archive under
the plain spelling of a collection held encoded marked only that plain
collection unknown, so a later update was blamed for the archive's fields
and dropped. Name that entity the way a modelled write does.

Co-authored-by: morluto <morluto@users.noreply.github.com>

* fix(environment): confirm no write through volatile fields or overwritten replacements, and bound the served clock

A members-only check no longer compares volatile fields, which render
from the read itself. A document replacement credits the members it
wrote or removed at its object, so a later merge that overwrites them
takes its confirmation away. A recorded time past the range of a date
gives no served clock instead of throwing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(environment): leave unknown entities unchecked by failed reads, volatile fields and unchecked calls

A failed item read that is no absence leaves an unknown entity unknown, so
a later usable read still checks its writes. A read showing an unknown
entity again checks no write on a volatile field. An update of an unknown
entity takes its recorded outcome and adds nothing to a built plan. A later
update that does not answer with its entity is observed only by a read
whose view shows every learned clock; without one it is unmodelled.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(environment): fill a create's path fields from the collection serving selects

The gate built a later create's path fields from the recorded request path,
while serving fills them from the held collection it selects for that path,
so a read showing the other spelling's parent could confirm a create serving
cannot reproduce. Rules still learn from the recorded path.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(environment): name unmodelled writes as modelled ones, count fields updates write, and keep later members' types

An unmodelled write below an item path names its entity through itemTarget,
and one at or above a collection resolves the held collection, as modelled
writes do, so an encoded spelling no longer hides the entity it changed.
An update's answer counts the fields its rules write, other than increments,
as available, so a field only the update writes no longer makes it
incomplete-entities. A member a later call first stores keeps that call's
JSON type in the body's kinds, which the body check falls back to.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(environment): let a document replacement overwrite everything under its object

A replacement claims only the members it wrote or removed, but it now
overwrites every earlier effect under its object, so a merge that wrote
the object it replaces loses its confirmation.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(environment): start the served clock from the records whose times a date can hold

A record timed past the range of a date no longer leaves the served clock
without a start, which the store read as zero and stamped writes in 1970;
the clock starts after the latest record a date can hold.

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: morluto <76467478+morluto@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 14:15:35 +08:00

27 KiB
Raw Permalink Blame History

Verification

Verified base: cbb7b613 (this branch before this file). Executed on macOS with Node v22.23.1 (the repository pins 24.18.0 in .nvmrc, which was not installed); the AgentEnv runs and the live comparison ran on a disposable Linux VM. Every check below ran as written after the last source change.

Gates

  • npm run build:cached, npm run check:fast (typecheck and lint), npm run format:check, npx knip, npm run scan:todos, npm run docs:check and npm run verify:test-discovery: passed.
  • npm run jscpd: 3.43% duplicated lines over 3538 files, under the 3.5% gate.
  • npx vitest run --project domain --project services: 7407 passed, 5 pre-existing skips.
  • npm run test:prepare, then npm run test:focused -- the new write MCP boundary, the serve MCP, CLI and process boundaries, the export CLI boundary, the analysis-view CLI and MCP boundaries, the compact input schema boundary and the contract description tests: 10 files, 72 passed.
  • npm run mcp-catalog:check: passed.

New tests: environmentStateWriteValues (94), environmentStateWriteExplain (101), environmentStateWriteApply (52), environmentStateWrites (16, the gate through the view), environmentStateWriteAnswer (14, serving) and environmentServeWritesMcp (1, the built CLI over MCP). The replay card pinned by the serve MCP boundary changed with the response summaries tool descriptions carry; its digest now matches the combined branch.

Real captures

The environment-state view ran over the 50 showcase captures (45 HAR inspections and 5 capture_browser_scenario captures), each within its budget in 8–110 ms. It served 33 write tools in 16 captures (11 creates, 12 updates, 10 deletes). Of the other 150 state-changing tools, 102 name no state type or document (most write to literal single-entity paths the manifest left untemplated, or to collections whose reads are not state-backed), and 48 are modelled but not served:

  • 15 unconfirmed (no later read checked them);
  • 13 no-observation;
  • 8 contradicted, for example a deleted Airbyte source and EspoCRM task still readable, Immich and BookStack item reads showing fields the create response lacked, and Sonarr's tag list ordering;
  • 7 unexplained-change, such as BookStack's description_html and Paperless's derived text colour;
  • 4 side-effect, for example a Sonarr tag also listed by /tag/detail;
  • 1 incomplete-entities: WordPress answers a delete with the post in its edit context, which the reads never showed for seeded posts.
Capture Served (create / update / delete) Not served
airbyte 2 / 2 / 0 create contradicted, delete contradicted, delete unconfirmed
baserow none no-state-target ×3
bookstack 0 / 0 / 1 create contradicted, create side-effect, update unexplained-change
directus none no-state-target ×2
espocrm none create unconfirmed, delete contradicted, update unexplained-change
ghost none no-state-target ×2
gitea 1 / 1 / 0 no-state-target ×2
grafana none create unconfirmed, no-state-target ×5
grist none create no-observation, no-state-target ×5
grocy none create no-observation, delete no-observation, update no-observation, no-state-target ×2
healthchecks none create no-observation, delete no-observation, update no-observation, no-state-target ×2
homebox none document no-observation, no-state-target ×3
immich 0 / 0 / 1 create contradicted, update contradicted
jellyfin none no-state-target ×2
keycloak none no-state-target ×1
komga none create unconfirmed ×2, no-state-target ×4
linkding 2 / 2 / 2 create unconfirmed, delete unconfirmed, update unconfirmed, no-state-target ×2
listmonk 1 / 1 / 1 —
lychee none no-state-target ×4
mailpit none document no-observation, no-state-target ×5
mattermost 1 / 1 / 1 no-state-target ×3
mealie none create no-observation, no-state-target ×2
memos none no-state-target ×3
metabase 0 / 0 / 1 create side-effect, update unexplained-change
miniflux 0 / 1 / 0 no-state-target ×3
n8n none create unconfirmed, no-state-target ×6
navidrome none create no-observation, no-state-target ×2
netbox 1 / 1 / 1 —
nocodb none create no-observation, no-state-target ×4
nomad none no-state-target ×4
paperless 0 / 0 / 1 create unconfirmed, update unexplained-change, no-state-target ×3
photoprism 1 / 1 / 0 delete unconfirmed
pinry none create unconfirmed, delete unconfirmed, update unexplained-change
pocketbase none no-state-target ×4
redpanda-console none document no-observation, no-state-target ×5
sentry 1 / 0 / 0 update unexplained-change, no-state-target ×3
shlink none create unconfirmed, no-state-target ×1
sonarr 0 / 1 / 0 create contradicted, create side-effect, update contradicted
tandoor none no-state-target ×4
umami 1 / 1 / 1 —
vault none no-state-target ×7
vikunja none no-state-target ×4
wordpress none create side-effect, delete incomplete-entities, document unconfirmed, update unexplained-change

An in-process smoke derived each capture's served state and called every served write tool once with a varied body: all 33 applied (status equal to the recording), and every created or updated entity read back from the state; deleted entities answered the recorded 400 or 404 where one was recorded. Eight older published manifests no longer match the tool names the current view derives, so their state is unavailable and they serve replay, as designed; none of them has a served write tool.

End to end on a published environment

Five published environments serve a create, an update and a delete (Linkding, Listmonk, Mattermost, NetBox, Umami); random.SystemRandom().choice picked NetBox (NetBoxEnv-REA, NetBox 4.7.3).

  • rea environment-export (packed from this branch) wrote the bundle; its smoke task passed in AgentEnv framework 0.9.1334 with 42 served tools (39 reads and post_dcim_sites, patch_dcim_sites_by_id, delete_dcim_sites_by_id).
  • Four write tasks, each graded by an env_outcome_verifier that reads the environment's state through /agentenv data/get (not the agent's reply), with the replay-qa agent over the Hugging Face router, one run per task and model:
Task gpt-oss-120b GLM-5.3-Flash Tool calls (gpt-oss)
Create a site in the Copper Valley region 1.0 1.0 get_dcim_regions, post_dcim_sites
Update a seeded site's facility and description 1.0 1.0 get_dcim_sites, patch_dcim_sites_by_id
Delete one seeded site and only it 1.0 1.0 get_dcim_sites, delete_dcim_sites_by_id
Create a site, then set its facility 1.0 1.0 get_dcim_regions, post_dcim_sites, patch_dcim_sites_by_id ×2
  • The bundle's 12 retrieval tasks (one per seeded object) passed 12/12 for both models, as they did on the published replay bundle.

The served model against the live application

The same NetBox 4.7.3 image ran on the VM, seeded by the capture's own seed script (the same ids), and one scenario ran against both the live API and the environment served from the capture. Only created and last_updated were normalised; every other member had to be equal:

Step Live Environment Equal
List sites 200 200 yes
Create Tide Gauge Annex (id 5) 201 201 yes
Read it 200 200 yes
List after the create (sorted by name) 200 200 yes
Patch its description and facility 200 200 yes
Read after the patch 200 200 yes
Create Granite Spur (id 6) 201 201 yes
Patch a seeded site 200 200 yes
Delete Tide Gauge Annex 204 204 yes
Read the deleted site 404 404 yes
Create Ledger Point (id 7, not reusing 5) 201 201 yes
Delete a seeded site with devices 409 204 no
Final list 200 200 no (follows from the delete)

11 of 13 steps were equal. The difference is NetBox's referential protection: it refuses to delete a site that devices reference, while the environment's delete removes the entity alone. The capture only deleted a scratch site with no dependents, so the gate could not see the rule; it is listed under what is not modelled.

Not covered

  • A run of the hosted real-agentenv lane in CI (the AgentEnv runs here used the published framework release on a VM).
  • Document writes on a real capture: the four modelled ones are unserved.
  • Collections tied to a create's own collection on a real capture, and UUID or token identities through an AgentEnv run (both are covered by tests).
  • Agent token usage and cost.

After merging the base branches and main

The branch then merged the serving branch at 5a61853a, the coverage branch at b4b7ebc7 (their review fixes) and main at edbd291a, then main at 17f581ff (the analysis-view tool description string, which main changes often). The gates above ran again and passed: npm run jscpd 3.44% over 3579 files; domain and services 7442 passed with 5 pre-existing skips; the focused boundary set 72 passed. The write verdicts over the 50 captures were unchanged plan by plan, and the in-process smoke again applied all 33 served write tools. The AgentEnv runs and the live comparison above ran on cbb7b613, before these merges. After the second main merge: npm run check:fast, npm run format:check, npm run docs:check and npm run mcp-catalog:check passed; the contracts, analysis-view and environment tests 857 passed with 3 pre-existing skips; the analysis-view and environment MCP and CLI boundaries 53 passed.

After the review round

The branch then merged the serving branch at c00fde41, the coverage branch at f6c756aa and main at 1629266e, and took the review fixes (82556cd5, 138ea4b1, 6d21b62e). The coverage branch quotes field names in response summaries, so the replay card pinned by the serve MCP boundary is now 12,818 bytes; removing those quotes gives back the previous 12,618-byte digest exactly. The gates ran again and passed: npm run check:fast, npm run format:check, npx knip, npm run scan:todos, npm run docs:check, npm run verify:test-discovery, npm run verify:module-boundaries and npm run mcp-catalog:check; npm run jscpd 3.47% over 3601 files; domain and services 7463 passed with 5 pre-existing skips; the focused boundary set 75 passed; conformance 279 passed. The gate is stricter, but the write verdicts over the 50 captures were unchanged plan by plan, and the in-process smoke again applied all 33 served write tools. The AgentEnv runs and the live comparison above ran on cbb7b613, before these merges and fixes.

A second review round then made the gate and serving agree in five more places (reads matched to checked lines as serving matches calls, increments and slugs held for every call, insert rules still learned after an item read, the document body check in the gate, and decoded parent path values). Each new test fails on the previous head and passes here. The same gates passed again (domain and services 7468 passed with 5 pre-existing skips, the focused boundary set 75, conformance 279, jscpd 3.47%), the write verdicts over the 50 captures were again unchanged plan by plan, and the smoke applied all 33 served write tools.

A third round kept a checked line's path spelling (replay prefers a verbatim one, so two spellings of one parent stay separate lines) and compared a later slug rule with the values earlier calls recorded rather than only their member names; both new tests fail on the previous head. A fourth round chose a read's line as replay does, so a read spelled plainly is checked against the encoded line that answers it, and its list against that line's collection; its test fails on the previous head.

A review of the fidelity branch built on this one found four more places: a document write at another path is now unmodelled in the gate, a create picks the collection the call spells verbatim, a missing entity is answered with a recorded 2xx null the gate accepted as an absence, and data/add refuses a document whose data/get form would exceed the intake limit. Each new test fails on the previous head. The gates passed again (domain and services 7475 with 5 pre-existing skips, boundaries 75, conformance, jscpd 3.46%), the verdicts over the 50 captures were unchanged plan by plan, and the smoke applied all 33 served write tools.

A sixth round, after merging the serving and coverage branches and main 335c042d, settled five more findings: a read whose usable body no longer holds the entity or list fails its check, an entity lacking a field its view always carried confirms nothing, a write a later write replaced is not confirmed by reads of the replacement, sequences never reuse a value the state started with, and a collection keeps its spelling after it empties. Each new test fails on the previous head. Over the 50 captures one capture's verdicts change: Linkding's bundle create and patch, confirmed only by a list read after the same bundle was deleted, are now unconfirmed, so 31 write tools are served (10 creates, 11 updates, 10 deletes) and the in-process smoke applies all 31. The gates passed again (domain and services, boundaries, conformance and jscpd).

A seventh round settled two more findings. An update keeps its confirmation when a later write overwrites only part of what it changed: a derived change, such as an incremented version, counts as part of the update's effect, and a later increment adds to the field rather than replacing it. A reset or load forgets the collection spellings of the state it replaced. Both new tests fail on the previous head. The verdicts over the 50 captures are unchanged plan by plan, the in-process smoke applies all 31 served write tools, and the gates passed again.

An eighth round settled three more. A later write erases the fields of an earlier effect it overwrites, so two writes that together overwrite all of an update's changes leave it nothing to be confirmed by. A recorded outcome erases what earlier writes changed on its entity. A load keeps the Evidence's own collection spellings, so a create in a collection the loaded state does not hold keeps its recorded spelling. Each new test fails on the previous head. The verdicts over the 50 captures are unchanged plan by plan, the smoke applies all 31 served write tools, and the gates passed again.

A ninth round made a create prefer a collection the state holds over an equally fitting spelling known only from the Evidence, so a create after a load lands in the loaded collection and reads back. Its test fails on the previous head; the verdicts and the smoke are unchanged and the gates passed again.

A tenth round settled six findings Greptile raised on the list-lines branch against this branch's code:

  • a read with no query on a whole list is checked on the paging line serving answers it from, and a line whose query repeats a name is never checked;
  • a read made while a write to its targets was still running checks nothing;
  • a later call that stores a member otherwise than the plan refuses the plan;
  • a create keeps the spelling of a collection the capture listed empty;
  • a wide array no longer overflows the stack while checking path-dependent values;
  • the guide counts served write tools among the catalog's.

Each new test fails on the previous head. The verdicts over the 50 captures are unchanged plan by plan, the smoke applies all 31 served write tools, and the gates passed again.

An eleventh round settled six more findings, five raised on this branch and one on the fidelity branch:

  • a passing read confirms only the writes it shows something of;
  • an item read names its entity as serving does, percent-decoded spellings included;
  • a read a write's effect was learned from still contradicts the other writes;
  • a line repeating a query name with one value stays checkable;
  • document writes join the effect tracking, so a replacement overwrites an earlier merge;
  • a call naming a page that the collection has outgrown answers from its recording (page-exceeded), and the gate does not check such reads.

Each new test fails on the previous head. The verdicts over the 50 captures are unchanged plan by plan, the smoke applies all 31 served write tools, and the gates passed again.

A twelfth round settled four findings Greptile raised on the list-lines branch against this branch's code:

  • a create that answers with its id alone is observed only through a read of that id;
  • a later call that is the first to send the path's id in its body adds the identity member to the plan;
  • the copy search walks a request body with an index, so a wide body no longer takes quadratic time (500,000 zeros: 63 s before, 74 ms after);
  • the sent values of fixed members a plan keeps are counted against the view's allowance.

Each new test fails on the previous head. The verdicts over the 50 captures are unchanged plan by plan, the smoke applies all 31 served write tools, and the gates passed again.

A thirteenth round settled seven findings Greptile raised on this branch, the fidelity branch and the list-lines branch:

  • a passing read that shows nothing of a write leaves it for a later check, instead of clearing it;
  • a read begun while a write to an unknown entity still ran neither checks nor shows the entity again;
  • a later create whose sequence fields do not follow the learned sequence refuses the plan;
  • a document replacement below the root overwrites only what it replaced;
  • a built document plan writes later calls at its own object, even one sharing no member with the document;
  • an update or delete finds its entity as an item read does, percent-decoded spellings included;
  • a _start–_end range bounds a page as limit does, in serving and in the gate.

Each new test fails on the previous head. The verdicts over the 50 captures are unchanged plan by plan, the smoke applies all 31 served write tools, and the gates passed again.

A fourteenth round settled two findings Greptile raised on the list-queries branch against this branch's code:

  • the type's identity field never becomes a constant: an update whose id member the application ignored no longer writes the exemplar's id into the entity another call names;
  • a page size of zero bounds nothing, so a whole line recorded with limit=0 answers from the state.

Both tests fail on the previous head. The verdicts over the 50 captures are unchanged plan by plan, the smoke applies all 31 served write tools, and the gates passed again.

A fifteenth round settled nine findings Greptile raised on this branch, the fidelity branch and the list-lines branch, five of them on the previous rounds' own changes:

  • an entity field whose name looks like a pointer is matched by name; only a document's pointers cover the members under them;
  • a failed read contradicts each exercised write no unmodelled write came after, rather than skipping all of them when the oldest pending write came before one;
  • an item read, update or delete names its entity as serving does, decoding the id once, so book%2531 no longer reaches book1;
  • a plan whose calls use a member both as the identity and as a fixed member, or fix it at different values, is refused;
  • a size of zero bounds nothing only on a line whose recording listed items under it;
  • an update whose observation drops a field the entity held is refused;
  • a later create whose clock takes another format, or whose unique value is not a UUID, is refused;
  • a derived state data/get would write past 32 MiB is not served.

Each new test fails on the previous head. The verdicts over the 50 captures are unchanged plan by plan, the smoke applies all 31 served write tools, and the gates passed again.

A sixteenth round settled four findings Greptile raised on the fifteenth round's changes, here and on the fidelity, list-lines and list-queries branches:

  • a size of zero is no bound only by itself: a range or another size on the same line still bounds the page;
  • an update is refused for a dropped field only when a later read shows it dropped, since a write's own response may leave fields out;
  • interference is weighed against a tool's latest call on the read's entity or collection, not its latest call anywhere.

Each new test fails on the previous head. The verdicts over the 50 captures are unchanged plan by plan, the smoke applies all 31 served write tools, and the gates passed again.

A seventeenth round settled two more findings Greptile raised on the list-queries branch against this branch's interference rule: an unmodelled write counts against a tool when it followed the tool's call on any target the read covers, so a later call on another entity does not hide it; and a failed item read contradicts the writes no unmodelled write followed, checking only the others on their members. Both tests fail on the previous head. The verdicts over the 50 captures are unchanged plan by plan, the smoke applies all 31 served write tools, and the gates passed again. A follow-up weighs interference only on targets where the tool's effect survives, so a call whose effect a later write overwrote no longer excuses a later mismatch; its test fails on the previous head.

Collection-history accounting check

On 2026-10-22, Node 24.18.0, I compared the source before the write-history change (cfb42ddde) with the bounded-history implementation. Each operation inserted an entity at a new collection path with a 128-character repeated suffix and immediately deleted it. Across five fresh Vitest worker processes, the median for 1,000 completed cycles was 14.57 ms wall / 23,867 us CPU before and 9.98 ms / 16,914 us after. The process RSS samples were 381.1–387.9 MiB before and 380.9–387.6 MiB after. This fixture benchmark measures one matched completed trace, not a representative user task or process-family peak memory. The values include Vitest and V8 and are noisy; they do not establish a memory bound. The 1 MiB policy is a deterministic accounting allowance for additional identifier history, set to 1/32 of the separate 32 MiB state-intake ceiling; it is not derived from RSS samples and does not bound total store or process memory.