Skip to content

[UI updates 6/6] Add Settings confirmation, reconnect and retained drafts - #18

Open
srctl wants to merge 1 commit into
review/ui-updates-05-supervisionfrom
review/ui-updates-06-ui
Open

[UI updates 6/6] Add Settings confirmation, reconnect and retained drafts#18
srctl wants to merge 1 commit into
review/ui-updates-05-supervisionfrom
review/ui-updates-06-ui

Conversation

@srctl

@srctl srctl commented Sep 9, 2026

Copy link
Copy Markdown
Owner

Adds the Settings update flow with exact confirmation, durable request reconciliation, cancellation, multi-tab status, reconnect/reauthentication returning to Updates, and bounded per-tab composer draft retention without replay.

Layer 6 of 6 replacing original PR #11. Base: review/ui-updates-05-supervision. Merge in the order below; after each lower PR merges, retarget the next PR to main. If lower layers are squash/rebase merged, reconcile descendant branch ancestry before proceeding; do not merge the original monolithic PR.

Incremental scope: Settings/navigation, update notices and admission-aware controls, draft/state helpers, auth return navigation, draft/navigation tests, and both reusable browser/native acceptance harnesses. This top tree is byte-for-byte identical to original PR #11 head 57fb9ae. The existing matched rendered desktop/mobile screenshots and complete operator guidance remain below, with their original fixture labels and qualification limits.

Diff: 15 files changed, 1381 insertions(+), 6 deletions(-).

Verification at this cumulative head:

  • Typecheck and app/CLI production builds passed.
  • Full unit suite: 202 passed, 0 failed. Tests are introduced with their behavior, not deferred to the final PR.
  • Checks use the unchanged locked dependencies, Node 24.15.0, pnpm 9.15.0, umask 022, and no inherited Roost binary overrides. Builds use isolated RAM-backed temporary storage. From layer 2, an uncommitted test allocator redirects only the download fixture to RAM storage because staging requires >2 GiB free; actual capacity/extraction/security checks are not mocked. Initial environment-only failures were corrected before the passing runs.
  • This restructuring does not requalify intermediate packages for release or deployment. No new VM/native-browser lifecycle run was performed. The final aggregate tree equals original head 57fb9ae27ae602baba28d68e96af817208c89bdd; existing supported-platform evidence and limitations apply only to that complete tree.

Preserved operator guidance, matched screenshots and acceptance records · Full evidence and limits · Recovery boundaries

Stack and merge order:

  1. [UI updates 1/6] Validate offers and protect update storage
  2. [UI updates 2/6] Stage and verify bounded release artifacts
  3. [UI updates 3/6] Journal transactions and recover matching release/data pairs
  4. [UI updates 4/6] Enforce server admission and candidate readiness
  5. [UI updates 5/6] Connect supervised helper, enrollment, CLI and authenticated API
  6. [UI updates 6/6] Add Settings confirmation, reconnect and retained drafts

Original PR #11 cannot be the bounded top layer without changing its head: the new cohesive commits are not ancestors of its preserved head, and GitHub's merge-base comparison would include the original large implementation diff. Its history, review discussion, operator guidance and all 16 verified attachments are preserved. No docs/evidence are recommitted.

Additional final checks passed: repository lint, production native-auth smoke (with inherited Nitro host/port overrides cleared), and all site checks (types, six tests, marketing/docs builds).


Original implementation description and evidence, preserved verbatim below. Its aggregate diff/test statements describe the original complete implementation, not this layer alone.

Enrolled packaged installations can now confirm a release in Settings, drain existing work, update under a separately supervised helper, reconnect, and restore the previous release and matching data when candidate startup fails. This implements approved design phases 1–3 from 7415789 (0.1.40), including its coding-job protection/recovery behavior. Design PR #10 remains separate.

The temporary blanket activation gate is removed after supported-path qualification. Initial support remains explicitly enrolled Ubuntu 24.04 Linux x64/systemd/local ext4 installations with gate-aware compatible packages. Native recent passkeys, strict origin/CSRF, pinned release/artifact identity and digests, compatibility, fixed service authority and work/recovery gates remain enforced. HTTP cannot enroll, repair, choose commands/paths/units, or grant privileges.

The helper and current CLI share kernel locking and fence old CLIs. Durable idempotency/journals, boot and candidate gates, full protected data snapshots, pre-commit pair rollback, and explicit post-commit manual repair preserve recovery evidence. Active or uncertain workers defer without replay/forced termination. Settings supports exact confirmation, truthful status, cancellation, multi-tab reconciliation, reconnect/reauthentication and retained drafts.

Acceptance found and fixed transient SQLite staging contention, recovery startup validation between renames, referenced-asset/auth readiness checks, a privileged environment-file hazard, repair socket timeout/stale status, and expired-session return navigation. Test-harness setup/retry failures are documented separately and excluded from completed acceptance totals.

