* fix(environment): keep read-only POSTs out of the write list A POST that repeats the latest read of its path, tunnels only read operations, names a lookup in its path's last meaningful word, or renews a session no longer counts as a state write: it is replayed, does not cut initial windows, and does not end observation searches. A tool stays a write when a related read line changes across one of its calls. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(environment): take an entity an unplanned write made when a read first shows it A successful item read of an entity the gate's copy never held, after a write no built plan models, puts the entity into the copy instead of failing the read, so later writes to it are checked as usual. An entity a modelled write touched is never taken back. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(environment): plan a create into the type its returned id is read under A POST whose path names no state type becomes a create into the type whose item read first names an id its response returned, when the id is new to the state; serving creates in that read's collection. A successful POST that returned an id a type's reads only show after it also ends that type's initial window, so entities a capture created stay out of the initial state. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(environment): end a type's initial reads at the write its reads disagree across When a type's reads disagree across a write under another root, the type's initial window now ends at the earliest write within that change and the state is extracted again, instead of leaving every view of the type replayed. The cut composes with the creation cut; the earlier wins. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(environment): replay request lines that show entities in another form A read line that shows the same entities in another form, by an extra query parameter that adds or drops members, or conflicts with a base line on a shared entity, is a variant: it leaves the view's checks and the entity observations, the view names it with the query keys that pick it out, and a call matching those keys replays its recording. A filter role whose name matches no field needs two agreeing values. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(environment): specify and record the state-target changes Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(environment): keep a read-only POST a write when its evidence is incomplete Body-level container operations are skipped like query ones, a lookup is named only by the path's last literal segment and never alongside a write word, a tool with a redirected or unanswered call is not reclassified, and the mutation veto is resolved over all candidates at once so another read-only call cannot explain a read change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(environment): answer a field selection only when the state shows it exactly Serving tests a field selection before any variant key: a projection value a replayed read gave, or one no checked line gave other than `*`, replays. A list sets aside a line asking for some fields only before the identity proof, and a dropped variant's membership change no longer rewrites the type. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(environment): hold adopted entities and linked creates to what the recording shows An adopted entity is charged against the view's budget like a created one. A linked create holds only for its exemplar's path arguments: serving refuses others and the gate counts such a call as unmodelled. A response's identity-named id links at any length, but a short one only to a type read before the call. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(environment): record the review fixes' measurements Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(environment): release a discarded planning pass before planning again A rewrite cut clears the first pass, so the budget rewinds to a checkpoint taken after the write cutoffs and created ids before the state is extracted again. Rewrite spans are charged and appended without spreading them into an argument list. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(environment): link no create whose id an earlier read showed, and read only whole batches A linked create is rejected when an earlier list of its type or read of its collection showed the returned id. Body ids are walked without spreading children into an argument list. An RPC call reads only when every operations entry names an operation of its own. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(environment): let the gate skip exactly the reads serving replays Serving's replay decision moves to the domain as replayReason, and the write gate uses it: a read serving replays can neither approve nor veto a write, and a read of every field beside a replayed selection is checked. A selection read missing a required field is still skipped, since part of an entity can neither confirm nor check it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(environment): record the second review round's checks Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(environment): read writes, links and creation cuts only from evidence that rules out other causes - A read-only POST repeats only a read that finished before it began; the veto compares the latest read finished before a call with the first begun after it, and counts a recorded absence on one side as a change. `modify` and `replace` are write words. - A redirected or unanswered write lets a later item read adopt the entity it may have made. - A creation cut counts a read still running when the POST began as after it, and a list read shows its listed entities' own ids, not those they nest. - A linked create's item read must come before any other tool's write. - Line shapes are charged to the budget as they are built. - A `fields=*` list line is whole, not a field selection. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(environment): record the third review round's checks Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(environment): tie linked creates to their returned ids and order state by the lines left - A linked create is observed only in a read of an id its response returned, envelopes included, and serving takes its recorded path values in either spelling. - A list read shows a suffix-named identity (`projectId`) distinct across its entities. - A read-only POST compares the read that finished last before it began. - An entity's first sighting comes from the lines left after variant lines are dropped. - The pairwise variant pass stops at a work limit, leaving every line checked. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(environment): record the fourth review round's checks Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(environment): keep foreign keys, stale reads and double decoding out of write evidence - A list read counts a suffix-named field as its entities' ids only when no id-named field identifies them, so an `ownerId` beside `id` cuts no window. - A read-only POST is compared with the read that finished last among those no other write follows, so a consuming call stays a write. - A linked create's source path values compare as spelled on both sides, so no spelling is decoded twice. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(environment): record the fifth review round's checks Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(environment): compare linked sources by position, decode path fields once and bound read and variant scans - A linked create's source values compare segment by segment, so a template naming two parameters alike cannot accept a changed first one. - Serving fills a linked create's path fields with its arguments decoded once, as the gate learned them. - The read-only check reads each related line's records once per call, between the nearest other writes. - Member signatures are JSON arrays, so a name holding the separator still conflicts; the variant work limit counts line pairs as well as entities. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(environment): record the sixth review round's checks Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(environment): replay late and variant-only items, fill linked path fields from the exemplar and charge membership spans - An item line first recorded after the initial window that names a new shaping parameter and shows other members is a replayed variant, like an initial one. - An item only a replayed line showed, which the state never started with, answers with its own recording; one the state held and lost stays missing. - A linked create fills its path fields from its exemplar's path decoded once, whichever accepted spelling the call sends. - Each membership change span is charged against the budget before it is kept. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(environment): record the seventh review round's checks Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(environment): decide late item lines against the lines left, charge replay keys and bisect rewrite cuts - Item lines first recorded after the initial reads are decided once variants are dropped, against the lines left: a new shaping parameter showing other members, or a selection of some fields leaving members out, replays. - Each replay key is charged against the budget before it is kept. - A rewrite span's earliest write is found by bisection over the writes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(environment): record the eighth review round's checks Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(environment): count only reads finished before a linked create began as showing its id held A list still running when the call began may show what the call made, as the creation cut already decides, so it no longer refuses the link. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(environment): keep served creates' items missing, check same-type earlier reads, and bound late lines and read-only stretches - An item only a replayed line showed replays only while no served create has added to its type, so a created and deleted item stays missing. - A linked create's earlier item-read veto checks only reads of the candidate's type or collection that show the item, so another type's id does not hide it. - An item read answered with JSON null shows no id to a creation cut. - Late lines' member names are charged before they are kept. - The read-only check precomputes nearest writes and passes once over each write-free stretch per path, skipping lines whose reads all agree. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(environment): record the ninth review round's checks Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(environment): link creates past empty item reads, sweep repeated reads once and keep only returned ids for creation cuts - A linked create passes over a later item read naming its id whose body shows no item, such as JSON null, and links the next one. - Read-only repeated reads are found in one sweep: each path's reads wait by end time until a call's start passes them, and a write clears them. - A creation cut keeps, and charges, only the shown ids some creation returned. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(environment): record the tenth review round's checks Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * perf(environment): compare variant lines through shared entities and parse each compared body once The variant pass indexes a tool's lines by the entities they show and compares only lines that share one, charging the index to the budget while it is held; the pairwise work limit still decides when a tool keeps every line checked. Read-only decisions keep each response body's canonical digest, taken once while its parse is charged, across calls and veto passes. The creation cut no longer parses reads at or after the write that ends the initial reads, and reads the ids a body shows inside its lease. Adoption looks up a read's target only after an unplanned write before it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(environment): record the eleventh review round's checks Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * perf(environment): list a type's collections only when the path's own holds no id, and parse variant base lines once The write gate's item target lists the type's held collections, and the removed targets', only when the path's own collection holds none of its ids, so a served item view's reads no longer walk every entity of the type. The variant pass parses the base lines' queries once for all the variants they key, and not at all when it found none. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(environment): record the twelfth review round's checks and the sweep's cost Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Kaoru0822 <208281322+kaoru0822-kitauji@users.noreply.github.com>
8.1 KiB
Verification
Executed on macOS with Node v22.23.1, one change at a time on main, over the 50 published capture-built environments (each app's latest capture). The served smoke rebuilds each manifest with the tree under test and applies every served write tool; the serving check answers every recorded read made before the capture's first write through state serving and compares it with the recording. The serving check reads no records from the five browser-scenario captures (Art Institute collection, LoC photos, Lychee, PhotoPrism, Pinry), so its read counts cover 45 apps; their 41 views count among the state-backed views.
Gates
npm run build:cached,npm run check:fast(typecheck and lint, no new warnings in the changed files),npm run format:check,npx knip,npm run scan:todos,npm run docs:check,npm run verify:test-discoveryandnpm run mcp-catalog:check: passed.npm run jscpd: 3.49% duplicated lines over 4074 files.npx vitest run --project domain --project services: 8992 passed, 6 pre-existing skips, on mained069bb9.npm run test:prepare, thennpm run test:focused --the environment serve and export CLI boundaries, the serve and served-writes MCP boundaries, the analysis-view CLI and MCP boundaries, the compact input schema boundary and tool schema validity: 8 files, 88 passed.
Measurements
| Step | Write tools | Served | No state target | State-backed read views | Recorded reads matching |
|---|---|---|---|---|---|
| main | 195 | 46 | 91 | 1,098 | 1,833 of 1,886 |
| read-only POSTs | 189 | 46 | 84 | 1,104 | 1,911 of 1,964 |
| adoption | 189 | 47 | 84 | 1,104 | 1,911 of 1,964 |
| linked creates | 189 | 48 | 80 | 1,105 | 1,911 of 1,964 |
| rewrite cuts | 189 | 51 | 77 | 1,110 | 1,922 of 1,975 |
| variant lines | 189 | 61 | 61 | 1,150 | 2,068 of 2,126 |
| review fixes | 189 | 61 | 61 | 1,151 | 2,075 of 2,133 |
| third round | 189 | 61 | 62 | 1,151 | 2,075 of 2,133 |
The served smoke applies every served write tool at each step. No read view loses its state backing, and no served write tool is lost.
- Read-only POSTs: six tools leave the write list (Mattermost
members/ids, Minifluxdiscover, NocoDB's two internal tunnels, Paperlessselection_data, PocketBaseauth-refresh); Paperless's notes create gains a target. Grafana'squery-historystays a write. Nomadjobs/statusesrepeats its GET, but its integers exceed JSON's exact range, so it stays a write. - Adoption: Grocy's product delete is served; Mealie's recipe delete moves from no observation to unconfirmed.
- Linked creates: Jellyfin's item delete is served; its playlist create and item update, and Mailpit's send, gain
targets. Grafana's
/api/searchand Mailpit's messages no longer hold the dashboard and message their captures created. - Rewrite cuts: Tandoor's recipe create, update and delete are served.
- Variant lines: Baserow's row create, update and delete, Keycloak's user delete, Miniflux's category create and update, PocketBase's create, update and delete, and Vikunja's task delete are served. Ghost's post create and update, Keycloak's user update, Miniflux's category delete, n8n's tag create and Vikunja's task update gain targets.
- Review fixes: no write verdict changes. A NocoDB records list whose
fieldsline had rejected it becomes state-backed, and all 7 of its recorded reads match. Without its guard, a short id would cut 23 Tandoor views' initial windows at aPOST /api/view-log/returning{"id":1}. The second round (the gate skipping exactly the reads serving replays, earlier reads vetoing a linked create, whole RPC batches, and releasing a discarded planning pass) changes no measurement. - Third round: Mailpit's send, a linked create that was never observed, loses its target: other writes (marking messages read, tagging) ran between it and the item read naming its id. No served write, read view or matching read changes.
- Fourth round: no verdict, read view or recorded read changes. A purge of temporary files removed 32 of the local captures, so this round was measured on captures restored from the published datasets, byte-identical where a local copy survived; Baserow's, Grocy's and Keycloak's published captures predate the ones measured above. On that set the previous head and this one both give 187 write tools, 62 served (smoke 62 of 62), 1,151 views and 2,085 of 2,155 recorded reads matching.
- Fifth round (on main
6a34be41): no verdict, read view or recorded read changes on the same restored captures, once the serving check passes a repeated query value as an array, as main now expects. - Sixth round: no verdict, read view or recorded read changes on the restored captures.
- Seventh round (on main
96ea32b7): WordPress's post create moves from side-effect to unconfirmed, still unserved: its only later read,/wp-json/wp/v2/posts/22?context=edit, shows the edit representation (generated_slug), so it is now replayed rather than checked. No served write, read view or recorded read changes. - Eighth round (on main
3e7e102a): no verdict, read view or recorded read changes on the restored captures. - Ninth round (on main
b7ebccf8): no verdict, read view, recorded read or session replay changes on the restored captures (187 write tools, 62 served, 1,151 views, 2,085 of 2,155 initial reads and 2,764 of 3,022 session reads matching). - Tenth round (on main
a2235b09): no verdict, read view, recorded read or session replay changes on the restored captures. - Eleventh round (on main
2968d1e9, performance review): variant lines compare only lines that share an entity, read-only decisions parse each body once and keep its digest, and the creation cut skips reads that cannot count. Every census, serving-check and session-replay row equals the previous head's. On a synthetic capture of a list and 1,000 items read plainly and with?expand=, the variant pass falls from 401 ms to 2 ms and the state build from 659 ms to 216 ms (Node v22.23.1). - Twelfth round (on main
d87e73f2, second performance review): the write gate lists a type's collections only when the path's own collection holds no id, and the variant pass parses base lines once. Every census, serving-check and session-replay row equals the previous head's. The synthetic 1,000-item capture takes 259–264 ms against main's 244–247 ms (378–397 ms before this round; Node v22.23.1, medians of seven warm runs). Over the 50 captures, the environment-state and environment-manifest views together take 2.96 s wall and 3.82 s CPU with a 540 MiB peak RSS, against main's 2.61 s, 3.41 s and 516 MiB (one Node v24.22.1 process per sweep, warm medians of three runs; the cold first run is within 2% of them); the environment-state view alone takes 2,093 ms against 1,791 ms, and its results grow from 3.69 MB to 3.84 MB. Agent tokens were not measured.
Of the 40 views variant lines make state-backed, 146 of 151 recorded reads before the first write match; the other five
(Sentry projects) are replayed, and replay returns the first of several differing recordings, as before. A paged
Paperless document list answers all five documents, a PocketBase item without expand answers without it, and a
NetBox site_id filter learned from one line is not adopted.
Not included
- Making a value that differs between two request lines volatile: it contradicts the spec's distinct query strings and made 11 recorded reads answer otherwise than replay.
- Item actions (
/{type}/{id}/archive), bulk writes at a collection path, templating literal ids by value, and envelope masking: each needs further recording evidence or a separate change.