Files
beardthelionandKevin Codex 8680d0f9d6 feat(node): replication enforcement (Phase 2) for #18 (#34)
* docs(node): Phase 3 subtree-withholding plan with Task 0 spike findings

* feat(node): resolve withheld blob OIDs for path-scoped visibility

* feat(node): filtered upload-pack serve that omits withheld blobs

* feat(node): serve filtered pack when caller has withheld subtree blobs

* test(node): end-to-end assert served pack omits withheld blob

* docs(node): note why info/refs is not gated on subtree visibility

* fix(node): frame filtered serve as protocol v0 and prove it with a real clone

upload_pack_excluding emitted a v2 packfile section, but info_refs
advertises v0, so real clients negotiated v0 and rejected the response
with 'expected ACK/NAK, got packfile'. Frame the v0 stateless-rpc shape
instead (NAK, then the pack via side-band-64k when offered).

Add an end-to-end test that stands up info_refs + upload_pack_excluding
and runs a real git partial clone, asserting the withheld blob's bytes
never reach the client while its tree entry and SHA stay visible. A stock
full clone cannot consume the pack (it is not closed under reachability,
so fetch fails the connectivity check); a partial clone is required.

* docs(node): correct Phase 3 caveat (full clone refused at fetch, not checkout)

* test(node): prove incremental fetch still withholds; document negotiation choice

Add a real-git test that partial-clones, pushes a new commit server-side,
then fetches: the new object arrives and the withheld blob stays absent.
This pins down that ignoring have/want negotiation (always sending a
self-contained pack of all refs minus withheld, with NAK) is correct for
both clone and fetch; the only cost is a fetch re-sends the full object
set. Refactor the real-git tests onto a shared server harness and document
the negotiation decision in code and in the plan's follow-ups.

* refactor(node): address CodeRabbit review on PR #28

Move the two blocking git shell-outs in the filtered upload-pack path off
the async worker thread, matching the tokio::process / spawn_blocking usage
already in this file: build_filtered_pack (rev-list + pack-objects) and
withheld_blob_oids (per-ref ls-tree) now run inside spawn_blocking so a large
repo cannot stall the tokio runtime. Behavior is unchanged.

Also fix the Task 0 findings block in the Phase 3 plan: it still recorded v2
packfile framing, which is the exact path that failed against a real client
and was corrected to v0. The block now documents the shipped v0 contract.
Drop a stray trailing code fence flagged by markdownlint (MD040).

The speculative ls-tree timeout and the public/no-rules fast-path from the
review are intentionally left out: the timeout guards against adversarial
repos we do not yet host, and the fast-path is a micro-optimization not worth
the extra branch right now.

* chore(node): drop planning doc from PR per maintainer request

kevincodex1 asked to keep the superpowers planning docs out of the repo. The
Phase 3 plan was scaffolding for this change, not something the project needs
to carry. Removing it leaves only the code and tests in the PR.

* chore: gitignore local planning docs (docs/superpowers/)

* feat(node): replicable_objects filter for replication enforcement

* test(node): pin anonymous-caller contract of withheld_blob_oids

* feat(node): IPFS pinning skips withheld blob OIDs

* feat(node): Pinata pinning skips withheld blob OIDs

* feat(node): enforce visibility on push replication (IPFS/Pinata/gossip/Arweave)

* test(node): announce gate matches anonymous repo readability

* fix(node): run withheld_blob_oids off the async worker on push

The receive-pack replication chokepoint called withheld_blob_oids
directly on the tokio worker, where its blocking git ls-tree walk can
stall the runtime for repos with many refs. Wrap it in spawn_blocking
to match the upload-pack serve path.

---------

Co-authored-by: Kevin Codex <[email protected]>
2026-06-18 22:51:36 +08:00
..
2026-06-18 21:36:11 +08:00