Verification:

  • pnpm check: 202 tests, native-auth smoke, six site tests, lint/types, app/CLI/marketing/docs builds.
  • Real native-passkey Chromium → actual API → actual helper → pinned private HTTPS artifacts → real systemd update and startup rollback, using the normal production build. Mobile and desktop initiation; dropped acceptance response; exactly one POST; multiple tabs; stale asset reload; reauthentication returning to Updates; retained drafts with no prompt replay.
  • 42 audited boundaries × actual SIGKILL and actual QMP guest reboot = 84 completed interruption cases. Journal, pointer, data rename/fsync, activation, rollback and commit outcomes have operation IDs in the boundary table.
  • Guest app UID without broad sudo: exact-unit policy, peer UID checks, slow shutdown/orphans, port/assets/auth failures, corrupt evidence/failed rollback and post-commit new-data preservation/manual repair.
  • Actual ext4 block/inode exhaustion and SQLite WAL snapshot; large workspace copy; injected copy/fsync/restore faults; work/admission races and one automation catch-up.

Full evidence, artifact hashes, operation IDs and limitations · Operator setup/recovery/retention

All lifecycle/enrollment/reboot testing used a private disposable QEMU guest. No live host Roost configuration/data/service was changed, and no release was published. Fixtures kept production TLS/hostname/digest/compatibility checks enabled with a pinned guest-only test CA. No paid infrastructure, merge or deployment.

Limits: Chromium keyboard/AX and mobile viewport evidence does not replace physical screen-reader/Safari/device testing. Remote worker races use isolated transport fixtures; actual missing-worker observation is covered, while live Herdr jobs would require a disposable endpoint, credentials and authorized model quota. QMP guest resets are not physical drive power-loss tests. Other distributions/filesystems, arbitrary external writers and universal/post-commit rollback are not promised. Updates cause downtime. The PR awaits coordinator-controlled review.

Actual rendered matched screenshots, same screen/state and viewport:

View Before After
Settings search, desktop 1440 × 1000 Before desktop After desktop
Settings search, mobile 390 × 844 Before mobile After mobile
Expired-session actions, desktop (API fixture) Before reauth desktop After reauth desktop
Expired-session actions, mobile (API fixture) Before reauth mobile After reauth mobile

Actual native-auth/real-helper private-release fixtures (desktop 1440 × 1000; mobile 390 × 844):

State Desktop Mobile
Exact version confirmation Exact version confirmation desktop Exact version confirmation mobile
Successful update Successful update desktop Successful update mobile
Failed startup, matching pair restored Failed startup, matching pair restored desktop Failed startup, matching pair restored mobile
Earlier API-fixture confirmation views (not end-to-end lifecycle evidence)

Confirmation API-fixture desktop

Confirmation API-fixture mobile

Reviewability cleanup: all 19 newly added documentation/evidence files (including 16 PNGs and 551 text lines) are removed from the final code diff. All 16 original images were uploaded through the documented GitHub CLI attachment feature and downloaded again with HTTP 200, image/png, successful PNG decoding and byte-for-byte SHA-256 matches before replacing the image references. Original labels, matched viewports and fixture distinctions are retained. Full-page reauthentication desktop images are 1440 × 1018 from a 1440 × 1000 viewport.

The final diff is 62 files, 7,118 added lines and 47 deleted lines (previously 81 / 7,669 / 47). Production code, all regression tests and all three reusable browser/native/systemd lifecycle harnesses are unchanged. The harnesses assert confirmation, reconciliation, drafts, auth, service and recovery behavior; their optional screenshots do not make them disposable capture helpers. Cleanup verification checks documentation preservation, attachment integrity, link targets and diff scope; the previously reported implementation/VM tests were not rerun for this documentation-only cleanup.

Complete operator guidance and original acceptance records are preserved below. The immutable commit links above remain archival references, so recovery/setup instructions and original evidence remain available independently of this PR body.

Operator setup, recovery and retention — full preserved guidance

UI self-updates

The supervised update path is enabled for the supported, explicitly enrolled
installation contract below. Native passkey authorization, pinned compatible
releases, fixed-unit authority, work quiescence and durable recovery checks remain
mandatory. HTTP cannot enroll an installation or grant service-control privileges.
Enrollment is an explicit operator action.

The implementation starts from 7415789 (0.1.40), including its coding-job
protection and recovery changes. It follows the
approved design revision;
design PR #10 is separate from this PR.

Initial supported contract

Only explicitly enrolled, packaged Linux x64 installations on Ubuntu 24.04,
systemd and persistent local ext4 storage are supported.
The application unit must match Roost's generated unit, with only the updater
startup drop-in. Custom hooks, additional overrides, noncanonical/shared writable
installation paths (the installation root must be mode 0700), externally managed deployments and other service managers
are unsupported. Source installations show their external-management reason in
Settings and cannot activate an update.

Both the previous and candidate packages must include the startup gate and a
compatible compatibility.json: updater protocol 1, app/auth schema input ranges
and output versions, complete managed-data snapshots, unchanged excluded state,
and the same bundled Codex version. Packages predating this contract, including
the original 0.1.40 release, need a normal operator-managed upgrade to a gate-aware
release before enrollment. Merely adding a manifest to an old binary is unsafe.
Bundled Node and Codex must execute on the host; changed Codex versions require
terminal maintenance and separate compatibility review. No external model API
connection is required for candidate health.

The installation user is a trust boundary. Same-user arbitrary code can access
its installation, data and private helper socket. Independent operator service
changes, external writers and remote effects are outside the recovery guarantee.
Updates cause downtime; they do not provide universal recovery or undo external
actions such as messages, commits or remote provisioning.

