gabriel / muse public
test_mpack_schema.py python
159 lines 6.0 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.store import (
31 CommitRecord,
32 SnapshotRecord,
33 write_branch_ref,
34 write_commit,
35 write_snapshot,
36 )
37 from muse.core.types import blob_id
38
39
40 # ---------------------------------------------------------------------------
41 # Minimal repo fixture
42 # ---------------------------------------------------------------------------
43
44 @pytest.fixture()
45 def repo(tmp_path: pathlib.Path) -> tuple[pathlib.Path, str]:
46 """One-commit repo. Returns (root, commit_id)."""
47 dot = muse_dir(tmp_path)
48 dot.mkdir()
49 (dot / "repo.json").write_text(json.dumps({"repo_id": "schema-test"}))
50 for d in ("commits", "snapshots", "objects"):
51 (dot / d).mkdir()
52 (dot / "refs" / "heads").mkdir(parents=True)
53 (dot / "HEAD").write_text("ref: refs/heads/main\n")
54 (dot / "config.toml").write_text("")
55
56 content = b"hello schema test"
57 oid = blob_id(content)
58 write_object(tmp_path, oid, content)
59
60 manifest = {"src/hello.py": oid}
61 sid = compute_snapshot_id(manifest)
62 write_snapshot(tmp_path, SnapshotRecord(snapshot_id=sid, manifest=manifest))
63
64 import datetime
65 ts = datetime.datetime(2026, 1, 1, tzinfo=datetime.timezone.utc)
66 cid = compute_commit_id(
67 parent_ids=[],
68 snapshot_id=sid,
69 message="init",
70 committed_at_iso=ts.isoformat(),
71 author="gabriel",
72 )
73 write_commit(tmp_path, CommitRecord(
74 commit_id=cid, branch="main",
75 snapshot_id=sid, message="init", committed_at=ts,
76 parent_commit_id=None, parent2_commit_id=None,
77 author="gabriel", metadata={}, structured_delta=None,
78 sem_ver_bump="none", breaking_changes=[],
79 agent_id="", model_id="", toolchain_id="",
80 prompt_hash="", signature="", signer_key_id="",
81 ))
82 write_branch_ref(tmp_path, "main", cid)
83 return tmp_path, cid
84
85
86 # ---------------------------------------------------------------------------
87 # Schema-level: forbidden fields must not be declared on MPack
88 # ---------------------------------------------------------------------------
89
90 _FORBIDDEN = {"branch_heads", "pusher_id", "declared_objects_count"}
91
92
93 @pytest.mark.parametrize("field", sorted(_FORBIDDEN))
94 def test_mpack_bundle_type_does_not_declare_push_metadata(field: str) -> None:
95 """MPack.__annotations__ must not contain push-layer metadata fields.
96
97 These fields belong in the HTTP request body or Authorization header,
98 not in the content-addressed pack bytes.
99 """
100 assert field not in MPack.__annotations__, (
101 f"MPack declares '{field}' but that field belongs in the HTTP "
102 f"layer, not the pack. Remove it from the TypedDict."
103 )
104
105
106 # ---------------------------------------------------------------------------
107 # Naming: BundleMeta must not exist — it is MPackMeta
108 # ---------------------------------------------------------------------------
109
110 def test_bundle_meta_name_does_not_exist() -> None:
111 """BundleMeta is the old bundle-era name — it must not exist in muse.core.mpack."""
112 import importlib, muse.core.mpack as _mod
113 assert not hasattr(_mod, "BundleMeta"), (
114 "BundleMeta still exists in muse.core.mpack — rename it to MPackMeta."
115 )
116
117 def test_mpack_meta_name_exists() -> None:
118 """MPackMeta is the canonical name for the pack metadata TypedDict."""
119 from muse.core.mpack import MPackMeta # noqa: F401
120
121
122 # ---------------------------------------------------------------------------
123 # Naming: MPack must not exist — the type is MPack
124 # ---------------------------------------------------------------------------
125
126 def test_mpack_type_exists() -> None:
127 """MPack is the canonical TypedDict for the wire artifact."""
128 from muse.core.mpack import MPack # noqa: F401
129
130
131 # ---------------------------------------------------------------------------
132 # Runtime: build_mpack must never emit the forbidden fields
133 # ---------------------------------------------------------------------------
134
135 def test_build_mpack_does_not_emit_push_metadata(
136 repo: tuple[pathlib.Path, str],
137 ) -> None:
138 """build_mpack() must return a dict free of push-layer metadata fields."""
139 root, head = repo
140 pack = build_mpack(root, [head], have=[])
141 for field in _FORBIDDEN:
142 assert field not in pack, (
143 f"build_mpack() emitted '{field}' — that field belongs in the "
144 f"HTTP layer, not in the pack bytes."
145 )
146
147
148 # ---------------------------------------------------------------------------
149 # Positive: expected fields are present
150 # ---------------------------------------------------------------------------
151
152 def test_build_mpack_contains_required_fields(
153 repo: tuple[pathlib.Path, str],
154 ) -> None:
155 """build_mpack() must emit exactly the core pack fields."""
156 root, head = repo
157 pack = build_mpack(root, [head], have=[])
158 for field in ("commits", "snapshots", "objects", "summary", "meta"):
159 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