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.
13 KiB
Serve the environment from its state by default
Issue: https://github.com/morluto/rea/issues/4933
Outcome
rea environment-serve <manifest> --evidence <capture> serves the environment
from the initial state the capture observed, with nothing else to supply: at
startup it derives the environment-state view of the capture in-process with
the manifest's template_rules. Read tools whose views are state-backed answer
from one live state, the /agentenv data plane implements data/reset,
data/add and data/get against it, and the card advertises those operations
and an intake declaration. --replay-only serves exactly the stateless replay
main serves: every manifest tool replays its recordings, /agentenv answers every method as not found and the card is
byte-identical. A state that cannot be derived, or whose catalog exceeds the
discovery budget, starts the server in replay mode, logs why and reports it. Exported bundles serve the state by default,
and their verifier and README match what they serve. Serving is read-only in
this increment: write tools are not served from the state.
Requirements
- Derive the state in the application layer, shared by serve and export:
deriveEnvironmentState(source, capture, operation)authenticates the capture value the command already read (an owned snapshot authenticated by the source load is reused, not parsed again), projects theenvironment-stateview with the manifest'stemplate_rulesthroughinspectAnalysisViewValidatedwith the capture as retained Evidence (the Evidence id equals that of a projection by hand, and the capture's strings are not charged again as inline input, which refuses captures of a few MiB), then appliesloadEnvironmentState:inspect_analysis_viewEvidence of kindenvironment-state, itsparent_evidence_idequal to the capture's, itstemplate_rulesequal to the manifest's in the same order, and every view naming a manifest tool, refusing otherwise withinvalid_requestwhose issue path starts withstate. The source validates a HAR capture with the owned borrowing parse, so the view reuses that validation. prepareServedState(source, capture, {name, operation})first builds the state catalog under the served name, then derives the state, and reports{served: true, evidence_id}or{served: false, reason: "unavailable", code, message}with the refusal's projected code and message: a state catalog over the discovery budget is unavailable withresource_constraint, and no state is derived for it. A derived state whose catalog fits always serves from the state. The CLI reports{served: false, reason: "replay-only"}itself for--replay-onlyand derives nothing.- Keep one state store per server process, shared by every MCP session and the
data plane: entities keyed by type, collection and id with per-collection
order taken from the entities array, documents keyed by tool.
resetrestores the Evidence state,replaceinstalls a validated document whole, and mutations are synchronous. - Answer item views by rendering the entity the path arguments name into the
recorded envelope of
envelope_ordinal. Match arguments as replay does: a held collection segment equal to the argument as written or after percent-decoding, preferring verbatim spellings, and the identity as the last argument percent-decoded, then as written, never re-split on/. Any held entity answers, and query arguments only setreplay.exact_match. Refuse an entity the state does not hold, or one lacking a required field, as a tool error and never invent a body. - Answer list views from the state only when the arguments name one of the
view's
checked_record_ordinalsexactly (every path and query value equal and no recorded query parameter unsupplied), choosing among them as replay chooses: every collection entity in state order formembership: "all", otherwise the recorded members the state still holds. A member lacking one of the view'srequired_fieldsmakes the call a tool error stating that the state holds no complete member, never a partial body, as for items. Lines recorded after a write or with other filters were never checked and answer from recordings. - Answer document views with the state's value when the arguments name the document's checked line exactly.
- Answer recorded views, tools without a view and unrecorded requests from the
recording, labelled
state: {source: "recording", reason}; label state answersstate: {source: "state", view, …}with the base record's replay provenance andtruncated: false; refuse a state answer over 256 KiB as a tool error rather than cut it. - Serve
data/reset({}),data/addwith exactly onedataorfilepart (bytes, afile://regular file or anhttps://download with a 30 s timeout covering the body, no redirects and a streamed 32 MiB cap;mimeTypewhen given isapplication/json) anddata/get({parts: [{kind: "data", data: rea_state}]}). Afile://uri whose host is neither empty norlocalhost, or whose path is a UNC path, is-32602before anything is opened. A document thatdata/getwould write back past 32 MiB, once it adds the members a document may omit, is-32602, so a reported state always loads again. Anything else is-32602, including emptyparts; a failed download is-32000; errors never echo a uri or content and name a coarse failure category inerror.data.reason. Calls run one at a time in arrival order across requests; every message of a batch is queued when the batch arrives, so no other request's call lands between them, and notifications run without a reply. - Stopping the server (
closeorprepareShutdown) aborts the data plane's stop signal, and a request's own signal aborts when its client leaves: the download in progress ends with reasoncancelled, queued calls are answered-32000with reasoncancelledwithout running, and adata/addwhose file read finished after the cancellation does not replace the state. - Bound one
/agentenvresponse to 64 MiB of replies (twice the intake limit). Each reply is measured in bounded JSON parts as it is produced, in message order, and assembled only when it fits, so a reply past the budget is counted but never serialized whole. Room is kept for every later reply's budget error and for a batch's brackets and commas, so the body never passes the budget; a reply that does not fit is replaced by-32000withdata: {max_response_bytes, reply_bytes}, unless that error is no smaller than the reply, so the body is never built past the budget and small write replies are kept. - Validate
rea-state/v1documents against the served types and views: known types, collections that instantiate their template, ids equal to the identity field, unique keys,view_fieldsnaming item or list views of the type without setting the type's identity field to a value whose scalar text differs from the entity's id (refused at that view'sfields), documents naming document views once at the initial path, and the optionalstate_evidence_idandtypesthatdata/getreports equal to the served ones. Refuse numbers without an exact native value and__proto__members. Name the first problem by JSON pointer, never deeper than a caller-data member (fields,value,types), and leave the state unchanged. - Keep record citations Evidence provenance: refuse
record_ordinalsor arecord_ordinalother than the Evidence's for that entity or document, and hold the Evidence's citations only for loaded values equal to the Evidence's; edited or added values cite none. - In state mode, declare
operations: ["data/reset", "data/add", "data/get"]and theurn:agentenv:intake/v1extension, describe tools as answering from the state where it backs them, and leave outPOST,PUT,PATCHandDELETEtools other than queryPOSTs. The discovery budget reserves the card and descriptions actually served (servedCatalogreturns the tools and the data plane the card declares). A state catalog can exceed the budget a replay catalog fits, so such a state is unavailable and the environment and its export serve replay: everything main served and exported is still served and exported. - The CLI has no
--state;--replay-only(default false) serves replay without deriving. Otherwise it prepares the state under the name it serves (ENVIRONMENT_NAMEor the manifest's). When the state is unavailable the command logs a warning with the code and message through its listener logger before binding, and serves replay. The stopped document'sstatemember is the report.serveEnvironmentkeeps taking an optional prepared state and serves replay without one; the served catalog (servedCatalog) and the answer function (environmentToolAnswer) are shared with export. environment-exportkeeps the bundle commandrea environment-serve /env/manifest.json --evidence /env/evidence.jsonand, after its name checks, prepares the state under the bundle name as that server will, to predict what it serves, so a refused--namederives nothing; its report is served or unavailable, never replay-only. The verifier'sTOOL_COUNTand the command'stool_countare the served tool count, and its check tool and expected status come from calling the server's answer function with{}on the first servedGETtool without path parameters whose answer is not an error. The verifier and smoke task use the idserve. The README's "What the environment serves" section describes serving from the state, the replay fallback and--replay-only, says which applies to the bundle (with the code and message when the state is unavailable), and keeps the not-emulation and untrusted-content caveats. The export output addsstate. The hosted-lane verifier script expects the served count.
Acceptance and limits
Services tests cover derivation (the by-hand Evidence id and state, a capture
the manifest was not projected from, a capture inline admission refuses),
the prepared reports (served, unavailable for a capture_browser_scenario
capture with header values but no response bodies, unavailable with
resource_constraint for a 350-tool read-only manifest whose replay catalog
fits), bundle plans served from the state and in replay (tool count, check tool
and status, a check whose state answer differs from replay's in record and
status, a check that skips a tool the server cannot answer, the replay fallback
of that 350-tool manifest, name refusals before any preparation, a manifest
neither mode admits, verifier, smoke task and README wording), the loader
refusals, store reset, order, round trip and
atomic replace, the first-problem pointers, citation provenance after a load and
every answer path: item (including percent-encoded collection segments and
ids), list all, list recorded, lines recorded after a write or with other
filters, a line replay's tie-break would not pick, document, recorded view, tool
without a view, unrecorded request, missing entity, missing required field and
the body limit, a view value that changes an entity's identity, and a list
member missing a required field. Adapters tests cover the data-plane shapes,
-32602 for empty and malformed parts and invalid documents, inexact numbers,
bytes, file:// and its failure categories, file:// uris naming another
host or a UNC path, injected https downloads (status, declared and streamed
size, the timeout whether or not the fetcher honours its signal), the default
fetch's refused redirects, arrival-order application across requests, a batch
another request's reset cannot split, cancellation by the server's stop and by
a departed caller, a server that closes while a download is stalled, the
response budget, batches and notifications, the card declaration, absent write
tools, a served query POST, the per-mode discovery reservation and a
data/add that changes the next tool answer over MCP. The mcp-boundary tests serve the state the
built CLI derives from only a manifest and a capture, and --replay-only with
every manifest tool, -32601 data-plane answers and a card whose SHA-256
equals that of the card main serves for the fixture; the process-boundary test checks the stopped document's
state for a derived state, --replay-only and an unavailable state, and the
warning logged before binding; the cli-boundary export test checks the served
tool count, the verifier and the README of an exported bundle. No test reaches
an external network. Write effects, list filters beyond recorded lines and
tables in the intake declaration are out of scope.
Deferred issue criteria
Write effects are a later increment of #4933. This PR must reference rather than close the issue.