Operator enrollment

Do this only during an authorized maintenance window, using a packaged,
gate-aware release. These commands are documentation, not automatic deployment:

roost auth setup --origin https://your-roost-host
# Register the native passkey using the private link before relying on UI controls.
roost updates enroll
roost updates status

Enrollment verifies installation identity/layout, Python 3 with Linux
SO_PEERCRED, systemd, ext4 and the application unit. It requires interactive
operator sudo authorization and installs root-owned files:

  • /etc/systemd/system/roost-UID-updater.service, running as the installation
    user, pinned to the enrolled release's Node/CLI outside the app control group;
  • /etc/systemd/system/roost-UID.service.d/updater.conf, ordering startup after
    the helper; the application CLI reads its private startup gate as the installation user;
  • /etc/sudoers.d/roost-UID-updater, allowing only noninteractive
    /usr/bin/systemctl start roost-UID.service and
    /usr/bin/systemctl stop roost-UID.service.

HTTP has no enrollment, repair, shell, path, URL or unit-control operation.
The daemon verifies that it is the fixed helper unit's main PID; it cannot be run
as an unsupervised CLI subprocess. The helper accepts bounded protocol messages on ROOST_HOME/updates/helper.sock,
mode 0600, through a separately supervised Python peer-credential bridge. Only
its own UID is accepted. The socket and operation directories are private.

Enrollment leaves ROOST_HOME/operation.lock as a permanent legacy CLI fence.
Do not delete it as a stale PID file. Current CLI setup/update/start/stop and
the helper share a kernel flock domain, updater.lock; enrolled updates and
start/stop route through the helper. Enrolled roost update prints the offer;
roost update --version EXACT_VERSION explicitly confirms it. Never unlink that kernel lock file either.
updater-owner.json records PID, boot ID and process-start ticks for diagnostics;
it may be stale after exit and never grants ownership. A separate lifetime
updater-supervisor.lock prevents a second helper invocation from replacing the
active socket or changing its gate; it never replaces the shared transaction lock. flock (util-linux),
Python 3, findmnt and systemd/sudo are required on the initial platform.

Repeating enrollment with the same pinned helper is supported after partial
setup. Re-enrolling from a different release refuses to replace a running helper.
Helper upgrades are separate operator maintenance: finish/recover all operations,
stop the app and helper, review the new helper's protocol compatibility, then
replace the pinned helper unit and enrollment metadata under the installation
lock. There is intentionally no automatic helper upgrade or uninstall command.
Do not remove the legacy fence while any older CLI can access this installation.

For a private repository, provision a read-only release credential explicitly as
ROOST_HOME/updates/github-token, a regular installation-user-owned file, mode
0600, at most 1024 bytes. Restart only the helper during authorized idle
maintenance to load a changed credential. It is never returned to the browser or
inherited from the web process. API requests cannot redirect that credential;
artifact redirects permit only GitHub's fixed release asset hosts without the
authorization header.

Transaction and recovery

Settings checks the configured repository's published stable release. The offer
pins repository, release ID, asset ID, version, SHA-256 digest, size and expiry.
“Published” is not a promise of compatibility: downloaded manifests, schemas and
bundled runtime execution are checked before stopping. A same-publisher API digest
detects replacement/corruption, not publisher compromise. Missing digests, changed
identities, downgrade/equal versions, drafts/prereleases and expired offers fail.
Downloads, metadata, request bodies, redirects and extraction are bounded.
Extraction accepts regular files/directories only, rejects links, devices, path
escapes, duplicates and unsupported extensions, and caps expanded bytes and files.

Activation requires exact version confirmation and a native passkey session less
than five minutes old, exact Origin/host, JSON, a session-bound CSRF token and a
bounded idempotency key. Session validity is rechecked after reading the body.
Acceptance is journaled/fsynced before acknowledgement. Repeating the accepted
key returns its operation; changing its actor/offer/version conflicts. Once
accepted, session expiry does not cancel recovery. No lost response triggers an
automatic POST retry.

The helper holds the installation lock across staging, drain, stop, snapshot,
activation, probe and commit. It blocks new conversation/steering/automation/
delegation/coding claims transactionally while continuing existing coding-worker
observation. Running, missing, unknown, stale or identity-uncertain workers defer
the update. Review status alone is insufficient: idle identity must be verified
and fresh. Queued jobs remain queued. In-flight requests, login, desktop viewers/
actions and background tasks must finish before stopping. The five-minute drain
deadline never authorizes forced termination or replay. Cancellation is durable
before the stop boundary; a later cancel request is refused.

After closing all writers, the helper stops the exact service and verifies its
control group empty. The complete data tree (both SQLite stores, sidecars,
managed files/memory/workspaces) is copied, integrity/digest checked and fsynced.
Config is retained alongside the snapshot for diagnosis/operator recovery.
Symlinks, hard links, special files and nested mounts are rejected rather than
silently excluded. Capacity checks reserve staged bytes, snapshot/restore space
and inode headroom. Snapshot permissions are reduced to owner-only access.
Snapshots are secrets; they can contain credentials and conversation content.

The candidate starts behind a capability-bound gate that blocks ordinary HTTP,
auth writes, worker claims, login and external effects. A two-minute probe checks
expected version/operation, app and auth database integrity/schema, packaged
assets and worker initialization. A durable commit precedes opening admission.
A previously stopped CLI installation is probed but returned to stopped state.

