Status: proposed — awaiting review. Test-only PR. No production fix in this branch.
Issue #48 (aliases.json/alias_history.json committed into the xorq catalog repo →
assert_consistency fails → every xorq catalog add after the first silently
no-ops) shipped through all of V0 unnoticed. The reason it hid: the only
integration test that exercises real xorq catalog add
(test_xorq_catalog_add_registers_after_genesis) adds exactly one entry, so
it never crosses the checkpoint that commits the two bookkeeping files. Every
other reset/catalog test uses synthetic entries/aaaa dirs and never runs xorq
at all.
Reproduced both bugs against the real demo flow (genesis → catalog_create → catalog_create → reset_to), driven through the MCP tools so the checkpoint
wrapper fires exactly as in production:
- #48 — with two real entries, only entry 1's
entries/<hash>.zipis git-tracked; entry 2's add silently no-ops;Catalog.from_kwargs(init=False)raisesAssertionError. - reset ↔ xorq-catalog divergence (the second bug) — a back→forward reset
round-trips the tallyman
entries/<hash>/dir through the bullpen, but the xorq.ziprecipe is reconciled by a different mechanism (git reset) and is never regained. The two views of "what entries exist" drift apart, andreset_tonever re-validates the xorq catalog. "Git-backed, recompute from history" is false for any entry after the first.
[after both] dirs=[023fb..,49b47..] tracked_zips=[49b47..zip] head=2
[reset->s1] dirs=[49b47..] tracked_zips=[49b47..zip] head=1
[forward->s2] dirs=[023fb..,49b47..] tracked_zips=[49b47..zip] head=2 # entry2 recipe never durable
- Test-only. This branch adds failing integration tests and nothing else. No
edits to
src/. The #48 fix is a separate future PR. - Every test marked
@pytest.mark.integrationso it runs in the CIintegrationjob (visible failing on CI before any fix lands — satisfies the TDD rule). - Driven through the real surfaces:
tallyman_mcp.server.catalog_create/catalog_revise(which apply the checkpoint wrapper, the actual #48 trigger), not synthetic dirs.
tests/test_catalog_xorq_integration.py — a cohesive suite. test_reset_to_revision.py
is already 637 lines and its one related integration test
(test_xorq_catalog_add_registers_after_genesis) stays where it is; the new
multi-entry/reset-coexistence suite is more discoverable in its own file. Shared
helpers (_git, _open_xorq_catalog, _tracked_entry_zips) live in the new file.
_git(project, *args)— shell togit -C <catalog_dir>(the sanctioned test exception to the no-bare-git guard, same as the existing reset suite)._tracked_entry_zips(project) -> set[str]—git ls-files entries/*.zip, stripped to bare hashes. The durable, content-addressed xorq artifact set._open_xorq_catalog(project)—Catalog.from_kwargs(path=catalog_dir, init=False); returns the catalog or re-raises so a test can assert it opens._make_entries(project, n)— drivecatalog_createn times with distinct aggregations overorders_parquet, returning the hashes/steps.
-
test_every_entry_registers_in_xorq_catalogCreate 3 real entries viacatalog_create. Assert every entry hash is in_tracked_entry_zips. Today: only the first → fails. -
test_catalog_openable_after_checkpointgenesis + onecatalog_create(which checkpoints). Assert_open_xorq_catalogdoes not raise. Today:AssertionError→ fails. Directly pins theassert_consistencyroot cause. -
test_bookkeeping_files_not_tracked_in_xorq_repoAssertgit ls-filescontains neitheraliases.jsonnoralias_history.json. Today: both tracked → fails. This is the precise #48 invariant; the fix (move them out ofcatalog_dir) makes it pass.
-
test_reset_keeps_xorq_catalog_consistentBuild 2 entries,reset_tostep-1, assert_open_xorq_catalogopens. Today: unopenable → fails. -
test_xorq_entry_set_matches_tallyman_pointersAt each step, assert_tracked_entry_zips(project)== set ofentry_hashesin catalog.yaml. Today: diverges (1 zip vs 2 pointers) → fails. -
test_back_then_forward_restores_xorq_recipe2 entries → reset back to step-1 → reset forward to step-2. Assert entry 2 is durably present in the xorq catalog (tracked.zip), not only as a bullpen dir copy. Today: zip never existed → fails. This is the divergence pinned end to end.
test_failed_registration_is_observableForce a failingxorq catalog add(run against the already-inconsistent catalog) and assert the failure is observable rather than reported as success. Proposed assertion: an ERROR-level log record is emitted (caplog) — matching the issue's "log it at error level" suggested fix. Today: only WARNING in the best-effort wrapper, andbuild_and_persistdiscards the return value → no ERROR → fails. Open question for review: keep this loose ("not silently swallowed" — assertbuild_and_persistexposes a registration-failed signal) or specific (ERROR log +errors.jsonlrow)? The specific form is more fix-shaped; the loose form pins the property without dictating the API. Default: loose, asserting aBuildResult.catalog_registered-style signal exists and isFalseon failure.
test_reset_rolls_back_alias_state— passes today becausealiases.jsonis git-tracked andgit reset --hardrolls it back. Once the #48 fix moves the bookkeeping files out of the repo, reset stops rolling back alias state unless the fix reconciles it explicitly. So this is a regression-guard that must ship with the fix, per the rule that a test passing today is never bundled into a failing-tests commit. Noted here so the fix PR doesn't forget it.
- Branch
test/catalog-xorq-integrationoffmain. - Run
uv run pytest -m integration tests/test_catalog_xorq_integration.pylocally first; confirm all Group A/B/D tests fail for the expected reasons (assertion text, not collection/import errors). - Commit the failing tests as a single commit (all bugs, one commit — they share the "seen failing on CI" axis).
- Push
-u, open PR, watch the CIintegrationjob go red. - PR body: links #48, states this is the failing-test half of a TDD split, lists which assertion pins which bug, and names the deferred alias-rollback guard.
- Each test runs real xorq builds (~3–15s each per the integration marker doc). Entry counts kept at 2–3; ~7 tests → roughly 1–2 min added to the integration job. Acceptable; integration job is already separate and not on the fast path.
- No
src/changes, so the fast suite and lint are untouched.