Files
reverse-engineering-anything/docs/specs/agent-environments/write-rules/verification.md
T
41cf869949 fix(environment): bound create learning and invalidate stale write proofs (#6929)
* feat(environment): report what an environment serves with an environment-coverage view

A new inspect_analysis_view kind over capture Evidence. It projects the
state an environment derives with the given template rules and reports
each tool as answered from state, replayed with its view reason, a served
write, or a withheld write with its reason, the records behind it and a
hint about what to record next. The hints cover every write reason, so a
new reason without one fails to compile.

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

* feat(environment): build an environment bundle in one command

rea environment-build <capture> [directory] inspects a HAR (or reads
capture Evidence), projects the environment manifest from the in-memory
Evidence, authenticates the pair, prepares the state and writes the bundle
environment-export writes. Every bundle file is byte-identical to the
three-step path for the same capture and options.

The report adds the capture and manifest identities, the coverage summary,
warnings (a loopback capture named after its file, credential-named body
members by pointer) and the next command. --dry-run writes nothing. The
default template rules are the four the published environments use.

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

* test(environment): cover environment-build and the coverage view

Domain tests check the hint table against the state view's reasons, the
summary, and the view against the environment-state view it summarizes.
Service tests build the fixture capture and compare every file with the
three-step path, a dry run, loopback naming and the refusals. A CLI test
compares a HAR built in one step with the three CLI steps byte for byte,
and an MCP test projects the view from every source kind.

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

* test(environment): build the real-agentenv lane's bundle with environment-build

The lane now runs the command users run: it builds the Grafana-shaped HAR
with environment-build and the packed checkout, checks the report, then
runs the bundle with agent-env as before.

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

* docs(environment): document environment-build and the coverage view

The agent environments guide opens with the one-step build: the report,
the hint for each withheld write reason, and what a bundle ships. The CLI
guide, the capture guide and the skill reference name the command and the
environment-coverage view.

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

* fix(environment): harden environment-build after review

- The credential scan walks with its own stack, so a deeply nested body
  cannot crash the build, and it also reports credential-named header
  names, query names, form body members and string arrays.
- The build checks SIGINT between stages: cancelled before the bundle
  stage it writes nothing, during the write it names the written bundle.
- A dry run opens the package tarball, so it refuses what a build refuses.
- Repeated template rules are refused, a HAR saved as .json gets a hint,
  file-derived names follow the bundle name rules, `localhost.` counts as
  a loopback origin, and the next command is shell-quoted.
- --capture-timeout-ms passes through to the HAR inspection, and a
  missing directory is refused before inspection.
- The no-observation hint covers updates and deletes of entities the state
  never held; the coverage view states that it does not apply the
  serve-time admission checks; the inspect_analysis_view description
  names the view.

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

* docs(environment): state the build's conditions and record its verification

The guides now state when the one-step bundle equals the three-step one,
which headers inspection excludes, what the credential warning scans, how
cancellation behaves, and that the coverage view does not apply the
serve-time admission checks. The spec folder records the census, the end
to end runs, the gates and the review.

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

* fix(environment): dispatch a pending interrupt before each build stage check

The manifest, state and coverage stages run synchronously, so a SIGINT
that arrived during them was only dispatched once the bundle write had
begun. Each check now yields first, so an interrupt before the bundle
stage cancels the build with nothing written.

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

* fix(environment): scan scenario credentials, own the dry-run tarball and serialize Evidence once

A browser scenario's retained header values and the query parameters of its recorded URLs are scanned for credential
names as a HAR's are, through a review projection of its records. A dry run admits and releases the package tarball
through the export's regular-file scope, so a close it cannot confirm keeps its owner, and checks for cancellation
after that last await. Evidence is written into one exactly sized buffer instead of chunks joined by a copy.

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

* docs(environment): record the review round's checks

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

* style(environment): format the build spec

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

* fix(environment): scan every form occurrence and MIME-less scenario bodies

The build's credential scan collapsed form bodies with Object.fromEntries, so a
repeated field kept only its last occurrence: `api_key=secret&api_key=` warned
about nothing although the Evidence ships the value, and repeated values were
undercounted. Form fields are now scanned as ordered name/value pairs, as
headers and query strings already are.

The review projection of a capture_browser_scenario dropped a request body
recorded without a media type, so the scan never saw it. Review keeps such a
body's text with an empty media type; the manifest projection still reads only
bodies it can classify.

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

* fix(environment): scan structured HAR form params and the request side of unattributed scenario transactions

A HAR request recorded with postData.params and no text is scanned by those parameters, each occurrence counting, and
the review projection keeps the request side of a scenario transaction whose response cannot be attributed, so the
credential warning covers what evidence.json ships in both cases.

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

* fix(environment): validate build paths and read dry-run tarballs

* fix(environment): report bodies too large for the credential scan

Text bodies over the scan's 4 MiB limit, including a response decoded only as a prefix, are reported as a
credential-scan-incomplete warning by record and side, never by content, so an absent credential-named-members
warning no longer reads as a complete check. The spec and agent-environments guide also state the path validation and
dry-run tarball read that e52ae1cd added.

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

* fix(verification): release cold source transform services

* fix(javascript): retain capture context along covered mutation paths

* fix(environment): admit serialized exports and scan HAR form fallbacks

* fix(environment): admit in-memory build inputs against heap headroom and scan SaveHar null-text forms

The inspected HAR's Evidence and the projected manifest are checked against live heap headroom before they are
serialized, with the same check the file route uses, so a capture too large to parse and seal is refused as
resource_constraint before anything is written. A request whose postData text is SaveHar's null is scanned by its
structured params.

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

* fix(environment): measure the credential scan limit in UTF-8 bytes

A body counts against the 4 MiB limit by its UTF-8 length, not its UTF-16 code units, so multibyte text over the
limit is reported as unscanned instead of parsed.

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

* fix(javascript): erase inline type imports before binding values

* fix(environment): parse credential form bodies by their media type

* fix(environment): review untyped retained form fields

* fix(environment): bound request comparisons and preserve write proofs

* fix(environment): keep write proofs and bundle review complete

* fix(environment): finalize entity overrides with simulated views

* test(environment): restore 0895c89b's untyped form tests and hint

Integrating main took main's credential scan and coverage view, which
dropped 0895c89b's tests and its body-unusable hint. Main's scan passes
the tests, now reading the scenario by event. The hint names the
response too, since extraction and serving also withhold a write as
body-unusable when its recorded response cannot be read.

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

* test(environment): scan a scenario content event recorded twice

Main's event scan reads both content events of a transaction whose
content the capture recorded twice; the test fails on 0895c89b, whose
review projection dropped that transaction's response. The spec names
event pointers among the first 20.

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

* docs(environment): record the review round's checks

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

* fix(environment): retain contradictions and trim retired state

* fix(environment): distinguish mixed array order from changed values

* fix(environment): keep dependent create state pinned

* fix(environment): keep checking retired view observations

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Kaoru0822 <208281322+kaoru0822-kitauji@users.noreply.github.com>
Co-authored-by: morluto <76467478+morluto@users.noreply.github.com>
Co-authored-by: gensokyo-dev <342580529+gensokyo-dev@users.noreply.github.com>
2026-10-24 23:57:30 +08:00

181 lines
13 KiB
Markdown

# Verification
Executed on macOS with Node v22.23.1, one change at a time on the state-targets branch, 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; it reads no calls from the five
browser-scenario captures. The session replay answers every recorded call in order, writes included, through state
serving, reading records with REA's own capture parser, and compares the reads; it covers all 50 captures.
## Gates
- `npm run -s check:fast`, `npm run -s format:check`, `npx knip`, `npm run -s scan:todos`, `npm run -s docs:check`,
`npm run -s verify:test-discovery` and `npm run -s mcp-catalog:check`: passed.
- `npm run jscpd`: 3.57% duplicated lines over 4235 files.
- `npx vitest run --project domain --project services`: 9545 passed, 6 pre-existing skips. The 10 failures (JavaScript
Map endpoints, a returned invocation's alias graph, Frida cleanup) fail the same way on main.
- `npm run -s test:prepare`, then `npm run -s 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, 91 passed.
## Measurements
| Step | Served | No state target | State-backed read views | Reads before first write | Session reads |
| ---------------------------- | ------ | --------------- | ----------------------- | ------------------------ | -------------- |
| state targets | 62 | 59 | 1,151 | 2,085 of 2,155 | 2,764 of 3,022 |
| blame and constant guard | 63 | 59 | 1,151 | 2,085 of 2,155 | 2,764 of 3,022 |
| update rules | 68 | 59 | 1,151 | 2,085 of 2,155 | 2,763 of 3,022 |
| view order | 69 | 59 | 1,151 | 2,085 of 2,155 | 2,763 of 3,022 |
| bulk writes and item actions | 71 | 48 | 1,151 | 2,085 of 2,155 | 2,763 of 3,022 |
| per-response members | 71 | 46 | 1,153 | 2,087 of 2,162 | 2,766 of 3,031 |
| nested counters | 71 | 46 | 1,153 | 2,087 of 2,162 | 2,766 of 3,031 |
| created nested clocks | 71 | 46 | 1,153 | 2,087 of 2,162 | 2,766 of 3,031 |
| unshown writes refused | 69 | 46 | 1,153 | 2,087 of 2,162 | 2,766 of 3,031 |
| catalog values, atomic bulk | 69 | 46 | 1,153 | 2,087 of 2,162 | 2,766 of 3,031 |
| review fixes | 69 | 46 | 1,153 | 2,087 of 2,162 | 2,766 of 3,031 |
| scoped envelopes, bulk rows | 69 | 48 | 1,151 | 2,085 of 2,155 | 2,763 of 3,022 |
All 187 write tools are counted at every step, and the served smoke applies every served write tool.
- **Scoped envelopes, bulk rows:** a shared envelope masks only members minted on every path, so Vault's identity entity
and group item views, which matched one of four and one of three recorded reads from the state, replay their
recordings, and Vault's entity update and delete lose their state target. Serving bulk responses that list each
written entity changes no capture's verdict.
- **Review fixes:** literal bulk ids, per-entity bulk values, pinned bulk delete members, comparison ids per
collection, echoed creation clocks, required held clocks, nested clocks checked against every call, learned sorts
kept open to contrary reads and limited to the read's own entities, one query check per recorded call, guards
relaxed only by successful calls with the exemplar's query, and slash-named counters. No tool's verdict and no
session read changed; each fix has a test that fails without it.
- **Newly served:** Ghost's post create and update, Metabase's card create and update, n8n's workflow update,
Navidrome's playlist create, and the NocoDB and Mailpit bulk deletes.
- **No longer served:** Gitea's issue update. Its create, update and read share one second, so the evidence cannot show
the update stamping `updated_at`, and serving it left the clock as it was.
- **Refused that were briefly served on this branch:** EspoCRM's task update, for the same reason, with `modifiedById`
absent from its create response.
- **Session reads:** one read is lost at Ghost #74, a 404 right after the served create whose delete the recording
lacks; Vault #80 answers 404 after an unserved create, as a served read on an entity only a replayed write made does.
- Ghost's create and update accept only the recorded html, and Metabase's create only the recorded collection,
display, type and query; the served catalog shows those values.
Every change had an independent review that rebuilt it and tried to refute it; the corrections they found are part of
the change. A final review of the whole branch found one wrongly served update (EspoCRM) and the hidden pinned values,
both fixed.
## Cost
Over the 50 captures, one Node v24.22.1 process per sweep builds the environment-state and environment-manifest views
of every capture (macOS arm64, warm medians of three runs; the cold first run is within 2% of them):
| Tree | Wall | CPU | Peak RSS | Environment-state view | Its results |
| ----------------------- | ------ | ------ | -------- | ---------------------- | ----------- |
| main `d87e73f2` | 2.61 s | 3.41 s | 516 MiB | 1,791 ms | 3.69 MB |
| state targets (`#6385`) | 2.96 s | 3.82 s | 540 MiB | 2,093 ms | 3.84 MB |
| this branch | 3.06 s | 4.07 s | 601 MiB | 2,224 ms | 3.85 MB |
The write rules add 0.10 s wall, 0.25 s CPU and 61 MiB of peak memory to the sweep while serving 7 more write tools.
Agent tokens were not measured; the views' results grow by 0.3%.
## Not included
- Templating literal ids by value: an opt-in rule served Grafana's folder delete and Vault's policy delete, but both
targets came from unserved writes, so serving them answered their own recorded calls wrongly.
- Completing a created entity from a later read: it served Bookstack's page create wrongly.
- Soft deletes, appended relation rows, rendered markup, reverse embeds, identity aliases and resource names.
## Linux integration and review corrections, 2026-10-24
The following is separate from the historical 50-capture measurements above.
Node 24.22.1, baseline `3ce311757`, reviewed implementation `352c0278d`:
- 1,236 selected source tests passed. Typecheck, built lint/module boundaries,
format, knip, metadata, architecture guards and documentation checks passed.
- Seven review regression checks failed before correction and passed after it.
Earlier structured update values now retain canonical equality digests within
the history budget. A shared envelope masks only members independently proved
minted on every concrete path. Empty advertised literals are widened while
execution enforces their values, and pinned queries are optional so their
execution defaults can run. Numeric and string bulk id arrays stay distinct.
- Nineteen compiled SDK/schema checks passed, including real HTTP MCP discovery,
empty fixed values, omitted empty and nonempty queries, and unchanged state
after refused writes. Live Gemini API execution was not performed.
- Four public datasets were downloaded at immutable Hugging Face revisions:
NocoDB `d0f7e040`, Ghost `6d5b2d1c`, Metabase `fa2aa0fd`, and Mailpit
`416b29be`, under `earakely-scale/*Env-REA`. Capture hashes, full calls,
independent recorded JSON comparisons, runtime identities and resource
receipts were retained locally. These were recorded real applications;
the original applications were not re-recorded in this verification.
| Capture | Eligible write tools before / after | Initial reads matching | Session reads matching |
| -------- | ----------------------------------- | ---------------------- | ---------------------- |
| NocoDB | 0 / 3 | 20 / 20 | 80 / 92 |
| Ghost | 1 / 1 | 19 / 20 | 72 / 88 |
| Metabase | 1 / 2 | 19 / 22 | 99 / 122 |
| Mailpit | 0 / 1 | 51 / 52 | 51 / 56 |
Every previously matching read remains matching, record by record. Real CLI
listeners and the MCP SDK reproduce the shared application's outcomes. The
feature session applies six NocoDB mutations, one Ghost deletion and one
Metabase update; the baseline applies only the Ghost deletion. A `state`
answer with view `missing` is not counted as an applied write. Mailpit exchange
65 is refused because its target was introduced only by an unserved create.
Eligibility does not establish that every recorded write can run on the initial
state, and the remaining read mismatches are preserved coverage gaps.
Three alternating fresh producer sweeps over all four captures had median
wall/CPU/cgroup peak of 5.360 s / 4.263 s / 327.3 MiB before and
4.960 s / 4.357 s / 323.1 MiB after. Ranges overlap. One complete SDK session
per app/revision, 403 versus 413 calls, took 20.905 s / 19.039 s / 508.7 MiB
before and 23.426 s / 19.396 s / 462.8 MiB after; these are single observations
with different write coverage, not a speed or memory improvement claim.
Catalog estimates (`cl100k_base`) were 8,630 / 9,408 tokens for NocoDB,
11,355 / 11,355 for Ghost, 12,162 / 12,648 for Metabase and 2,955 / 3,138
for Mailpit. Arguments, instructions and text replies were also counted in
retained receipts; actual model usage, prompts and client rendering were not
measured. No token saving is claimed.
Processes were fresh, OS caches warm or uncontrolled, on a shared Linux host.
Each task used one CPU, a 3 GiB early stop / 4 GiB hard limit and zero swap.
All eight recorded-data task groups exited zero, verified payload cleanup,
and observed no OOM or limit events. Payload-exit counters were verified;
final whole-scope counters were unavailable. Downloads, builds and runtime
identity preparation are excluded from task measurements. The current
four-capture verification does not requalify the earlier 50-capture totals.
### Bulk-response follow-up qualification
The follow-up at `f4bc513dd` classifies every returned bulk element and checks
all recorded calls with the shared serving renderer. Nested parsed-body leases
retain their enclosing scratch charge. Literal JSON IDs never pass through a
synthetic URL. The author's subsequent bulk regressions and measurement note
were retained when integrating `a319ba1af`; their historical census does not
measure this follow-up.
A fresh four-capture producer sweep and real CLI/MCP replay retained every
previously matching read (109/114 initial, 302/358 session). NocoDB, Ghost and
Metabase plans and complete SDK calls equal `9942863b2`. Mailpit's text/plain
`ok` deletion acknowledgement is not parsed by the state JSON owner, so its
contents remain unknown and its write tool is now withheld as `body-unusable`.
The prior session already refused this deletion for a missing target; no
previously applied mutation was lost. Plain-text acknowledgement support remains
unverified rather than treating unknown contents as empty. The three other
captures retain the same eight applied mutations.
This qualification-only sweep took 5.857 s wall / 5.413 s CPU / 329.9 MiB peak;
the complete SDK replay took 23.264 s / 22.298 s / 474.0 MiB. These single
observations are not pooled with the earlier performance cohort. Payload cleanup
and payload-exit resource counters were verified, with no OOM/limit events;
final whole-scope counters and actual model token usage remain unmeasured.
## Review round on main `3d84215b`, 2026-10-24
Three automated review findings were fixed in `d0198389f` and `7c25ad4dc`: a create compares the other calls' request
bodies one at a time inside their parsing reservations, an echoed reference to a nested object stays pinned when the
plan cannot rebuild the object, and a sorted read view a later read contradicts is recorded and loses the
confirmations it gave. On macOS with Node v24.22.1 at `05566419e`, the static gates passed, `npx vitest run --project
domain --project services` passed (10,164, 6 skipped; the reference-lifetimes file whose worker crashes on main was
excluded) and the focused build, export, serve and analysis-view boundaries passed (10 files, 93). Over the same 50
captures every write plan, session replay and served read equals `0895c89bc`'s: 187 write tools with 68 served, 68 of
68 smoke calls, 1,151 views, and 2,085 of 2,155 initial and 2,763 of 3,022 session reads matching.