Before commit, failure restores a verified copy of the snapshot and selects the
matching previous release. Candidate data is retained separately as failed-data;
the original snapshot remains untouched. Recovery resumes interrupted pointer/
data renames and re-probes the previous version behind the gate. After commit,
automatic recovery never restores old data or discards new work. Missing/corrupt
evidence, an unexpected pointer or failed recovery keeps maintenance closed.
Boot recovery runs in the pinned helper; the app also rejects startup without
current-boot readiness and a valid open/verification gate.
The helper validates stable installation ownership before recovery without
requiring a readable candidate contract or an already-present data directory:
those may be broken or between renames. Full layout and compatibility validation
still applies to enrollment and every new activation.

Status, reconnect and repair

The searchable Settings section shows actual phases, blockers and downloaded
bytes, never invented percentages. It supports cancellation, deferral, recent
authentication, cached outage messaging, multi-tab observation and manual asset
reload while preserving the route. Polling backs off after disconnection. An
unconfirmed response stays pending until its request key matches durable status
or the operator explicitly dismisses it after inspection. A closed app cannot
serve fresh progress; the browser says it is reconnecting.
An expired or revoked session disables update actions and shows a sign-in link
that returns to Updates after native passkey authentication. The return destination
is fixed; this flow does not accept an arbitrary redirect URL.
After native reauthentication, read-only key lookup can recover an earlier
operation even if another tab has since completed a later update. Historical
results never replace the current installation's admission state.

Unsubmitted composer text and uploaded attachment references are retained in
bounded browser-local storage, separately per tab and conversation. Sending
clears that tab's draft; reload never submits it. Storage failure is surfaced.
Do not rely on browser drafts as a backup, and use a trusted browser profile.
Mutating submit controls pause during drain while read/navigation and deliberate
stop controls remain available; server barriers enforce the admission decision.

The independent terminal diagnostic command works even when the app/socket is down:

roost updates status
roost updates status --id OPERATION_UUID
journalctl -u roost-UID-updater.service
journalctl -u roost-UID.service

It reports the fixed units, installation, gate, advisory lock owner, phases and
matching snapshot locations under ROOST_HOME/updates/OPERATION_UUID/.
Do not publish those directories or raw data/config files. Retain journals,
snapshot.json, snapshot/, saved config, failed-data/ and both releases while
investigating. Never guess a snapshot or delete a lock to resume service.

For an intact manual-recovery operation, after fixing the underlying problem,
the terminal-only repair retries the recorded decision with explicit version:

# Only for an uncommitted operation, using its recorded previous version:
roost updates repair --id OPERATION_UUID --decision restore --confirm-version 0.1.40
# Only for a committed operation, using its recorded candidate version:
roost updates repair --id OPERATION_UUID --decision resume --confirm-version 0.1.41

The helper rejects the wrong decision/version. Repair cannot bypass failed
snapshot integrity or resurrect corrupt/missing journals; those require manual
forensic recovery from independently verified backups with the app stopped.
After commit, no automatic “restore previous data” option is offered.
Repair can take several minutes while copying data or probing startup. Its
private transport waits up to ten minutes; service start/stop waits up to two
minutes. If the connection ends first, inspect roost updates status --id ...
and the journal before repeating anything. A lost response does not cancel the
helper's work. Status requests remain available while repair is running.

Retention is conservative: no automatic deletion of operations, snapshots,
failed data, or retained releases. Operators may archive old terminal operations
only during idle maintenance after keeping the last verified recovery pair and
the helper's pinned release. Never reclaim the only recovery pair to make an
update fit. Large installations may need operator-managed backup/update instead.

Startup tokens are read from the private gate by the application CLI after it is
running as the installation user. The systemd drop-in contains only dependency
ordering; it does not make privileged systemd read a user-writable environment
file. HTTP cannot choose a token, environment file, service unit or command.

An operation's owner-only diagnostic.json retains a bounded failure reason for
terminal inspection. Browser status exposes only sanitized recovery messages.
If storage itself fails, diagnostics may be unavailable; retain the journal,
snapshot and service logs rather than deleting evidence.

Verification and qualification

See verification evidence for matched rendered
screenshots, exact test results, disposable VM setup and scope limitations. The
normal helper permits the qualified installation path; unsupported environments,
missing enrollment/authentication and incompatible releases remain blocked.

Acceptance evidence, artifact hashes, fixture setup and limitations — full preserved record

UI self-update acceptance evidence

PR #11 implements phases 1–3 of the approved design, starting from 7415789
(0.1.40), including that baseline's coding protection/recovery changes. Design
PR #10 is separate; this implementation does not assume it merged.

The supported enrolled path is enabled. The temporary blanket qualification gate
has been removed after real systemd/reboot acceptance. The final native-browser
update and rollback below used the normal production build, with no qualification
transform or override. Capability, enrollment, compatibility, work and native-auth
checks remain mandatory.

All service, enrollment, update, rollback and reboot experiments ran in a private
disposable VM. No live Roost service, configuration, credentials or data was
enrolled, restarted or updated. No release was published. Private fixture keys,
guest disk images and raw logs are excluded from the PR.

