[PR #7260] [MERGED] feat(environment): serve the writes callers make without recorded form fields #7753

Closed
opened 2026-11-03 13:40:10 +00:00 by bsakaguchi · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/morluto/rea/pull/7260
Author: @earakely-scale
Created: 10/26/2026
Status: ✅ Merged
Merged: 10/27/2026
Merged by: @morluto

Base: main ← Head: feat/environment-write-quality


📝 Commits (10+)

  • 0381a18 feat(environment): serve the writes callers make without recorded form fields
  • 5499355 docs(skill): show how to read signed-in pages before recording
  • f1675f8 fix(environment): accept every empty spelling recorded creates sent
  • 5a3c676 docs(skill): keep the vary-fields step neutral on UI or API writes
  • 2fe849d docs(environment): record verification for blank members and sorted writes
  • 0ceff6f Merge remote-tracking branch 'origin/main' into feat/environment-write-quality
  • 053264b fix(environment): address review of blank members and sorted writes
  • 0c0c6ef Merge main 19b2972d into feat/environment-write-quality
  • b490c8f fix(environment): answer sent empty members as sent, and keep a placed create's order
  • 8432ea6 Merge main c6893d1a into feat/environment-write-quality

📄 Description

Summary

Environments that agents build from a prompt now record writes through an application's forms. The write model then
refused or withheld many of the writes natural-language tasks ask for:

  • Form members. A form sends every field it shows, the ones left alone as "", null, false, [] or {}.
    Every member a recorded create sent was required, so a call naming a person and an email was refused for twenty
    EspoCRM members, a Homebox tag for color and icon.
  • Paperless all. Paperless-ngx lists carry all, the listed ids in order. The state did not render it, so the
    first list read after every Paperless create contradicted it: no Paperless create was ever served.
  • Sorted collections. Grafana lists folders by title and Paperless tags by name. An update kept its entity in place,
    so a rename that sorts elsewhere was contradicted, and served renames listed in the wrong order.
  • Unloaded answers. Vikunja answers a create before loading the owner it stored (empty name, Go's zero time) and
    spells empty lists [] where reads spell null, so its label creates and a task update were withheld.

This PR:

  1. Makes a create member a recording sent empty optional: a call that leaves it out is applied as sending the recorded
    empty value (body.blank), and either recorded spelling is accepted.
  2. Renders list envelope members that list the listed ids (listed_ids).
  3. Moves an updated entity where its collection's sort puts it, in replay and serving, and learns that sort from a
    failed whole-collection read after a rename when no create showed one.
  4. Takes a create's item read-back over values its answer had not loaded, and treats an unsent field changing only
    between null, [] and {} as unchanged.
  5. Withholds a later create that stores an unruled field otherwise at the create, so later reads still check other
    writes.
  6. Updates the skill: one create sends every field and the second only what a person must give; how to read
    signed-in pages' accessibility trees; a UI write whose body the browser did not expose is re-recorded once with
    request steps.

Results:

  • Over the 109 recorded captures, served writes go from 247 to 279 and none goes from served to withheld: every
    Paperless create is now served (23), plus Vikunja label creates and a task update, Grafana's folder rename and
    delete, and others. 65 plans require fewer members.
  • Rebuilding the same 14 recordings, the 42 write tasks pass 24/42 and 23/42 (gpt-oss, GLM) against 18/42 and 19/42
    on main, and no recording passes fewer.
  • Fresh recordings vary more than builds do (below). With the final skill, Linkding (0/2 to 2/2) and Miniflux (no
    environment to 3/3) gain; Grafana loses because its agent created through the API, which needs #7162's address
    fixes.
  • Earlier rounds' Homebox and Linkding task specs checked for objects an older round left behind, so their 0/2 was the
    spec's. With the corrected specs, main passes them too.

Problem and expected behavior

  • A create should not require a member that a recording only ever sent empty.
  • A list envelope member listing the listed ids should follow the state.
  • A rename in a collection kept sorted by the renamed field should move the entity, as the application does.
  • A create's answer that has not loaded a stored value, or spells an empty list differently from reads, should not
    contradict the write.
  • Captures that need none of this should build the same plans as before.

Change and scope

  • Blank members (environmentStateWriteExplain.ts, environmentStateWriteCreateCalls.ts,
    environmentStateWriteApply.ts): creates explain empty members as body.blank; merges keep every recorded empty
    value, the earliest first, also once a later call teaches a default; created fields apply a left-out member as the
    earliest recorded it unless it has a default; the body check accepts any recorded empty value. Write answers spell
    empty lists and maps as their recorded answers did. Updates are unchanged.
  • Listed ids (environmentStateListFacts.ts, environmentStateRules.ts, environmentStateGate.ts,
    environmentStateListQuery.ts): a top-level envelope member listing two or more listed entities' identity values in
    list order; a rendered list sets it, except on a page of more matches.
  • Sorted collections (EnvironmentStateWriteSimulation.ts, environmentStateWriteChecks.ts,
    environmentStateStore.ts, environmentStateWriteAnswer.ts, environmentStateBulkAnswer.ts): the replayed state
    moves an entity whose sorted field changed; update plans carry the sort of their type's placed creates as insert; serving computes the
    final order once as replay moves the written entities in turn (resortedMembers) and installs it with the
    replacements; #retrySort tries a sort by a field the update wrote when a whole-collection read fails and the
    creates are not already sorted by such a field, unless a create was placed by a sort that excludes it.
  • Observations (environmentStateWriteObserve.ts, environmentStateWriteCreate.ts,
    environmentStateWriteExplain.ts): loadedLater accepts empty, zero, false, Go zero time and nested objects;
    emptySpellings for unsent update fields; unruledDiffers for later creates (volatile fields excepted).
  • Skill (version 51) and docs: docs/web-network-captures.md and
    docs/specs/agent-environments/write-form-calls/.

Not included: derived members (EspoCRM's name and emailAddressData, Paperless's text_color); request bodies
Chromium does not expose for some UI writes (Vikunja's); Vikunja's task create under its project path; the saved-values
work of #7162, whose Grafana fixes this PR does not include.

Contract and boundary impact

  • Semantic owner and earliest changed stage: the environment-state write model (src/domain/analysisView) and the
    served state store.
  • Contribution scope fit and linked maintainer decision: within the environment-state writes of #4933.
  • CLI and MCP/tool-catalog contract: environment-state views may carry body.blank on write plans, insert on
    update plans, and listed_ids on list views. The full catalog grows 648 bytes; the compact catalog, 554,640 bytes,
    does not change.
  • Provider, bridge, target-format, or platform compatibility: unchanged.
  • Evidence, artifact, provenance, or reconstruction contract: views that carry the new members are refused by
    earlier REA versions, whose schemas are strict; earlier views parse unchanged. Served environments built by this
    version apply blank members and sorted moves.
  • Target selection, authority, upstream behavior, and cleanup details: not applicable.
  • Generated metadata, package, or installation impact: the skill version goes from main's 50 (#7267) to 51.

Expected effects:

  • Read existing files or captured data.
  • Launch or attach to a process.
  • Inject instrumentation or run scripts/code.
  • Change process memory, registers, files, or configuration.
  • Contact network services.
  • Recover protected code or work around protections.
  • Documentation or usability only; no target interaction.
  • Not sure yet; investigation or maintainer guidance needed.

Evidence and regression coverage

Write plans (local, main 99062fea against this branch, 109 captures):

Captures Write plans Served on main This branch
50 published 187 70 70
59 recorded from a prompt 337 177 209

The same recordings, built by main and by this branch (Claude Code claude-sonnet-5-5 recordings; write tasks run
with fireworks_ai/gpt-oss-120b then fireworks_ai/glm-5p3-flash, each checked from the environment's state):

Recording Write tasks Main This branch
Paperless, saved-values 3 1/3 2/3 3/3 3/3
Paperless, main (two) 3 + 3 3/6 3/6 5/6 5/6
Grafana, saved-values 2 0/2 0/2 1/2 1/2
Grafana, main (two) 2 + 2 3/4 3/4 4/4 3/4
Vikunja, saved-values + main 5 + 5 3/10 3/10 3/10 3/10
EspoCRM, saved-values + main 3 + 3 1/6 1/6 1/6 1/6
Homebox, saved-values + main 2 + 2 2/4 2/4 2/4 2/4
Linkding, saved-values + main 2 + 2 2/4 2/4 2/4 2/4
Miniflux, saved-values 3 3/3 3/3 3/3 3/3
Total 42 18 19 24 23

Fresh recordings (served writes, then tasks; the branch's final-skill recording where there were several):

Application Write tasks Main This branch
Paperless 3 2, 1/3 2/3 8, 1/3 2/3
Linkding 2 0, 0/2 0/2 5, 2/2 2/2
Miniflux 3 no bundle 4, 3/3 3/3
Grafana 2 3, 2/2 2/2 0, 0/2 0/2
Vikunja 3 + 2 3, 1/3 1/3 + 2/2 2/2 1, 1/3 1/3 + 0/2 0/2
EspoCRM 3 2, 1/3 1/3 2, 1/3 1/3
Homebox 2 4, 2/2 2/2 3, 2/2 2/2
  • Each fresh recording builds the same plans with main and with this branch, apart from the gains above (Paperless 0
    to 8 served on the branch's recording, 2 to 3 on main's; Vikunja's main recording 3 to 4), so the per-application
    differences are what each agent recorded. The spec folder's verification has every round.

  • The first two branch rounds showed two skill problems, both fixed here. The sixth step said "fill every field the
    form offers", and Vikunja's agents then wrote through the UI, whose request bodies Chromium did not expose, so every
    write was body-unusable; the step now says how the creates differ, not where they are made, and such a write is
    re-recorded with request steps. EspoCRM's minimal second contact sent null and false where the first stored
    strings and false; the merge refused it until this PR accepted every recorded empty spelling.

  • Miniflux's branch agent created an API token in the UI and sent it as a literal header value, which REA records as
    written. The instance and token were destroyed after the run. A follow-up should warn on literal credential-named
    headers; #7162's secret saved values are the way to carry such a token.

  • Tests added or updated: src/domain/analysisView/environmentStateFormWrites.test.ts (plans) and
    src/application/environment/environmentStateFormAnswer.test.ts (serving), with fixtures in
    tests/fixtures/environmentStateFormWrites.ts.

  • Base reproduction or other evidence: every new test fails on main, which requires every form member, contradicts the
    Paperless and folder fixtures and leaves renamed entities in place.

  • User-visible CLI/MCP output: served write tool descriptions list fewer required members.

  • Remaining proof gaps: see "Not verified" in the spec folder's verification.

For evidence-bearing changes:

  • Observed, derived, and inferred claims remain distinguishable.
  • Artifact identity, source provenance, and failed attempts remain preserved.
  • Unsupported, incomplete, unavailable, or uncertain outcomes remain visible.

Validation performed

  • npm run -s check:fast, scan:todos, docs:check, verify:test-discovery, mcp-catalog:check, jscpd — passed
    at 053264bd (main 662ec2e4 merged).
  • npx oxfmt --check . — reports scripts/verify-apk-signature.mjs only, which main 662ec2e4 reports too.
  • npx knip — reports isToolFamilyId in src/contracts/toolFamilies.ts only, which main reports too.
  • npx vitest run --project domain --project services — 11,142 passed, 6 skipped at 053264bd.
  • npx vitest run --project domain --project services --project adapters (before the merge) — 18,281 passed, 25
    skipped, 1 failed ("stops Inspector target authorization after the current stat settles"), as on main here.
  • npm run -s test:focused -- environment build, export and serve CLI, analysis-view CLI and MCP, environment serve
    MCP boundaries — 7 files, 75 passed. tests/boundary/mcp/outputSchemaProfile.test.ts — 4 passed.
  • The 13 first tests fail on main 99062fea, and the 6 added for review fail on 0ceff6fa; all 19 pass on this
    branch.
  • Census and agent runs as above, on Ubuntu 24.04 VMs with snap Chromium.

Second review round at b490c8fd (main 19b2972d merged in 0c0c6ef7), on macOS with Node v24.22.1:

  • b490c8fd answers an empty member a call sent as sent (an unsent one keeps its recorded answer's spelling), and lets
    a whole-collection read that placed a create rule out the sorted inserts its order does not follow, so a later rename
    cannot teach a sort an earlier read excluded (Greptile follow-ups). Its two tests fail on 0c0c6ef7.
  • check:fast, format:check, knip, scan:todos, docs:check, verify:test-discovery, mcp-catalog:check and
    jscpd: passed; knip and oxfmt no longer report anything since the main merge. domain + services: 11,282
    passed, 1 failed (SetupRegistrationPrewarm's detached-descendant test); adapters: 7,382 passed, 1 failed (the
    Inspector stat test). Both fail the same way on main. Focused CLI and MCP boundaries: 7 files, 77 passed.
  • Census over the 130 captures: 615 plans and 328 served on 053264bd and on this head, with no plan's rules, body,
    insert or verdict changing.

Third review round at 3de7d9a7 (main c6893d1a merged in 8432ea6e), on macOS with Node v24.22.1:

  • 3de7d9a7 keeps the values a create's answer had not loaded (observation.read.unloaded) in served create answers,
    checks recorded bulk answers with each target's sent members, and narrows a create's sorted inserts only on reads in
    collection order, not ones their query sorts (Greptile follow-ups, one posted on #7274). Its three tests fail on
    8432ea6e.
  • check:fast, format:check, knip, scan:todos, docs:check, verify:test-discovery, mcp-catalog:check and
    jscpd: passed. domain + services: 11,336 passed; adapters: 7,415 passed, 1 failed (the Inspector stat test,
    as on main). Focused CLI and MCP boundaries: 7 files, 79 passed; outputSchemaProfile: 4 passed.
  • Census over the 130 captures: 615 plans, 322 served. Main's 63220edc ("withhold clock writes without an anchor")
    withholds six clock-stamping writes as unknown-clock in three recordings whose request completions are not all
    known (Homebox tag create and update, Vikunja label create and update twice): 8432ea6e, the main merge alone,
    serves 322 too. This round's fixes change no plan's rules, body, insert or verdict.

Fourth review round at 28cda1f3, on macOS with Node v24.22.1:

  • 28cda1f3 lets a rename teach a sort only when the entities no exercised write changed already follow it, since
    serving moves only written entities, so seeds listed out of order no longer teach a sort serving would answer
    differently (Greptile follow-up). Its test fails on 3de7d9a7.
  • check:fast, format:check, knip, scan:todos, docs:check, verify:test-discovery, mcp-catalog:check and
    jscpd: passed. domain + services: 11,337 passed; adapters: 7,415 passed, 1 failed (the Inspector stat test,
    as on main). Focused CLI and MCP boundaries: 7 files, 79 passed.
  • Census over the 130 captures: 615 plans and 322 served, the same as 3de7d9a7 with no plan changing.

Fifth review round at 4b233f76 (main 8706068b merged in 4231166b), on macOS with Node v24.22.1:

  • 4b233f76 keeps a create answer's unloaded recorded value unless the call sent that member and the plan stores it as
    sent, and stops a rename teaching a sort from a read its query sorts (Greptile follow-ups posted on #7274). Its two
    tests fail on 4231166b.
  • check:fast, format:check, scan:todos, docs:check, verify:test-discovery, mcp-catalog:check and jscpd:
    passed. knip reports staticFileUrlPath and ObjcClassImportBindingCoverage only, as main 8706068b does.
    domain + services: 11,405 passed; adapters: 7,437 passed, 3 failed (the Inspector stat test and two AppKit
    NSCustomResource image key tests), which fail the same way on main. Focused CLI and MCP boundaries: 7 files, 79
    passed.
  • Census over the 130 captures: 615 plans and 322 served, the same as 28cda1f3 with no plan changing.

Sixth review round at 94460e80, on macOS with Node v24.22.1:

  • 1da04f69 gives an update its type's sort only from a create a list read showed placed, and replaces the set of
    entities a write ever changed with those changed since a list last showed them, so a rename a list confirmed in place
    no longer blocks a later sort (Codex on #7274, Greptile on #7332). 94460e80 makes replay move updated entities by
    that same placed sort, and clears an entity from that set only on a read whose order the state decides for every
    entity it lists, not one its query sorts (Greptile follow-ups). Their four tests fail on the commit before each.
  • check:fast, format:check, scan:todos, docs:check, verify:test-discovery, mcp-catalog:check and jscpd:
    passed. knip reports staticFileUrlPath and ObjcClassImportBindingCoverage only, as main does. domain +
    services: 11,409 passed. Focused CLI and MCP boundaries: 7 files, 79 passed. The adapters project was not rerun;
    this round changes only the write model.
  • Census over the 130 captures: 615 plans and 322 served, as on 4b233f76. One plan changes: Metabase
    put_card_by_id stays served without the name sort its create only guessed (insert null).

Seventh review round at 6976a156, on macOS with Node v24.22.1:

  • 6976a156 makes #retrySort try a sort by the order serving gives, moving each write since a list in collection
    order last showed its entity in turn against the others' values before their own writes, instead of sorting the
    whole collection, which a list could match while serving answered otherwise (Greptile on #7274). The state records
    the values each write replaced for this. Its test fails on 94460e80.
  • Static checks pass but knip, as on main; domain + services 11,410 passed; focused boundaries 79 passed.
  • Census over the 130 captures: 615 plans and 322 served, with no plan or view changing against 94460e80.

Main merge at 60876546 (main 5aa23c3d), on macOS with Node v24.22.1:

  • 26b2e8cc merges main up to 60822e07. Main's #7267 rewrites the lines beside the blank-member docs and the
    empty-spelling check, so 70c9a7c2 sets those two hunks aside, fa647e89 merges the rest of main without a
    conflict (a conflicted merge's pre-commit autofix rewrites main's own files), and 60876546 puts them back on main's
    text and takes skill 51, as main is at 50. The diff against main touches the same 30 files as before.
  • Static checks pass but knip, which reports staticFileUrlPath, ObjcClassImportBindingCoverage and
    AndroidManifestComponentInventory, as main does. domain + services: 11,649 passed, 8 failed
    (appleApplication.test.ts), which fail the same way on main. Focused boundaries 79 passed; outputSchemaProfile,
    toolSchemaValidity, mcpSchemaValidation, outputSchemaSemanticConstraints and analysisViewMcp 54 passed.
  • Census over the 130 captures: main serves 279 and this branch 322, as before the merge, and no write goes from served
    to withheld. Against 6976a156 only three Grafana folder creates change, to the rules main gives them.

Eighth review round at 2526c652, on macOS with Node v24.22.1:

  • 2526c652 rules out a sort for a type when a whole list in collection order showed a renamed entity where moving it
    by that sort would not have put it, so a later rename of the same entity cannot teach it; and answers a create
    without members its item read added inside an object the answer showed, as the recorded answer did, while the state
    keeps them (Greptile follow-ups, posted on this PR and #7274). Its three tests fail on 60876546.
  • Static checks pass but knip, which reports main's three findings. domain + services: 11,652 passed, 8 failed
    (appleApplication.test.ts, as on main). Focused boundaries 79 passed.
  • Census over the 130 captures: 615 plans and 322 served, with no plan or view changing against 60876546.
  • CI: every failing job (the eight test shards, portable and source tests) fails the same tests on main 5aa23c3d.

Compatibility, safety, and release

  • Breaking changes or migration steps: none. Views with body.blank, update insert or listed_ids are refused by
    earlier REA versions, as any new view member is.
  • Real Hopper/Ghidra, browser, or OS coverage: Chromium on Ubuntu 24.04 for the recordings; the change itself is in
    the offline write model and served state.
  • Package or release metadata impact: skill version 51.
  • Security, privacy, process, or containment review: no new credential handling. A blank member is applied only as a
    recorded call sent it. The Miniflux run above shows agents may put a created token in a literal header.

Merge update at fe06d5009: resolved conflicts against main aa210b7cc, which already includes this PR’s changes through #7274. The resulting tree is identical to that main commit, so this PR has no remaining diff. check:fast and the focused form-write, later-read, and serving source tests passed (48 tests). No new real-provider run was performed.

Author readiness

Human owner: @earakely-scale

  • I reviewed the final diff and relevant existing code, understand the change and its edge cases, and take responsibility for the complete PR, including AI-assisted work.
  • I checked the evidence above, confirmed it supports the current change, and recorded meaningful validation gaps.
  • I declare this PR ready for initial maintainer review.

Before merge

  • This PR addresses a concrete problem or an agreed enhancement.
  • Material scope and authority questions are resolved, and any required maintainer scope decision covers the final diff.
  • The PR has one focused outcome and the title follows type(scope): outcome.
  • Related issue is linked, or the reason for not linking one is stated above.
  • Tests cover changed observable behavior and meaningful failure paths.
  • Owning docs, contracts, and generated metadata are updated where needed.
  • User-visible CLI/MCP changes include representative output.
  • I checked the final diff for secrets, unrelated cleanup, and unsupported claims.

🤖 Generated with Claude Code


🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/morluto/rea/pull/7260 **Author:** [@earakely-scale](https://github.com/earakely-scale) **Created:** 10/26/2026 **Status:** ✅ Merged **Merged:** 10/27/2026 **Merged by:** [@morluto](https://github.com/morluto) **Base:** `main` ← **Head:** `feat/environment-write-quality` --- ### 📝 Commits (10+) - [`0381a18`](https://github.com/morluto/rea/commit/0381a18df41725ed641025ac0d804361e0d05fe6) feat(environment): serve the writes callers make without recorded form fields - [`5499355`](https://github.com/morluto/rea/commit/54993559f44a86be4fcd82e312542f533e4684b1) docs(skill): show how to read signed-in pages before recording - [`f1675f8`](https://github.com/morluto/rea/commit/f1675f82a621deaad71c74073f030251da0d56d5) fix(environment): accept every empty spelling recorded creates sent - [`5a3c676`](https://github.com/morluto/rea/commit/5a3c6762ac1851752e8c639a9629da84cbd6c585) docs(skill): keep the vary-fields step neutral on UI or API writes - [`2fe849d`](https://github.com/morluto/rea/commit/2fe849da0d06cccd76d1cbdf260a32b91e54a368) docs(environment): record verification for blank members and sorted writes - [`0ceff6f`](https://github.com/morluto/rea/commit/0ceff6fa690246365d577d2deb3e3954710a3243) Merge remote-tracking branch 'origin/main' into feat/environment-write-quality - [`053264b`](https://github.com/morluto/rea/commit/053264bd77993d383c25e0075348019886884f4d) fix(environment): address review of blank members and sorted writes - [`0c0c6ef`](https://github.com/morluto/rea/commit/0c0c6ef7c51918eec03ac79915a6b1b5e041d59a) Merge main 19b2972d into feat/environment-write-quality - [`b490c8f`](https://github.com/morluto/rea/commit/b490c8fd51705fba8314628bd62d24f7b98163e8) fix(environment): answer sent empty members as sent, and keep a placed create's order - [`8432ea6`](https://github.com/morluto/rea/commit/8432ea6e8ab09d23af9bd696ea6112a318be66c5) Merge main c6893d1a into feat/environment-write-quality ### 📄 Description ## Summary Environments that agents build from a prompt now record writes through an application's forms. The write model then refused or withheld many of the writes natural-language tasks ask for: - **Form members.** A form sends every field it shows, the ones left alone as `""`, `null`, `false`, `[]` or `{}`. Every member a recorded create sent was required, so a call naming a person and an email was refused for twenty EspoCRM members, a Homebox tag for `color` and `icon`. - **Paperless `all`.** Paperless-ngx lists carry `all`, the listed ids in order. The state did not render it, so the first list read after every Paperless create contradicted it: no Paperless create was ever served. - **Sorted collections.** Grafana lists folders by title and Paperless tags by name. An update kept its entity in place, so a rename that sorts elsewhere was contradicted, and served renames listed in the wrong order. - **Unloaded answers.** Vikunja answers a create before loading the owner it stored (empty name, Go's zero time) and spells empty lists `[]` where reads spell `null`, so its label creates and a task update were withheld. This PR: 1. Makes a create member a recording sent empty optional: a call that leaves it out is applied as sending the recorded empty value (`body.blank`), and either recorded spelling is accepted. 2. Renders list envelope members that list the listed ids (`listed_ids`). 3. Moves an updated entity where its collection's sort puts it, in replay and serving, and learns that sort from a failed whole-collection read after a rename when no create showed one. 4. Takes a create's item read-back over values its answer had not loaded, and treats an unsent field changing only between `null`, `[]` and `{}` as unchanged. 5. Withholds a later create that stores an unruled field otherwise at the create, so later reads still check other writes. 6. Updates the skill: one create sends every field and the second only what a person must give; how to read signed-in pages' accessibility trees; a UI write whose body the browser did not expose is re-recorded once with request steps. **Results:** - Over the 109 recorded captures, served writes go from 247 to 279 and none goes from served to withheld: every Paperless create is now served (23), plus Vikunja label creates and a task update, Grafana's folder rename and delete, and others. 65 plans require fewer members. - Rebuilding the same 14 recordings, the 42 write tasks pass 24/42 and 23/42 (gpt-oss, GLM) against 18/42 and 19/42 on main, and no recording passes fewer. - Fresh recordings vary more than builds do (below). With the final skill, Linkding (0/2 to 2/2) and Miniflux (no environment to 3/3) gain; Grafana loses because its agent created through the API, which needs #7162's address fixes. - Earlier rounds' Homebox and Linkding task specs checked for objects an older round left behind, so their 0/2 was the spec's. With the corrected specs, main passes them too. ## Problem and expected behavior - A create should not require a member that a recording only ever sent empty. - A list envelope member listing the listed ids should follow the state. - A rename in a collection kept sorted by the renamed field should move the entity, as the application does. - A create's answer that has not loaded a stored value, or spells an empty list differently from reads, should not contradict the write. - Captures that need none of this should build the same plans as before. ## Change and scope - **Blank members** (`environmentStateWriteExplain.ts`, `environmentStateWriteCreateCalls.ts`, `environmentStateWriteApply.ts`): creates explain empty members as `body.blank`; merges keep every recorded empty value, the earliest first, also once a later call teaches a default; created fields apply a left-out member as the earliest recorded it unless it has a default; the body check accepts any recorded empty value. Write answers spell empty lists and maps as their recorded answers did. Updates are unchanged. - **Listed ids** (`environmentStateListFacts.ts`, `environmentStateRules.ts`, `environmentStateGate.ts`, `environmentStateListQuery.ts`): a top-level envelope member listing two or more listed entities' identity values in list order; a rendered list sets it, except on a page of more matches. - **Sorted collections** (`EnvironmentStateWriteSimulation.ts`, `environmentStateWriteChecks.ts`, `environmentStateStore.ts`, `environmentStateWriteAnswer.ts`, `environmentStateBulkAnswer.ts`): the replayed state moves an entity whose sorted field changed; update plans carry the sort of their type's placed creates as `insert`; serving computes the final order once as replay moves the written entities in turn (`resortedMembers`) and installs it with the replacements; `#retrySort` tries a sort by a field the update wrote when a whole-collection read fails and the creates are not already sorted by such a field, unless a create was placed by a sort that excludes it. - **Observations** (`environmentStateWriteObserve.ts`, `environmentStateWriteCreate.ts`, `environmentStateWriteExplain.ts`): `loadedLater` accepts empty, zero, `false`, Go zero time and nested objects; `emptySpellings` for unsent update fields; `unruledDiffers` for later creates (volatile fields excepted). - **Skill** (version 51) and docs: `docs/web-network-captures.md` and `docs/specs/agent-environments/write-form-calls/`. **Not included:** derived members (EspoCRM's `name` and `emailAddressData`, Paperless's `text_color`); request bodies Chromium does not expose for some UI writes (Vikunja's); Vikunja's task create under its project path; the saved-values work of #7162, whose Grafana fixes this PR does not include. ## Contract and boundary impact - **Semantic owner and earliest changed stage:** the environment-state write model (`src/domain/analysisView`) and the served state store. - **Contribution scope fit and linked maintainer decision:** within the environment-state writes of #4933. - **CLI and MCP/tool-catalog contract:** `environment-state` views may carry `body.blank` on write plans, `insert` on update plans, and `listed_ids` on list views. The full catalog grows 648 bytes; the compact catalog, 554,640 bytes, does not change. - **Provider, bridge, target-format, or platform compatibility:** unchanged. - **Evidence, artifact, provenance, or reconstruction contract:** views that carry the new members are refused by earlier REA versions, whose schemas are strict; earlier views parse unchanged. Served environments built by this version apply blank members and sorted moves. - **Target selection, authority, upstream behavior, and cleanup details:** not applicable. - **Generated metadata, package, or installation impact:** the skill version goes from main's 50 (#7267) to 51. Expected effects: - [x] Read existing files or captured data. - [ ] Launch or attach to a process. - [ ] Inject instrumentation or run scripts/code. - [ ] Change process memory, registers, files, or configuration. - [ ] Contact network services. - [ ] Recover protected code or work around protections. - [ ] Documentation or usability only; no target interaction. - [ ] Not sure yet; investigation or maintainer guidance needed. ## Evidence and regression coverage **Write plans** (local, main `99062fea` against this branch, 109 captures): | Captures | Write plans | Served on main | This branch | | ------------------------- | ----------- | -------------- | ----------- | | 50 published | 187 | 70 | 70 | | 59 recorded from a prompt | 337 | 177 | 209 | **The same recordings, built by main and by this branch** (Claude Code `claude-sonnet-5-5` recordings; write tasks run with `fireworks_ai/gpt-oss-120b` then `fireworks_ai/glm-5p3-flash`, each checked from the environment's state): | Recording | Write tasks | Main | This branch | | ----------------------------- | ----------- | --------- | ----------- | | Paperless, saved-values | 3 | 1/3 2/3 | 3/3 3/3 | | Paperless, main (two) | 3 + 3 | 3/6 3/6 | 5/6 5/6 | | Grafana, saved-values | 2 | 0/2 0/2 | 1/2 1/2 | | Grafana, main (two) | 2 + 2 | 3/4 3/4 | 4/4 3/4 | | Vikunja, saved-values + main | 5 + 5 | 3/10 3/10 | 3/10 3/10 | | EspoCRM, saved-values + main | 3 + 3 | 1/6 1/6 | 1/6 1/6 | | Homebox, saved-values + main | 2 + 2 | 2/4 2/4 | 2/4 2/4 | | Linkding, saved-values + main | 2 + 2 | 2/4 2/4 | 2/4 2/4 | | Miniflux, saved-values | 3 | 3/3 3/3 | 3/3 3/3 | | Total | 42 | 18 19 | 24 23 | **Fresh recordings** (served writes, then tasks; the branch's final-skill recording where there were several): | Application | Write tasks | Main | This branch | | ----------- | ----------- | -------------------- | -------------------- | | Paperless | 3 | 2, 1/3 2/3 | 8, 1/3 2/3 | | Linkding | 2 | 0, 0/2 0/2 | 5, 2/2 2/2 | | Miniflux | 3 | no bundle | 4, 3/3 3/3 | | Grafana | 2 | 3, 2/2 2/2 | 0, 0/2 0/2 | | Vikunja | 3 + 2 | 3, 1/3 1/3 + 2/2 2/2 | 1, 1/3 1/3 + 0/2 0/2 | | EspoCRM | 3 | 2, 1/3 1/3 | 2, 1/3 1/3 | | Homebox | 2 | 4, 2/2 2/2 | 3, 2/2 2/2 | - Each fresh recording builds the same plans with main and with this branch, apart from the gains above (Paperless 0 to 8 served on the branch's recording, 2 to 3 on main's; Vikunja's main recording 3 to 4), so the per-application differences are what each agent recorded. The spec folder's verification has every round. - The first two branch rounds showed two skill problems, both fixed here. The sixth step said "fill every field the form offers", and Vikunja's agents then wrote through the UI, whose request bodies Chromium did not expose, so every write was `body-unusable`; the step now says how the creates differ, not where they are made, and such a write is re-recorded with request steps. EspoCRM's minimal second contact sent `null` and `false` where the first stored strings and `false`; the merge refused it until this PR accepted every recorded empty spelling. - Miniflux's branch agent created an API token in the UI and sent it as a literal header value, which REA records as written. The instance and token were destroyed after the run. A follow-up should warn on literal credential-named headers; #7162's secret saved values are the way to carry such a token. - Tests added or updated: `src/domain/analysisView/environmentStateFormWrites.test.ts` (plans) and `src/application/environment/environmentStateFormAnswer.test.ts` (serving), with fixtures in `tests/fixtures/environmentStateFormWrites.ts`. - Base reproduction or other evidence: every new test fails on main, which requires every form member, contradicts the Paperless and folder fixtures and leaves renamed entities in place. - User-visible CLI/MCP output: served write tool descriptions list fewer required members. - Remaining proof gaps: see "Not verified" in the spec folder's verification. For evidence-bearing changes: - [ ] Observed, derived, and inferred claims remain distinguishable. - [ ] Artifact identity, source provenance, and failed attempts remain preserved. - [ ] Unsupported, incomplete, unavailable, or uncertain outcomes remain visible. ## Validation performed - `npm run -s check:fast`, `scan:todos`, `docs:check`, `verify:test-discovery`, `mcp-catalog:check`, `jscpd` — passed at `053264bd` (main `662ec2e4` merged). - `npx oxfmt --check .` — reports `scripts/verify-apk-signature.mjs` only, which main `662ec2e4` reports too. - `npx knip` — reports `isToolFamilyId` in `src/contracts/toolFamilies.ts` only, which main reports too. - `npx vitest run --project domain --project services` — 11,142 passed, 6 skipped at `053264bd`. - `npx vitest run --project domain --project services --project adapters` (before the merge) — 18,281 passed, 25 skipped, 1 failed ("stops Inspector target authorization after the current stat settles"), as on main here. - `npm run -s test:focused --` environment build, export and serve CLI, analysis-view CLI and MCP, environment serve MCP boundaries — 7 files, 75 passed. `tests/boundary/mcp/outputSchemaProfile.test.ts` — 4 passed. - The 13 first tests fail on main `99062fea`, and the 6 added for review fail on `0ceff6fa`; all 19 pass on this branch. - Census and agent runs as above, on Ubuntu 24.04 VMs with snap Chromium. Second review round at `b490c8fd` (main `19b2972d` merged in `0c0c6ef7`), on macOS with Node v24.22.1: - `b490c8fd` answers an empty member a call sent as sent (an unsent one keeps its recorded answer's spelling), and lets a whole-collection read that placed a create rule out the sorted inserts its order does not follow, so a later rename cannot teach a sort an earlier read excluded (Greptile follow-ups). Its two tests fail on `0c0c6ef7`. - `check:fast`, `format:check`, `knip`, `scan:todos`, `docs:check`, `verify:test-discovery`, `mcp-catalog:check` and `jscpd`: passed; `knip` and `oxfmt` no longer report anything since the main merge. `domain` + `services`: 11,282 passed, 1 failed (`SetupRegistrationPrewarm`'s detached-descendant test); `adapters`: 7,382 passed, 1 failed (the Inspector stat test). Both fail the same way on main. Focused CLI and MCP boundaries: 7 files, 77 passed. - Census over the 130 captures: 615 plans and 328 served on `053264bd` and on this head, with no plan's rules, body, insert or verdict changing. Third review round at `3de7d9a7` (main `c6893d1a` merged in `8432ea6e`), on macOS with Node v24.22.1: - `3de7d9a7` keeps the values a create's answer had not loaded (`observation.read.unloaded`) in served create answers, checks recorded bulk answers with each target's sent members, and narrows a create's sorted inserts only on reads in collection order, not ones their query sorts (Greptile follow-ups, one posted on #7274). Its three tests fail on `8432ea6e`. - `check:fast`, `format:check`, `knip`, `scan:todos`, `docs:check`, `verify:test-discovery`, `mcp-catalog:check` and `jscpd`: passed. `domain` + `services`: 11,336 passed; `adapters`: 7,415 passed, 1 failed (the Inspector stat test, as on main). Focused CLI and MCP boundaries: 7 files, 79 passed; `outputSchemaProfile`: 4 passed. - Census over the 130 captures: 615 plans, 322 served. Main's `63220edc` ("withhold clock writes without an anchor") withholds six clock-stamping writes as `unknown-clock` in three recordings whose request completions are not all known (Homebox tag create and update, Vikunja label create and update twice): `8432ea6e`, the main merge alone, serves 322 too. This round's fixes change no plan's rules, body, insert or verdict. Fourth review round at `28cda1f3`, on macOS with Node v24.22.1: - `28cda1f3` lets a rename teach a sort only when the entities no exercised write changed already follow it, since serving moves only written entities, so seeds listed out of order no longer teach a sort serving would answer differently (Greptile follow-up). Its test fails on `3de7d9a7`. - `check:fast`, `format:check`, `knip`, `scan:todos`, `docs:check`, `verify:test-discovery`, `mcp-catalog:check` and `jscpd`: passed. `domain` + `services`: 11,337 passed; `adapters`: 7,415 passed, 1 failed (the Inspector stat test, as on main). Focused CLI and MCP boundaries: 7 files, 79 passed. - Census over the 130 captures: 615 plans and 322 served, the same as `3de7d9a7` with no plan changing. Fifth review round at `4b233f76` (main `8706068b` merged in `4231166b`), on macOS with Node v24.22.1: - `4b233f76` keeps a create answer's unloaded recorded value unless the call sent that member and the plan stores it as sent, and stops a rename teaching a sort from a read its query sorts (Greptile follow-ups posted on #7274). Its two tests fail on `4231166b`. - `check:fast`, `format:check`, `scan:todos`, `docs:check`, `verify:test-discovery`, `mcp-catalog:check` and `jscpd`: passed. `knip` reports `staticFileUrlPath` and `ObjcClassImportBindingCoverage` only, as main `8706068b` does. `domain` + `services`: 11,405 passed; `adapters`: 7,437 passed, 3 failed (the Inspector stat test and two AppKit `NSCustomResource` image key tests), which fail the same way on main. Focused CLI and MCP boundaries: 7 files, 79 passed. - Census over the 130 captures: 615 plans and 322 served, the same as `28cda1f3` with no plan changing. Sixth review round at `94460e80`, on macOS with Node v24.22.1: - `1da04f69` gives an update its type's sort only from a create a list read showed placed, and replaces the set of entities a write ever changed with those changed since a list last showed them, so a rename a list confirmed in place no longer blocks a later sort (Codex on #7274, Greptile on #7332). `94460e80` makes replay move updated entities by that same placed sort, and clears an entity from that set only on a read whose order the state decides for every entity it lists, not one its query sorts (Greptile follow-ups). Their four tests fail on the commit before each. - `check:fast`, `format:check`, `scan:todos`, `docs:check`, `verify:test-discovery`, `mcp-catalog:check` and `jscpd`: passed. `knip` reports `staticFileUrlPath` and `ObjcClassImportBindingCoverage` only, as main does. `domain` + `services`: 11,409 passed. Focused CLI and MCP boundaries: 7 files, 79 passed. The `adapters` project was not rerun; this round changes only the write model. - Census over the 130 captures: 615 plans and 322 served, as on `4b233f76`. One plan changes: Metabase `put_card_by_id` stays served without the name sort its create only guessed (`insert` null). Seventh review round at `6976a156`, on macOS with Node v24.22.1: - `6976a156` makes `#retrySort` try a sort by the order serving gives, moving each write since a list in collection order last showed its entity in turn against the others' values before their own writes, instead of sorting the whole collection, which a list could match while serving answered otherwise (Greptile on #7274). The state records the values each write replaced for this. Its test fails on `94460e80`. - Static checks pass but `knip`, as on main; `domain` + `services` 11,410 passed; focused boundaries 79 passed. - Census over the 130 captures: 615 plans and 322 served, with no plan or view changing against `94460e80`. Main merge at `60876546` (main `5aa23c3d`), on macOS with Node v24.22.1: - `26b2e8cc` merges main up to `60822e07`. Main's #7267 rewrites the lines beside the blank-member docs and the empty-spelling check, so `70c9a7c2` sets those two hunks aside, `fa647e89` merges the rest of main without a conflict (a conflicted merge's pre-commit autofix rewrites main's own files), and `60876546` puts them back on main's text and takes skill 51, as main is at 50. The diff against main touches the same 30 files as before. - Static checks pass but `knip`, which reports `staticFileUrlPath`, `ObjcClassImportBindingCoverage` and `AndroidManifestComponentInventory`, as main does. `domain` + `services`: 11,649 passed, 8 failed (`appleApplication.test.ts`), which fail the same way on main. Focused boundaries 79 passed; `outputSchemaProfile`, `toolSchemaValidity`, `mcpSchemaValidation`, `outputSchemaSemanticConstraints` and `analysisViewMcp` 54 passed. - Census over the 130 captures: main serves 279 and this branch 322, as before the merge, and no write goes from served to withheld. Against `6976a156` only three Grafana folder creates change, to the rules main gives them. Eighth review round at `2526c652`, on macOS with Node v24.22.1: - `2526c652` rules out a sort for a type when a whole list in collection order showed a renamed entity where moving it by that sort would not have put it, so a later rename of the same entity cannot teach it; and answers a create without members its item read added inside an object the answer showed, as the recorded answer did, while the state keeps them (Greptile follow-ups, posted on this PR and #7274). Its three tests fail on `60876546`. - Static checks pass but `knip`, which reports main's three findings. `domain` + `services`: 11,652 passed, 8 failed (`appleApplication.test.ts`, as on main). Focused boundaries 79 passed. - Census over the 130 captures: 615 plans and 322 served, with no plan or view changing against `60876546`. - CI: every failing job (the eight test shards, portable and source tests) fails the same tests on main `5aa23c3d`. ## Compatibility, safety, and release - Breaking changes or migration steps: none. Views with `body.blank`, update `insert` or `listed_ids` are refused by earlier REA versions, as any new view member is. - Real Hopper/Ghidra, browser, or OS coverage: Chromium on Ubuntu 24.04 for the recordings; the change itself is in the offline write model and served state. - Package or release metadata impact: skill version 51. - Security, privacy, process, or containment review: no new credential handling. A blank member is applied only as a recorded call sent it. The Miniflux run above shows agents may put a created token in a literal header. Merge update at fe06d5009: resolved conflicts against main aa210b7cc, which already includes this PR’s changes through #7274. The resulting tree is identical to that main commit, so this PR has no remaining diff. `check:fast` and the focused form-write, later-read, and serving source tests passed (48 tests). No new real-provider run was performed. ## Author readiness Human owner: @earakely-scale - [x] I reviewed the final diff and relevant existing code, understand the change and its edge cases, and take responsibility for the complete PR, including AI-assisted work. - [x] I checked the evidence above, confirmed it supports the current change, and recorded meaningful validation gaps. - [x] I declare this PR ready for initial maintainer review. ## Before merge - [ ] This PR addresses a concrete problem or an agreed enhancement. - [ ] Material scope and authority questions are resolved, and any required maintainer scope decision covers the final diff. - [ ] The PR has one focused outcome and the title follows `type(scope): outcome`. - [ ] Related issue is linked, or the reason for not linking one is stated above. - [ ] Tests cover changed observable behavior and meaningful failure paths. - [ ] Owning docs, contracts, and generated metadata are updated where needed. - [ ] User-visible CLI/MCP changes include representative output. - [ ] I checked the final diff for secrets, unrelated cleanup, and unsupported claims. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
bsakaguchi 2026-11-03 13:40:10 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
morluto/rea#7753
No description provided.