Skip to content

fix: read an unprovisioned archive tier without reporting schema skew - #4687

Merged
Sinity merged 2 commits into
masterfrom
lane/reds-schema-readers
Sep 5, 2026
Merged

fix: read an unprovisioned archive tier without reporting schema skew#4687
Sinity merged 2 commits into
masterfrom
lane/reds-schema-readers

Conversation

@Sinity

@Sinity Sinity commented Sep 5, 2026

Copy link
Copy Markdown
Owner

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_supported raised SchemaSkewError: source schema skew: expected 41, found 0 on archives whose tier file exists but was never provisioned. The guard exempted found == 0 for the index tier only, so a durable tier in the same state was reported as corruption.

  • Blob conservation (6 tests) — the fixture creates genuinely empty source.db/index.db; polylogue/daemon/backup.py:539 then raised instead of reading an absent tier.
  • CLI status (1 test)_archive_one_tier_status probes with validate_schema=False and correctly derives version_status="mismatch", then opens a second, validating connection for table_counts, which raised through the status surface. A tier this runtime cannot read still has reportable existence/size/version facts.
  • Archive debt (11 tests) — the fixture hand-builds a populated source.db/index.db but never stamps the version it emulates.
  • Durable change train (2 tests) — the tests patch bootstrap's version/DDL tables but not migration_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_supported treats a tier with user_version == 0 and 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_status catches SchemaSkewError alongside sqlite3.Error and records it in status["error"]; the shared probe_archive_tier facts still populate the payload.

Fixtures:

  • Archive-debt tiers declare the version their hand-built schema emulates.
  • The synthetic source-train tests patch migration_runner's version/DDL tables alongside bootstrap's, matching the convention already used elsewhere in the file.
  • test_index_write_profiles_refuse_stale_sibling_before_attach built its stale sibling as expected - 1, which is 0 for 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 == 0 tier 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.py

================== 218 passed, 3 warnings in 67.21s (0:01:07) ==================

All 19 tests named in the cluster pass, confirmed against last-pytest.json node ids.

.venv/bin/python -m devtools verify --quick — all 13 static gates ok, exit 0, no out of sync.

Residuals

  • Selected verification only: these five files plus the quick gate. No corpus run; other clusters from the same corpus log are untouched.
  • The guard change widens tolerance for every tier, so any caller that previously relied on SchemaSkewError to 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

Sinity and others added 2 commits September 5, 2026 14:40
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
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 5, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-05T13:39:58.149062Z 162f11d PR opened
ℹ️ 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" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@coderabbitai

coderabbitai Bot commented Sep 5, 2026

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Team

Run ID: 727d6f18-6c67-4f85-ac4d-b16af8c4777c


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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@Sinity
Sinity enabled auto-merge (squash) September 5, 2026 13:35

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge 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 👍 / 👎.

Comment on lines +602 to +606
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.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

@Sinity
Sinity merged commit 839c9fe into master Sep 5, 2026
4 checks passed
@Sinity
Sinity deleted the lane/reds-schema-readers branch September 5, 2026 15:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant