Review-001 S-4. The store core's Error::Display — which for
Error::Io carries paths, DSN fragments, schema names, instance ids,
engine forms — was interpolated into the wire-error messages delivered
to external callers. Every src/ops/ synthesis point now answers a
stable domain text; the declared error codes and details schemas are
byte-identical (the wire contract is the code + schema; the message
content was the leak).
- store_internal_error and fetch_establish_failure emit
'the store core failed at {stage}' — the Error detail dropped
- put_refused_error's refusal family emits
'{prefix}: the store core refused the put — nothing committed'
(the offered digest hex stays in the prefix where the caller
offered it — the schema-gated canonical form the caller
presented); VERIFICATION_REFUSED's stable arm untouched
- finish_put's mint-refusal arms, the join-error arms (stable
cancelled|panicked classification), and the data-plane arm drop
their raw-error interpolations
- fetch_ranges_ingress verified: s2 already landed the stable texts
(no raw payload Values remain)
- stale doc lines claiming 'details carry the store core's message'
corrected to the stable-text posture
- disposition judged and recorded (store_internal_error's doc): detail
dropped — the lean default; no log/tracing dependency added. An
operator reads the stable text (the stage named) on the wire and
consults the store's own failure surfaces
Tests:
- the d2 broken-shard round (stat/delete/establish) extended to the
verbatim stable texts plus absence-of-detail checks (no path
fragments, no io: forms, no os-error text)
- a new wire-driven PUT_REFUSED test asserts the commit-verification
arm's stable text from a real commit-rename failure (the committed
shard dir pre-made unwritable — the failure lands strictly after
the stream's EOF), zero residue and pin release asserted
Verification: cargo test (default: 149 lib + 54 conformance + 4
git-interop), cargo test --all-features server-less and against the
live pglo-poc harness (ALKBLOBS_PG_* — 327 lib + 125 conformance + 4
git-interop), cargo clippy --all-targets -- -D warnings across both
feature variants, cargo fmt --check, cargo doc --no-deps (the
pre-existing walk_committed intra-doc warning, unchanged)
9.7 KiB
id, name, status, depends_on, scope, risk, impact, level, tags
| id | name | status | depends_on | scope | risk | impact | level | tags | |||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| s4-wire-error-detail | S-4 — internal error detail off the wire: stable domain texts at the synthesis points | completed |
|
narrow | low | component | implementation |
|
Description
Review-001 S-4. The store core's Error::Display — which for
Error::Io carries paths, DSN fragments, schema names, instance ids,
engine details — is interpolated into the wire-error messages
delivered to external callers. The synthesis points (post-split
locations):
- src/ops/wire.rs:82-91 —
store_internal_error:format!("the store core failed at {stage}: {error}")— the raw error rides theCallError::internalmessage (stat/have/delete's store-failure arms route through it). - src/ops/wire.rs:158-174 —
put_refused_error: theotherarm interpolates{other}(theVERIFICATION_REFUSEDarm is already stable text). - src/ops/fetch.rs:515-519 —
fetch_establish_failure:format!("the store core failed at {stage}: {error}"). - src/ops/put.rs —
finish_put's mint-refusal arms (:489, :495) interpolate the mint error; check the remaining put-path synthesis points for the same pattern. - src/ops/fetch.rs:480-500 —
fetch_ranges_ingress's rejection texts interpolate the raw payloadValues ({values},{value}) — raw wire input reflected into the error message. (If the s2 task already landed stable texts here, verify and skip.)
The fix: stable domain texts at every synthesis point — the
stage/shape named, the store core's Display detail not
interpolated. The declared error codes and their details schemas
are unchanged (the wire contract is the code + schema; the message
content is the leak).
Design point — the detail's server-side disposition: the review
says "keep detail on the server's log side", but the crate has no
logging infrastructure (no log/tracing dependency). Judge in-task
and record in Notes; the lean default is detail dropped at the
synthesis points (the embedder's own ops logging covers its surface;
adding a logging dependency for this is a posture change, not a
security fix) — with the dropped-detail consequence named (an
operator debugging a store failure sees the stable text on the wire
and must consult the store's own failure surfaces). If a log-crate
emit is chosen instead, it rides the ops feature and the decision
is recorded.
Tests: the d2 round's error-arm tests exist — extend the assertions to the message content: a store failure driven through stat/have/ delete (and a put refusal) answers the stable text with no store-core detail (no path, DSN, schema, or instance-id fragments in the wire message).
Acceptance Criteria
- Every synthesis point (the list above + a sweep for the
pattern across
src/ops/) emits a stable domain text; noError::Displaydetail and no raw payloadValues ride the wire messages - The declared error codes and
detailsschemas unchanged - The detail's disposition (dropped vs logged) judged and recorded in Notes
- Tests assert the stable texts and the absence of store-core detail fragments on the driven error arms
- Gates green:
cargo test(default),cargo test --all-featuresserver-less,cargo test --all-features+ live harness, clippy--all-targets -- -D warningsacross the feature variants,cargo fmt --check
References
- docs/reviews/001-storage-surface-general-review.md (S-4 — pre-split coordinates)
- docs/plans/implementation.md (§Wave 10; §Review gates — the security gate)
- src/ops/wire.rs (
store_internal_error,put_refused_error) - src/ops/fetch.rs (
fetch_establish_failure,fetch_ranges_ingress) - src/ops/put.rs (
finish_put's arms) - src/ops/tests.rs (the d2 error-arm round — the assertion extensions' home)
Notes
- The detail's disposition (the design point, judged): detail
dropped — no logging dependency added. The lean default the task
pre-approved: the crate has no
log/tracinginfrastructure and this was not the moment to make that posture change (adding a dependency is a posture change, not a security fix). The disposition and its named consequence are recorded onstore_internal_error's doc (src/ops/wire.rs): an operator debugging a store failure reads the stable text (the stage named) on the wire and must consult the store's own failure surfaces — the embedder's own ops logging covers its side. If a futurelog-crate emit is wanted, it rides theopsfeature and this note is superseded by an ADR. - The synthesis-point sweep (the full inventory) — beyond the
task's five listed points, the sweep over
src/ops/found the remaining raw-error interpolations, all stabilized:- wire.rs
store_internal_error:{error}dropped; the message isthe store core failed at {stage}(the two stat/delete store-failure arms ride it; the parameter stays_error— kept for the arms' shape, deliberately not content). - fetch.rs
fetch_establish_failure:{error}dropped; same stable text over both stores' arms (whole and ranged). - wire.rs
put_refused_error'sotherarm:{other}(the backendError::Io(String)— the paths/DSN/schema/instance-id carrier) replaced bythe store core refused the put — nothing committed. The prefix keeps the offered digest hex where the caller offered one — the schema-gated canonical hex the caller itself presented, not store-core detail. TheVERIFICATION_REFUSEDarm was already stable (untouched). - put.rs
finish_put's mint-refusal arms (:489/:495):{error}dropped —the fleet token mint refused/the custodial token mint refused. (The mint refusals' ownError::Iopayloads — the fleet-declaration/owner-mismatch refusals, the entropy failure — stay server-side the same way.) - put.rs the join-error arms (:422/:448):
{join_error}(aJoinErrorwhose panic variant renders the panic payload) replaced by the stable classificationthe put's store task failed (cancelled|panicked). - put.rs the data-plane-arm (:292):
{error}(the alkcall transport error) dropped —the put's data plane never opened. - put.rs the unreachable post-schema digest ingress (:269) formats
the crate's own
CallErrorDebug(stable code + message + schema-gated hex) — not store-core detail; left. - fetch.rs
fetch_ranges_ingress: verified — s2 already landed the stable texts here (the task's own "verify and skip"); no raw payloadValues remain.
- wire.rs
- The contract is unchanged, verified: the declared codes
(
INTERNAL,INTERNAL/PUT_REFUSED,VERIFICATION_REFUSED,DIGEST_MISMATCH…) and everydetailsschema are byte-identical; the stale doc lines claiming "details carry the store core's message" onVERIFICATION_REFUSED/PUT_REFUSED/put_message_details_schemawere corrected to the stable-text posture (no details attach store-core messages anywhere on the put paths today — the schemas stay as declared). - The test drive (why the commit-verification arm): a real media
failure driven deterministically through
blobs/put's wire path — the dualmem+localstore's committed-shard directory pre-made unwritable, so the staged put's commit-rename fails strictly after the stream's EOF — settles asPUT_REFUSEDwiththe put refused at the commit verification: the store core refused the put — nothing committed, asserted verbatim, plus the absence checks (/path fragments,io:engine forms,Permissionos-error text) and the zero-residue/pin-release asserts. The shared helper's other call site (the store arm'sput_failed_responsesettle) is the same stable template; driving it over the wire is timing-racy (the put fails at staging before the tee's EOF — the tee parks mid-read on an exactly-consumed stream), so one drive covers the template that both arms share. The d2 round's broken-shard test (stat/delete/establish, the read-failure forms) was extended to the verbatim stable texts plus the same absence checks; have is excluded by construction (its probe folds failures topresent: false— no wire message exists to assert).
Summary
S-4 landed. Every src/ops/ wire-error synthesis point now answers a
stable domain text: store_internal_error and fetch_establish_failure
emit the store core failed at {stage}; put_refused_error's refusal
family emits {prefix}: the store core refused the put — nothing committed (offered-digest hex in the prefix where the caller offered
it, the canonical form the caller presented); the mint refusals, the
join-error arms, and the data-plane arm drop their interpolations in
favor of stable stage-naming texts; the ranges ingress kept s2's
stable texts. The store core's Display detail (paths, DSN fragments,
schema names, instance ids, engine forms) and the raw transport/join
errors no longer ride any wire message. Declared codes and details
schemas byte-identical. Disposition judged: detail dropped — no
log/tracing dependency; recorded on store_internal_error's doc.
Tests: the d2 broken-shard round (stat/delete/establish) extended to
verbatim stable texts + absence-of-detail checks; a new wire-driven
PUT_REFUSED test asserts the commit-verification arm's stable text
from a real commit-rename failure (nothing committed, pin released).
Gates: cargo test (default: 149 lib + 54 conformance + 4
git-interop, green), cargo test --all-features
server-less and against the live pglo-poc harness (327 lib + 125
conformance + 4 git-interop, green), clippy --all-targets -- -D warnings across both feature variants, cargo fmt --check.