Matched rendered screenshots

Actual Chromium screenshots, isolated empty data, light theme, device scale 1.
Baseline 7415789 and this branch both show /settings, search software update,
at identical viewports. Full-page image height follows the content. Baseline has
no matching setting; the source build now gives its external-management reason.

Viewport Before After
Desktop 1440 × 1000 Before After
Mobile 390 × 844 Before After

Matched expired-session state, same Updates card scrolled to its actions, using
controlled API responses at the same desktop/mobile viewports. The change adds
an actionable native sign-in link and disables mutations until reauthentication.

Viewport Before reauth fix After reauth fix
Desktop 1440 × 1000 Before After
Mobile 390 × 844 Before After

Actual native-auth/real-helper lifecycle with private release fixtures:

State Desktop 1440 × 1000 Mobile 390 × 844
Exact version confirmation Desktop Mobile
Successful update Desktop Mobile
Failed startup, matching pair restored Desktop Mobile

Earlier confirmation-fixture-*.png images use deterministic API responses and
are labeled API-fixture views. They are not end-to-end lifecycle evidence. No
image is a mockup.

Actual authenticated lifecycle

Candidate Result Actual operation Initiating viewport
0.1.202 succeeded 7488215c-f28a-44a6-bf12-c157ebc7e462 Mobile
0.1.203 rolled-back bd9ca65b-2754-448d-bf65-6db1bf745a12 Desktop

Both runs preserved the original data sentinel, used exactly one activation POST,
reconnected across the real service outage, retained drafts across reload/closed
tabs, and returned to Updates after a second native passkey login. Rollback
retained the candidate-written sentinel separately in failed-data and restored
the original data with release 0.1.202. Both services finished active with admission open.

Normal helper SHA-256: f5972432b9a0ab1e3da14dbe5860b1edbef9d38232754cf7505d8f8df8f7ffe8.
Recovered production app entry SHA-256 (matches local normal build): 831b567646703829727d29a504479acc31ab8ef382a5b2346934aef8564d05d1.
Test TLS certificate SHA-256: 999304dc18fce0a235a1ed8b0d8984e519114cdfe563f9489c31bfa7524513e2.

Private artifact Bytes SHA-256
0.1.202 145074737 cca2f9dd83d160cfdce63acdfdedd6a8e80d55e37bd73be290dfe403f2d8a7a7
0.1.203 145001639 4f482bcebd6ed87db7cdf57d1c307d90cd5a58a95651796380dfeb396b897e81

These are local fixture versions, not published Roost releases. Both bundle
Node 24.15.0 and Codex 0.153.4; the bad candidate deliberately throws after
writing its sentinel. The healthy candidate uses the checked production app.

scripts/test-updates-native-browser.mjs uses Chromium's native WebAuthn virtual
authenticator to sign real login challenges with a credential registered through
native setup. It sends actual
Origin/CSRF-protected requests to the production API and enrolled helper. Its
only update-response interception delivers the real acceptance POST, verifies HTTP 202,
then drops that response. It does not mock authentication, update status, artifact
validation or service control. The test checks durable request reconciliation,
multiple tabs, desktop/mobile drafts, explicit reauthentication, historical key
lookup after a new session, keyboard focus/Escape/Enter, the accessibility tree,
and absence of any replayed prompt or update POST.

Private local fixtures use built compatible packages with test version manifests
and real SHA-256/size metadata. A private HTTPS server inside the disposable
container answers the fixed GitHub API and release-asset hostnames. Only the
guest maps those hostnames to the fixture server and trusts its generated test
CA through a root-owned helper override. TLS verification, production hostname,
redirect, artifact digest/identity and compatibility checks remain enabled.
Nothing was uploaded to GitHub releases or an external fixture service.

The initial native path passed 0.1.49 success and 0.1.50 startup rollback using a
test-only qualified helper build. The final results above repeat the supported
path with the normal production build, after removing the temporary qualification
gate; no build transform or runtime override is used for that final run.

Real service authority and lifecycle faults

The app runs as guest UID 1000 (ubuntu) without general sudo or sudo-group
membership. Its only noninteractive authority is the generated exact app-unit
start/stop rule. The authenticated test administrator is a separate guest user.
Policy checks deny root shell/id, app restart and unrelated-unit start. A separate
real SO_PEERCRED check rejected guest root and accepted the app UID against the
same stable helper process, distinguishing peer authorization from file access. A root-only
canary plus an app-user symlink at the obsolete start.env path verified that
privileged systemd no longer reads an app-writable environment file.

  • The earlier real initially-stopped 0.1.44→0.1.46 fixture update succeeded,
    kept the app stopped, retained its data and left the helper active. CLI
    start/stop used the private helper route. A standalone/duplicate helper was
    rejected without replacing its socket or gate. These retained checks precede
    the final normal-build native acceptance and are not counted as new runs.
  • A slow SIGTERM handler and a separately detached orphan in the app control
    group were stopped cleanly; elapsed time was 6.28 seconds, the group was empty,
    and the independently supervised helper survived.
  • A candidate with a missing referenced CSS asset (and an unrelated decoy asset)
    rolled back. A separately malformed auth database also rolled back. Both
    restored the previous release and matching data before reopening admission.
  • A separate guest listener occupying the app port caused both candidate and
    rollback probes to fail. The updater retained manual recovery and did not kill
    the unrelated listener. After the test operator removed that fixture listener,
    explicit CLI repair restored the verified previous pair.
  • A truncated journal remained intact while the helper held manual recovery.
    Restoring its known original bytes was an explicit test-operator action.
  • A corrupt snapshot was refused, preserving candidate data and owner-only
    diagnostics. Restoring the known snapshot bytes and requesting explicit CLI
    repair recovered the old pair and retained failed candidate data.
  • During the real native-browser drain, both the exact baseline 7415789 CLI
    and the current CLI refused setup, update, start and stop races. Current CLI
    auth recovery also refused. No additional operation, app restart or auth
    configuration change occurred.

