J102: give each restarted recovery its own safety wait #309
Closed
opened 2026-11-02 11:28:13 +00:00 by kayg
·
39 comments
No Branch/Tag specified
main
j470b-server-mesh-rejoin
mesh-compat-382
j444b-d381-key-wins-flood
j461b-macos-utun-reader-thread
j475b-remove-host-motion
fix-reconnect-new-link
j481-d380
dev
j479-mesh-soak
fix/j476-host-facts
j475-motion-pill-split
j470-mac-node-join
root-upgrade-d376
j474-review-merge
latency-targets-120hz
nightly-suite-shards
gotatun-pin-j469
macos-utun-reader-thread
fix-flood-echo-stall
wip/j416
motion-fast-ease-out
mesh-addhost-j259
takeover-close-terminals-d368
kbd-instant-followups
fix-trust-key-double-click
nightly-suite-partial-report
appwin-icons-j207
rusqlite-slice1
swipe-content-first-d374
mesh-mtu-381
addhost-instant-open
glass-contrast-d365-mock
glass-contrast-d365
signout-followups-442
fix-startup-toast-red
research-gotatun-matrix-j317
themes-sync-j304
fix-load-flakes
fix-nix-zig-patch
fix-compute01-browser-baseline
ratchet-size-baseline-419
predict-auto-d130
revoke-coverage-d367
nightly-suite-queue-timeout
nightly-browser-suite
fix-rustsec-2026-0459
fonts-ioskeley-obel
log-retention-d375
fix-kbd-no-anim-427
fix-dev-red-specs-2
takeover-proof-305-j205
wip/j410-server-pump
ui-consistency-d372
fix-osc52-clipboard
libghostty-vt-1.0
forget-retired-host-d369
j404-merge-426
fix-setup-lock-race
fix-touch-select-426
revoke-access-report-ui
fix-predictive-echo-specs
fix-selinux-agent-exec
wip/j395-checkpoint2
wip/j395
j391-review-scratch
ui-consistency-j195-deferred-rebased
fix-rustc-paths-421
decisions-wording-341
fix-install-suite-red
fix-dev-rtt-specs
fix-pending-removal-align
fix-privileged-skip
fix-install-suite-404
review-j368
mesh-dns-j254
revoke-access-report-d356b
revoke-privilege-round5
fix-removal-confirm
review-j360
gotatun-rotation-367
deps-refresh-340
review-j354
review-J351
review-j350
addhost-takeover-d359
revoke-report-items-347
revoke-end-sessions-d362
review-j348
size-mesh-ssh-346
size-trim-337
revoke-access-diff-d356
j259-pre-rebase-b06c5a6
j305-ended-at-start-spec
addhost-sudo-consent
fix-layout-real-specs-411
fix-shell-open-411
fix-console-hosts-373
fix-shell-exit
fuzz-host-name-353
fix-host-details-410
macos-filter-j197
research-gotatun-tricks
revoke-p3-j216l
stale-state-pill
revoke-followups-j216k
revoke-progress-j216e
backup/macos01-20261106/lume/j197
backup/macos01-20261106/lume/appwin-icons-j207-stash0
backup/macos01-20261106/claude/stash-0
backup/macos01-20261106/claude/stash-1
backup/macos01-20261106/claude/J80-macos-s1
backup/macos01-20261106/claude/J80
backup/macos01-20261106/claude/J80-slice1-20261102
backup/macos01-20261106/claude/J16b-appwin-on-dev
backup/macos01-20261106/claude/appwin-launch-idle-146
backup/macos01-20261106/claude/J80-s1-run
wip/j259
trust-285-p3c
runtime-lock-j216f
mesh-ssh-p3-j175
tun-proof-p3b
trust-285-p3
terminal-polish-j271
tun-proof-p3
ssh-install-flakes-j216i
fuzz-fixes-j264
trust-285-a
trust-285-b
restart-oversized-fixture-j198
backup/macos01-20261106/lume/appwin-icons-j207
restart-bound-j216h
up-preflight-p3-j198
backup/macos01-20261106/lume/j251
up-preflight-j198
wip/up-preflight-j198-j245-p2
wip/up-preflight-j198-j245-p1
agent-fsync-offloop-j196
wip/up-preflight-j198-j245
settings-offscreen-j231
wip/up-preflight-j198-j226-inspection
wip/j103f
themes-j206
wip/up-preflight-j198-j226
flakes-j216c
backup/macos01-20261106/claude/j197
flakes-j216b
backup/macos01-20261106/claude/j197-red
wip/up-preflight-j198-review
flakes-j216
backup/macos01-20261106/claude/appwin-icons-j207
engineering-j208b
appwin-mock-j209
toast-stack-j165
engineering-j208
decisions-d351
decisions-d350
decisions-d349
appearance-presets-mock-j129
decisions-d348
docs-audit-spec
doc-coverage-baseline-raise
decisions-d347
decisions-d346b
decisions-d346
j88h-profile
ui-consistency-j195-deferred
ui-consistency-j195
engineering-practices-j194
preferred-login-ui-j166
signin-pools-263-j174
macos-node-265-s1
decisions-d345
decisions-d344
proxy-cgroup-j164
verify-quick-fuzz-329
proxy-start-io-j168
timing-flakes-j163
up-263-s1
mcp-events-j147
verify-quick-fuzz-j158
proxy-p3-j159
agent-resource-j157-detect
wip/j103d-final
remote-cancel-hang-j155
proxy-traefik-281-j62d
j149-verify-quick
proxy-traefik-281-j62b
j150-drop-unnamed
wip/j79d-red-r4
wip/j103d-d332
rootless-toast-j142
wip/j103d-head
wip/j103d
j125c-mcp
macos-build-j136
backup/macos01-20261106/claude/j136
wip/j79d-red-cli
wip/j79d-red-server
j121-link-liveness
j118c-tracer-failures
wip/j79c-head
release-desktop-j131
j124c-signed-macos
wip/j79c-red-p3
wip/j79c-red-p23
wip/j79c-red-p22
merge-j128-check
perf-deferred-j128
sidebar-toggle-260
j127-shortcut-changed-dot
j125-mcp
j118-tracer-failures
j126-account-summary
settings-sections-mock-j92
j120-pinch-lock
sidebar-292
backup/macos01-20261106/claude/j120
taste-2026-11-03
row-actions-242-215
backup/macos01-20261106/claude/stash-0-untracked
backup/macos01-20261106/claude/J80-265-s1-run
backup/macos01-20261106/claude/J80-macos-s1-20261102
j112-canvas-commands
ui-no-overshoot-scroll
fix-recovery-restart
backup/macos01-20261106/claude/stash-1-untracked
taste-2026-11-02
size-baseline-ratchet
machine-cards-structure-j108
j109-boot-copy-mockups
fix-dev-red
wip/up-263-s1
j95b-settings-rows
proxy-traefik-281
j95-option-rows
docs-human-j91
docs-keys-palette
fix-flood-1013-270-j38d
mesh-ssh-s1
mesh-ssh-s0
addhost-d291-tunnel
test-stability-load
mesh-relay-220
repro-296
shim-thp-off-24
mesh-pins-258-v2
mesh-dns
signin-d311-merged
docs-pages
ui-size-283
fix-ui-batch-specs
wip/j12b-edge-update
gate-on-arm
fix-rename-sync-293
trust-285-j60b
fix-focus-ring-clip
integ-ui-1101
edge-update-v2
signin-d311
taste-2026-11-01
layout-per-tab-250
ui-approved-splitopt-224
ui-approved-splitopt-224-on-dev
icon-text-centre-282
ui-approved-189-212-216
research-reverse-proxy
transfer-budget-320
review-mesh-pins-258-r2
sidebar-pill-259
review-mesh-relay-220
wip/j12-edge-update-v2
nix-package
status-crash-loop-237
fix-dev-test-failures
fix-fixture-socket-path
wip/mesh-relay-220-pre-dev-advance-rebase
fix-canvas-side-clip
research-head-to-head
frosted-real-256
review-mesh-pins-258
wip/mesh-relay-220-pre-final-rebase
review-layout-per-tab-250-r2
broad-suite-triage
nix-devshell
review-status-crash-loop-237-r3
wip/nix-devshell-pre-p3
taste-frosted-real
wip/code-serve-demo-2026-10-30
wip/code-serve-glass-2026-10-30
wip/code-serve-clusters-2026-10-30
wip/code-merge-release94-dev-2026-10-30
wip/rproxy-delta-scratch-2026-10-30
wip/stale-note-257-staged-2026-10-30
wip/d241-size-staged-2026-10-30
review-broad-suite-207
wip/mesh-pins-258-pre-split
wip/mesh-relay-220-pre-rebase
mesh-measure-220
plan-211-update
review-edge-autoupdate-r3
perf24-reconnect
review-stale-note-r2
release-prod-live
d241-shell-marker
review-mesh-pins-server-258
review-revocation-r3
review-setup-token-r2
review-sync-journal-r3
wip/review-mesh-pins-258-prune-review-1995fee46
frosted-scroll-256
sync-journal-r3
revocation-per-account-wip
review-tokio-asyncfd-0386
review-sync-p3s
wip/stash-8by64-stash0-2026-10-30
mesh-drag-r2
setup-token-data-wip
release-9-4
auth-busy-1013
mesh-view-overlap
sync-p3s
review-auth-busy-wake
review-readd-r8-wip
mesh-drag-bounds
audit-auth-93
app-windows-stream-start
review-authority-cap-r3
app-windows-r7-measure
readd-minimal-r6
readd-minimal-r5
app-windows-r7-soak
readd-minimal-r4
app-windows-r7
app-windows-r6
readd-minimal-r3
readd-minimal-r2
readd-minimal
merge-release8-dev
review-busy-page-liveness-r2
review-busy-page-liveness
mesh-view-trim
review-authority-read-retry-r2
review-remove-uninstall-r2
release-retry-reattach
release-retry
review-remove-uninstall
review-authority-read-retry
app-windows-ship
mesh-autoswitch-p2
mesh-ssh
mesh-relay
mesh-macos-node
screen-and-control
session-labels
review-real-logins-r5
review-real-logins-r4
review-real-logins-r3
shim-memory-r2
review-real-logins-r2
shim-memory
shadow-addhost-instant
shadow-real-logins
shadow-same-session-flood
shadow-prod-perf-fix
hosts-from-server
app-windows-real
app-windows-transport
release-prod-6w
mesh-policy-verify
quiet-close-toast
dev-merge-r4
review-mesh-agent-transport
review-app-windows-transport
settings-roundtrip
review-release-pipeline-r4
mesh-agent-transport
ended-strip-fix
review-audit-ca-update-d244
settings-rows
phone-width
signout-clear
fix-restart-tests
dev-merge-rp
signer-hardening
review-audit-ca-update
terminal-fidelity
review-audit-ca-update-wip
mesh-integrate
review-mesh-agent-transport-515d3712
review-mesh-dataplane-feec1988
mesh-dataplane
signout
research-canvas-node
wip/review-renderer-cost
wip/review-terminal-fidelity
padlock-glyph
review-release-pipeline-2e95b689
phone-polish
sidebar-polish
inbox-generic
review-mesh-dataplane-378d4055
prod-batch
ui-inbox
ui-ca-unlock
ui-machine-sections
review-mesh-dataplane-a5f744fb
tls-session-prod
session-bounds-prod
fix-dead-session
session-bounds
tls-session
palette-polish
shortcuts-d222
research-appmeta
phone-gutter
ui-centre-button
wip/ui-centre-button
wip/tls-session
review-addhost-breakit
ui-platform
renderer-cost
remote-agents
mesh-api
ssh-bootstrap
prod-hotfix2
prod-fixes
review-remote-agents
review-mesh-dataplane
session-life
research-browser
review-mesh-api
research-capture
review-mesh-api-round3
review-mesh-api-round1
gate-stability
review-gate-stability
review-relay-direct
review-mesh-api-findings
proto-sessions
font-picker
proto-addhost
proto-windows
mesh-core
latency-gate
research-bsd
research-discovery
keytray-mods
mock-core
ui-tweaks
fix-motion-sel
fix-motion
integrate2
fix-robust
t-web
t-server
vt-ghostty
fix-snap-echo
t-web-auth
review-vt2
t-agent
lane-protocol
lane-shim
lane-auth
s1-base
s1-contracts
s1-workspace
No results found.
No labels
launch-qa
Milestone
Clear milestone
No items
No milestone
Projects
Clear projects
No items
No project
Assignees
Clear assignees
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".
No due date set.
Dependencies
No dependencies set
Reference
kayg/infraternal#309
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Owner approved 2026-11-02; D25/D113; finding 8 on #302. Start: fix-recovery-restart from origin/dev
16bf736c7. Plan: inspect request persistence and API-key clock (15 min); failing tests for expired/pending/cancelled restart and exact 24h/48h boundaries (30 min); minimal fix, notifications and docs (30 min); focused tests and verify fast on VM, baseline evidence for dev failures, commit/push/report (60 min). Estimate 2h15m; stop/report at 4h30m. Pending attempts return existing request without resetting its wait; expired/cancelled requests are terminal and fresh attempts start new deadlines. Both VM queues checked: compute01 12 GB below floor, dev-02 32 GB with free slot; use dev-02. No deployment or dependencies.Start: branch fix-recovery-restart; base
16bf736c7. Root cause: production verifier forces resume mode, whose ON CONFLICT overwrites expires but preserves ready even on expired rows; expired rows also suppress the new start audit event. Existing audit test explicitly expects unsafe readiness at 48h. Using dev-02, target ~/worktrees/j102/target. Failing tests exercise both HTTP entry points, pending expiry preservation, cancellation, boundaries, notifications and persisted clock rollback.Start: fix-recovery-restart, base
16bf736c7. Root cause: production verifier forces resume mode, whose ON CONFLICT overwrites expiry but preserves ready even when expired. Audit start is also suppressed for expired rows. Existing test audit_recovery_after_page_discard_can_resume_at_original_deadline explicitly expects the unsafe 48h behavior and must change. Tests now cover both HTTP entry points, pending expiry preservation, cancellation, notifications and persisted clock rollback. Red run on dev-02; target ~/worktrees/j102/target.Red evidence (dev-02, ~/j102-red.log): 4 tests, 1 passed/3 failed, RED_EXIT=101. Expiry restart ready_at=1800086400 instead of 1800259200; pending retry extended expires from 1800172800 to 1800172801; rollback delay=86399 instead of 86400. Cancellation regression passes already. Fix rotates bearer only while pending, replaces both deadlines at expiry equality, records each fresh start, preserves commit-coupled notifications, reuses API-key persisted high-water time. Recovery suite now running.
Green recovery suite (dev-02,
/j102-green.log): test result: ok. 35 passed; 0 failed; 3 ignored; 0 measured; 444 filtered out; GREEN_EXIT=0. Includes both entry-point regressions and existing 16-device notification/socket isolation tests. Full scripts/verify fast started on dev-02 (/j102-gate.log). No new dependency or migration; creation uses the existing persisted API-key high-water mark.Validation blocker: dev-02 filled its shared disk during the workspace gate (No space left on device). Recovery-focused suite was already 35 passed / 0 failed. Stopping this job validation and preserving logs; removed only J102 target/debug/incremental (8.2 GB), freeing 8.1 GB. Will reclaim the remaining own target and retry with incremental/debug symbols disabled after checking both VMs. No other job targets touched.
Crash-safety checkpoint pushed: fix-recovery-restart at the WIP head (recovery suite green; full gate incomplete due shared disk exhaustion). Reclaimed only this job target. Retry uses CARGO_INCREMENTAL=0, CARGO_PROFILE_DEV_DEBUG=0 and CARGO_PROFILE_TEST_DEBUG=0 to keep debug artifacts bounded; static release flags remain the repository defaults. Final report will include the actual gate result, baseline failure evidence and size deltas.
Full workspace tests found two additional assertions tied to deliberately changed behavior: reviewer_disk_restart_and_concurrent_resume expects old readiness after 72h and only one start audit; reviewer_server_clock_rollback_retains_deadline_and_reports_more_than_day expects 90000s after rollback instead of the persisted high-water duration of 86400s. Updating these to fresh readiness/two starts and the persisted duration. No production change needed. Capture this test row, then restart the full gate with updated tests and include that file in the base comparison snapshot.
Checkpoint
2840d00c7includes the two updated legacy expectations. Superseded dev-02 test row: 712 passed / 11 failed / 56 ignored; 2 are those obsolete expectations, 9 are bootstrap/enrollment failures pending baseline confirmation. Compute01 now has a free slot and 210 GB, while dev-02 has 4 jobs queued, so cancelled our queued retry and removed our arm target before moving validation to compute01. One target remains per job: ~/worktrees/j102/target on compute01. Final validation snapshot includes reviewer_tests.rs; baseline server tests explicitly enable remote to reproduce the workspace feature set.Contract clarification required by the authorized restart/time policy: contracts/keywrap.md currently says a resume preserves the deadline even beyond expiry and reports a 25h remainder after clock rollback. I will update that prose to distinguish pending resumes from fresh post-expiry attempts, describe the shared persisted server time, and count each new attempt in the start audit. No wire fields, client wait bounds, cryptography or signed-in exemption change. Expired rows may remain visible as historical notice until cancellation/replacement; they grant no capability authority.
Progress: latest pushed checkpoint
42f0bd423includes the contract clarification. Compute01 final gate has passed fmt, VT fmt, CLI compile-out, clippy, SSH helper and fixture-path checks; workspace tests are compiling. Still one target (~worktrees/j102/target), compact debug profiles, release flags unchanged. How to test: (1) cargo test --locked -p infraternal-server --lib recovery_restart; (2) cargo test --locked -p infraternal-server --lib reviewer_disk_restart_and_concurrent_resume; (3) cargo test --locked -p infraternal-server --lib reviewer_server_clock_rollback_retains_deadline_and_uses_persisted_time. Run on a build VM. Full gate/baseline/static evidence still pending.Compute01 gate progress: all four new restart regressions and both updated disk/clock tests PASS; full server row 714 passed / 9 failed / 56 ignored. Failures are the same nine bootstrap/enrollment names captured on dev-02, with no auth failure. fmt, CLI compile-out, clippy, helper/fixture checks, SSH inode fixture, shim process-per-user, cargo-deny and gate architecture PASS. Both static release builds are running, then standalone TS (if Rust remains red), unmodified-base remote-feature server tests, and baseline static size comparison. Head
42f0bd423is pushed; no uncommitted work. Evidence remains ~/j102-*.log on compute01, target ~/worktrees/j102/target.Full gate result recorded: all Rust rows PASS except workspace test (the nine bootstrap/enrollment failures); both static builds PASS; standalone TypeScript TS_EXIT=0. Fixed sizes: x86_64 22418064 B, aarch64 20450160 B. Baseline tracked tree is clean at
16bf736c7c... (saved/j102-base-tree.log). Baseline command initially omitted the CARGO_TARGET_DIR export normally supplied by scripts/verify, so four notification fixture tests failed on env lookup. Ran that exact baseline test binary with CARGO_TARGET_DIR exported: all four PASS, BASE_NOTIFICATION_EXIT=0 (/j102-base-notifications.log). No repository code changed for that harness correction. Main baseline server run and baseline static builds still active.J102 finish. Branch
fix-recovery-restart, head4bb4b5aef8c7272d482f397e0462c6ecbe978563, pushed from base16bf736c7cc6b474474d6b148077b516b0893cf1.Expired and cancelled attempts cannot carry their old readiness into a new attempt. New attempts get fresh 24h/48h deadlines using statement time and the existing persisted server time high-water mark; D113's same-account signed-in exemption remains. Pending start/resume reauthenticates and rotates the bearer for the existing request, preserving BOTH ready and expiry. Each new attempt records its start and notifies the signed-in devices after commit. Updated
docs/user/keys.mdandcontracts/keywrap.md.Test-first evidence: the expired-restart, pending-expiry and rollback regressions failed before the fix (3 failed / 1 passed); cancellation already worked. All four new HTTP regressions plus both disk/concurrency and clock reviewer tests pass in the full workspace run. Full server result: 714 passed, 9 failed, 56 ignored. All nine failures were reproduced individually on the unmodified base with the proper target-directory environment. The baseline full run also exposed six harness-environment errors; all six passed on corrected individual reruns. No auth failure remains in the gate.
scripts/verify fastran on compute01 through build-slot with compact debug artifacts (CARGO_INCREMENTAL=0, dev/test debug=0); static release settings were unchanged. The Rust failure stops the dispatcher before TypeScript, so its complete TypeScript function was run separately and passed. Final documentation/tree checks: repository artifact gate PASS (7 changed paths), fmt PASS, clean worktree at the pushed head.Verbatim gate summary, baseline failures and size evidence:
Evidence is preserved on compute01 in
~/j102-*.logand~/j102-report.txt; control-host copy:/home/kayg/.local/state/infraternal-orchestrator/evidence/j102/. The initial red/green recovery logs from dev-02 are included. Every started run has finished or was explicitly stopped. Cleanup: no owned orphan processes; both VM targets removed. No owner decision is needed for this slice; the nine proven baseline bootstrap/enrollment failures remain outside its scope. Issue left open for review/integration.Review start: read-only review of fix-recovery-restart at
4bb4b5aef8against base16bf736c7c. I am tracing recovery state, bearer rotation, high-water time, notifications, and production migration risk.REQUEST CHANGES
P1 — A forward clock step can bypass D25's 24-hour wait.
auth/http.rs:194-209returns rawSystemTime, andauth/http.rs:324-326passes it torecovery_ready.auth/recovery.rs:193-205accepts the bearer whenready <= now < expires; completion rechecks againstMAX(now, unixepoch())inauth/webauthn.rs:143-150andauth/recovery.rs:240-249. The persisted high-water mark is consulted for start/resume atauth/recovery.rs:94-96, but not for capability use or completion. Concrete scenario: start an unauthenticated attempt at time t, wait one real hour, then step the server clock forward to t+24h. The still-valid original bearer can obtain a registration challenge and complete within its five-minute ceremony lifetime, before 24 real hours have elapsed.P2 — The completion state notification is not delivered to signed-in device subscribers. Recovery completion deletes all account sessions (
auth/recovery.rs:256-258) and then commits with revocation plus recovery events (auth/webauthn.rs:216-219;auth/account_events.rs:46-53). The WebSocket recovery-event branch requires live session authority before sending (websocket.rs:716-720, 1090-1100); the completion revocation observes the deleted session (websocket.rs:729-744). Therefore an active signed-in device cannot receive theauth.recoverycompletion snapshot; it gets session teardown without an explicit completion notice.No other findings. Restart/cancel/concurrent mutations serialize in the writer transaction; bearer rotation replaces the stored hash atomically, so the old bearer fails the later hash check. Pending retries preserve both deadlines, the D113 same-account live-session exemption remains, and expired/cancelled attempts get a fresh wait. No migration was added, and I found no recovery bearer in logs or argv.
Read-only review; no code changed and no gate was run. The implementation's gate and base-failure evidence is in the existing finish comment 75003. Head
4bb4b5aef8c7272d482f397e0462c6ecbe978563, base16bf736c7cc6b474474d6b148077b516b0893cf1.J102 round 2 start: fix-recovery-restart at
4bb4b5aef8. Verified both findings in comment 75039. Plan: (1) prove a forward-jump regression, replace recovery wall-time authority with persisted monotonic elapsed accounting shared by start/resume/status/ready/complete, test rollback and restart conservation, gate and commit; (2) prove completion notification loss with a signed-in socket, deliver an explicit completion teardown notice, gate and commit. Estimate: 3 hours. No other changes. Both VM queues checked; dev-02 is less loaded. Target: ~/worktrees/j102/target, compact debug artifacts. Only the nine previously proven inherited failures are acceptable.P1 design/contract clarification before implementation: use a recovery-only persisted logical clock advanced solely by monotonic elapsed process uptime. Every authority/status path shares it. Wall time is used only for session/ceremony policy, audit timestamps and estimated display dates. Process restart loads the recorded clock and credits no unrecorded downtime; a backward wall step adds zero waiting time, while each restart can lose at most the remaining 24h wait (plus downtime). No timer/background infrastructure. Existing wall-based pending rows must conservatively begin a full elapsed wait on upgrade because their prior real elapsed duration cannot be proven. No dependency added. The browser already counts delay_seconds using performance.now().
Red evidence on compute01: ~/j102-r2-red.log has P1_RED_EXIT=101, four failures: forward step releases envelope, valid completion bypasses elapsed wait, resumed delay is 0 instead of 82800, and restart reports 0 instead of 82800. Corrected socket fixture to use the server-returned ready time; ~/j102-r2-p2-red.log now fails at the intended notice assertion: None instead of Some("recovery completed"), P2_RED_EXIT=101. Implementing P1 first; P2 regression retained separately until its own commit. Dev-02 queued run was stopped before compilation; its target removed. Only compute01 ~/worktrees/j102/target remains.
Crash-safety checkpoint pushed at
802288d48(P1 only; P2 still separate). Clock authority now records nanoseconds to avoid rounding a subsecond start early, and migration ends unprovable legacy wall-based requests rather than reviving expired bearers. First iterate log ~/j102-r2-p1-iterate.log was red: two local clippy style findings (corrected) and latency_fixture_accepts_the_unsolicited_startup_inventory timed out before the server test row. Current snapshot is rerunning at/j102-r2-p1-iterate2.log; final gate must have no failures outside the nine accepted inherited names. P2 browser red evidence: unknown completion reason became generic terminal-ended text, and no terminal window meant the sign-out handler received no notice (TEXT_RED_EXIT=1, CLIENT_RED_EXIT=1). After wiring the existing sign-out flow, isolated actual-transport tests are 10 passed / 0 failed (/j102-r2-p2-client-green.log). Server P2 red is None instead of the explicit recovery-completed close reason. One target remains on compute01: ~/worktrees/j102/target.J102 round 2 checkpoint:
17e46c9c4onfix-recovery-restartsaves both findings separately as WIP commits. P1 targeted elapsed-clock/reviewer/migration tests and clippy are queued on compute01, log~/j102-r2-p1-targets.log, single target/home/claude/worktrees/j102/target. P2 TypeScript check and 116 connection/close-reason tests pass on dev-02 (~/j102-r2-p2-ts.log). The socket and browser regressions failed before their fixes. I found a commit/publication race during P2 inspection and added a deterministic barrier regression; its implementation and Rust checks are next. Final commits will replace these WIP messages after verification, followed by the full fast gate. No merge or deployment.J102 round 2: final two-commit head
81cf331ffba9a1d14bc6df75d983e7b9be260699is pushed. P1 commite21376b8apreserves recovery waits using recorded monotonic nanoseconds; P2 commit81cf331ffcarries the completion notice in the flushed close frame and signs out the browser. P1: 16 targeted elapsed/reviewer/migration tests and clippy pass. P2: completion/publication barrier test failed first (exit 101), then the eight completion tests and all 17 account-event tests pass; clippy passes. Browser: typecheck and 116 connection/close-reason tests pass. Fullscripts/verify fastnow runs on this exact head on compute01, log~/j102-r2-fast.log; full TypeScript gate follows separately if Rust's inherited failures stop the dispatcher. Single target remains/home/claude/worktrees/j102/target. No merge or deployment.J102 round 2 gate checkpoint at pushed head
81cf331ffba9a1d14bc6df75d983e7b9be260699: server suite is 723 passed / 9 failed / 56 ignored. Exact failing-name comparison against the saved unmodified-dev reproductions is 9/9, with zero additional failures. All recovery regressions, including the signed-in socket completion notice and publication barrier, pass in the full run. Other workspace crates, cargo-deny and architecture checks pass. The x86_64 stripped static build/key-memory probe passed: 22,424,752 bytes, +6,688 versus round-1 head (22,418,064), +11,424 versus the original dev base (22,413,328). aarch64 static compilation is running, then the full TypeScript gate and production browser notice captures follow. Logs:~/j102-r2-fast.log,~/j102-r2-ts-full.log; one target/home/claude/worktrees/j102/target. No source changes since the pushed head.J102 round 2 complete on
fix-recovery-restart, head81cf331ffba9a1d14bc6df75d983e7b9be260699. Both findings from comment 75039 are fixed, one atomic commit each:e21376b8a20527ef921ef6ce13b8fde5602bb62d. Recovery authority uses persisted monotonic nanoseconds for start/resume, status, ready and complete. Wall-clock steps add zero credit or wait. Restarts retain committed progress only; each restart can add up to 24 hours to the wait and lose up to 48 hours of lifetime progress, plus downtime. Migration 0037 ends legacy wall-based requests because their elapsed age cannot be proven; credentials remain intact. Pending retries keep their existing wait and reissue the bearer. Docs and the contract are updated.81cf331ffba9a1d14bc6df75d983e7b9be260699. Completion is delivered as the flushed WebSocket close frame (1008,recovery completed), then the browser shows “Recovery completed. This device has been signed out.” on the existing sign-in screen. Atomic credential replacement is retained. A committed completion generation survives coalescing, and a commit/publication barrier closes the deletion-before-publication race.Failing-first evidence: the original clock tests failed before P1 (
P1_RED_EXIT=101); the signed-in socket test failed before P2 (P2_RED_EXIT=101), as did its text/transport tests. The deterministic commit/publication barrier test also failed without the barrier (BARRIER_RED_EXIT=101). Green: all five elapsed-clock regressions, reviewer/migration tests, completion/socket tests and 17 account-event tests pass; clippy passes. The full server suite is 723 passed, 9 failed, 56 ignored. The nine names exactly match the previously reproduced unmodified-dev failures, with no additional failures.scripts/verify faston compute01 at the exact head, verbatim summary:The Rust failure stops the dispatcher before TypeScript, so the complete
ts_verify_fastfunction was run separately on the same head. Typecheck, all 656 tests, production build, compressed transfer budgets and protocol oracle passed:The only inherited failures (exact names):
Stripped static sizes, unchanged release profile:
Production browser capture: six checks passed at 1440×900, 393×852 and 375×667, in ink and paper, DPR 2. Each receives the real teardown reason through the transport, sees the completion notice and sign-in button, has no old windows/sidebar, and remains signed out after reload. Notice/button centers differ by at most 0.5 px. All six screenshots were inspected at DPR 2; attached for independent visual review. The capture uses public fixture identity and mocked HTTP/WebSocket authority; the separate Rust socket test exercises real session revocation. The first browser launch lacked
libnspr4.so; rerunning with the VM's packaged Chromium passed without repository changes.How to test: keep an owner device signed in, complete recovery from another device, and observe the completion notice on the old device's sign-in screen. For clock steps/restarts and exact 24/48-hour boundaries, use the deterministic elapsed-clock regressions rather than waiting real days.
Raw logs and capture harness/screenshots are attached and saved under
/home/kayg/.local/state/infraternal-orchestrator/evidence/j102/. Both VM targets are absent and no own build/test/browser processes remain. Issue remains open; no merge or deployment.J102 round 3 started: mechanical rebase only of
fix-recovery-restartfrom81cf331ffba9a1d14bc6df75d983e7b9be260699onto requested dev99668d8abc8dd55bba038c2c48a04e444c33e324. Plan: rebase without behavior changes, inspect any resolutions, run the completescripts/verify fastand require every row green, then push and report verbatim output. Estimate: 90 minutes including cold static builds. Both queues checked: compute01 has two free slots; dev-02 is full with a queue. Gate will use compute01 with one target/home/claude/worktrees/j102/targetand compact dev/test debug information. J111's parallel review remains on the prior head; no logic changes without owner findings.Mechanical rebase completed at
716183781bb46df735686c1b8299d52c0e04c5e2on requested dev99668d8abc8dd55bba038c2c48a04e444c33e324. One conflict, entirely inaccount_events' cancelled-commit test: retained dev's 60-second bound and database-read-before-notification wait, then kept J102's completion-generation assertion.git range-diffreports the first two commits unchanged and only this test-context adaptation in the third; no production logic or behavior changed. Fullscripts/verify fastis running on compute01, exact head verified, log~/j102-r3-fast.log, sole target/home/claude/worktrees/j102/target. This round requires a fully green result.J111 review start: read-only review of
fix-recovery-restartat81cf331ffba9a1d14bc6df75d983e7b9be260699against base16bf736c7cc6b474474d6b148077b516b0893cf1. I am tracing recovery elapsed-time authority and displayed state, completion notice ordering, bearer rotation, D113, secret handling, and migration compatibility. No code changes; no gate run.J111 finding — P3 (status accuracy; no early-completion bypass):
apps/tracer/src/recovery.ts:170sets a page-localreadyClockfrom the initialdelay_seconds;:108-116then updates the displayed countdown fromperformance.now()without querying the server. The server reloads only its persisted elapsed epoch on restart (crates/infraternal-server/src/auth/recovery.rs:14-20) and persists progress at recovery/status calls (:57-88); the restart regression intentionally discards unrecorded progress (crates/infraternal-server/src/auth/recovery_restart_tests.rs:247-270). Concrete scenario: an unauthenticated attempt starts, the page stays foreground with no API calls for one hour, then the server restarts. At 24 hours after the original start, the browser can show “Ready now” while the server has credited only 23 hours since restart and rejectsrecovery_ready(recovery.rs:274-280). Server-side completion remains blocked until 24 hours of credited elapsed time, but the displayed state is not aligned with the authoritative elapsed clock across restart, contrary to the requested status check.REQUEST CHANGES
P1 — Closed for server-side authority.
Clockis a persisted epoch plusInstant::elapsed()(crates/infraternal-server/src/auth/recovery.rs:6-28);recovery_timesamples it after taking SQLite's writer lock and persistsMAX(elapsed,candidate)(:50-89). Start/resume (:173-210), ready (:274-280), completion (:328-343) and signed-in status (sessions.rs:63-67→pending_recovery,recovery.rs:248-265) all derive recovery remaining time from that function. Concrete forward-step case: start at t, wait one real hour, set wall time to t+24h, then call ready or complete; elapsed remains about one hour, so the bearer is denied. A challenge issued earlier is also rechecked at completion. Cancel removes the row; a new attempt gets new deadlines, while pending resume preserves both deadlines and rotates the bearer in the same transaction (:181-210). D113's same-account live-session exemption remains at:188-200; the 24-hour lower bound is for the unauthenticated path. A backward wall step itself adds zero wait. A server restart can discard at most the unpersisted portion of the active 24-hour wait per restart, plus the actual downtime; repeated restarts therefore have no finite cumulative extension bound. This is fail-closed and acceptable for preventing early recovery, with an availability cost.P2 — Closed. Recovery completion deletes old sessions and credentials in the completion transaction (
recovery.rs:345-360). The commit path holds the publication barrier through commit and publishes a completion generation after commit (auth/account_events.rs:61-73). The socket waits for that generation and flushes close code 1008 with reasonrecovery completedbefore dropping transport (websocket.rs:991-1039); the client maps it to “Recovery completed. This device has been signed out.” and invokes sign-out (connection.ts:521-523,main.ts:615-616). There is no post-notice authority window: the old session is already deleted, and inbound frames are guarded bywhile_authorized(websocket.rs:881-898). The socket regression asserts the notice and old-session denial (auth/socket_tests.rs:1027-1089).P3 — Open: the recovery-page countdown can get ahead of server status after restart.
apps/tracer/src/recovery.ts:170sets a local deadline from the initial serverdelay_seconds;:108-116advances the visible “Ready now” state withperformance.now()and makes no server request. The server reloads only persisted elapsed progress on restart (recovery.rs:14-20) and persists progress on recovery/status calls (:57-88). Concrete scenario: start an unauthenticated request, leave the page foreground with no further API calls for one hour, then restart the server. At 24 hours after the original start, the page can say “Ready now” while the server has credited only 23 hours since restart and rejectsrecovery_ready(`recovery_restart_tests.rs:247-280). This does not bypass the wait, but does not meet the requested requirement that displayed status use the same elapsed clock.Other checks: bearer rotation stays atomic (
recovery.rs:202-210, checked by the stored hash at:274-280); D25/D113 are unchanged inDECISIONS.md; I found no recovery words, tokens or keys added to runtime logs or command arguments. Migration 0037 creates the clock and deletes legacy wall-based request rows (migrations/0037_recovery_elapsed.sql:3-10); the existing-database migration test verifies the user recovery verifier remains unchanged and the clock row is created (auth/migration_data_tests.rs:188-217). It is safe for existing DB data, with the intentional behavior that an in-flight legacy recovery is invalidated and must be started again with a fresh wait.Read-only review; no code changed and no gate rerun. Gate is not applicable to this review; the exact-head implementation gate report is already in #309 comment 75280. Reviewed
fix-recovery-restartat81cf331ffba9a1d14bc6df75d983e7b9be260699against base16bf736c7cc6b474474d6b148077b516b0893cf1.Owner scope update received: J111 closed both prior fixes (#309 comment 75349). Two additional test-first commits are authorized: (a) token-bound recovery-status plus page refresh on visibility, countdown zero and every 60 seconds; the page never promotes itself to Ready now. A status read must not restart expired/cancelled recovery or rotate its bearer. (b) measured monotonic counter checkpoints every 60 seconds and on graceful shutdown; no wall/downtime credit. Migration remains 0037 as explicitly coordinated with J61b/J60b. The superseded mechanical-only gate was stopped (RUN_EXIT=143); its single warm target is retained. Updated estimate: 3 hours for the two fixes and a fully green complete gate. No other logic changes.
Crash-recovery checkpoint: rebase plus pending regression tests and additive status contract pushed as marked WIP. Production logic is still the reviewed rebased code. Red runs are queued behind the VM build slots; source and logs are
/home/claude/worktrees/j102,~/j102-red-a.logon compute01. Single retained target:/home/claude/worktrees/j102/target. Both VM queues were checked. The WIP will be split into the two requested atomic commits after red/green verification; migration 0037 is unchanged.Failing-first evidence confirmed and saved:
j102-r3-red-confirmed.logunder the control-host evidence directory. The browser enabled Continue at local zero without any status response; the minute-checkpoint regression observed persisted elapsed 0 instead of 60 seconds. The token-bound server-status regression first returned 400 instead of 200, and its implementation is now in the queued verification slice. Client duration/readiness validation: 14 passed, 0 failed.Both requested fixes are crash-checkpointed as marked WIP. The countdown slice is staged independently and queued for production-build browser checks plus
scripts/verify fast(~/j102-a-green.log, compute01). The checkpoint slice is implemented locally but unverified; it adds a minute timer only while a request is active, shared measured-counter persistence, and SIGINT/SIGTERM flushes. Final history will replace WIP with the two atomic commits. Migration remains 0037. Single target remains/home/claude/worktrees/j102/target.Countdown slice targeted verification is green: 10 Rust recovery regressions, 14 client duration/readiness validations, typecheck, and all 16 production-build recovery browser tests. The new browser test confirms local zero does not enable continuation, minute polling and visibility reads use the same token, and a failed refresh clears previously confirmed readiness. The 2× screenshot is saved under the control-host evidence directory as
j102-r3-server-status-unavailable-2x.pngand alignment is asserted within 0.5 px.The first full fast gate has passed workspace and shim tests and is building the static releases (
~/j102-a-green.log, compute01). On success, the driver automatically applies~/j102-b-source.tar, runs the checkpoint regressions, then a second full fast gate. This avoids another queue wait while preserving a separately tested source tree per commit. Latest exact source is pushed as marked WIP; final history will contain the two requested atomic commits. No migration changes; 0037 stays ours. One target only:/home/claude/worktrees/j102/target.Countdown fix is now atomic commit
52ad241aee36ce970f5ea509660bc7e8e34b0fbd: its complete fast gate passed, all Rust rows PASS and TypeScript zero failures. The branch is pushed with that parent plus a clearly marked checkpoint WIP while final verification continues.Verification finding: extracting the second slice preserved timestamps older than the first build, so Cargo initially reused the previous 10-test binary. Source timestamps were refreshed, but that intermediate full gate had already fingerprinted its inputs. It is explicitly unqualified evidence. The same driver will now rebuild/run all 12 recovery regressions, then run a fresh complete
scripts/verify fast(markedQUALIFIED_GATE_STARTandQUALIFIED_RUN_DONEin~/j102-a-green.log). Final reporting will use only that fresh gate. No production changes were needed for this harness correction.Intermediate (unqualified) run ended with one failure:
crates/infraternal/tests/cli_envelope.rs::status_exits_5_when_something_that_should_run_is_down, line 271, expected exit 5 but got 0 forstatus --json. This is not being waived as an inherited failure. All other Rust gate rows, including both rebuilt static images, passed. The CLI status sources are unchanged by J102. I am rechecking that exact test in isolation before the final full gate; no out-of-scope CLI fix is being made.Fresh driver
~/j102-final.shis queued on compute01 after checking both VM queues. It refreshes source timestamps before any Cargo invocation, runs the 12 recovery regressions (must show the two new checkpoint tests), rechecks the CLI failure, then runs the completescripts/verify fast. Log:~/j102-final.log. The prior combined log is retained asj102-r3-a-green-and-unqualified-b.login the control-host evidence directory. Final reporting will quote only a fully green fresh run. Source checksums match between control worktree and VM.Fresh checkpoint regressions passed: 12 passed, 0 failed, including idle minute persistence and graceful flush/repeated restart loss. The isolated CLI exit-code recheck also passed without any CLI source changes.
The fresh full gate then exposed two denied
expect()calls in the new worker ownership code and test-only callers still unpacking the old four-field router tuple. I stopped that run. Fixed both expects using the repository's existingPoisonError::into_innerpattern and mechanically updated all three external test callers to ignore the retained fifth Auth field. No recovery behavior changed for these integration corrections.Latest exact source pushed as marked WIP above the atomic countdown commit
52ad241ae. Driver~/j102-final2.shstarts with workspace clippy across all targets, then the 12 regressions and fullscripts/verify fast; log~/j102-final2.log, compute01. Both queues checked before launch. Prior stopped run retained as~/j102-final-stopped-clippy.log. No gate failure is being waived.The next full gate encountered a separate fixture failure:
infraternal-agenttestlogin_binary::tests::anonymous_kernel_image_can_be_published_after_unlink, line 484, failed to spawn its private copied executable with OS error 26,ETXTBSY(“Text file busy”). Agent sources are unchanged by J102. I stopped the run rather than spend time on static builds after a known red test row. No failure is waived.The complete gate is being retried with
RUST_TEST_THREADS=1so external-executable fixtures do not race other Rust tests in the same process. All tests remain enabled. Driver~/j102-final3.shfirst rechecks that exact fixture, then runsscripts/verify fast; log~/j102-final3.log, compute01. Both VM queues were checked. Existing corrected-source evidence remains: workspace clippy across all targets passed; all 12 recovery regressions passed; CLI exit-code test passed both isolated and in the full run. Exact source is already pushed as WIP645377958above atomic countdown parent52ad241ae.J102 round 3 progress: control and compute01 source checksums match. The serialized full
scripts/verify fastrun has passed the recovery/checkpoint/socket regressions and the x86 static build (22,466,288 bytes, +48,032 versus baseline). Arm64 static compilation is running. No gate waiver; waiting for the full green result before replacing the checkpoint WIP with its atomic commit. Log: compute01~/j102-final3.log.J102 round 3 complete. Branch
fix-recovery-restart, head77616905a01eebf2917617b49d5aa719c15d7e15, rebased onto requested dev99668d8ab.Separate atomic commits:
52ad241ae: recovery page uses server-confirmed readiness, refreshing on visibility, zero and every 60 seconds. Local countdown cannot enable recovery.77616905a: measured monotonic recovery progress is checkpointed every 60 seconds while active and flushed on SIGINT/SIGTERM. No idle timer when no request is active. Under successful saves, abrupt restart loses at most about a minute of measured uptime plus downtime; storage/scheduler delays remain fail-closed. No unmeasured time is credited.Both changes have failing-first evidence. The full gate includes all 12 recovery regressions and the completion-before-sign-out socket regression. Additional validation: 14 client validation tests, typecheck with 0 errors/warnings, 16 production-build recovery browser tests. Screenshot attached for independent visual review, including alignment assertion within 0.5px. Control/VM source checksums match. Migration remains
0037_recovery_elapsed.sql; no migration renumbering.The final full
scripts/verify fastis entirely green on compute01 withRUST_TEST_THREADS=1, no test filters and no inherited-failure waiver. Earlier stopped attempts and their findings remain documented above. Verbatim Rust gate and test summaries follow; the complete unabridged gate output is attached.Static sizes: x86_64 22,466,288 bytes (+48,032 versus 22,418,256); arm64 20,488,072 bytes (+34,792 versus 20,453,280).
How to test: start recovery, hide/return to the page, and confirm readiness follows server status. Restart during the wait and confirm only saved measured uptime survives. Complete recovery and confirm the signed-in device sees completion before sign-out.
Evidence:
J210 owner-authorized housekeeping: closing this completed scope because its work is merged on dev in 92ee8d5ea. Verified the merge commit is an ancestor of current origin/dev. The implementation reports and evidence remain in this issue.