ZDDC/zddc/internal
ZDDC 8a049ca2a4 feat(client): outbox — offline write queue + replay with If-Unmodified-Since
PUT / POST / DELETE in client mode now work end-to-end. Online: the
cache layer forwards to upstream and (on success) drops any cached
entry for the path so the next read fetches fresh. PUT/DELETE include
If-Unmodified-Since derived from the cached file's mtime so the master
can reject conflicting writes with 412 Precondition Failed.

When upstream is unreachable, the request is captured in the outbox
at <root>/.zddc-outbox/<id>/ — directory per queued write, mode 0700,
containing meta.json (method, RawURI, Content-Type, base mtime,
queued-at) and body.bin (request body, capped at 256 MiB). The client
gets 202 Accepted + X-ZDDC-Cache: queued and a JSON envelope.

A background replay loop started by runClient processes the queue:
- 2xx → delete entry; drop cached path so next read fetches fresh
- 412 → rename to <id>.conflict-<RFC3339>/ for manual reconciliation
       (body + meta intact for inspection or re-submit)
- 4xx other → drop (retry won't help; logged at WARN)
- 5xx / transport error → leave for next pass

Replay schedule: eager at startup, then 30s while pending falling
back to 5min while idle. Loop honors graceful-shutdown context.
Disabled in --mode=proxy (proxy persists nothing by design — offline
writes return 503 instead of queueing).

Outbox IDs are <unix-nano-base16>-<hex-random> so lex-sort = queue
order; concurrent enqueues never collide. Conflict-rename appends a
4-char random suffix on the unlikely same-second collision.

The local cache is intentionally not updated for offline writes:
until upstream confirms the user reads still see the upstream-cached
version (or 503 if uncached). Trade-off: no "did my queued write
actually win?" ambiguity, at the cost of not seeing one's own
offline edits immediately. Phase 5 will surface .conflict-<ts>/
directories in browse views.

Tests (20 new in outbox_test.go, 5 new in cache_test.go covering
the write path): NewOutbox creates 0700 dir, Enqueue persists meta
+ body, Pending returns lex-sorted entries excluding conflicts,
Replay deletes on 2xx / renames on 412 / leaves on transport error
/ leaves on 5xx / drops on 4xx-other, IUS sent only for PUT/DELETE
with base mtime, query string preserved, ServeHTTP online write
forwards + evicts cache, ServeHTTP offline write queues with 202,
ServeHTTP offline + no outbox returns 503, ServeHTTP PUT sends IUS
from cached mtime, oversize body rejected, IDs lex-sortable,
RunReplayLoop stops on context cancel, concurrent Enqueue 30×
no collisions. Full suite + go vet clean.

Doc updates: zddc/README.md gains a "Writes (online + offline
outbox)" subsection covering both paths and replay outcomes;
"What client mode is NOT, yet" now lists only conflict UI and
multi-tenancy. AGENTS.md client-mode pipeline gains writes +
mirror-mode bullets. ARCHITECTURE.md adds a "Writes: outbox +
offline replay" subsection with the trade-off rationale and the
phase-5-deferred conflict UI hand-off.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 08:20:07 -05:00
..
apps chore(embedded): cut v0.0.17-beta 2026-05-07 12:13:04 -05:00
archive refactor(archive): use shared zddc.ParseTransmittalFolder 2026-05-07 09:14:19 -05:00
auth feat(server): self-issued bearer tokens + --no-auth flag 2026-05-08 07:40:28 -05:00
cache feat(client): outbox — offline write queue + replay with If-Unmodified-Since 2026-05-08 08:20:07 -05:00
config feat(client): mirror mode — access-triggered subtree walker + listing cache 2026-05-08 08:11:30 -05:00
fs feat(fs): synthesise per-user virtual home in working/ listings 2026-05-07 09:20:25 -05:00
handler feat(server): self-issued bearer tokens + --no-auth flag 2026-05-08 07:40:28 -05:00
jsonschema feat: form-data system v0 (sixth tool + zddc-server endpoints) 2026-05-02 20:12:16 -05:00
listing feat(fs): synthesise per-user virtual home in working/ listings 2026-05-07 09:20:25 -05:00
policy feat(server): self-issued bearer tokens + --no-auth flag 2026-05-08 07:40:28 -05:00
tlsutil feat(server): TLS hardening per NIST SP 800-52 Rev. 2 + HSTS 2026-05-04 17:55:52 -05:00
zddc docs(zddc): tighten inherit/strict-mode docstrings + AllowedAtLevel deprecation 2026-05-07 11:10:31 -05:00