diff --git a/docs/architecture/deployment.md b/docs/architecture/deployment.md index 21bea4c..9e3c649 100644 --- a/docs/architecture/deployment.md +++ b/docs/architecture/deployment.md @@ -1,6 +1,9 @@ --- status: stable -last_updated: "2026-10-11 (the log-facade decision of record landed — +last_updated: "2026-10-11 (the pg identifier-truncation wake-collision +note — review 004 item 13's doc-fix disposition — added to the +Postgres engine section). +Earlier the same day: the log-facade decision of record landed — [ADR-025](decisions/025-log-facade.md): engine diagnostics route through the `tracing` facade, silent by default, subscriber wiring on the consumer/binary; the diagnostics-posture section added below. @@ -90,6 +93,28 @@ engine-owned PostgreSQL schema (default `alkstore`, per-engine option) — layout per [ADR-010](decisions/010-queue-semantics-depth.md) §8 (resolved from [queues.md](queues.md)'s namespace bullet). +One pg-specific naming caveat, dated per the family's amendment +convention *(annotated 2026-10-11 — review 004 item 13, recorded +"not fixed" in the review with a fold-into-the-next-doc-pass +disposition; this note is that fix)*: the wake machinery +interpolates the channel as a quoted SQL identifier on +`LISTEN`/`UNLISTEN`, and PostgreSQL truncates identifiers to +**63 bytes (`NAMEDATALEN` − 1)** server-side — with only a +`NOTICE`, never an error — so two distinct names longer than 63 +bytes sharing a 63-byte prefix register the **same** server-side +wake channel (a silent cross-channel collision). The `pg_notify` +path is unaffected by the truncation — it carries the channel as a +text parameter, not an identifier, and the server rejects channels +of 64 bytes or more there ("channel name too long") — so the +collapse is on the registration side. The +[naming contract](core-contract.md#naming--reserved-namespace) +deliberately caps no name (the length-independence verdict stands), +so nothing engine-side warns. Mitigation: +**keep mechanism names under the PostgreSQL identifier limit — +at most 63 bytes; the limit counts bytes, not characters, so +multibyte names reach it sooner.** Distinct names within the limit +cannot collide. + ### Mem engine - No connections — there is nothing to budget. The engine instance diff --git a/docs/architecture/engine-postgres.md b/docs/architecture/engine-postgres.md index 4e1e81c..0cd9c9f 100644 --- a/docs/architecture/engine-postgres.md +++ b/docs/architecture/engine-postgres.md @@ -1,6 +1,9 @@ --- status: stable -last_updated: "2026-10-10 (review-wave-5 gate: the verification backlog fully discharged in the version-stamped contract suite, green against this engine; spec flipped draft → stable)" +last_updated: "2026-10-11 (review 004 item 13's wake-channel +identifier-truncation note — the doc-fix disposition — added to the +connection-architecture wake-substrate section). +Earlier, 2026-10-10 (review-wave-5 gate: the verification backlog fully discharged in the version-stamped contract suite, green against this engine; spec flipped draft → stable)" --- # Postgres engine @@ -60,6 +63,26 @@ obligations live in the core spec. broadcast re-subscribe (no server round-trips); per-channel connections are never warranted at this scale (POC-verified). +One wake-channel naming hazard, noted per the review 004 item 13 +disposition *(annotated 2026-10-11)*: the forwarder's +`LISTEN`/`UNLISTEN` carry the channel as a quoted SQL identifier +(`quote_ident` — the POC's quoting shape), and PostgreSQL truncates +identifiers to 63 bytes +(`NAMEDATALEN` − 1) server-side — with only a `NOTICE`, never an +error — so two distinct names longer than 63 bytes sharing a +63-byte prefix register the same server-side wake channel, silently. +The `pg_notify` path is unaffected by the truncation — it carries +the channel as a text parameter, not an identifier (channels of 64 +bytes or more are rejected there server-side) — so the collapse is +on the registration side, not the send side. Names are unbounded by +the contract's naming rules (deliberate; [ADR-008](decisions/008-contract-v1-pinning.md) +§4), nothing engine-side warns, and the machinery itself is +name-length agnostic (63 bytes is a server-side limit, not an +engine check). Mitigation is a consumer obligation: keep channel +names under the PostgreSQL identifier limit (at most 63 bytes; the +limit counts bytes, not characters) — the operator-facing statement +lives in the [deployment matrix](deployment.md). + ## Mapping the contract | Contract piece | Engine realization | diff --git a/tasks/docs-pg-identifier-truncation-note.md b/tasks/docs-pg-identifier-truncation-note.md index 1643803..26d7260 100644 --- a/tasks/docs-pg-identifier-truncation-note.md +++ b/tasks/docs-pg-identifier-truncation-note.md @@ -1,7 +1,7 @@ --- id: docs-pg-identifier-truncation-note name: Document the pg identifier-truncation collision in the release text (review 004 item 13) -status: pending +status: completed depends_on: [] scope: single risk: trivial @@ -32,12 +32,12 @@ annotation per the family's convention. ## Acceptance Criteria -- [ ] `deployment.md` carries the truncation-collision note with a +- [x] `deployment.md` carries the truncation-collision note with a dated annotation (mechanism + mitigation, honestly worded) -- [ ] `engine-postgres.md` carries it too if a wake-channel section +- [x] `engine-postgres.md` carries it too if a wake-channel section exists there (or the absence recorded in Notes) -- [ ] The validation contract's no-cap verdict untouched -- [ ] Doc gate green +- [x] The validation contract's no-cap verdict untouched +- [x] Doc gate green ## References @@ -51,8 +51,33 @@ annotation per the family's convention. ## Notes -> To be filled by implementation agent +Deployment matrix note: `deployment.md` has no standalone wake-channel +section — the landing site is the Postgres engine's co-tenancy +paragraph under "Connection budgets" (the one place the matrix +discusses naming), as a dated amendment annotation per the family +convention. `engine-postgres.md` has no dedicated wake-channel section +either; the note landed at the end of "Connection architecture" (the +wake substrate lives there) with a cross-reference to the +deployment-matrix statement. Accuracy sharpening beyond the review's +text (verified against Postgres master `commands/async.c`): +`pg_notify` does not merely escape identifier truncation — the server +*rejects* channels of 64 bytes or more there ("channel name too +long", an error), while quoted identifiers on the `LISTEN` side +truncate to 63 bytes silently (a `NOTICE`); the notes state the +collapse is on the registration side. The mitigation is phrased as a +consumer obligation (names at most 63 bytes; a byte count, not a +character count) — no validation contract change, per the task +description. ## Summary -> To be filled on completion \ No newline at end of file +Landed the pg identifier-truncation collision note in +`docs/architecture/deployment.md` (dated annotation, Postgres engine +section) and `docs/architecture/engine-postgres.md` (connection +architecture, wake-substrate section) — mechanism, honest send-vs- +registration-side statement, and the keep-names-under-63-bytes +mitigation. Frontmatter `last_updated` of both files carried forward. +Verified: `RUSTDOCFLAGS="-D warnings" cargo doc --no-deps` green, +`cargo fmt --check` green, `cargo clippy --all-targets -- -D warnings` +green; `core-contract.md`'s no-cap naming verdict untouched (diff +touching only the two doc files). \ No newline at end of file