A separate normal-production helper test committed the candidate, wrote new data,
then made its startup fail. Recovery kept the committed release/data and closed
admission for manual recovery; it did not restore an old snapshot. After the
operator restored the known entry bytes, explicit repair --decision resume
completed, retained the new data and cleared the stale public failure message.
Operation: bdb3296b-c1f1-4381-b158-9237b546b571.

Journal, activation and boot boundaries

42 distinct boundaries × two real interruption mechanisms = 84 completed cases:
42 actual helper SIGKILLs and 42 actual guest reboots. See the
audited boundary table for every trigger, operation and result.
This includes journal transitions before/after commit, pointer replacement,
shutdown/startup, snapshot completion, both data renames and rollback activation,
including rename-before-directory-fsync windows.

The boundary driver uses the real systemd adapter, snapshots, kernel locks and
recovery engine; only staging is replaced with preinstalled private fixtures.
Independent native lifecycle tests above exercise the actual download/staging
path. Rollback-boundary tests inject candidate readiness rejection to reach the
required boundary without redundantly repeating the real two-minute health
failure tests. Markers include run/operation UUID, phase, process PID, guest boot
ID and timestamp. SIGKILL cases require systemd's recorded signal 9. Reboot cases
require a QMP RESET event and a changed guest boot ID. Both require a terminal
journal and the expected release/data/admission/service state before counting.

Not every trigger is a different recovery algorithm: several pre-stop journal
boundaries share old-release preservation, and several pre-commit boundaries
share matching-pair restore. Repeated setup/debug attempts are excluded from
unique-case totals. One unsynced private preinstalled fixture lost its candidate
contract on reset. That attempt was excluded, exposed a real recovery-startup
validation defect, and was recovered with the fix while retaining its evidence.
Subsequent fixture setup explicitly fsyncs before the audited reset.

Automated and storage checks

pnpm check passed: 202 tests, native-auth smoke test, six site tests,
Biome lint/format, TypeScript, and application/CLI/marketing/docs production builds.
Rendered browser checks passed at desktop and mobile sizes. The native-browser
runs above are additional actual-guest acceptance, not mocked API checks.

The final native run initially exposed transient SQLite schema-read contention
while the old app was still serving. That pre-stop operation failed safely,
leaving its release/data available. Schema reads now use a bounded five-second
busy timeout. A real cross-process regression failed before the fix and passes
for transient locks on both databases and persistent-lock refusal. The final
normal-helper lifecycle was rerun with rebuilt, newly pinned artifacts after
that fix; the failed preparation attempt is excluded from successful totals. A separate
harness retry initially matched an older operation for the same version before
its new acceptance returned. Matching now requires the accepted operation UUID.
That accepted update completed successfully after the browser closed, but its
incomplete browser run is excluded; fresh fixture versions exercise the entire
corrected browser flow above.

The real post-commit repair case exposed an 18-second socket cutoff even though
repair eventually succeeded. Fixed, action-specific bounded transport budgets
now cover service control and repair, while status stays responsive. A real
socket regression delays repair for 21 seconds and requires concurrent status
within five seconds. Successful repair also clears its stale public error while
retaining private diagnostics; that regression failed before the fix and passed
after it.

A separate real ext4 guest filesystem verified a killed SQLite writer's committed
WAL and a 16 MiB workspace snapshot. Actual block/inode headroom exhaustion
refused snapshot creation with no partial snapshot. A separate temporary-root
stress run copied and verified 536,923,050 bytes across 2,053 entries, including a
512 MiB workspace file. Deterministic I/O fault injection at the filesystem
boundary covers snapshot/restore copy and fsync failures and a post-rename
acceptance fsync failure; assertions confirm the injection was reached and check
pairing, admission, retained data and manual-recovery evidence.

Release tests cover offline/429/private-token failure, changed offers/assets,
missing/mismatched digests, identity/tag/platform/protocol/schema/Codex mismatch,
exact runtime output, bad ABI, archive limits/traversal/links/devices, credential
stripping on redirects, and compatibility of both the candidate and rollback
release with the actual database snapshot. Existing schema-upgrade tests cover
legacy v8/v9 shapes, preservation and repeatability; newer schemas are refused.

Authorization tests cover no native auth, recent/expired/revoked sessions,
Origin/host/CSRF, bounded fields/queries/bodies, GET mutation rejection and
revocation during streamed request reading. Kernel-lock/socket tests cover
concurrent owners, SIGKILL release, fixed peer UID and legacy CLI fencing.

