Repository navigation
feat(git,store): H1 cut 2: persistent GitOid map, native import, export from the store (#48) - #96
aien-spark wants to merge 12 commits into
Conversation
… and export (#48) Part of #48 (ADR 0003 step 28, H1). The Git side of the bridge, natively in synapse-git, with the system git used only as a test oracle: - object: Git object kinds, IDs, tree entries, commit and tag links; every other commit and tag byte kept opaque. - inflate: RFC 1950/1951 decoder (stored, fixed, dynamic blocks), bounded, reports consumed input, typed refusals. - pack: pack v2 and index v2 codec per gitformat-pack: deterministic writer (whole objects in object ID order), reader through Git's index or native indexing, OFS and REF delta resolution, CRC-32 and SHA-1 trailers checked. - repo: read loose objects, packs, refs, packed-refs and HEAD; walk the full history; export an object set as a new bare repository with one pack. Fixture and two real repositories (synapse 1,784 objects, omega 29,635) export with identical object IDs and pass git fsck --full --strict. Red before green: six stubs in evidence/h1-git-bridge/red-green/red.txt. Remaining for #48 (cut 2): persistent GitOid map, native import (blobs, TreeDigest, revisions with stored original headers) and export from it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…, review of #90) Review findings B1 and B2 on PR #90: B1: every repository file (loose refs, packed-refs, HEAD, loose objects, .pack, .idx) is now inspected with symlink_metadata and opened only if it is a regular file (same device and inode after open), read through take(cap + 1). Pipes, sockets, devices and symbolic links are refused with PathInvalid; the refs/ walk uses DirEntry::file_type and never follows links. Caps: ref file and HEAD 4 KiB, packed-refs 64 MiB, all pack files 2 GiB (QuotaPayloadTooLarge). B2: a total object budget (ReadLimits::max_total_object_bytes, default 1 GiB) is checked at open from the sizes every pack entry declares (entry headers plus the first 20 inflated bytes of each delta stream, nothing resolved) and for each loose object from its header before it is inflated. Per-object cap (default 1 GiB) on entries, delta streams, delta results and loose objects before allocation; apply_delta no longer reserves the declared result size up front. Also: *.lock files under refs/ skipped, detached HEAD commit collected (GitHistory::detached_head). Red: tests/h1_hostile_repo.rs run unchanged on b4fe463 fails 8 of 8 (pipes block to the 10 s deadline, /dev/zero link OOM under ulimit, outside link and 1 MiB ref accepted, 82 KB delta bomb resolves 5 x 256 MiB, detached HEAD commit dropped); stubs A (budget removed) and B (links followed) fail by assertion. Green: 357 passed, 0 failed, 2 ignored (workspace). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…view of #90) Closes the residual check-then-open race: a link swapped in after the symlink_metadata check fails with ELOOP, a pipe swapped in opens at once and fails the regular-file, device and inode check. libc is already a workspace dependency (synapse-bench, synapse-coord); Cargo.lock gains only the synapse-git edge. Red: stub C (custom_flags(0)) blocks on the pipe until the unit test's 10 s deadline. Green: 358 passed, 0 failed, 2 ignored (workspace). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…rt from the store (#48) WAL record kind 10 (GitBridgeImported; open PR #88 claims 9) holds the persistent map: Git blobs to store blobs, Git trees to canonical C1 nodes (TreeDigest), commits to imported revisions keeping every header byte, tags, references and HEAD. import_repository converts a whole history before the first write and refuses what has no exact native form (GitObjectNotRepresentable) or unsafe reference names (PathInvalid) with the store unchanged. export_store rebuilds every imported object from the store and checks it hashes to its imported Git ID; native B1 revisions export deterministically (fixed author/committer/time, Synapse trailers), including native children on their parent's result tree. The map survives replay, checkpoint load and compaction. Also folds in the #90 re-review: fan-out directories under objects/ must be real directories; the standalone pack indexer honours ReadLimits' total budget; peak RSS against the budget is stated in docs/H1_GIT_BRIDGE.md. Red before green: 7 cut 2 stubs and 2 re-review stubs fail by assertion (evidence/h1-git-bridge/red-green/). Real repositories (synapse, omega) round trip through the store with identical IDs and fsck --strict clean. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Independent review (session omega repo issues, reviewer did not author)Head reviewed: Verdict: CHANGES REQUESTED. Two blocking findings. I confirmed both with throwaway probe tests (not pushed). Everything else I checked holds up. BlockingB1. A hostile repository can get a partial import that never finishes. This breaks the claim "a refused repository leaves the store unchanged" ( B2. Non-blocking
Verified OK
Tests rerun
Not checked
🤖 Generated with Claude Code |
…it IDs re-hashed (#48) Review of #96 at b69db2d, blockers B1 and B2 plus N3 and the export tag check. B1: SynapseStore::record_git_imports checks a whole import before the first write. Each record runs against a copy of the map with the batch's earlier records applied: codec, blob caps, max_total_changeset_bytes, digests, the map checks and the exact WAL frame size against max_wal_record_bytes. Then every record is written, checked again against the live map. record_git_import is a batch of one. The importer builds every record first and makes one call; Objects batching also bounds the frame size. B2: GitBridgeLedger::check refuses a Git ID twice in one entry and a second Git ID for one commit's content, and re-hashes every newly mapped Git ID with the workspace SHA-1: blob bytes, tree rebuilt from its C1 node by the new synapse_core::git_tree_payload_of_node (export uses it too), commit and tag from their link lines plus stored bytes. GitObjectCorrupt on mismatch. N3: a tree file entry must name a blob mapped by a Git blob of the same repository with the same size; subtrees, commit trees and targets resolve in the same repository. Export refuses a Tag(oid) target that is not an imported tag. N1 and N2 are documented as limits. Red: review96-red.txt (baseline on b69db2d: both new tests fail; 8 stubs on the fixed code each fail by assertion). Green: review96-green.txt, 374 passed, 0 failed, 3 ignored; synapse and omega round trips identical, fsck strict clean. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Review of b69db2d addressed in ceae255 (fixes in d0e9a83, crumb recompile in ceae255). Both blockers fixed red before green, plus N3 and the export tag check. B1 partial import. New B2 map poisoning. Non-blocking. N3: a tree file entry must name a blob mapped by a Git blob of the same repository, with the same size ( Red (
Green (
Limits. An I/O failure during the write phase can still stop a batch part way (a re-import finishes it). The dry run copies the whole bridge map while it runs. CodeQL default setup does not run on this PR (base is not the default branch). "AIEN Verification" fails because synapse is not pipeline-enrolled (main fails it too). |
Independent review (session omega repo issues, reviewer did not author): re-reviewHead reviewed: Verdict: APPROVE-READY (no blocking findings). Both blockers are fixed. I confirmed each fix in the code, with the test suite, with red checks, and by rerunning my original two probes. B1 (partial import): fixed
B2 (map poisoning): fixed
New holes in the batch path: looked for, none found
Non-blocking
Tests rerun
Not checked
🤖 Generated with Claude Code |
crates/synapse-store/src/j1_corpus.rs (test only) writes and seals evidence/j1-codec-corpus/corpus.bin: 48 bases (28 accepted records and checkpoint contents with their logical form, every record kind on main 1..8; 20 hand-made refusals no mutation reaches) and 55,670 cases (the A1 malformed mutation set) with the Rust outcome of each. The seal is a test; the second test checks every record kind, refusal kind and field id is covered. Kinds 9..11 (open PRs #88, #96, #95) are refused today and slot in as documented. docs/J1_OMEGA_CODEC.md: scope, logical form, file format, reproduction, speed against the section 6.2 bound, limits. The Omega codec that passes the corpus is aien-dev/omega branch feat/synapse-j1-codec. Red: evidence/j1-codec-corpus/red-green/red.txt (no sealed corpus). Green: green.txt; workspace 329 passed, 0 failed; clippy and fmt clean. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The rebase train ( Files that need a person (or an agent) to resolve:
Rebase the branch onto |
|
Dependency audit: prerequisite #90 is already merged as All-PR merge desk: aien-dev/aien-architecture-mv#190. Existing implementation ownership is retained; this is not a merge approval. |
|
parked: GPU-stack focus; label returns when resumed |
# Conflicts: # .crumb # crates/synapse-core/src/lib.rs # crates/synapse-git/src/lib.rs # crates/synapse-git/src/pack.rs # crates/synapse-git/src/repo.rs # crates/synapse-git/tests/h1_git_bridge.rs # crates/synapse-git/tests/h1_hostile_repo.rs # crates/synapse-store/src/a1_tests.rs # crates/synapse-store/src/codec.rs # crates/synapse-store/src/lib.rs # crates/synapse-store/src/revision.rs # crates/synapse-store/tests/a1_malformed_corpus.rs # docs/H1_GIT_BRIDGE.md # evidence/h1-git-bridge/SHA256SUMS
…, reseal the J1 corpus, follow cut 1 ref refusal codes
Stacked on #90, retarget to main after #90 merges. Base is
feat/48-git-bridge, so this diff shows only cut 2.Closes #48 (with #90): all three acceptance conditions are met once both merge.
What cut 2 adds
GitBridgeImported(binary codec only). Open PR feat(b4): revision state machine Private..Canonical (ADR 0003 B4) (#28) #88 claims kind 9, so this takes 10; nothing here depends on 9.synapse-store/src/git_bridge.rs): Git blob to (BlobDigest, size), Git tree toTreeDigest(canonical C1 node stored), Git commit to (ChangesetId,TreeDigest) and back, tags, references,HEAD. Imported revision ID = SHA-256 over tree, parents and the original header bytes; the Git ID is rechecked at every export. Rebuilt on replay and checkpoint load; compaction keeps the blobs it names. B1 revisions may name an imported commit as a parent.record_git_importis the only writer and checks every entry first.synapse-git/src/bridge.rs): the whole history is converted in memory before the first write. Anything with no exact native form (gitlinks, legacy100664, padded040000, non-UTF-8 or C1-refused names such as.git, empty subtrees, non-canonical trees) isGitObjectNotRepresentable(new refusal code). Unsafe reference names arePathInvalid. Both leave the store unchanged. Importing the same history again writes nothing.0 +0000,Synapse-Changeset-Id/Synapse-Change-Idtrailers). A native child exports on its parent's result tree.objects/must be real directories (refs/already were); (b) the standalone pack indexer honours theReadLimitstotal budget (and resolves late reference-delta bases with a waiter list); (c) peak RSS against the budget is stated in the doc.Red before green (
evidence/h1-git-bridge/red-green/)cut2-red.txt: 7 stubs, each removing one behaviour, each fails its test by assertion. S1 canonical-form check skipped, S2 headers dropped on import, S3 map not rebuilt on checkpoint load, S4 native commit time from the clock, S5 reference names left to the store (objects written first), S6 compaction drops bridge blobs, S7 native result trees not remembered.rereview-red.txt: N1 fan-out check removed, N3 indexer budget removed, both fail.cut2-green.txt: passing runs.Commands and counts
cargo test --locked --workspace -j 4: 371 passed, 0 failed, 3 ignored (the ignored are real-repo tests that need a mirror path).synapse-gittests:h1_bridge_import8 + 1 ignored,h1_git_bridge6 + 1 ignored,h1_hostile_repo9,h1_read_limits4, plus 2 new codec/ID unit tests insynapse-store.cargo clippy --locked --workspace --all-targets -- -D warnings: clean.cargo fmt --all --check: clean.crumb verifyOK in a clean detached worktree with the tool pinned bycrumb.lock(aien-architectured0f0b95).Real repositories through the store (mirror clones; import into a fresh store, reopen, export from the store, compare every reference and
rev-list --objects --allagainst the export,git fsck --full --strict). Release test binary,/usr/bin/time -v:3d78efe)f4a9849)Omega peak RSS is 0.9316 times the 1 GiB default read budget (cut 1: 1.063 times). The budget bounds resolved object bytes, not the whole process. The times are measured, not profiled, and no section 6 performance claim is made.
Limits (details in
docs/H1_GIT_BRIDGE.mdsection 4)HEADcommit is imported, but the export'sHEADnamesrefs/heads/main.base_treeto be empty, imported, or the result of a native parent exported in the same call. A base registered only in the API tree store isTreeNodeNotFound. Native refs are passed in by the caller and not stored. Native commits carry no signature.O_NOFOLLOWonly guards the last path component, so a directory swapped for a link between the check and the open is not caught. Fixing it needsopenatwalks.CI note: the "AIEN Verification" check fails because synapse is not enrolled in the Spark pipeline (main fails it too). CodeQL is checked on the head after each push.
🤖 Generated with Claude Code