gabriel / musehub public
feat BREAKING feat/230-wire-identity-push-validator #1 / 1
AI Agent gabriel · 2 days ago · Oct 22, 2026 · Diff

feat: wire IdentityPushValidator into the real push pipeline (musehub#230)

Audit: #227's own audit table (written for an unrelated naming-collision fix) claimed push_validator.py was "real, wired into wire_push, and actually rejects bad pushes" -- in contrast to quorum.py's dead code (#225). That contrast was wrong: grep for "IdentityPushValidator(" found only the class's own definition and its unit test -- zero production call sites. None of I1 (acyclicity), I2 (root distance), or I3 (authorization) were enforced server-side on any real push.

Design decision (confirmed via AskUserQuestion): wire it in now rather than confirm-and-document-as-deferred, given I3 is a genuine security/ authorization boundary, not just a data-integrity nicety.

Implementation: in wire_push_unpack_mpack (the real wire-push pipeline), after the snapshot-replay loop resolves the head snapshot's full manifest and before any row is committed, detect identity-domain repos (MusehubRepo.domain_id == "identity"), reconstruct the complete identities/relationships record set (reading content from this push's own decompressed blobs, falling back to existing stored objects for unchanged records from earlier commits), run IdentityPushValidator, and raise ValueError (-> HTTP 422 at the route layer, same pattern as the existing zip-bomb/blocked-hash/snapshot-integrity rejections in this function) when the result is invalid.

TDD: wrote an end-to-end test (test_identity_push_validator_wire_integration.py) pushing a real identity-domain commit containing a 2-node cycle (alice spawns bob, bob spawns alice -- an I1 violation) through the actual wire_push_unpack_mpack function, not just unit-testing the validator class directly. Confirmed genuine RED (push succeeded, cycle persisted) before the fix, GREEN after, plus a control test proving a valid acyclic push still succeeds. Re-confirmed via muse-dev shelf save/pop isolation on musehub_wire_push.py alone.

Corrected push_validator.py's own module docstring, which made the same "called by wire_push" claim while it was still false -- now true and cross-referenced to this ticket.

Regression: 329 passed across identity/graph-domain tests plus every existing wire-push test file (root-snapshot integrity, external-parent reconstruction, authz, mwp1-4/8 job-pipeline suites, multi-tip manifest union) -- no regressions in the push pipeline this change touches.

Note: musehub's org-management REST API (musehub_orgs.py, hardened separately in musehub#231) commits identity records via commit_files_to_repo, a server-authored path with its own _assert_org_write_access authorization check -- distinct from the wire push protocol this ticket wires validation into. I1/I2/I3 enforcement via IdentityPushValidator now covers the wire-push (muse CLI `push`) surface, which is this ticket's and push_validator.py's documented scope.

Co-Authored-By: Claude Sonnet 5 <[email protected]>

sha256:066f0f58448cc0553bff3c845cbb8e1f7a6bdecf53fce6bc5c2ff5e0ddad3d71 sha
+30 ~1 symbols
3 changed · 1101 in snapshot files
sha256:2e3740ded83a2a22bebd29d9c42140b4265daa582665a02b23780081f2f91723 snapshot
+30
symbols added
~1
symbol modified
3
files changed
1101
files in snapshot
0
dead code introduced
Semantic Changes 31 symbols
+ _IDENTITY_ID variable variable _IDENTITY_ID L43–43
+ _OWNER variable variable _OWNER L42–42
+ _cid function function _cid L46–47
+ _identity_record function function _identity_record L87–95
+ _mock_backend function function _mock_backend L76–84
+ _now function function _now L50–51
+ _push_identity_commit function async_function _push_identity_commit L108–148
+ _raw_commit function function _raw_commit L54–73
+ _spawns function function _spawns L98–105
+ AsyncMock import import AsyncMock L22–22
+ AsyncSession import import AsyncSession L25–25
+ IdentityRecord import import IdentityRecord L30–30
+ MagicMock import import MagicMock L22–22
+ MusehubCommit import import MusehubCommit L38–38
+ RelationshipRecord import import RelationshipRecord L30–30
+ annotations import import annotations L19–19
+ blob_id import import blob_id L29–29
+ build_wire_mpack import import build_wire_mpack L28–28
+ compute_identity_id import import compute_identity_id L37–37
+ create_repo import import create_repo L39–39
+ datetime import import datetime L21–21
+ hash_snapshot import import hash_snapshot L27–27
+ identity_path import import identity_path L30–30
+ patch import import patch L22–22
+ pytest import import pytest L24–24
+ record_to_bytes import import record_to_bytes L30–30
+ relationship_path import import relationship_path L30–30
+ wire_push_unpack_mpack import import wire_push_unpack_mpack L40–40
+ test_identity_cycle_is_rejected_by_the_real_wire_push_endpoint function async_function test_identity_cycle_is_rejected_by_the_real_wire_push_endpoint L152–190
+ test_valid_identity_push_is_accepted_by_the_real_wire_push_endpoint function async_function test_valid_identity_push_is_accepted_by_the_real_wire_push_endpoint L194–227
Files Changed
+1 ~2
1101 in snapshot
← Older Oldest on feat/230-wire-identity-push-validator
All commits
Newer → Latest on feat/230-wire-identity-push-validator

0 comments

No comments yet. Be the first to start the discussion.

To add a comment, use the Muse CLI: muse hub commit comment sha256:066f0f58448cc0553bff3c845cbb8e1f7a6bdecf53fce6bc5c2ff5e0ddad3d71 --body "your comment"