fix: read an unprovisioned archive tier without reporting schema skew - #4687
Conversation
A tier file with no version stamp and no schema object has never been provisioned; the read guard now treats it as an absent tier instead of skew, and the CLI status reader records a skewed tier's version facts as data rather than raising through the status surface. The archive-debt and durable-change-train fixtures are corrected at the fixture layer: a hand-built tier holding tables now declares the version it emulates, and the synthetic source train patches the migration runner alongside the bootstrap tables so fresh-DDL parity stops projecting the real source history onto it. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DNGJ3awfNrsLaMdHgQZvid
Stepping one below the expected version collapses onto 0 for a version-1 tier, which the read guard treats as never provisioned rather than skewed. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DNGJ3awfNrsLaMdHgQZvid
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
|
Important
This repository does not receive automatic reviews because it has fewer than 10 stars. ⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Team Run ID: Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 162f11dba7
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| finally: | ||
| conn.close() | ||
| except sqlite3.Error as exc: | ||
| except (sqlite3.Error, SchemaSkewError) as exc: |
There was a problem hiding this comment.
Catch skew in the maintenance status pass
When the daemon is unavailable and any present tier has a mismatched schema version, this catch lets _archive_one_tier_status continue, but the same direct-status workflow subsequently calls _sqlite_maintenance_status, whose validating open catches only sqlite3.Error. Because SchemaSkewError is a separate project exception, both JSON status and the plaintext status path still abort instead of returning the version facts this change intends to preserve; handle the exception in that second pass as well.
AGENTS.md reference: AGENTS.md:L109-L115
Useful? React with 👍 / 👎.
| if found == 0 and _tier_holds_no_schema(conn): | ||
| # A tier file with neither a version stamp nor any schema object has | ||
| # never been provisioned. Reading it is reading an absent tier: the | ||
| # caller fails on the missing table it asked for, which is a truthful | ||
| # not-provisioned answer, where skew would misreport corruption. |
There was a problem hiding this comment.
Limit the empty-tier exemption to readers
When a tier-named durable file such as user.db exists but is byte-empty, this shared guard now also allows open_connection and open_daemon_connection to return a writable connection. A mutation such as _archive_set_setting therefore passes its existence check and fails later with no such table: user_settings (wrapped as a generic RuntimeError) rather than refusing the uninitialized tier before issuing SQL; keep this absent-tier behavior on the read-only path, or make writers return a typed not-provisioned refusal.
AGENTS.md reference: AGENTS.md:L107-L115
Useful? React with 👍 / 👎.
Summary
Fixes the 19-test schema-reader cluster from the 2026-09-05 master corpus run (106 failed / 20,455 passed). The reds had three distinct causes, not one; each is fixed at its own layer.
Problem
open_readonly_connection(path, tier)→_assert_schema_supportedraisedSchemaSkewError: source schema skew: expected 41, found 0on archives whose tier file exists but was never provisioned. The guard exemptedfound == 0for the index tier only, so a durable tier in the same state was reported as corruption.source.db/index.db;polylogue/daemon/backup.py:539then raised instead of reading an absent tier._archive_one_tier_statusprobes withvalidate_schema=Falseand correctly derivesversion_status="mismatch", then opens a second, validating connection fortable_counts, which raised through the status surface. A tier this runtime cannot read still has reportable existence/size/version facts.source.db/index.dbbut never stamps the version it emulates.bootstrap's version/DDL tables but notmigration_runner's, so fresh-DDL parity projected the real source migration history onto a two-table synthetic tier:sqlite3.OperationalError: no such table: main.raw_sessions.Solution
Production:
_assert_schema_supportedtreats a tier withuser_version == 0and no non-internal schema object as never provisioned and reads it as an absent tier; the caller fails on the missing table it asked for, which is a truthful not-provisioned answer. A populated tier stamped zero is still skew, so the durable contract that durable state is never rebuilt implicitly is unchanged. The existing index exemption is untouched._archive_one_tier_statuscatchesSchemaSkewErroralongsidesqlite3.Errorand records it instatus["error"]; the sharedprobe_archive_tierfacts still populate the payload.Fixtures:
migration_runner's version/DDL tables alongsidebootstrap's, matching the convention already used elsewhere in the file.test_index_write_profiles_refuse_stale_sibling_before_attachbuilt its stale sibling asexpected - 1, which is0for the version-1 ops tier — the never-provisioned sentinel. It now stamps above the expected version, unserviceable in either direction and unable to collide with the sentinel.Two new guard tests each name their anti-vacuity condition: restoring the version-only comparison makes the unprovisioned open raise; exempting every
found == 0tier regardless of schema makes the populated-at-zero open succeed.Verification
.venv/bin/python -m devtools test tests/unit/cli/commands/test_status.py tests/unit/operations/test_archive_debt.py tests/unit/storage/test_blob_conservation.py tests/unit/storage/test_durable_change_train.py tests/unit/storage/test_connection_profile.pyAll 19 tests named in the cluster pass, confirmed against
last-pytest.jsonnode ids..venv/bin/python -m devtools verify --quick— all 13 static gates ok, exit 0, noout of sync.Residuals
SchemaSkewErrorto detect a byte-empty durable file now sees a missing-table error instead. No such caller was found in this repo, but the change is behavioral at the read boundary.🤖 Generated with Claude Code
https://claude.ai/code/session_01DNGJ3awfNrsLaMdHgQZvid