gabriel / musehub public
test_wire_fetch_mpack.py python
604 lines 23.9 KB
Raw
sha256:94ef169c149a452bff7c604ded8b280b19bd477c2dabcb56972780b0b784c7aa Merge 'fix/assignee-sigil-inline' into 'dev' — proposal: As… Human 1 day ago
1 """TDD — MPack-based fetch path: wire_fetch_mpack (issue #47).
2
3 The core principle: content-addressing is a proof, not a label.
4 sha256(mpack_bytes) == mpack_id IS the integrity check for everything inside.
5 No per-object hash loop. No secondary verification.
6
7 New function: wire_fetch_mpack(session, repo_id, want, have, ttl_seconds)
8
9 Returns FetchMPackResult:
10 mpack_id — "sha256:<hex>" — content-addressed ID of the mpack
11 mpack_url — presigned GET URL for the entire mpack (None when empty)
12 commit_count — number of commits in the delta
13 object_count — number of unique new objects
14
15 Tests:
16 FB0 Single commit + single object → mpack_id present, mpack_url set.
17 FB1 sha256(mpack_bytes) == mpack_id — the content-addressing proof.
18 FB2 MPack deserializes to expected commits and objects.
19 FB3 have cuts the delta — commits the client already has are excluded.
20 FB4 Empty want list → empty result (mpack_id=None, mpack_url=None).
21 FB5 All objects in the mpack satisfy sha256(content) == object_id (individual proofs).
22 """
23 from __future__ import annotations
24
25 import hashlib
26
27 import msgpack
28 import pytest
29 from sqlalchemy.dialects.postgresql import insert as pg_insert
30 from sqlalchemy.ext.asyncio import AsyncSession
31 from unittest.mock import AsyncMock
32
33 from datetime import datetime, timezone
34
35 from sqlalchemy import select
36 from muse.core.types import blob_id, fake_id
37 from muse.core.mpack import parse_wire_mpack
38 from musehub.db import musehub_repo_models as db
39 from tests.factories import create_repo
40
41 type ObjectStore = dict[str, bytes]
42
43
44 # ── helpers ───────────────────────────────────────────────────────────────────
45
46 def _now() -> datetime:
47 return datetime.now(tz=timezone.utc)
48
49
50 def _stub_backend(monkeypatch: pytest.MonkeyPatch) -> ObjectStore:
51 store: dict[str, bytes] = {}
52
53 async def _put(oid: str, data: bytes, **_: typing.Any) -> str:
54 store[oid] = data
55 return f"mem://{oid}"
56
57 async def _get(oid: str) -> bytes | None:
58 return store.get(oid)
59
60 async def _exists(oid: str, **_: typing.Any) -> bool:
61 return oid in store
62
63 async def _presign_get(oid: str, ttl: int) -> str:
64 return f"https://minio.test/{oid}?ttl={ttl}"
65
66 async def _get_mpack(mpack_id: str) -> bytes | None:
67 return store.get(mpack_id)
68
69 async def _put_mpack(mpack_id: str, data: bytes) -> str:
70 store[mpack_id] = data
71 return f"mem://mpacks/{mpack_id}"
72
73 async def _presign_mpack_get(mpack_id: str, ttl: int) -> str:
74 return f"https://minio.test/mpacks/{mpack_id}?ttl={ttl}"
75
76 backend = AsyncMock()
77 backend.put = _put
78 backend.get = _get
79 backend.exists = _exists
80 backend.presign_get = _presign_get
81 backend.get_mpack = _get_mpack
82 backend.put_mpack = _put_mpack
83 backend.presign_mpack_get = _presign_mpack_get
84 backend.supports_presign = True
85 monkeypatch.setattr("musehub.services.musehub_wire.get_backend", lambda: backend)
86 monkeypatch.setattr("musehub.services.musehub_wire_fetch.get_backend", lambda: backend)
87 monkeypatch.setattr("musehub.services.musehub_wire_shared.get_backend", lambda: backend)
88 return store
89
90
91 async def _store_object(
92 session: AsyncSession,
93 repo_id: str,
94 oid: str,
95 content: bytes,
96 store: dict[str, bytes],
97 ) -> None:
98 # Store content in mock backend (legacy fallback path)
99 store[oid] = content
100
101 # Build a per-object fake mpack so the MPackIndex path also works
102 fake_mpack_bytes = msgpack.packb(
103 {"blobs": [{"object_id": oid, "content": content}],
104 "commits": [], "snapshots": []},
105 use_bin_type=True,
106 )
107 fake_mpack_id = blob_id(fake_mpack_bytes)
108 store[fake_mpack_id] = fake_mpack_bytes
109
110 await session.execute(
111 pg_insert(db.MusehubObject)
112 .values(
113 object_id=oid,
114 path="file.dat",
115 size_bytes=len(content),
116 storage_uri=f"mem://{oid}",
117 )
118 .on_conflict_do_nothing(index_elements=["object_id"])
119 )
120 await session.execute(
121 pg_insert(db.MusehubObjectRef)
122 .values(repo_id=repo_id, object_id=oid)
123 .on_conflict_do_nothing()
124 )
125 await session.execute(
126 pg_insert(db.MusehubMPackIndex)
127 .values(entity_id=oid, mpack_id=fake_mpack_id, entity_type="object")
128 .on_conflict_do_nothing()
129 )
130 await session.commit()
131
132
133 async def _make_commit(
134 session: AsyncSession,
135 repo_id: str,
136 *,
137 manifest: dict[str, str],
138 seed: str = "c1",
139 parent_ids: list[str] | None = None,
140 ) -> tuple[db.MusehubCommit, db.MusehubSnapshot]:
141 snap_id = fake_id(f"snap-{seed}")
142 snap = db.MusehubSnapshot(
143 snapshot_id=snap_id,
144 directories=[],
145 manifest_blob=msgpack.packb(manifest, use_bin_type=True),
146 entry_count=len(manifest),
147 created_at=_now(),
148 )
149 session.add(snap)
150 await session.execute(
151 pg_insert(db.MusehubSnapshotRef)
152 .values(repo_id=repo_id, snapshot_id=snap_id)
153 .on_conflict_do_nothing()
154 )
155 commit_id = fake_id(f"commit-{seed}")
156 commit = db.MusehubCommit(
157 commit_id=commit_id,
158 branch="main",
159 parent_ids=parent_ids or [],
160 message=f"commit {seed}",
161 author="gabriel",
162 timestamp=_now(),
163 snapshot_id=snap_id,
164 )
165 session.add(commit)
166 await session.execute(
167 pg_insert(db.MusehubCommitRef)
168 .values(repo_id=repo_id, commit_id=commit_id)
169 .on_conflict_do_nothing()
170 )
171 await session.execute(
172 pg_insert(db.MusehubCommitGraph)
173 .values(
174 commit_id=commit_id,
175 parent_ids=parent_ids or [],
176 generation=0,
177 snapshot_id=snap_id,
178 )
179 .on_conflict_do_nothing()
180 )
181 # Update branch head
182 branch_q = await session.execute(
183 select(db.MusehubBranch).where(
184 db.MusehubBranch.repo_id == repo_id,
185 db.MusehubBranch.name == "main",
186 )
187 )
188 branch = branch_q.scalar_one_or_none()
189 if branch:
190 branch.head_commit_id = commit_id
191 await session.commit()
192 return commit, snap
193
194
195 # ══════════════════════════════════════════════════════════════════════════════
196 # FB0 — single commit + single object → mpack_id and mpack_url returned
197 # ══════════════════════════════════════════════════════════════════════════════
198
199 @pytest.mark.asyncio
200 async def test_fb0_single_commit_returns_mpack(
201 db_session: AsyncSession, monkeypatch: pytest.MonkeyPatch
202 ) -> None:
203 """Single commit with one object → wire_fetch_mpack returns mpack_id and mpack_url."""
204 from musehub.services.musehub_wire import wire_fetch_mpack
205
206 store = _stub_backend(monkeypatch)
207 repo = await create_repo(db_session, owner="gabriel", visibility="public")
208
209 raw = b"hello world content"
210 oid = blob_id(raw)
211 await _store_object(db_session, repo.repo_id, oid, raw, store)
212 commit, _ = await _make_commit(
213 db_session, repo.repo_id, manifest={"hello.txt": oid}, seed="fb0"
214 )
215
216 result = await wire_fetch_mpack(
217 db_session, repo.repo_id, want=[commit.commit_id], have=[]
218 )
219
220 assert result["mpack_id"].startswith("sha256:"), "mpack_id must be sha256-prefixed"
221 assert result["mpack_url"], "mpack_url must be non-empty"
222 assert result["commit_count"] == 1
223 assert result["blob_count"] == 1
224
225
226 # ══════════════════════════════════════════════════════════════════════════════
227 # FB1 — sha256(mpack_bytes) == mpack_id — the proof
228 # ══════════════════════════════════════════════════════════════════════════════
229
230 @pytest.mark.asyncio
231 async def test_fb1_mpack_id_equals_sha256_of_bytes(
232 db_session: AsyncSession, monkeypatch: pytest.MonkeyPatch
233 ) -> None:
234 """sha256(mpack_bytes) == mpack_id — content-addressing is the integrity check."""
235 from musehub.services.musehub_wire import wire_fetch_mpack
236
237 store = _stub_backend(monkeypatch)
238 repo = await create_repo(db_session, owner="gabriel", visibility="public")
239
240 raw = b"content for proof test"
241 oid = blob_id(raw)
242 await _store_object(db_session, repo.repo_id, oid, raw, store)
243 commit, _ = await _make_commit(
244 db_session, repo.repo_id, manifest={"proof.bin": oid}, seed="fb1"
245 )
246
247 result = await wire_fetch_mpack(
248 db_session, repo.repo_id, want=[commit.commit_id], have=[]
249 )
250
251 actual_bytes = store[result["mpack_id"]]
252 expected_id = blob_id(actual_bytes)
253
254 assert result["mpack_id"] == expected_id, (
255 f"mpack_id {result['mpack_id']!r} != blob_id(stored_bytes) {expected_id!r}"
256 )
257
258
259 # ══════════════════════════════════════════════════════════════════════════════
260 # FB2 — mpack deserializes with expected commits and objects
261 # ══════════════════════════════════════════════════════════════════════════════
262
263 @pytest.mark.asyncio
264 async def test_fb2_mpack_contains_commits_and_objects(
265 db_session: AsyncSession, monkeypatch: pytest.MonkeyPatch
266 ) -> None:
267 """MPack bytes deserialize to a dict with commits and objects matching what was pushed."""
268 from musehub.services.musehub_wire import wire_fetch_mpack
269
270 store = _stub_backend(monkeypatch)
271 repo = await create_repo(db_session, owner="gabriel", visibility="public")
272
273 raws = [f"object-{i}".encode() for i in range(3)]
274 oids = [blob_id(r) for r in raws]
275 for oid, raw in zip(oids, raws):
276 await _store_object(db_session, repo.repo_id, oid, raw, store)
277
278 manifest = {f"file{i}.bin": oid for i, oid in enumerate(oids)}
279 commit, _ = await _make_commit(
280 db_session, repo.repo_id, manifest=manifest, seed="fb2"
281 )
282
283 result = await wire_fetch_mpack(
284 db_session, repo.repo_id, want=[commit.commit_id], have=[]
285 )
286
287 mpack = parse_wire_mpack(store[result["mpack_id"]])
288
289 assert "commits" in mpack, "mpack must contain 'commits'"
290 assert "blobs" in mpack, "mpack must contain 'objects'"
291
292 mpack_commit_ids = {c["commit_id"] for c in mpack["commits"]}
293 assert commit.commit_id in mpack_commit_ids, "expected commit must be in mpack"
294
295 mpack_oids = {o["object_id"] for o in mpack["blobs"]}
296 assert set(oids) == mpack_oids, (
297 f"mpack objects {mpack_oids} != expected {set(oids)}"
298 )
299
300
301 # ══════════════════════════════════════════════════════════════════════════════
302 # FB3 — have cuts the delta
303 # ══════════════════════════════════════════════════════════════════════════════
304
305 @pytest.mark.asyncio
306 async def test_fb3_have_excludes_known_commits(
307 db_session: AsyncSession, monkeypatch: pytest.MonkeyPatch
308 ) -> None:
309 """Commits in have are excluded from the mpack delta."""
310 from musehub.services.musehub_wire import wire_fetch_mpack
311
312 store = _stub_backend(monkeypatch)
313 repo = await create_repo(db_session, owner="gabriel", visibility="public")
314
315 raw1 = b"first commit content"
316 oid1 = blob_id(raw1)
317 await _store_object(db_session, repo.repo_id, oid1, raw1, store)
318 c1, _ = await _make_commit(
319 db_session, repo.repo_id, manifest={"f1.bin": oid1}, seed="fb3-c1"
320 )
321
322 raw2 = b"second commit content"
323 oid2 = blob_id(raw2)
324 await _store_object(db_session, repo.repo_id, oid2, raw2, store)
325 c2, _ = await _make_commit(
326 db_session, repo.repo_id, manifest={"f1.bin": oid1, "f2.bin": oid2},
327 seed="fb3-c2", parent_ids=[c1.commit_id],
328 )
329
330 # Client already has c1 — only c2's delta should be in the mpack
331 result = await wire_fetch_mpack(
332 db_session, repo.repo_id, want=[c2.commit_id], have=[c1.commit_id]
333 )
334
335 mpack = parse_wire_mpack(store[result["mpack_id"]])
336 mpack_commit_ids = {c["commit_id"] for c in mpack["commits"]}
337
338 assert c2.commit_id in mpack_commit_ids, "new commit must be in mpack"
339 assert c1.commit_id not in mpack_commit_ids, "known commit must be excluded by have"
340 assert result["commit_count"] == 1
341
342 mpack_oids = {o["object_id"] for o in mpack["blobs"]}
343 assert oid1 not in mpack_oids, "object from have-side commit must be excluded"
344 assert oid2 in mpack_oids, "new object must be included"
345
346
347 # ══════════════════════════════════════════════════════════════════════════════
348 # FB4 — empty want → empty result
349 # ══════════════════════════════════════════════════════════════════════════════
350
351 @pytest.mark.asyncio
352 async def test_fb4_empty_want_returns_empty(
353 db_session: AsyncSession, monkeypatch: pytest.MonkeyPatch
354 ) -> None:
355 """Empty want list → zero commits, zero objects, no mpack produced."""
356 from musehub.services.musehub_wire import wire_fetch_mpack
357
358 _stub_backend(monkeypatch)
359 repo = await create_repo(db_session, owner="gabriel", visibility="public")
360
361 result = await wire_fetch_mpack(
362 db_session, repo.repo_id, want=[], have=[]
363 )
364
365 assert result["commit_count"] == 0
366 assert result["blob_count"] == 0
367 assert result["mpack_id"] is None
368
369
370 # ══════════════════════════════════════════════════════════════════════════════
371 # FB5 — per-object integrity (each object's content hashes to its ID)
372 # ══════════════════════════════════════════════════════════════════════════════
373
374 @pytest.mark.asyncio
375 async def test_fb5_object_content_hashes_to_id(
376 db_session: AsyncSession, monkeypatch: pytest.MonkeyPatch
377 ) -> None:
378 """Every object in the mpack has sha256(content) == object_id."""
379 from musehub.services.musehub_wire import wire_fetch_mpack
380
381 store = _stub_backend(monkeypatch)
382 repo = await create_repo(db_session, owner="gabriel", visibility="public")
383
384 raws = [f"verifiable-content-{i}".encode() for i in range(4)]
385 oids = [blob_id(r) for r in raws]
386 for oid, raw in zip(oids, raws):
387 await _store_object(db_session, repo.repo_id, oid, raw, store)
388
389 manifest = {f"f{i}.bin": oid for i, oid in enumerate(oids)}
390 commit, _ = await _make_commit(
391 db_session, repo.repo_id, manifest=manifest, seed="fb5"
392 )
393
394 result = await wire_fetch_mpack(
395 db_session, repo.repo_id, want=[commit.commit_id], have=[]
396 )
397
398 mpack = parse_wire_mpack(store[result["mpack_id"]])
399 for obj in mpack["blobs"]:
400 actual_id = blob_id(obj["content"])
401 assert actual_id == obj["object_id"], (
402 f"object integrity failure: sha256(content)={actual_id} != id={obj['object_id']}"
403 )
404
405
406 # ══════════════════════════════════════════════════════════════════════════════
407 # FB6 — server-side merge commit snapshot is included even when CommitGraph
408 # has snapshot_id=None (defensive fallback via MusehubCommit row)
409 # ══════════════════════════════════════════════════════════════════════════════
410
411 @pytest.mark.asyncio
412 async def test_fb6_merge_commit_snapshot_included_via_commit_fallback(
413 db_session: AsyncSession, monkeypatch: pytest.MonkeyPatch
414 ) -> None:
415 """wire_fetch_mpack returns the merge snapshot even when MusehubCommitGraph.snapshot_id is NULL.
416
417 Regression test for the original wire-fetch bug: server-side merge commits
418 were not included in fetch mpack responses because _walk_commit_delta only
419 read snapshot_id from CommitGraph rows (which were NULL before the proposals
420 fix), ignoring the snapshot_id already present on the MusehubCommit row.
421
422 The fix: wire_fetch_mpack falls back to MusehubCommit.snapshot_id for any
423 commit whose CommitGraph row has snapshot_id=None.
424 """
425 from musehub.services.musehub_wire import wire_fetch_mpack
426
427 store = _stub_backend(monkeypatch)
428 repo = await create_repo(db_session, owner="gabriel", visibility="public")
429
430 # Two parent commits (client already has these)
431 raw1 = b"parent-1-content"
432 oid1 = blob_id(raw1)
433 await _store_object(db_session, repo.repo_id, oid1, raw1, store)
434 c1, _ = await _make_commit(
435 db_session, repo.repo_id, manifest={"f1.bin": oid1}, seed="fb6-c1"
436 )
437
438 raw2 = b"parent-2-content"
439 oid2 = blob_id(raw2)
440 await _store_object(db_session, repo.repo_id, oid2, raw2, store)
441 c2, _ = await _make_commit(
442 db_session, repo.repo_id, manifest={"f2.bin": oid2}, seed="fb6-c2",
443 parent_ids=[c1.commit_id],
444 )
445
446 # Merge commit: has a real snapshot on MusehubCommit, but CommitGraph row
447 # has snapshot_id=None — this is the pre-fix bug state.
448 merge_manifest = {"f1.bin": oid1, "f2.bin": oid2}
449 merge_snap_id = fake_id("fb6-merge-snap")
450 merge_snap = db.MusehubSnapshot(
451 snapshot_id=merge_snap_id,
452 directories=[],
453 manifest_blob=msgpack.packb(merge_manifest, use_bin_type=True),
454 entry_count=len(merge_manifest),
455 created_at=_now(),
456 )
457 db_session.add(merge_snap)
458 await db_session.execute(
459 pg_insert(db.MusehubSnapshotRef)
460 .values(repo_id=repo.repo_id, snapshot_id=merge_snap_id)
461 .on_conflict_do_nothing()
462 )
463
464 merge_commit_id = fake_id("fb6-merge-commit")
465 parent_ids = [c1.commit_id, c2.commit_id]
466 merge_commit = db.MusehubCommit(
467 commit_id=merge_commit_id,
468 branch="main",
469 parent_ids=parent_ids,
470 message="Merge branch 'feat' into 'main'",
471 author="gabriel",
472 timestamp=_now(),
473 snapshot_id=merge_snap_id, # MusehubCommit has it
474 )
475 db_session.add(merge_commit)
476 await db_session.execute(
477 pg_insert(db.MusehubCommitRef)
478 .values(repo_id=repo.repo_id, commit_id=merge_commit_id)
479 .on_conflict_do_nothing()
480 )
481 # CommitGraph row with snapshot_id=None — the pre-fix bug state
482 await db_session.execute(
483 pg_insert(db.MusehubCommitGraph)
484 .values(
485 commit_id=merge_commit_id,
486 parent_ids=parent_ids,
487 generation=1,
488 snapshot_id=None, # ← bug: missing snapshot_id
489 )
490 .on_conflict_do_nothing()
491 )
492 await db_session.commit()
493
494 # Client has both parents; needs the merge commit
495 result = await wire_fetch_mpack(
496 db_session, repo.repo_id,
497 want=[merge_commit_id],
498 have=[c1.commit_id, c2.commit_id],
499 )
500
501 assert result["mpack_id"] is not None, "mpack must be produced for the merge commit"
502 assert result["commit_count"] == 1
503
504 mpack = parse_wire_mpack(store[result["mpack_id"]])
505 snap_ids_in_mpack = {s["snapshot_id"] for s in mpack.get("snapshots", [])}
506 assert merge_snap_id in snap_ids_in_mpack, (
507 f"merge snapshot {merge_snap_id!r} must be in mpack snapshots; got {snap_ids_in_mpack}"
508 )
509
510
511 # ══════════════════════════════════════════════════════════════════════════════
512 # FB7 — server-side merge commit snapshot included when CommitGraph has it
513 # (the post-fix happy path; also ensures no regression)
514 # ══════════════════════════════════════════════════════════════════════════════
515
516 @pytest.mark.asyncio
517 async def test_fb7_merge_commit_snapshot_included_via_commit_graph(
518 db_session: AsyncSession, monkeypatch: pytest.MonkeyPatch
519 ) -> None:
520 """wire_fetch_mpack returns the merge snapshot when CommitGraph.snapshot_id is set.
521
522 This is the happy path after merge_proposal was fixed to always insert
523 CommitGraph rows with snapshot_id populated.
524 """
525 from musehub.services.musehub_wire import wire_fetch_mpack
526
527 store = _stub_backend(monkeypatch)
528 repo = await create_repo(db_session, owner="gabriel", visibility="public")
529
530 raw1 = b"fb7-parent-1"
531 oid1 = blob_id(raw1)
532 await _store_object(db_session, repo.repo_id, oid1, raw1, store)
533 c1, _ = await _make_commit(
534 db_session, repo.repo_id, manifest={"f1.bin": oid1}, seed="fb7-c1"
535 )
536
537 raw2 = b"fb7-parent-2"
538 oid2 = blob_id(raw2)
539 await _store_object(db_session, repo.repo_id, oid2, raw2, store)
540 c2, _ = await _make_commit(
541 db_session, repo.repo_id, manifest={"f2.bin": oid2}, seed="fb7-c2",
542 parent_ids=[c1.commit_id],
543 )
544
545 merge_manifest = {"f1.bin": oid1, "f2.bin": oid2}
546 merge_snap_id = fake_id("fb7-merge-snap")
547 merge_snap = db.MusehubSnapshot(
548 snapshot_id=merge_snap_id,
549 directories=[],
550 manifest_blob=msgpack.packb(merge_manifest, use_bin_type=True),
551 entry_count=len(merge_manifest),
552 created_at=_now(),
553 )
554 db_session.add(merge_snap)
555 await db_session.execute(
556 pg_insert(db.MusehubSnapshotRef)
557 .values(repo_id=repo.repo_id, snapshot_id=merge_snap_id)
558 .on_conflict_do_nothing()
559 )
560
561 merge_commit_id = fake_id("fb7-merge-commit")
562 parent_ids = [c1.commit_id, c2.commit_id]
563 merge_commit = db.MusehubCommit(
564 commit_id=merge_commit_id,
565 branch="main",
566 parent_ids=parent_ids,
567 message="Merge branch 'feat' into 'main'",
568 author="gabriel",
569 timestamp=_now(),
570 snapshot_id=merge_snap_id,
571 )
572 db_session.add(merge_commit)
573 await db_session.execute(
574 pg_insert(db.MusehubCommitRef)
575 .values(repo_id=repo.repo_id, commit_id=merge_commit_id)
576 .on_conflict_do_nothing()
577 )
578 # CommitGraph row WITH snapshot_id — the post-fix state
579 await db_session.execute(
580 pg_insert(db.MusehubCommitGraph)
581 .values(
582 commit_id=merge_commit_id,
583 parent_ids=parent_ids,
584 generation=1,
585 snapshot_id=merge_snap_id, # ← correct
586 )
587 .on_conflict_do_nothing()
588 )
589 await db_session.commit()
590
591 result = await wire_fetch_mpack(
592 db_session, repo.repo_id,
593 want=[merge_commit_id],
594 have=[c1.commit_id, c2.commit_id],
595 )
596
597 assert result["mpack_id"] is not None
598 assert result["commit_count"] == 1
599
600 mpack = parse_wire_mpack(store[result["mpack_id"]])
601 snap_ids_in_mpack = {s["snapshot_id"] for s in mpack.get("snapshots", [])}
602 assert merge_snap_id in snap_ids_in_mpack, (
603 f"merge snapshot {merge_snap_id!r} must be in mpack snapshots; got {snap_ids_in_mpack}"
604 )
File History 3 commits
sha256:94ef169c149a452bff7c604ded8b280b19bd477c2dabcb56972780b0b784c7aa Merge 'fix/assignee-sigil-inline' into 'dev' — proposal: As… Human 1 day ago
sha256:6b1949fc2797ca4c1936a637a4cbfec828ef56cf52398a2e74ca3c4f494e728f fix: use wire_bytes not mpack_bytes_raw in compute_object_b… Sonnet 4.6 patch 9 days ago
sha256:4aed3d8601c8dd3ed37074de35f11f4a9699a0a4b99d43727048fd3f8e6fd13d chore: doc sweep, ignore wrangler build state, misc fixes Sonnet 4.6 minor 12 days ago