Repository navigation
docs(rfc-0033): plan Phase 3 Blob writes against current main - #900
Conversation
Records the decisions the put/clear plan rests on after checking each assumption in Lance 11 and current main: separate payload and framing budgets for every keyed writer, so the 32 MiB inclusive limit holds; the sibling carry rule and its denying-policy limitation, which Lance's merge-insert imposes; Session entry points; edge cells through the same adapter; re-prepare for supplied values; ETag evidence from the detached commit; PUT as a raw ingress route with 415 and 408; the served-read prerequisite as met and the stale-generation check left to the runtime; an ungated clear; one If-Match parser. Phase 3 now lists its steps in landing order.
ragnorc
left a comment
There was a problem hiding this comment.
Recommendation: revise the two contract gaps below before treating this plan as ready for implementation. This is a COMMENT review because the authenticated account is the PR author. Reviewed d08c1dce5492cc1039da6e568dcf4dce65d008be.
This PR updates the design for replacing or clearing one Blob through the engine, HTTP, and CLI. Today, callers must use broader write paths and can encounter limits that count payload and row overhead together. The plan adds an exact-ID adapter, reuses Mutation publication, and separates payload accounting from row framing. It also gathers the commit and ETag evidence before publication. No runtime behavior changes in this PR.
The shared design addresses causes: Blob writes should have the same snapshot, policy, atomic publication, and failure rules as other graph writes. This matters on local storage and object storage, where a completed table write must not expose a partial graph commit. The first-release workload is bounded, individual Blob replacement. Large streaming uploads and descriptor-preserving rewrites remain outside this phase.
The main tradeoffs and liability assessment are:
- One shared accounting rule and one publication path remove duplicated decisions. Five more writer types should compose with those owners. A Blob-only counter or publisher would reverse that benefit.
- Separate payload and framing budgets make the inclusive payload limit possible, but permit more retained input than the old combined ceiling. The sum is an accounting envelope, not a measured peak-heap guarantee: transport buffers and Lance writer copies also exist. No memory or throughput improvement was measured here.
- Lance 11 whole-row replacement carries untouched Blob values. This preserves row correctness but adds read/write amplification. An allowed external sibling becomes managed; a denied sibling blocks the write. The policy and recovery costs need to be clear for both nodes and edges.
- Bounded retries are justified for caller-supplied replacement bytes. They still need the first-attempt schema and branch identity checks. Predicate updates keep their existing non-replay rule.
- Detached commit evidence avoids a fallible storage read after successful graph publication. This adds request-local evidence and adapter obligations, but avoids another persistent authority. The RFC adds about 170 net lines of documentation; that is not evidence of higher runtime liability by itself.
I checked the pinned Lance 11.0.0 implementation, including whole-row merge-insert and stable-row-ID handling, default write parameters, Blob update restrictions, and the relevant upstream docs. I traced OmniGraph's loader admission, Blob carry, Mutation staging/retry/publication, and server ingress boundaries. The inline findings concern gaps in the proposed contract, not new runtime regressions.
Local validation used Rust 1.97.1 at the reviewed head:
cargo +1.97.1 test -p omnigraph-engine --locked --features failpoints --lib strict_graph_batch_framing_and_structure_are_bounded_before_dom
cargo +1.97.1 test -p omnigraph-engine --locked --features failpoints --lib strict_graph_batch_loads_graph_rows_with_crlf_and_blank_lines
cargo +1.97.1 test -p omnigraph-compiler --locked --lib test_mutation_update_edge_not_supportedEach selected one test and passed. Builds reused a compatible target with RUSTFLAGS='--cfg tokio_unstable --cfg tokio_unstable'. I temporarily extended the existing strict-load owner with one Doc row and a generated 32 MiB Blob. Strict load rejected the 44,739,302-byte JSON line before decoding. Compatibility load rejected 33,554,437 parsed bytes. Both preserved the graph version; a one-byte strict-load control succeeded. I restored the source and reran the original owner successfully. The checkout is clean. Documentation validation passed for 217 files; AGENTS links and diff whitespace checks passed.
Base 2351e260ec4754119ffeb88ede60a264bf4521b1 and head have identical runtime code. This probe establishes current behavior; it is not a before-fail/after-pass proof of an implemented fix. The implementation PR must supply that proof. Use minimal GQT cases for query-visible semantics. Use the existing loader/server owners for encoded framing and transport limits, which a normal GQT query does not exercise. Do not add a large superficial GQT fixture to replace those checks.
Exact-head GQT CI passed. The main CI run passed its selected checks; workspace and cloud integration jobs were skipped. I did not run live S3/Azure validation or performance tests.
One non-blocking correction: §12.3 says the default-refusal guard will turn red when Lance adds a caller WriteParams API. An additive API can leave defaults unchanged, so that test can stay green. Keep the behavior guard, but require an explicit capability check during Lance upgrades before reopening descriptor preservation.
A 32 MiB value is about 42.7 MiB of base64 text, above the load's 32 MiB line and parse ceilings, so the payload/framing split admits exact-limit values from PUT, embedded .gq parameters and carried cells, not from loads.
ragnorc
left a comment
There was a problem hiding this comment.
Follow-up at c4fb309079d8bde631d8d69f7e74bde6b738146e. Recommendation: changes still needed. I checked both commits since d08c1dce5492cc1039da6e568dcf4dce65d008be. This is a COMMENT review because the authenticated account is the PR author.
This RFC plans direct Blob replacement and clear through the shared engine write path. The revision narrows the exact 32 MiB promise to raw PUT, embedded query parameters, and carried values. Loads retain their existing admission limits. That is a reasonable scope decision for bounded single-cell writes: it avoids expanding every JSON transport budget to accommodate base64. It needs the accurate distinction between strict NDJSON framing and compatibility-load accounting noted inline.
The earlier edge recovery finding remains open. Sections 4.3 and 12.2 still prescribe an update assigning both external cells, but T16 still rejects edge updates. State the node-only scope and the supported edge recovery path, or explicitly state the edge limitation. I have not duplicated that inline comment.
The Lance guard correction is sound. A change to default behavior can fail the guard; an added optional API requires the separate dependency review now stated. I rechecked pinned Lance 11.0.0, source ab6b5bbe46009ed78746b444df8db59a8bc5d842: the whole-row writer still passes default parameters.
Liability is lower when the design names the actual owners: transport bounds encoded input, the loader checks parsed input, shared staging accounts payload and framing, and one manifest publication makes the graph visible. These responsibilities must compose without pretending they measure the same bytes. Five more entry points should reuse those owners. The amendment adds no runtime state or API. Its tradeoff is an explicitly smaller effective upload size on some load paths; whole-row Blob carry still incurs read/write amplification. This is a contract clarification, not a new workaround. No efficiency improvement was measured.
Validation on this head: scripts/check-docs.py passed for 217 Markdown files, bash scripts/check-agents-md.sh passed, and git diff --check 2351e260ec4754119ffeb88ede60a264bf4521b1 HEAD passed. The isolated checkout is clean.
The earlier local loader probe remains relevant: the runtime tree and dependency lockfile are unchanged between d08c1dce5492cc1039da6e568dcf4dce65d008be and this head. It observed 44,739,302 encoded line bytes for strict load, but 33,554,437 charged bytes for compatibility load of the same 32 MiB payload. Those are prior local results, not new-head test executions. I cancelled my queued local rerun while it waited for another active build's lock; no test ran and no source was edited. Exact-head GQT CI passed. The main CI run was still in progress when checked; skipped jobs are not validation.
There is no runtime fix in these documentation commits, so no before-fail/after-pass GQT proof is claimed. The implementation must provide minimal GQT regressions for query-visible changes and existing loader/server tests for framing and transport behavior. This follow-up reuses the full upstream-document review and runtime evidence in the original review; it does not claim new cloud or performance coverage.
The compatibility loader's pre-decode forecast charges a base64 value by its decoded length and splits like the batch check, so it admits an exact-limit value; the strict NDJSON loader keeps its 32 MiB encoded-line limit and HTTP bodies their encoded limits. A row with two denied external cells is recovered by a merge load of the whole row, for a node or an edge; the .gq update escape applies to nodes only, since edges have no update.
ragnorc
left a comment
There was a problem hiding this comment.
Follow-up at dd5fc54e12fb3066315e8f869a66653962238c32. Recommendation: approve this RFC amendment. No remaining blocking finding. This is a COMMENT review because the authenticated account is the PR author.
The RFC plans direct Blob replacement and clear through the existing engine write path, then HTTP and CLI entry points. This revision corrects the admission rules and gives both nodes and edges a recovery path when policy denies their stored external Blobs. It changes the plan, not runtime behavior.
The earlier findings are resolved in the design:
- Sections 4.3 and 12.2 now include the compatibility loader's decoded-size forecast in the payload/framing split. Strict NDJSON still bounds encoded lines; HTTP routes keep their encoded body limits. The same payload therefore need not fit through every transport.
- The recovery rule now uses a whole-row merge load for either entity kind. A GQ update assigning both cells is correctly limited to nodes. This matches the loader's Upsert staging and UpdateAll/InsertAll writer.
- The prior revision already separated the default-behavior Lance guard from dependency review for a newly added optional API.
The design addresses the cause at its owner. Payload and framing have different units; the pre-decode forecast and shared staging must apply the same distinction. Recovery reuses complete input rows and the existing publisher, without adding an edge-update language feature or a separate recovery API. This lowers design liability. Five more entry points should reuse these owners rather than add their own exceptions. The tradeoffs remain explicit: two budgets increase the admitted per-operation envelope; some transports admit smaller payloads; untouched Blob siblings can require materialization. Recovery is an upsert, so callers must provide the complete replacement row with the intended identity and edge endpoints. The RFC adds no runtime state. The planned implementation still must justify and test its new retry and evidence obligations.
Validation, with commit scope:
scripts/check-docs.py: 225 Markdown files passed at the final head.bash scripts/check-agents-md.shandgit diff --check 95f62a094622647563bfe48347458d37e2850264 HEADpassed.- At
f57eac19ac31aeac50a2019800c7cce99226988d, the originalmutation_update_replaces_stored_external_reference_under_denyowner passed (1 test). A temporary extension in that owner also passed (1 test): it seeded two external Blob cells on a node and two on an edge, reopened under the denying policy, removed the source file, then replaced both complete rows through merge load. All four cells contained the supplied managed bytes. External probe/read counters stayed at zero, the graph manifest advanced once, and row counts showed replacement without duplicates. All temporary source edits were restored; the isolated checkout is clean.
The focused command was:
RUSTFLAGS='--cfg tokio_unstable --cfg tokio_unstable' \
cargo +1.97.1 test -p omnigraph-engine --locked --features failpoints \
--test writes mutation_update_replaces_stored_external_reference_under_deny \
-- --exact --nocaptureIt reused an existing CARGO_TARGET_DIR. A first build hit an unrelated stale planner artifact; rebuilding the workspace crates cleared it. The first temporary fixture was rejected because edge property @key is unsupported; the corrected fixture uses an explicit edge ID. Neither setup failure is a behavioral regression result.
Separately, exact-head GQT CI passed, as did the main CI run, including server tests with AWS enabled. Skipped jobs provide no validation.
The final commit merges main. I inspected the changed mutation execution and updated architecture, invariant, execution, and testing guidance. The RFC text, loader, staging, table-store implementation, test owner, and Lance lockfile are unchanged from the locally tested head. A local rerun on the merged head was stopped during a fresh dependency build; it did not execute a test. The recovery result above is therefore prior-head evidence, while the documentation checks and linked CI are final-head evidence.
The before/after documentation commits are c4fb309079d8bde631d8d69f7e74bde6b738146e and f57eac19ac31aeac50a2019800c7cce99226988d; their runtime tree and lockfile are identical. Thus no before-fail/after-pass runtime proof is claimed. The future implementation needs minimal GQT regressions that fail before the behavioral fix and pass after it, plus the stated existing test owners for admission, transport, and policy behavior. Please retain the node-and-edge recovery assertion in the planned writes.rs coverage. GQT cannot express the policy switch, deleted source file, and I/O counters used in this recovery probe.
I reused the full repository and upstream review from the original review and previous follow-up. I rechecked the merge source/target behavior against pinned Lance 11.0.0, commit ab6b5bbe46009ed78746b444df8db59a8bc5d842, and reread the complete upstream Blob and read/write guides. Current upstream docs include behavior newer than that pin; the pinned source controls this assessment. No cloud recovery, peak-memory, or performance improvement was measured.
The ceilings stay as a fixed envelope, not a setting. Section 10.1 records the three separate limits beyond it and what blocks each: references need merge-insert write parameters (lance#6426, lance#7969; lance#9532 already keeps carried references), managed values above 32 MiB need the Lance 12 streaming sidecar writer and an RFC for a write outside the key-fenced merge-insert, and atomic writes above 32 MiB need a chained single publication with streamed validation.
… claude/rfc-0033-phase-3-plan
ragnorc
left a comment
There was a problem hiding this comment.
Follow-up at aef858a0ad4857784620095a13da9a65faea33ac, after dd5fc54e12fb3066315e8f869a66653962238c32. Recommendation: correct the new Lance 12 prerequisite before accepting this addition. One blocking planning error is inline. The memory-evidence comment is optional. This is a COMMENT review because the authenticated account is the PR author.
The RFC plans direct Blob replacement and clear through the shared engine write path, then HTTP and CLI. New §10.1 explains why raising a size constant is insufficient. It separates three needs: storing large external references, streaming one large managed value, and publishing a large atomic operation. The practical decision is to keep Phase 3 bounded while naming the work needed for larger inputs. This PR changes documentation only.
The separation is sound. An external reference avoids copying a payload but introduces source availability and policy obligations. Streaming a managed value reduces retained payload, but sidecar placement must compose with key conflict checks, detached staging, and cleanup. Chaining bounded table transactions preserves one graph publication only if validation and failure recovery cover the whole chain. Operator-sized load commits remain a simpler option when cross-chunk atomicity is unnecessary.
The new dependency statement is incorrect: both DedicatedBlobWriter and BlobDescriptorArrayBuilder are already public in pinned Lance 11.0.0. The unresolved work is integration with OmniGraph's sealed keyed writer, not acquiring those APIs from Lance 12. A version bump alone does not discharge that obligation. See the inline source references. The earlier admission and node/edge recovery corrections remain intact; I have not repeated their resolved findings.
Liability: these 50 added documentation lines introduce no runtime state, public API, or storage format. They help by keeping three different resource problems separate. Five similar extensions should reuse one keyed-write contract and one publisher. A Blob-specific replacement path would add conflict, retention, and recovery obligations; keeping that behind its own design review is justified. Remove the false version gate rather than adding a workaround. The benefit of streaming is a design hypothesis here; no new CPU, RSS, or cloud measurement was made.
I rechecked the repository instructions, invariants, testing and systems guides, and Lance alignment. The prior full storage review remains applicable. I reread the complete Blob and read/write guides and inspected the cited upstream changes. Lance #7969 remains open. #9532 merged into a 13.0.0 beta tree and separates carried references from source-supplied references. Neither establishes bounded managed-sibling materialization; the RFC correctly leaves that qualification open. The Lance 11 source used for the finding matches the upstream pinned file byte for byte.
Local validation at this exact head:
/tmp/omnigraph-review-896/docs-env/bin/python scripts/check-docs.py
bash scripts/check-agents-md.sh
typos docs/rfcs/0033-blob-management.md
git diff --check dd5fc54e12fb3066315e8f869a66653962238c32 HEAD
git diff dd5fc54e12fb3066315e8f869a66653962238c32 HEAD -- crates Cargo.toml Cargo.lockThe documentation checker passed for 225 files. AGENTS links, spelling, and whitespace checks passed. The last command produced no runtime or dependency changes. The first checker attempt used a Python without the required packages; rerunning in the existing pinned documentation environment passed. No source or test edits were made; the isolated checkout is clean.
Exact-head ordinary/DST GQT CI passed. The main CI run passed its selected checks, including server AWS tests; workspace, live storage jobs, and the separate pinned DST suite were skipped. These are CI results, not local runtime tests.
No before-fail/after-pass runtime claim applies to these documentation-only commits. The future implementation must include minimal GQT tests for query-visible behavior, proved red before its fix and green afterward. Streaming memory, sidecar placement, conflict fencing, and cleanup need the existing Rust mechanism owners. This review does not qualify a new writer or a Lance upgrade.
DedicatedBlobWriter and BlobDescriptorArrayBuilder are public in the pinned Lance 11.0.0, so streamed managed values wait on the integration design (keyed-write conflict checks, staging, publication, collection), not a Lance bump. The memory evidence is RFC 0023's cost gate under the old combined accounting; qualifying the split envelope remains open.
|
@azimafroozeh, three PRs form one sequence and are ready for your review. Their CI is green, and every review finding so far is addressed:
The engine put/clear (step 3A) builds on #903. I'm starting its groundwork now, limited to the parts that don't depend on a decision still open here. |
ragnorc
left a comment
There was a problem hiding this comment.
Follow-up at 7efb3b31b61b85431c14d51399c230e3d946ae05, compared with aef858a0ad4857784620095a13da9a65faea33ac. Recommendation: approve this RFC amendment. Both comments from the previous review are resolved. No remaining actionable finding in this revision. This is a COMMENT review because the authenticated account is the PR author.
This RFC plans direct Blob replacement and clear through the shared engine write path, followed by HTTP and CLI entry points. This amendment corrects two claims in the plan: which streaming helpers Lance already provides, and what the existing memory evidence establishes. It changes documentation only.
The pinned Lance 11 already provides DedicatedBlobWriter and BlobDescriptorArrayBuilder. The writer accepts successive chunks and returns a descriptor on finish. The revised plan correctly removes the false Lance 12 prerequisite. It retains the real design obligation: streamed sidecars and their data files must compose with key conflict checks, detached staging, graph publication, and collection. Availability of these helpers does not establish that the current merge-insert path accepts a prepared sidecar unchanged.
The memory paragraph now links RFC 0023's cost gate. It limits the historical paired peak-RSS evidence to that gate's workloads and the old combined 32 MiB accounting. Qualification of the split envelope—up to 32 MiB payload plus 32 MiB framing—remains explicit open work. It does not claim a general process-memory bound.
These corrections reduce long-term liability. They place the unresolved work at the owners of the actual contracts and add no runtime API, state, format, or benchmark framework. Five similar write paths should reuse the same conflict, publication, and cost owners. The tradeoff remains a bounded first release that excludes larger writes. Streaming can reduce retained payload, but its conflict, recovery, and cleanup obligations still require proof. No memory or throughput improvement was measured here.
I reused the complete repository-guide, upstream-document, and runtime review from the previous review. I verified that the instructions, architectural/testing/systems/Lance guides, runtime tree, and dependency files did not change. I rechecked the changed claims against Lance 11.0.0 at source commit ab6b5bbe46009ed78746b444df8db59a8bc5d842; the freshly fetched upstream Blob source matches the local pinned source byte for byte.
Local validation at this exact head:
/tmp/review-900-0328-docs/bin/python scripts/check-docs.py
bash scripts/check-agents-md.sh
typos docs/rfcs/0033-blob-management.md
git diff --check aef858a0ad4857784620095a13da9a65faea33ac HEADAll passed; the documentation check covered 225 files. The isolated checkout is clean. No temporary source or test edits were made.
Exact-head ordinary/DST GQT CI passed. Main CI passed its selected checks, including server AWS and storage upgrade compatibility. Skipped jobs are not validation.
No local runtime test or before-fail/after-pass claim applies to this documentation-only revision. The implementation must still include minimal GQT regressions for query-visible fixes, with a behavioral failure before the fix and success afterward. Streaming, conflict fencing, and cleanup require the existing Rust mechanism owners. This review does not qualify a new writer, the split memory envelope, or live cloud behavior.
ragnorc
left a comment
There was a problem hiding this comment.
Follow-up at 4acfce5cb5a6d40a9b5833ab7cdf7b0c4396bff5, after 7efb3b31b61b85431c14d51399c230e3d946ae05. Recommendation: approve this RFC amendment. No new blocking finding.
This PR updates the Phase 3 Blob write plan to match the current write path. It separates payload and framing budgets, states which load paths admit an exact-limit value, and defines recovery when policy denies carried cells. The latest commit merges main's scan and hydration work. The RFC is byte-for-byte unchanged from the reviewed head. Against its main parent 4c7a160acd1ae689773613c30069d4fed27f19e0, the entire PR diff remains one documentation file.
The merge preserves the relevant loader, Blob and server contracts, the required review guides, and Cargo.lock. Its table-store addition is a scan tuning setter for io_buffer_size; it does not change keyed-write staging or publication. The prior pinned-Lance implementation review therefore still applies. Section 10.1 still separates the three routes beyond the resource envelope. It correctly says Lance 11 already has the streaming sidecar helpers. The remaining managed-value blocker is integration with conflict checks, detached staging, publication and collection.
The tradeoff and liability assessment are unchanged: fixed admission budgets keep bounded inputs and a simple contract, but larger managed writes remain deferred. The plan puts accounting at its existing owners and preserves one publication door. It adds no runtime API, state, or abstraction in this PR. Five similar changes should extend those owners and their tests, not create parallel budgets or write paths. Historical process-memory measurements do not qualify the new split envelope; that qualification remains explicit open work.
Validation on the exact clean head: git diff --check 4c7a160a HEAD passed; scripts/check-docs.py passed for 226 Markdown files; bash scripts/check-agents-md.sh passed for 53 links and 50 docs. The exact-head GitHub checks report passing documentation, dependency and storage-upgrade checks. Workspace, format and most integration cells were skipped by change classification. Passing aggregate GQT contexts are not evidence that a new behavioral regression ran.
No production fix or new test is introduced here, so a new GQT red-before/green-after proof does not apply to this merge. The implementation must still supply the minimal acceptance/refusal regressions specified by the RFC and prove each behavior on the pre-fix commit and exact implementation head. I did not rerun the Rust suite or claim a new performance or memory result. No temporary source or test edits remain. The PR was open at this exact head, with no newer substantive review or inline finding, immediately before posting.
…-3-plan # Conflicts: # docs/rfcs/0033-blob-management.md
…-3-plan # Conflicts: # docs/rfcs/0033-blob-management.md
What & why
RFC 0033 Phase 3 adds Blob writes: engine
put/clear, HTTPPUT/DELETE, and CLIblob put/blob clear. Its text was written before the server runtime landed and against Lance 10. I checked every assumption it makes against Lance 11.0.0 and current main (2351e260). Several were wrong or out of date. This amendment records the decisions, so implementation can start without reopening them. It is docs only.What the check found
idmeasures 33,555,112 bytes (measured, arrow 58.3), so the 32 MiB ceiling refuses it. The same refusal already hits a 32 MiB value inserted through an embedded.gqparameter or carried by an update, althoughdocs/user/blobs.mdpromises 32 MiB per value. A load stays bounded earlier: a 32 MiB value is about 42.7 MiB ofbase64:text, above its 32 MiB line and parse ceilings.UpdateBuilderandRewriteColumnsrefuse Blob columns, and stored descriptors are bound to their data file. Merge-insert writes with defaultWriteParams, which refuse external URIs outside the dataset's own storage locations. So an external sibling becomes managed, or fails withStoredExternalBlobDeniedwhen the policy denies it. Under a denying policy, a row with two external cells can only be changed by an update that assigns both..gqupdate, stated explicitly. Keeping a sibling's reference moves to Phase 4, behind a guard that goes red when Lance lets callers passWriteParams(§4.3, §12.3).OmnigraphSession.Session::put_blob_at_as/clear_blob_at_as(§4)..gqhas no edgeupdate(T16), and no engine path rewrites an edge row while carrying its untouched cells.commit_allcurrently discards it. Lance keeps the stable row id on a merge-insert update, but nothing pins that.GraphRequestlease is held by the response body through EOF./mutateincluded./mutateauthorizes, admits the actor's workload, and registers then spawns the owned write./mutate, and its raw body follows/load/ndjson: 415, early 413, inclusive limit, 408 deadline (§5.2, §11).Also decided:
WriteParamstoMergeInsertBuilderlance-format/lance#7969; fix: preserve carried external blobs in merge insert lance-format/lance#9532 already keeps carried references), streamed managed values (Lance 11 already hasDedicatedBlobWriter; the integration with keyed writes needs an RFC), and atomic chained writes. I commented on MergeInsertBuilder: no API to set WriteParams (allow_external_blob_outside_bases) lance-format/lance#6426 offering to carry #7969 forward.clearis not gated by a confirmation prompt, consistent with ordinary mutations (§6).If-Matchparser lives in the API types and serves the CLI and all four server verbs (§6).Landing order (§13)
Sessionmethods: the exact-ID adapter, the extracted publish tail (still one call site), detached evidence fromcommit_all, and the bounded re-prepare loop.PUT/DELETEas an owned raw-ingress route, with the sharedIf-Matchparser and OpenAPI.put/clear, parity rows, and binary-body support in the remote test proxy.Notes for reviewers
updated:header. The text sections they change don't overlap.Checks:
check-docs.py,typos,check-agents-md.sh.