oficina / docs Public
K3 offline durability evidence
Continuation of native WIP 68a51d7, against reviewed spec 6049830 (§3.3–3.8, §4.3–4.4 and K3). Work/review records: rastrillo/native #9 and oficina/docs #69. Nothing has been merged or deployed.
Implementation and review
- Account-owned sealed outbox, metadata and replicas; Home activates replay.
- CommandsApplied is captured with snapshot/sequence. CommandsDurable advances only after enqueue; the writer requires equality again immediately at commit.
- Reopening captures the entire lane before reading its replica, applies update entries in order, and publishes only after replay. Held updates remain visible.
- Editor and headless senders merge with the latest durable replica using a compare/retry. A shared generation invalidates writers on discard. Sent updates are cached before ack, so Home replay preserves subsequent offline reopening.
- Metadata keys are registered before metadata writes. Attached identity is persisted before its repairable sqid alias; sealed paths never move. Creation tokens survive failed attachment writes and retries remain idempotent.
- Metadata-only outbox markers give titles/drafts the existing drainer's retries. Activation reconciles the metadata/enqueue crash boundary. A newer rename gets a successor marker behind an in-flight marker. Failed local title writes and the draft's initial paragraph are retained for unlock/retry.
- Account-level localized lost-content notice remains until dismissal. Opening a document loads the outbox before touching the index, preserving key-loss notice.
- Byte-cap pause is recomputed after queue changes and reopening; retry cannot bypass it. Kit status preserves known state while storage is unavailable and applies the spec's session-ended priority even for an empty outbox.
- Highest-capability branch review: gpt-6-astra. Its metadata, cache, loss-notice, cap and draft-seed findings were addressed with regressions. K4/K5/K6 features remain in their specified slices; no merge approval is implied by code review.
Failing-first evidence
All mutation controls below were restored before commits. Logs are local /tmp transcripts; this table records their observed assertions rather than merely claiming that a command failed.
| Probe | Failing control / observed failure | Passing evidence | |---|---|---| | Original watermark, lane replay, generated history | DocsPersistence/snapshot stubs: 3 tests, 238 assertions (k3-docs-red.log) | k3-docs-green.log | | Replace crash boundaries and model reopen | Omit lane replay: 2 tests, 5 missing-text assertions (k3-docs-integration-red.log) | k3-docs-integration-green.log | | Unlock resumes queued commands | Existing defect: 3 assertions (k3-unlock-red.log) | Same integration green suite | | Title before replica, title retry, concurrent rename, attachment alias, Home replay | Prior implementation: 5 tests, 15 assertions (k3-recovery-red.log) | 15 durability/account tests (k3-recovery-green.log) | | Concurrent cache writes and discard ordering | Omit merge/invalidation; delete lane first: 2 tests, 6 assertions (k3-cache-discard-red.log) | 13 durability tests (k3-cache-discard-green.log) | | Locked title, loss notice, cap, live fake stream | Prior behavior / nil loss-notice stub: 4 tests fail (k3-review-red.log; the fifth timing probe initially missed its race and was corrected below) | 24 durability/account tests (k3-review-green.log) | | Rename after title POST, before batch ack | Assert both entries reserved; old marker suppresses successor: First != Successor (k3-trigger-valid-red.log) | Same 24-test green suite | | Draft attachment across store operations | Stub attachment sender; alias, token, content, count and empty-lane assertions fail (k3-attachment-trigger-red.log) | Same green suite, exhaustive sweep through actual operation count | | Direct sealed-key loss before index read | Prior code opens index first: no projection/no loss notice (k3-attachment-trigger-red.log) | Same green suite | | Draft paragraph survives metadata failure | Omit retained seed: server text empty instead of Recovered draft (k3-seed-cap-red.log) | Both tests pass (k3-seed-cap-green.log) | | Reopen cannot bypass lane cap | Prior unconditional input reset permits editing (k3-seed-cap-red.log) | Same green log | | Parked update snapshot | Replace only snapshot's encodestateasupdatev1 with encodediffv1: healed text missing (k3-parked-red.log) | Existing snapshot passes native/crdt test (k3-parked-green.log) |
The generated histories cover 12 deterministic seeds × 60 events. They place writer ticks between command and enqueue, inject crashes before arbitrary event store operations, reserve overlapping compaction candidates, integrate then lose responses, reopen and replay, and compare I1a/I1b and final server convergence. Separate sweeps cover every replacement/delete boundary, every discard boundary, and every actual store operation in draft attachment. The fake uses production WASM replicas, serves since-cursors and publishes subsequent stream sequences.
Real SIGKILL and draft integration
native/scripts/test-offline-kill.py authenticates against actual Go handlers, opens the native model, verifies initial save, types 12 durable edits, and sends SIGKILL before the two-second replica writer. Recovery starts offline and checks exact text before reconnecting and comparing the real server's snapshot.
The original fixture configured localhost:8080 despite using a random loopback port. A NativeHTTP-style Origin POST reproduced 403 (k3-origin-red.log). It now uses the listener's actual origin; CSRF is not bypassed. Failed initial-save or pre-kill typing assertions stop the harness.
Negative control: omit only lane replay. Initial save and server convergence still pass, but offline reopen is empty instead of the 12 edits: exactly one assertion (k3-kill-valid-red.log). Restore replay: exact recovery/convergence, no duplicates (k3-kill-valid-green.log). Final-code rerun passed again (/tmp/k3-kill-final.log).
Existing test-document-drafts.py also passed (k3-draft-integration-green.log). Its detail GET used to return Untitled permanently, even after an accepted rename; it now snapshots the current title at request start, preserving realistic delayed stale replies. The unchanged title assertion failed before this fixture correction.
Spec differences and slice boundaries
- Discard removes the replica before the lane. The user accepted this ordering with crash proof; the exhaustive sweep shows no stranded unsent replica text.
- The index also registers immutable local keys, and the app uses metadata-kind outbox markers. These minimal additions make metadata-only replay discoverable after a crash; they introduce no kit update/retarget API.
- Kit load/entries/beginSend/endSend express unavailable storage, held-entry replay and atomic send reservations. Their rationale is recorded on native #9.
- Equality is used for the durability watermark as explicitly requested by the user (the spec allows <=). Existing snapshot is used; no new CRDT operation.
- K4 offline library/draft presentation, K5 reauthentication/wipe, and K6 terminal resolution, first-400 handling, relaxed close and iOS background task remain outside K3. Existing close/signout protection remains in place.
Inherited WIP problems and gates
The inherited kit was mostly no-op stubs. Tests included an unchecked empty-array index and an undeclared AppleTests → TestSupport dependency hidden by incremental builds. Further implementation checks caught rotation's /var versus /private path handling, retrying-lane starvation, and early idle from concurrent drain calls. Their red/green evidence is in the kit's docs/testing/k3-evidence.md.
Final required gates are serial: native make ci; Docs .amadan/ci.d/10-gate; Docs native/scripts/check-native.sh. Use rustup cargo first, TMPDIR=/tmp, xcodegen before xcodebuild. Final head/results are recorded on discussions #9/#69, with logs /tmp/k3-kit-release-ci.log, /tmp/k3-docs-final-gate.log, and /tmp/k3-docs-final-native.log. Native hub CI lacks a macOS runner and fails on Sparkle extraction (missing unzip); the user directed reliance on local make ci.
Disk is measured on /System/Volumes/Data, with a mandatory stop below 1.5 GiB. Only outputs created by this session are removed. The kit's inherited .build is moved aside for full gates, preserved, then restored; Docs targets, framework, .build and DerivedData created here are removed between stages.
Fable review follow-up (2026-09-24)
- Missing, malformed-JSON, and tampered sealed metadata now hold only that lane with
rejected("metadata"); genuine unavailable I/O still throws. The new test failed all three hold assertions against the inherited implementation, then passed while another document drained. - Held metadata markers are deduplicated using
entries(lane:excludingInFlight:). The new repeated-flush test first found five markers instead of one; it now passes, as does the existing rename-after-title-POST/in-flight-successor regression. - A durable offline lane uses SyncStatus without setting the generic errorKey. The new test first observed
native.pendingrather than nil, and now observes nil plus.offline(pending: 1). - All 24 durability tests passed. Logs:
/tmp/k3-docs-review-{red,green}.log(ephemeral); the red run had five expected assertions across three tests. The existing key-loss test was adjusted to create a fresh sealed store on simulated process restart: reusing the old instance now intentionally retains its cached key. - Native pins (all five) advance to
0ff1de9d542ce96430e158708e62be04d87d65a4; native final-head localmake cipassed (119 Swift tests and remaining platform/Go/Python gates). Its hub runner still fails before tests on missing unzip / absent macOS infrastructure. - Deferred as directed: review finding 4 (WASM decode per writer tick) belongs to K4/K7; finding 6 (sessionRestored on existing-account/window access) belongs to K5. No new K3 spec departure.
Random SIGKILL mode: native/scripts/test-offline-kill.py --random --seed 17 leaves typing unpaused and runs concurrent replica writer ticks. The controller kills after a seeded random delay, without a writer-side sleep/handshake. Recovery must contain the last atomically recorded durable text as a prefix, contain exactly the ordered k3 tokens (no gaps/duplicates/torn edits), then equal the real server. Failing-first control: stub the outbox write in DocsPersistence.enqueue while still advancing commandsDurable. Seed 17 killed at 0.260992 s; recovery was empty and failed the durable-prefix and ordered-token assertions (two failures). Restore the write: the same seed passed offline recovery and exact server convergence. Logs: /tmp/k3-docs-review-kill-{red,green}.log. The stub is not committed.
Final-head gates and repeated kill runs are reported on #69; local logs use /tmp/k3-docs-review-final-{gate,native,kill*}.log. Build stages run serially, with rustup cargo first and TMPDIR=/tmp; xcodegen runs before xcodebuild.
At 8f25e2f the full local Docs gates passed (82 XCTest cases, four fixture skips, 16 Swift Testing cases, parked-update and wire checks); deterministic SIGKILL and random seeds 1/17/2 all passed. Final harness review moved the existing writer-log assertion check after process termination so random typing cannot emit an assertion between the check and SIGKILL without failing the script. Final-head reruns and cleanup are recorded on #69.
Landing prep (2026-09-30): the native half merged, so all five native pins move from the K3 branch head 0ff1de9d542ce96430e158708e62be04d87d65a4 to rastrillo/native main 529f356f476e43174882361511032e0bdefa5317, after merging fresh Docs origin/main. Gate results for this head are recorded on #69. Hub CI at de13d64 failed in the gate's wire-compatibility stage ("JS reads what Rust wrote": verify-rust: empty update on stdin). The runner log shows cargo stopping partway through replaying cached yrs warnings (40 of 67), then error, before emit_update ran. K3 does not touch core/, native/vendor/, scripts/ or fixtures/. A fresh clone of de13d64 passed scripts/wire-gate.sh locally, and the JS-reads-Rust pipe passed five times in a row.