The actual native-browser cancellation and five-minute deferral tests used an
inert, identity-uncertain coding-worker record. The real observer's timestamps
continued advancing while admission was closed. Both preserved the app PID and
created no prompt inputs or reports. The test-only record was then removed.

Work tests keep existing observations running while claims/submissions stop.
They cover active/missing/unknown/stale/changed identities, cancellation and
follow-up recovery, admission closing during launch/follow-up preparation,
post-shutdown uncertainty, chat/steering, queued automations, expiry and exactly
one catch-up after downtime. No model API or paid worker is used by these tests.

Disposable runner and limits

QEMU 8.2.2 with TCG boots Ubuntu 24.04.4 amd64, systemd 255, local ext4, 1 GiB
RAM and two virtual CPUs. The Docker container drops all capabilities, uses
no-new-privileges, and has no host mounts/devices or exposed host ports. Its
memory limit is 4 GiB. The private guest disk was grown from 8 to 16, then 20 GiB for
retained fixtures; only the disposable guest filesystem was resized, using
QEMU’s documented block_resize command.
Superseded fixture archives were removed; metadata, journals, snapshots and guest releases
were retained.
The official Noble image's SHA-256 was checked:
d0fe84bb5f80853425fa6be28e2c106f30104c3cfe8611933f2e65c9b63f0e30.
Generated test keys and strict pinned SSH host-key checking were used throughout.
Guest-only optional cloud/package services were disabled to reduce emulation
boot noise; Roost, SSH, network and storage services remained real.

Automated accessibility evidence is Chromium keyboard/focus behavior, its
accessibility tree and mobile viewport rendering. Physical screen-reader use,
VoiceOver/Safari and additional mobile browsers require those devices and a
manual assistive-technology pass; they are not claimed here. Remote working/idle
worker and submission races use isolated transport/runtime fixtures. The native
browser test uses actual missing-worker observation and deferral, without paid
model calls or live remote jobs. A live Herdr integration run would require a
disposable endpoint, credentials and authorized model quota.

QMP resets exercise an actual guest reboot and filesystem recovery, not a physical
drive's power-loss characteristics. Copy/fsync failures are deterministic injected
I/O errors; block/inode exhaustion is real. No claim is made for arbitrary hardware
failure or untested distributions/filesystems. No additional infrastructure or
release publication is needed to reproduce the completed narrow-path tests.

These results qualify the documented narrow installation contract, not arbitrary
Linux distributions, custom units, filesystems, publisher compromise, external
writers/effects, physical-device power-loss behavior or later application bugs.
Updates cause downtime. Automatic recovery never promises to undo post-commit
work. Snapshot and journal corruption remain explicit operator recovery cases.

Audited interruption boundaries — full preserved record

Audited interruption boundaries

Each cell is a completed disposable test, with its terminal result and operation UUID.
Separate triggers may share a recovery algorithm; repeated debugging runs are excluded.
All reboot entries include an observed QMP RESET and a changed boot ID. All SIGKILL entries
include systemd signal-9 evidence and a newly written helper readiness file.

