gabriel / muse public
test_mpack_schema.py python
161 lines 6.1 KB
Raw
sha256:36c3cb3e76619d4c30a6d9bf81b5ec4ff148e30dcfed913e3114ca7b43b81c7e fix: rename objects→blobs in push client and all stale test… Sonnet 4.6 patch 22 days ago
1 """TDD — MPack schema must contain only pack fields.
2
3 A pack carries commits + snapshots + objects + summary + meta + tags.
4 Nothing else. The fields that were historically embedded in a "bundle"
5 but belong in the HTTP layer must not appear in MPack:
6
7 branch_heads → POST /push request body (which branch, what tip)
8 pusher_id → Authorization header (MSign identity)
9 declared_objects_count → POST /push request body (advisory integrity hint)
10
11 These fields cannot live in the pack because the pack is content-addressed:
12 pack_id = sha256(pack_bytes)
13 Any push-specific metadata embedded in the bytes would make two identical
14 content pushes produce different pack IDs — defeating content-addressing.
15
16 Tests in this file are schema-level (TypedDict annotation checks) plus
17 a round-trip assertion that build_mpack never emits the forbidden fields.
18 """
19 from __future__ import annotations
20
21 import json
22 import pathlib
23
24 import pytest
25
26 from muse.core.mpack import MPack, build_mpack
27 from muse.core.object_store import write_object
28 from muse.core.paths import muse_dir
29 from muse.core.ids import hash_commit as compute_commit_id, hash_snapshot as compute_snapshot_id
30 from muse.core.refs import write_branch_ref
31 from muse.core.commits import (
32 CommitRecord,
33 write_commit,
34 )
35 from muse.core.snapshots import (
36 SnapshotRecord,
37 write_snapshot,
38 )
39 from muse.core.types import blob_id
40
41
42 # ---------------------------------------------------------------------------
43 # Minimal repo fixture
44 # ---------------------------------------------------------------------------
45
46 @pytest.fixture()
47 def repo(tmp_path: pathlib.Path) -> tuple[pathlib.Path, str]:
48 """One-commit repo. Returns (root, commit_id)."""
49 dot = muse_dir(tmp_path)
50 dot.mkdir()
51 (dot / "repo.json").write_text(json.dumps({"repo_id": "schema-test"}))
52 for d in ("commits", "snapshots", "objects"):
53 (dot / d).mkdir()
54 (dot / "refs" / "heads").mkdir(parents=True)
55 (dot / "HEAD").write_text("ref: refs/heads/main\n")
56 (dot / "config.toml").write_text("")
57
58 content = b"hello schema test"
59 oid = blob_id(content)
60 write_object(tmp_path, oid, content)
61
62 manifest = {"src/hello.py": oid}
63 sid = compute_snapshot_id(manifest)
64 write_snapshot(tmp_path, SnapshotRecord(snapshot_id=sid, manifest=manifest))
65
66 import datetime
67 ts = datetime.datetime(2026, 1, 1, tzinfo=datetime.timezone.utc)
68 cid = compute_commit_id(
69 parent_ids=[],
70 snapshot_id=sid,
71 message="init",
72 committed_at_iso=ts.isoformat(),
73 author="gabriel",
74 )
75 write_commit(tmp_path, CommitRecord(
76 commit_id=cid, branch="main",
77 snapshot_id=sid, message="init", committed_at=ts,
78 parent_commit_id=None, parent2_commit_id=None,
79 author="gabriel", metadata={}, structured_delta=None,
80 sem_ver_bump="none", breaking_changes=[],
81 agent_id="", model_id="", toolchain_id="",
82 prompt_hash="", signature="", signer_key_id="",
83 ))
84 write_branch_ref(tmp_path, "main", cid)
85 return tmp_path, cid
86
87
88 # ---------------------------------------------------------------------------
89 # Schema-level: forbidden fields must not be declared on MPack
90 # ---------------------------------------------------------------------------
91
92 _FORBIDDEN = {"branch_heads", "pusher_id", "declared_objects_count"}
93
94
95 @pytest.mark.parametrize("field", sorted(_FORBIDDEN))
96 def test_mpack_bundle_type_does_not_declare_push_metadata(field: str) -> None:
97 """MPack.__annotations__ must not contain push-layer metadata fields.
98
99 These fields belong in the HTTP request body or Authorization header,
100 not in the content-addressed pack bytes.
101 """
102 assert field not in MPack.__annotations__, (
103 f"MPack declares '{field}' but that field belongs in the HTTP "
104 f"layer, not the pack. Remove it from the TypedDict."
105 )
106
107
108 # ---------------------------------------------------------------------------
109 # Naming: BundleMeta must not exist — it is MPackMeta
110 # ---------------------------------------------------------------------------
111
112 def test_bundle_meta_name_does_not_exist() -> None:
113 """BundleMeta is the old bundle-era name — it must not exist in muse.core.mpack."""
114 import importlib, muse.core.mpack as _mod
115 assert not hasattr(_mod, "BundleMeta"), (
116 "BundleMeta still exists in muse.core.mpack — rename it to MPackMeta."
117 )
118
119 def test_mpack_meta_name_exists() -> None:
120 """MPackMeta is the canonical name for the pack metadata TypedDict."""
121 from muse.core.mpack import MPackMeta # noqa: F401
122
123
124 # ---------------------------------------------------------------------------
125 # Naming: MPack must not exist — the type is MPack
126 # ---------------------------------------------------------------------------
127
128 def test_mpack_type_exists() -> None:
129 """MPack is the canonical TypedDict for the wire artifact."""
130 from muse.core.mpack import MPack # noqa: F401
131
132
133 # ---------------------------------------------------------------------------
134 # Runtime: build_mpack must never emit the forbidden fields
135 # ---------------------------------------------------------------------------
136
137 def test_build_mpack_does_not_emit_push_metadata(
138 repo: tuple[pathlib.Path, str],
139 ) -> None:
140 """build_mpack() must return a dict free of push-layer metadata fields."""
141 root, head = repo
142 pack = build_mpack(root, [head], have=[])
143 for field in _FORBIDDEN:
144 assert field not in pack, (
145 f"build_mpack() emitted '{field}' — that field belongs in the "
146 f"HTTP layer, not in the pack bytes."
147 )
148
149
150 # ---------------------------------------------------------------------------
151 # Positive: expected fields are present
152 # ---------------------------------------------------------------------------
153
154 def test_build_mpack_contains_required_fields(
155 repo: tuple[pathlib.Path, str],
156 ) -> None:
157 """build_mpack() must emit exactly the core pack fields."""
158 root, head = repo
159 pack = build_mpack(root, [head], have=[])
160 for field in ("commits", "snapshots", "blobs", "summary", "meta"):
161 assert field in pack, f"build_mpack() missing required field '{field}'"
File History 1 commit
sha256:36c3cb3e76619d4c30a6d9bf81b5ec4ff148e30dcfed913e3114ca7b43b81c7e fix: rename objects→blobs in push client and all stale test… Sonnet 4.6 patch 22 days ago