Files
alkstore/docs/architecture/decisions/025-log-facade.md
T
glm-5.3-flash a9b6cf0c86 adopt the tracing log facade, engine crates only (review 004 item 7's family-level decision, ADR-025): the family precedent checked and unanimous (alkcall/alktunnels/alksocks/alktty all on tracing = "0.1" facade-only, tracing-subscriber env-filter a dev-dep where present, silent by default); user sign-off taken in-session (2026-10-11, adopt-tracing option chosen) and recorded in the ADR's status; core stays dep- and emit-free (the explicit core-dep answer — the payload serde deps remain the sole core deps, the ADR states the ADR-020-§4 reading explicitly); all 19 library-path eprintln! sites migrated to tracing::warn!/error! with message text + the alkstore: prefix + the secret-free discipline unchanged (17 at review time, +2 from the review's own inline tick/lease restructures — re-grepped, sqlite 10 / pg 9, zero remainder, tests' harness-skip prints untouched); levels: warn = retried/degraded ops, error = unrecoverable loss (watcher death, discarded pooled clients, panic-path rollback failures); the four substrate-lineage files ride PROVENANCE delta D-41 (message-identical macro swap); deployment.md gains the diagnostics-posture section (operator recipe RUST_LOG=warn/error at the consuming binary; listener application_name diagnosability joins it), ADR index + design-decisions table carry the 025 row, CHANGELOG's 0.1.0 body states the facade; pg-fix-forwarder-diagnosability's output form now pinned (warn on failed reconnect connects with attempt/backoff context, error on the loop-exit poll error); gates green: build, workspace tests server-less, clippy -D warnings, fmt, doc gate zero warnings, fuzz corpus replay 6/6; task -> completed
2026-10-11 14:19:07 +00:00

9.3 KiB

ADR-025: The log facade — adopt tracing, engine crates only

Status

Accepted (2026-10-11, Phase 1 — the family-level posture decision that docs/reviews/004-pre-release-general-review.md §"Findings recorded, not fixed" item 7 explicitly recorded rather than made: the review's item 7 named the posture gap (no facade, 17 library-path eprintln! sites carrying the diagnostics) and left the log/tracing/eprintln! call as a family-level decision. The class-1 amend-in-place window (ADR-017 §2) is still open — no versioned artifact exists — so the adoption lands pre-release, before the consumer task (the pg forwarder diagnosability fix) needs the output form. User sign-off recorded 2026-10-11: adopting tracing, this option chosen by the user in session — the sign-off is part of this record per the deciding task's acceptance criterion.)

Context

The workspace ships four published crates with diagnostics but no log facade: the engine crates emit operator-visible failures through eprintln! — unconditional, un-filterable, un-routable stderr writes whose verbosity the consumer cannot turn down and whose presence the consumer didn't necessarily ask for. The review verified all sites secret-free (no DSN/password material ever printed) and recorded the posture question rather than deciding it.

Three forces frame the decision:

  1. The family precedent is unanimous. All four family repos were checked this session (2026-10-11): alkcall, alktunnels, alksocks, and alktty each depend on tracing = "0.1" in their library crates, emit through tracing::{error,warn,info,debug} macros, and carry no subscriber in [dependencies] — where tracing-subscriber (env-filter) appears at all (alktunnels, alksocks) it is a [dev-dependencies] entry, so the integration tests can mount a sink and assert on emissions; the subscriber wiring in production is the consuming binary's/app's business. Nobody in the family uses the log facade.
  2. Core's dependency posture constrains the shape. Core (ADR-001 split; ADR-020 §4's dependency posture, serde/serde_json being the sole core deps) must not grow a facade dependency — a dep on alkstore is a contract-posture change and buys nothing: core has zero eprintln! sites (re-grepped) — core emits nothing and needs to emit nothing; the diagnostics live in the engines. The adoption is therefore engines-only: lightest shape, contract untouched. (Amendment note: ADR-020 §4's text is about payload encoding and says nothing about logging; the constraint applied here is ADR-020 §4's dependency line read with core-contract.md — the payload serde deps are the sole core deps. This ADR states the reading explicitly so the log decision doesn't silently stretch an encoding ADR beyond its text.)
  3. Published-crate consumer expectations. A facade that emits nothing until the consumer installs a subscriber is the consumer-respectful posture — silent by default, opt-in filtering (RUST_LOG-style env-filter at the wiring site), structured fields, zero output cost when compiled without a subscriber. The eprintln! posture forces stderr noise on every consumer. tracing's no-subscriber overhead is a cheap disabled-event call — invisible in the seam-latency budgets (ADR-007's measured postures).

The deciding task also named the disposition question: what happens to the existing eprintln! sites under whichever option lands.

Decision

Adopt tracing as the log facade, in the engine crates only. Core keeps its dependency posture untouched — no facade dep on alkstore, ever (the engines-only shape is the contract-safe one; moving diagnostics into core would be a separate decision with its own record anyway).

  • Dependency shape (mirrors the family, verified above): each engine crate that emits (alkstore-sqlite, alkstore-postgres) gains tracing = "0.1" in [dependencies]; tracing-subscriber (env-filter) appears only under [dev-dependencies], where a test needs to assert on emitted events. No subscriber is wired inside any library; the consuming binary/app owns that wiring (the family's uniform posture).
  • Core: silent. Zero tracing (or log) dependency; zero emission sites; the contract's failure reporting stays the typed error taxonomy (core-contract.md). This is the explicit answer to the core-dep question the deciding task required.
  • Semantics of an event without a subscriber: dropped — no output, no noise. Silent by default, loud on opt-in is the facade's standing guarantee and the operator-facing property this ADR pins.
  • The secret-free rule is the posture of record: no DSN, password, token, connection-credential material, or consumer-table content in any engine event — the review's verified property promoted to standing rule any new emission site inherits. Diagnostics name mechanisms, error classes, backoff/attempt state, and engine-owned identifiers (channel names, queue names, listener application_name-class labels), never credentials.
  • Site disposition: migrate all. The review counted 17 library-path eprintln! sites (verified secret-free); the re-grep at decision time finds 19 — sqlite 10, pg 9 — the delta arriving from the review's own inline fixes re-structuring the tick/lease failure arms. All 19 migrate from eprintln! to the tracing facade in this adoption (levels: warn for retried/degraded operations and failed diagnostics paths, error for unrecoverable-loss conditions such as watcher death or the discarded poll error); message text and secret-free discipline carry over verbatim-class. No site is kept in eprintln! and none is deleted: every emission the review verified carries operator value the facade preserves. The pg forwarder's swallowed connect/poll errors (the review's item 7 arms, the deciding task's consumer) surface through the same facade — warn on the failed reconnect connects with attempt/backoff context, error on the loop-exit poll error.
  • Levels and fields are engine-local detail, not contract: the contract's only log-adjacent pin is the error taxonomy's Display shape. Event field names may change without an ADR; removing an engine's diagnostics entirely would be a posture change (record one).

Consequences

Positive

  • The family posture is uniform: one facade across alkcall, alktunnels, alksocks, alktty, and alkstore's engines; operators learn one wiring recipe (env-filter subscriber) per family binary.
  • Consumers of the published crates get silence by default and opt-in structured diagnostics — the published-crate expectation the deciding task named is answered in the consumer-respectful direction.
  • The diagnosability consumer task has its output form pinned before it runs; the pg forwarder fixes land as tracing events.
  • The secret-free discipline is now stated law, not inherited hygiene re-verified per review.

Negative

  • Two engine crates gain a dependency (the facade only — no subscriber dep in [dependencies]); the dep-graph delta is one crate, family-adopted four times over.
  • Events are invisible until the consumer wires a subscriber — an operator running a bare integration sees no diagnostics where the old eprintln! posture printed unconditionally. The deployment matrix states this honestly (operators wire RUST_LOG at the consuming binary; the engine crates' docs say so).
  • No-op-subscriber sites still cost a disabled-event check per emission — unmeasured here, but the family carries it and the POC-measured budgets are Event-free paths.

References

  • docs/reviews/004-pre-release-general-review.md §"Findings recorded, not fixed" item 7 — the finding (the no-facade posture, the secret-free verification of the eprintln! set, the family-level framing); the §"What was checked and clean" security sweep is the secret-free verification of record.
  • The family precedent (verified in-session, 2026-10-11): alkcall/Cargo.toml:31, alktunnels/Cargo.toml:32, alksocks/Cargo.toml:40, alktty/Cargo.toml:44 — tracing facade-only; alktunnels/alksocks' tracing-subscriber(env-filter) entries are [dev-dependencies].
  • ADR-001 (crate split), ADR-020 §4 + this ADR §Context point 2's explicit reading — core stays payload-deps-only.
  • ADR-017 §2 — the class-1 window this adoption rides; no contract text moves (no trait, type, or error variant changes; zero consumer-inventory rows required — nothing here assumes a consumer need, the diagnostics belong to engines and operators).
  • docs/architecture/deployment.md — the operator-facing posture section this ADR's landing adds.
  • ADR-004 — the pg forwarder's application_name diagnosability note, the first operator-diagnosability posture in the family.
  • The deciding task: tasks/decide-log-facade-adoption.md (sign-off recorded in §Status).