Phase 2: fula-ingest - verified byte ingress (client-side CID, quota-gated)#75
Phase 2: fula-ingest - verified byte ingress (client-side CID, quota-gated)#75ehsan6sha wants to merge 19 commits into
Conversation
PUT /v0/block?cid=<declared>: verifies the blake3 raw-leaf CID over the body BEFORE storing (single flipped bit => 422, nothing written), then kubo block/put?cid-codec=raw&mhtype=blake3 and cross-checks kubo's key. S1 quota gate mirrors the gateway (GET /api/v1/storage with the client Bearer; INGEST_QUOTA_MODE=open matches gateway fail-open, strict denies when unverifiable); per-token 30s cache so a thousand-chunk upload makes one quota call per window. Size cap + bounded concurrency + /health + graceful shutdown. 10 Go tests (containerized run: all pass). Part of #74 (cross-repo: fula-api#31) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…, live mapping PUT I1-I10 on the test master: image build+run (strict quota vs the live webui storage API via a seeded legacy api key); valid block stored; tampered body 422 + never stored; suspended user 402 + never stored; missing token 401; bytes still ingested with the gateway container STOPPED; gateway rebuilt from the phase-2 branch flips /fula/capabilities false->true; live empty-body remote-cid mapping PUT returns ETag=cid and GET round-trips the exact ingested bytes; absent cid rejected (client-fallback contract). Part of #74 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ff, FxFiles flow, 1 GiB Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…lied) The previous commit re-encoded both files with a BOM + mojibake comments (PowerShell 5.1 round-trip). Restored from the clean parent and reapplied the scope=storage:* JWT claim via a targeted edit. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…t pins via it) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…parity) The registry pin forwards the user JWT to the pinning API, which validates sessions IT issued (/auth/google) - a minted drill token must be seeded into sessions (hashed per migration 009) for the pin to authenticate, exactly as prod tokens are. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ce matches) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… drills Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The 1 GiB upload alone runs ~2h on the contended 4-core box; the 2h fixed-expiry test token died at chunk 4019/4096 (real clients refresh). Measured insight recorded: chunked-upload throughput is metadata-commit bound (per-chunk bucket lock + tree flush) - pre-existing gateway property, unchanged by Phase 2. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Fidelity suite evidence (test master, real daemons)
Scale measurement worth recording: chunked-upload throughput on this path is metadata-commit-bound (each chunk's mapping/bytes PUT serializes through the per-bucket write lock + an index-tree flush; observed ~3× object amplification per chunk). This is a pre-existing property of the gateway's chunked path — Phase 2 neither worsens nor improves it (byte transport moves off the master; commit costs are identical). The plan's Phase-7 client-computed recursive root / batched commits is the designed fix. 🤖 Generated with Claude Code |
… failed test Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Large-file scale leg — honest dispositionThe ≥large-file e2e (
The test is committed and env-ready: Scale invariant otherwise PROVEN by fast green tests
Product findings flagged for prod sizing (pre-existing, not introduced here)
🤖 Generated with Claude Code |
…Postgres) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…integration Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Phase 2 — fula-ingest: verified byte ingress + e2e suites
The decentralized-upload byte path: any approved operator can run a
fula-ingestnode that accepts encrypted chunk BYTES so uploads stop depending on a single gateway, with tamper-evidence enforced server-side.What's in
docker/fula-ingest/(Go):PUT /v0/block?cid=<declared>verifies the blake3 raw-leaf CID over the body before anything touches the blockstore (one flipped bit ⇒ 422, nothing stored), then kuboblock/put?cid-codec=raw&mhtype=blake3with a cross-check of kubo's key. S1 quota gate mirrors the gateway exactly (GET {STORAGE_API_URL}/api/v1/storagewith the client's Bearer;INGEST_QUOTA_MODE=open= gateway-parity fail-open,strictdenies when unverifiable) with a 30s per-token cache (one quota call per window, not per chunk). Size cap, bounded concurrency,/health, graceful shutdown, Dockerfile.tests/e2e/phase-2/):40-ingest-drills.sh(I1–I10) +60-fidelity.sh(F1–F4).Evidence (clean Ubuntu 24.04 test master, real daemons)
/fula/capabilitiesfalse→true; live empty-body remote-cid mapping PUT ⇒ 200 withETag=<declared cid>and a byte-exact GET round-trip; absent cid ⇒ bounded 409 (client-fallback contract).Cross-repo: functionland/fula-api#31 (client routing + gateway mapping PUT; functionland/fula-api#30 lands first — the gateway image recipe).
In simple words
When your phone uploads a file today, the encrypted pieces must pass through one company server. With this, the pieces can go to any approved relay computer instead — and every piece carries a fingerprint, so a relay that corrupts even one byte gets caught and rejected instantly, and nobody can upload without an account in good standing.
Closes #74
🤖 Generated with Claude Code