Boundary SIGKILL recovery VM reboot recovery
accepted failed (f08e8063-b21e-4d17-b7dd-495a204c309c) failed (0d65e99e-ad48-4566-a87a-4f67119f8366)
staged failed (7400cea9-8f6c-49ec-b011-f3a5e8a401d7) failed (05476adf-4c1c-43d3-972b-e234fa0489af)
draining failed (abbe0189-21a8-465f-91a0-46781720a2f8) failed (b98ff028-48e3-47c2-9711-a212956af5b9)
stopping cancelled (c6238d99-a44f-4a33-a6aa-c351c7f5de26) cancelled (7a76614e-f5c5-4867-b655-f7b49656f66f)
service-stopped cancelled (c10ba423-1827-4f9a-a531-7f23c4639738) cancelled (7bc45ef1-6bc5-4921-95c7-811d7c492394)
snapshot-complete rolled-back (ce27ce9a-3499-4f07-9c0a-7f0421d38fbe) rolled-back (d1188f51-d28b-4d7e-9902-f08359083609)
activating rolled-back (6e4a0797-cc52-4c46-b187-e1276552edca) rolled-back (27934845-5ee4-47d8-b3d1-4ecd404d18b7)
pointer-renamed-before-sync rolled-back (f082588b-840d-494f-8f8e-37b0842e1194) rolled-back (0e1ff6d3-2332-4651-8945-40891a055fe5)
pointer-renamed rolled-back (30af7103-c08a-466a-9fc0-f5b5cf2ae443) rolled-back (bb03c38d-dac7-4ddc-a044-e9a51150ce92)
verifying rolled-back (df54e456-bf5d-4a5b-b804-0f725030509e) rolled-back (2d4729b8-b96c-402b-9635-260b0e9b8f19)
service-started rolled-back (bd595f27-4d62-4fff-9bb1-b8355e453e99) rolled-back (8df857aa-6286-4ea9-ba65-cf12c69f4899)
before-journal-staged failed (4569be4d-bef8-4904-8ca7-3009d6bb5d65) failed (7b8bbd5c-8dca-4ef7-912f-2dc694a29194)
before-journal-draining failed (1abe9d9b-189e-4f90-9f36-0015951f3f0d) failed (313472d6-d33c-49a1-8dcb-9c313009911e)
before-journal-stopping failed (976f1d43-3c19-4412-b171-7587ed4a4e85) failed (f92070ea-8c56-42c1-b3df-272f3d684cc5)
before-journal-snapshot-complete cancelled (604c84de-b8fd-499e-a053-cde4d0b99760) cancelled (974cf536-45bb-43ef-be6f-3837a8e48341)
before-journal-activating rolled-back (70721c3d-e06b-47ee-9291-22523f7f7109) rolled-back (c63fd846-713f-4573-b123-cecabbd283a5)
before-journal-verifying rolled-back (d4dbf5ab-5d81-46df-b85b-a785071ea361) rolled-back (ca42de76-27c3-4383-ba50-e69fe41a4798)
before-journal-committed rolled-back (b5bba1b6-bdeb-420b-a815-8c03b51e9434) rolled-back (bf77fd6b-80f1-44ae-acd7-f376710383c9)
committed succeeded (805402e3-dc88-47c0-be13-113c456f6df3) succeeded (cf022d06-601f-46d7-833f-4687b3153eae)
admission-open succeeded (28bd96e1-25c7-46ea-8966-83d6851f8d1b) succeeded (1c9142d3-a4c0-45c1-b3eb-de0b10632545)
before-journal-succeeded succeeded (c10d8ec7-0572-47b9-bea8-c2b26ebaa53f) succeeded (c2e65ca4-5910-47b7-bd10-270a0f2457bf)
succeeded succeeded (c4c771fe-74da-42bb-a30a-a1f3c955eaa1) succeeded (cf8e8f02-f836-4ada-b448-1e7f4beca26f)
before-journal-restoring rolled-back (51bfd931-8756-4620-8f63-663a8e6c67e6) rolled-back (ebca0912-cd10-4daf-9b54-2574d494ca2b)
restoring rolled-back (297fa737-4641-460d-bd24-fa7d9074e04b) rolled-back (7c256561-e0ac-4f4c-af70-f5c19e4a4939)
failed-data-renamed-before-sync rolled-back (1b25f6c4-994c-4be2-af15-f40d6ef1f834) rolled-back (e0bf5237-cd7a-42db-ba22-12b34a3ad30d)
failed-data-renamed rolled-back (f06b82d8-a463-442b-9949-aba5e5118bf5) rolled-back (36e1d31d-bff6-4a81-aaaa-e86433e84893)
restored-data-renamed-before-sync rolled-back (7852fe25-afe7-4edd-8b1c-309737536b4b) rolled-back (03cc326e-d57a-4a3a-8708-394f7d70335b)
restored-data-renamed rolled-back (9064b438-99d4-42f6-a148-4f4824b3e82f) rolled-back (23770fc0-7b1d-42cc-8191-0aff450396d7)
before-journal-rolled-back rolled-back (4b118b7f-69f9-49cb-97d7-8ba2a1941ce7) rolled-back (1c10fa0c-c7c4-4f5e-9a30-71914706ccaa)
rolled-back rolled-back (5a6cfa44-5bd5-4940-8af2-23e9f940b3f4) rolled-back (70d6c7e1-a18b-4b0b-aa47-5fc1cd723120)
before-journal-accepted no operation accepted (72675633-2d9d-49ce-a389-642fd4814060) no operation accepted (66834ed9-12a5-465b-9301-2ccedcba6480)
before-journal-failed failed (ea958297-d997-49a6-abab-86893dff693a) failed (fb6e0338-5036-4dd3-9bff-c58623d89651)
failed failed (57a6ce3f-f80f-4174-a09b-72f682f25174) failed (e5b66a6f-183d-4464-90a8-c573d7c32b4a)
before-journal-cancelled failed (2731d147-4bf9-4d7f-972f-f1b6683cca8b) failed (73e1004f-254c-4b3e-8241-f524818c5fac)
cancelled cancelled (4bc0887f-ec07-4bf6-8bc3-092ebdaa7cbf) cancelled (6d467419-79af-47e4-b089-c291a22b889f)
before-journal-deferred failed (8470a096-b665-4f69-a769-ba7d619d93eb) failed (34eecb7e-b1dd-437f-a440-b5ea98ce9151)
deferred deferred (4687d3cc-3c37-451d-8e83-3c4fd4f5d559) deferred (2a4fffa0-8d98-4302-a1af-319516075009)
before-journal-manual-recovery rolled-back (0d35886f-f782-4efe-b19d-920cdfc9f938) rolled-back (4acb17eb-a34e-4226-9a30-a2345091a3f9)
manual-recovery manual-recovery (359c5cda-caca-4cd0-9924-420381691020) manual-recovery (7b972ebe-d2e8-4cbe-9502-5b850ed640fb)
rollback-pointer-renamed-before-sync rolled-back (004f471a-276a-497a-bedf-d043d4a5a8c8) rolled-back (027efc05-9af3-47da-96b2-768a06166170)
rollback-pointer-renamed rolled-back (ddfa87f4-fcf5-4dbe-9067-e3cac7c0a866) rolled-back (d1f82a68-4a79-4d45-8aaa-72e301875a67)
rollback-service-started rolled-back (b306674f-2084-445a-8db8-fdada8e2eddb) rolled-back (79c24730-d861-4fe4-bee6-9fa46217374e)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant