Plan: battery wave 2 — lane partition, topology helpers, run-scoped cleanup, honest coverage (#1377/#1381/#1379/#1382, epic &76)

On this page

Status

Step Description Status

0

Commit this plan + nav link; amend #1379’s kill-9 AC on the issue (cross-run auto-sweep is unsafe as written).

Done (2026-08-08) — plan 7ab950c1; AC amendment note 3662592196

1

MR-1 fix/battery-topology-lanes (#1381 + #1377): topology renderer unification, canopy-test-lib::topology helpers, ~150-site URL migration (7 batches), test-lanes-lint (URL check), infra_tests classification (6 batches), lane partition + CI pins.

Done (2026-08-08) — !1092 merged (edc2b5ad; force-merged past the fleet-wide runner disk-full #1397 per the documented procedure, pre-push battery green). Partition live-verified 5109 = 3203 ⊎ 1906; poisoned-env tripwire 3203/3203; both-modes evidence on the MR. #1377 closed with evidence; #1381 stays open pending #1393/#1394/#1396. #1391 filed en route.

2

MR-2 fix/battery-schema-lifecycle (#1379): armed lifecycle guards, run-scoped schema names, tagged connections, evidence-gated sweep, PG connection/lock logging.

Done (2026-08-08) — !1093 merged (939bffab). Battery green including the first live end-to-end schema-sweep gate (isolated: 82 current-run schemas dropped across 7 DBs, zero failures; shared-db: 58/0 earlier); one battery rejection en route (MR-1’s own URL lint caught 3 parser fixtures — waivered). #1379 + #1395 closed with evidence. Filed en route: #1393/#1394/#1395/#1396.

3

MR-3 fix/battery-honest-coverage (#1382): unit-scoped coverage + rebaselined floor, informational integration-coverage lane.

Done (2026-08-08) — !1094 merged (86dad825), battery green first try. Floor honestly reset: 42.1566 % measured in the pinned CI image (188 234 lines / 79 353 covered) → DEFAULT_THRESHOLD 40.65, drift-pinned (const ↔ coverage-baseline.tomlUNIT_FILTER). The measurement’s first run caught #1398 (contract-pinned fix bf0a3f59); coverage --integration live-verified 1909/1909 under instrumentation, 28.67 % line informational, sweep clean (its first run exposed the missing junit dir — fixed d51f02c3). #1382 + #1398 closed with evidence.

4

Delivery tail: file the bootstrap-vs-schema-migrations follow-up issue; close issues with evidence; epic &76 ticks; plan → Archive.

Done (2026-08-08) — #1392 (migrations split) + #1399 (bootstrap-extraction debt) filed; #1377/#1379/#1382/#1395/#1398 closed with implementation + merge SHAs; #1381 held open pending #1393/#1394/#1396 per the honesty condition; #1397 (fleet CI disk) filed with the green→red flip evidence; standards-page coverage drift escalated upstream (claude-quickstart#27); epic &76 ticked; plan archived.

Epic: &76
Issues: #1377, #1381, #1379, #1382 (all T1 — Correctness)
Branches: fix/battery-topology-lanesfix/battery-schema-lifecyclefix/battery-honest-coverage (stacked)

Three stacked MRs rather than one: a single ~126-file MR carrying topology changes, lane semantics, destructive cleanup, and a coverage rebaseline is unreviewable and unbisectable (external review round 2; the earlier one-MR batching grant was permission, not mandate).

Context (verified by exploration)

  • #1377: Dockerfile.integration’s ENTRYPOINT `cargo nextest run --workspace reruns every workspace test. Infra-backed tests live in lib/bin targets (~62 src files, no usable naming convention, private-item access blocks moving them to tests/ targets). Renewals' devstack-driving lib tests (no EphemeralSchema) belong in the set too.

  • #1381: ~124 postgres + ~25 AMQP hand-rolled URL sites across ~126 files in 8 shapes; program DBs (snap/tanf/medicaid/caps/wic) have no URL channel in-network at all; TestConfig is the canonical seam (db.rs:63 documents a never-built *_db_url field).

  • #1379: EphemeralSchema Drop is a detached tokio::spawn cancelled at runtime shutdown; the schema leaks on migration and scoped-pool-connect failures before the RAII guard exists; sweep_orphans has zero callers; 259/484 cleanup() call sites discard errors with .ok(); no run-id plumbing exists.

  • #1382: cargo xtask coverage runs infra-less, every DB-backed test skips vacuously, and the 41.0 floor defends that number labeled as workspace coverage.

External review dispositions (round 2, folded into this design)

Finding Resolution

Older-runs sweep can destroy live schemas; a dropped search_path entry mid-migration-wait redirects unqualified DDL into public

Automatic sweep = current run only, plus an evidence-gated older pass (schema-COMMENT age > 6h AND no referencing backend). Unmarked schemas are manual-only. #1379’s kill-9 AC openly amended on the issue.

Sweep skipped when tests fail

Lanes capture their Result; the sweep always runs; the original failure is returned as primary. Forced-failing-test verification.

Lifecycle repair starts too late

Armed guard immediately after CREATE SCHEMA, covering scoped-pool-connect failure too; cleanup() no longer forgets the guard before success.

Drop teardown connection storm

Teardown pools max_connections(1) + explicit close + global Semaphore(4) + Handle::try_current guard.

sqlx per-database migration advisory lock × 16-thread infra arm

Validate arms unify onto the same partition as every other lane: pure-unit@16 (zero infra), one complete infra lane@4. Migration locking untouched; bootstrap-vs-schema migration separation filed as a follow-up issue.

Stale topology at render time; two divergent env renderers; .ports.env never self-heals

One pure renderer render_test_env(&Topology); topology passed explicitly by callers; file writer and env-vec builder consume the same rendering; CANOPY_PORTS_ENV_VERSION forces regeneration on format change.

Program DB fallback tiers can silently select the wrong physical database

Programs are tier-1-or-panic (fail-closed, loud). Generic derivation only for services genuinely colocated on shared PG.

validate-in-network omitted; program depends_on missing

Its injection set becomes renderer-derived (invariant: injects every CANOPY_TEST__* key the renderer emits, with in-network values); compose canopy-integration gains service_healthy deps on all five program PGs.

Shared-db overrides applied too narrowly

Folded into the renderer — every env-rendering path (dev start/reload/restart, e2e, test, validate, refresh) gets identical topology inputs.

Filterset placeholders not executable; complement-by-construction (candidate B) unsafe under proc-macro kinds

Exact literal filtersets below; candidate A chosen now; xtask consts + a Dockerfile-parity lint check.

Classification lint cannot enforce the criterion; out-of-line mod tests; files; test_support modules; poisoned proof partial

Lint is a necessary-signal check (module-path-aware, including out-of-line files); test_support modules exempt but must contain zero test fns; CI cargo-test with required is the standing dynamic backstop (an infra-less runner is a fully poisoned environment); the local proof poisons all CANOPY_TEST__* inputs.

URL lint too coarse (file-level allowlist hides violations)

syn string-literal parsing; per-literal // canopy-lint: allow-url-literal (<reason>) markers; chain_genesis migrates to the helpers instead of a blanket exemption.

Blocking checks absent from MR CI

Static checks run in the MR cargo-test job; the partition check stays in pre-push validate per the repo’s CI policy (pre-push is the sole functional gate); the resulting MR-CI infra-test gap is documented explicitly in testing.adoc.

application_name gives live metadata, not historical logs

All six PG services gain log_connections/log_disconnections/log_lock_waits + a %a-bearing log_line_prefix + docker json-file rotation.

Run id too short / untyped / lost across env refresh

16-hex typed TestRunId, injected by the renderer (re-carried through every refresh); malformed ambient values rejected in test-lib.

URL-concat connection tagging fragile

PgConnectOptions throughout (from_str then .application_name() / .options()); admin/teardown/sweep connections tagged too; the length check is a real assert!.

eprintln invisible under nextest output capture

The reliable surface is the sweep report (a nonzero current-run swept-count means cleanup failures) plus captured output on test failure; AC reworded accordingly.

Sweep API cannot represent its failure modes

Result<SweepStats, SweepError> with instance/db/schema/phase; exact DB allowlist; datallowconn + owner checks; validated + quoted identifiers; DROP SCHEMA IF EXISTS + re-enumeration; statement/lock/connect timeouts; sequential per instance; canonicalized deduped instance URLs; the manual command takes the battery locks.

test_migseam_* schemas unowned

Every test schema becomes <label>_<run16>_<hex12> via a shared run_scoped_schema_name(label); sweeps match on the run component.

nextest list comparison underspecified

JSON output, filter-match.status == "matches" records only, composite (binary-id, test-name) identity, --ignore-default-filter for the raw inventory; the group check iterates every override in every profile; new validate gates get validate_report::STAGES entries.

Coverage profiles inherit `default’s junit path (stale-report clobbering)

Coverage profiles get an explicit distinct junit path test-results/coverage/results.xml (gitignored).

Coverage false-greens; baseline artifact not commit-ready

Coverage sets CANOPY_TEST_INFRA=required; the floor is measured in the CI image with pinned cargo-llvm-cov/cargo-nextest; the committed artifact is a small normalized coverage-baseline.toml, not the 155 KB gitignored JSON.

Integration coverage rots silently

Documented as an informational developer command; terminate-after = 2; workspace-wide-coverage claims corrected in docs.

"Both modes green" contradicted deferring newly-lit failures

Both-modes green is required to close #1381; newly-exposed pre-existing defects get fix: issues, MR-1 uses Relates to #1381, and #1381 stays open until green.

Design

1. Classification (#1377)

Criterion: any lib/bin test needing live infrastructure (EphemeralSchema, infrastructure_available(), PG/AMQP/devstack HTTP). Convention: the test sits in a module whose path contains an infra_tests segment.

File state Transform

All tests infra

mod testsmod infra_tests

Mixed

Infra tests move to a sibling mod infra_tests; shared helpers stay pub(super) in mod tests

Several infra modules (batch_tests, keyset_tests, …)

Nest under one mod infra_tests

Out-of-line mod tests; (reporting/tanf/medicaid worker/*/tests.rs)

Rename declaration and file to infra_tests.rs (no #[path])

canopy-renewals/src/pr_pipeline.rs mod pipeline_tests

mod infra_tests, atomic with retargeting test(pr_pipeline::pipeline_tests)test(pr_pipeline::infra_tests) in every nextest profile

No test fn is renamed (three serialized-group filtersets are fn-name-based).

2. Lane partition (#1377) — one partition, everywhere

Exact expressions, defined once as consts in xtask/src/lanes.rs; the Dockerfile literal is lint-checked for parity:

INFRA  = test(/(^|::)infra_tests::/)                      # regex: catches crate-root modules
UNIT   = !test(/(^|::)infra_tests::/)                     # used with --lib --bins
INTEG  = kind(test) | test(/(^|::)infra_tests::/)         # candidate A, chosen now
VUNIT  = !kind(test) & !test(/(^|::)infra_tests::/)       # validate pure-unit arm
Lane Change

xtask test --unit

--lib --bins -E UNIT + CANOPY_TEST_INFRA=required (tripwire)

xtask test --integration (host)

Drop --test *; -E INTEG

Container ENTRYPOINT (Dockerfile.integration)

-E INTEG literal; CMD (profile) unchanged

Validate unit arm

-E VUNIT, profile validate-unit, 16 threads — now genuinely infra-free

Validate infra arm

-E INTEG, profile validate, 4 threads — the one complete infra lane (absorbs the ~172 infra lib tests; also resolves the sqlx per-database advisory-lock serialization pressure at high thread counts)

CI cargo-test job

--lib --bins -E UNIT --profile ci + job var CANOPY_TEST_INFRA: "required" (the infra-less runner is a fully poisoned environment — the standing dynamic backstop) + cargo xtask test-lanes-lint --static before tests

ci-integration profile

test-threads = 4

The partition (UNIT-on-lib/bins ⊎ INTEG == full list) is enforced by lint check 3 on every validate. Follow-up issue filed at delivery: separate DB-global bootstrap migrations from schema-private migrations (prerequisite for any future migration-lock tuning; out of scope here).

3. Topology (#1381)

API (crates/canopy-test-lib/src/topology.rs, SPDX header, re-exported from lib.rs):

pub fn service_database_url(service: &str) -> String  // programs: tier-1-or-panic
pub fn admin_database_url() -> String                  // db `canopy`
pub fn rabbitmq_url() -> String                        // always /%2f vhost
pub fn rabbitmq_url_for(user: &str, password: &str) -> String

Precedence (each tier unit-pinned; load_ports_env_file becomes pub(crate)):

Service class Resolution

Programs (snap/tanf/medicaid/caps/wic)

CANOPY_TEST__<PROG>_DATABASE_URL or panic with regeneration guidance — a derived guess can silently hit an unmigrated same-named DB on the wrong physical instance. Tests probe infrastructure_available() first, so devstack-down still skips before reaching this.

Non-programs (colocated on shared PG by design)

CANOPY_TEST<SVC>_DATABASE_URL → derive from CANOPY_TESTDATABASE_URL (path → canopy_<svc>) → localhost:5432 default

Admin

CANOPY_TEST__DATABASE_URLlocalhost:5432/canopy

AMQP

CANOPY_TEST__RABBITMQ_URLlocalhost:5672/%2f

Single renderer (xtask/src/docker.rs): render_test_env(topology: &Topology) → Vec<(String, String)>Topology { shared_db: bool, ports: … } passed explicitly by callers (dev start/reload/restart from CLI flags; others read the marker once, before rendering). It subsumes today’s write_ports_env + build_env_for_ports + SHARED_DB_ENV (all three currently divergent). The file writer serializes the rendering plus CANOPY_PORTS_ENV_VERSION=2; reconcile regenerates on version mismatch or rendering diff. Emits per-program CANOPY_TEST__<PROG>_DATABASE_URL in both topologies.

In-network: compose canopy-integration gains five program lines ${CANOPY_<PROG>_DB_URL:-postgres://canopy:canopy@postgres-<prog>:5432/canopy_<prog>} plus service_healthy depends_on entries for all five program PGs; validate_in_network.rs’s injection set becomes renderer-derived (invariant: it injects every `CANOPY_TEST__* key the renderer emits, with in-network values) so host-.ports.env tier-1 values can never leak into the container.

Replacement sweep (grep-driven, per shape): A → admin_database_url(); B → service_database_url("<svc>"); C/D/E → service_database_url("<prog>") (shape C’s skip-if-unset behavior disappears — flagged in the MR); F/G → rabbitmq_url() / rabbitmq_url_for(); xtask/tests/chain_genesis_test.rs + cmd/chain_genesis.rs migrate to the helpers too. Per-batch gate: CANOPY_TEST_INFRA=required cargo nextest run -p <pkgs> with devstack up (bare -p runs would false-green by skipping).

4. Schema lifecycle (#1379)

Run id: 16-hex typed TestRunId minted after acquire_battery_locks; carried by the renderer (survives every env refresh); compose passthrough CANOPY_TEST_RUN_ID: "${CANOPY_TEST_RUN_ID:-}"; test-lib validates the ambient value against ^[0-9a-f]{16}$, else treats it as absent (loud eprintln).

Naming: shared run_scoped_schema_name(label) → String = <label>_<run16|local16>_<hex12> — used by EphemeralSchema (test), migration_phase_test (test_migseam), and inbox_parking_test (migrates). At creation, stamp COMMENT ON SCHEMA … IS 'canopy-test run=<id> created=<epoch from now()>'.

Connections: PgConnectOptions::from_str(base_url) + .application_name() + .options([("search_path", …)]) — no URL concatenation (preserves query params/TLS/IPv6/percent-encoding). application_name = canopy-test:<service>:<schema> (≤ 59 bytes; real assert!). Admin/teardown/sweep connections tagged canopy-test-admin:<purpose>. Pool width untouched (#1207 pin).

Lifecycle: an armed guard is constructed immediately after CREATE SCHEMA; any later failure (scoped-pool connect, migration replay) triggers an awaited compensating DROP (scoped pool closed first — sqlx can return before releasing its session advisory lock), the original error primary with the cleanup error attached; disarmed only on success. cleanup() reordered: mem::forget only after the DROP succeeds; on error the guard drops normally (best-effort + sweep backstop). Drop teardown: max_connections(1), explicit close, global Semaphore(4), Handle::try_current guard; the detached mechanism otherwise unchanged; the false "periodic devstack refresh" comment and stale "12-hex v7" doc fixed.

Sweep (sweep_schemas in db.rs returning Result<SweepStats, SweepError>; orchestrator xtask/src/schema_sweep.rs):

  1. URLs come from the renderer output (never topology::* — its .ports.env load is Once-cached per process), canonicalized, instances deduped.

  2. Instances: shared PG ∪ dedicated program PGs (per topology). Databases: an exact allowlist from the known-service const (∩ pg_database where datallowconn), owner-checked.

  3. Current run (always — lanes capture their Result, the sweep runs, the original failure is returned as primary): drop schemas matching the run component (LIKE '%_<run16>\_%' ESCAPE '\' on validated [a-z0-9_] names, identifier-quoted, DROP SCHEMA IF EXISTS … CASCADE, re-enumerate after), with statement/lock/connect timeouts, sequential per instance. One retry pass (~2s) for failures — a schema gone on retry is success — then bail loud.

  4. Older pass (evidence-gated): only schemas whose COMMENT parses AND age > 6h AND no backend in that DB references the schema in application_name — positive inactivity evidence. Unmarked/foreign-comment schemas: reported, never auto-dropped.

  5. Per-instance/DB report table in battery output; a nonzero current-run count is the reliable cleanup-failure surface (nextest captures test stdout on success, so `cleanup()’s eprintln alone is not).

  6. Manual cargo xtask dev sweep-schemas [--older-than <dur>] [--include-unmarked] — takes the battery locks; --include-unmarked is the explicit acknowledged-risk path.

AC amendment (posted on #1379): "kill -9 → zero schemas after the next battery" is unsafe as written (cross-run auto-sweep can destroy live runs; the migration-wait window even redirects DDL to public). Amended: killed-run schemas are removed by the next battery once evidence-gated (>6h + no backends) or immediately via dev sweep-schemas.

PG observability (same MR): all six PG services get -c log_connections=on -c log_disconnections=on -c log_lock_waits=on -c log_line_prefix='%m [%p] app=%a db=%d ' plus docker json-file log rotation (max-size/max-file), so application_name yields historical logs, not just live pg_stat_activity.

5. cargo xtask test-lanes-lint (blocking; static parts also in the MR CI cargo-test job)

  1. URL literals: syn string-literal parse for postgres:///amqp:// credential-bearing literals; per-literal // canopy-lint: allow-url-literal (<reason>) markers (parser tests, redaction fixtures, deliberately-unreachable pool tests, renderer internals); no file-level blanket exemptions.

  2. Classification: module-path-aware walk (inline mods + out-of-line files by filesystem layout): infra identifiers (EphemeralSchema, qualified new_for_*, infrastructure_available, topology helpers) inside [cfg(test)] code must have an infra_tests path segment; test_support modules exempt but must contain zero [test]/#[tokio::test] items. This is a necessary-signal check; sufficiency is the required-mode dynamic backstop (CI + the poisoned local proof).

  3. Partition + groups (after nextest-build): nextest list --message-format json, matching records only (filter-match.status == "matches"), composite (binary-id, test-name) identity, --ignore-default-filter for the raw inventory; assert unit ⊎ integ == full and disjoint; iterate every override filterset in every profile of nextest.toml, assert each matches ≥ 1 test. New validate gates (test-lanes-lint, schema-sweep) get validate_report::STAGES entries.

6. Honest coverage (#1382)

  • coverage.rs: cargo llvm-cov nextest --workspace --lib --bins -E 'UNIT' --profile coverage --summary-only --fail-under-lines <t> with CANOPY_TEST_INFRA=required (a stowaway infra test fails instead of skipping vacuously). Labeled unit coverage everywhere (docs currently claim workspace-wide — corrected).

  • Profiles coverage (8 threads) / coverage-integration (4 threads, slow-timeout 120s, terminate-after 2) with an explicit junit path test-results/coverage/results.xml (profiles inherit default’s junit — omission would clobber `test-results/unit/).

  • Floor: measured in the CI image with pinned cargo-llvm-cov + cargo-nextest versions (pinned in the job); the committed artifact is a normalized coverage-baseline.toml (scope, line totals, tool/toolchain versions, date) — the 155 KB .coverage-baseline.json stays gitignored. DEFAULT_THRESHOLD = CI-measured − 1.5; CHANGELOG documents the one-time reset.

  • coverage --integration: mirrors the test --integration bootstrap (locks → ensure_ready → required → run id → -E 'INTEG' → sweep). Documented as an informational developer command (no CI invocation, no floor) — explicit, not silent.

Commits

MR-1 fix/battery-topology-lanes (Relates to #1381 until both-modes green, then close; Closes #1377)

# Subject

1

fix(xtask): unify test env rendering behind one topology renderer (#1381)

2

fix(test-lib): add topology-aware service URL helpers (#1381)

3–9

fix(tests): migrate <pkgs> to topology URL helpers (#1381) — batches: mq/api/db/composition/test-lib+chain_genesis → reporting → applications+persons → medicaid+tanf → security+enrollment → renewals+eligibility+appeals → snap/notices/caps/wic/web/rules

10

fix(xtask): add test-lanes lint with URL-literal check (#1381)

11

fix(renewals): classify infra tests, retarget filterset (#1377)

12–16

fix(tests): classify infra-backed lib tests in <pkgs> (#1377)

17

fix(xtask): enforce infra_tests classification in lint (#1377)

18

fix(xtask): partition all test lanes on infra_tests (#1377) — lane table + lint check 3 + compose deps + validate-in-network renderer derivation + testing.adoc + CHANGELOG

MR-2 fix/battery-schema-lifecycle (Closes #1379)

# Subject

19

fix(test-lib): armed guards, run-scoped schemas, tagged conns (#1379)

20

fix(xtask): evidence-gated schema sweep after each battery (#1379)

21

fix(devstack): postgres connection/lock logging with rotation (#1379)

MR-3 fix/battery-honest-coverage (Closes #1382)

# Subject

22

fix(ci): rebaseline unit coverage honestly (#1382)

23

fix(xtask): add integration coverage lane (#1382)

Verification (per MR battery, plus these)

# Check

1

Partition set-verify (JSON, matching-only, composite IDs): unit ⊎ integ == full, disjoint — pasted into MR-1; permanent via lint check 3

2

Both in-network modes (--shared-db, isolated) + one host run each; every program URL verified to land on the expected physical instance (SELECT current_database(), inet_server_port())

3

Poisoned local proof: unit lane with all CANOPY_TEST__* env pointed at dead ports + required → green ⇒ no stragglers (CI cargo-test repeats this by construction)

4

First shared-db startup and isolated↔shared transitions render correct URLs (no stale-marker window); an in-sync pre-wave .ports.env upgrades via the version bump; renderer parity: file content == child env

5

Forced-failing-test battery: the sweep still runs, the original failure is reported as primary

6

Lifecycle unit tests: scoped-pool connect failure → schema dropped; migration failure → schema dropped, advisory lock released, original error preserved; cleanup() error → guard still fires

7

Two concurrent run ids cannot touch each other (current-run-only + evidence gate); invalid ambient run id rejected; quoting/%/_ pattern tests

8

Post-battery: current-run prefix = zero rows across every allowlisted DB on every instance (sweep --dry-run report); kill a battery, confirm orphans reported-not-dropped until evidence-gated/manual

9

pg_stat_activity shows canopy-test:* per pool type; docker PG logs show connect/disconnect lines with app= and rotation configured

10

Coverage floor reproduced in the CI image (pinned tools) before commit; badge regex verified in the MR pipeline; coverage --integration runs with devstack

11

Connection-count and wall-clock deltas before/after (from PG logs + validate-report stage timings) — posted in closing comments

Risks

  • Cross-run deletion is the top hazard — bounded by current-run-only auto-sweep + evidence gates; the residual is the manual --include-unmarked path, which is explicit and lock-held.

  • Classification completeness is load-bearing (CI cargo-test reds on stragglers) — syn lint + poisoned proof + required backstop.

  • The validate infra arm at 4 threads runs more tests than today’s integration arm — the wall-clock delta is measured in MR-1’s battery; the advisory-lock serialization means the 16-thread arm was largely illusory parallelism for DB tests anyway.

  • Renderer consolidation touches every devstack path — verification #4 (parity + transitions) covers; e2e bring-up is exercised by MR-1’s battery.

  • MR CI still runs no live infra tests (repo policy: pre-push validate is the functional gate) — now documented rather than implicit.

Errata

  • SweepError typed enum not built (post-completion audit, 2026-08-08). The review-disposition table promised Result<SweepStats, SweepError> with instance/db/schema/phase fields. Delivered shape: sweep_schemas returns Result<SweepStats, sqlx::Error> (a typed passthrough — canopy-test-lib’s only failure source here IS sqlx) and the xtask orchestrator attaches instance/db/schema context via anyhow::Context, which is the coding-conventions split (thiserror-class enums for library error taxonomies, anyhow + context in application code). A bespoke enum would wrap one variant for one caller; the failure-mode information the review demanded is all present in the sweep report + error chains. Recorded as a deviation, resolved in favor of the conventions.

Edit this page · default