Skip to content

feat(installer): preserve managed install authority across updates - #288

Draft
hyperb1iss wants to merge 13 commits into
mainfrom
nova/managed-install-authority
Draft

hyperb1iss wants to merge 13 commits into
mainfrom
nova/managed-install-authority

Conversation

@hyperb1iss

Copy link
Copy Markdown
Owner

What this changes

Linux installation can adopt managed authority with separate release and state roots, preserve the original service/release identity, and recover through a permanent versioned locator. CLI installation follows the recorded authority before preparing another candidate.

This layer targets nova/product-experience-engine. The shared engine fixes belong to that preceding layer.

Why

Update/recovery must retain one durable authority across interrupted adoption, rollback, and changes to the caller's XDG environment. The old journal location becomes a permanent locator only after the receipt and new journal are durable; an older CLI must refuse that authority instead of starting a competing installation.

Verification

  • Added or updated tests: 131 focused CLI tests passed independently (99 library, three dispatch, 29 platform), including a failing-before/passing-after receipt-only restart regression through the real native constructor.
  • Added or updated docs (broader installer guidance remains pending)
  • just verify passes locally (full current-head command pending; focused tests and strict Clippy passed)
  • just deny passes (no dependency version/license change)
  • just ui-test and just ui-build pass (not applicable; no UI source changes in this layer)
  • just sdk-lint, just sdk-check, and just sdk-build pass (not applicable)
  • just python-verify passes (not applicable)
  • just compat-check passes (not applicable)
  • just docs-build passes (not applicable)
  • cd docs && zola check passes (not applicable)
  • Packaging scripts were syntax-checked (the existing unmodified distribution producer generated the qualification archive)
  • just e2e-build passes with the normal Servo stack (full product artifact qualification is separate)
  • just e2e-build-cpu passes (not applicable to the installer authority proof)
  • just e2e passes against the Servo stack (full runtime journey pending)
  • Tested on real hardware, simulator, or e2e harness: an ordinary-user Ubuntu systemd guest adopted a generated artifact using explicit secure XDG roots, changed service invocation, and stayed healthy. The old CLI refused both normal and no-service activation afterward; authority and active service identity stayed unchanged.

Notes for reviewers

The actual positive systemd proof uses explicit 0755 roots. Default app directories created under umask 002 are 0775 and correctly fail the current ownership policy. A workable secure default migration path remains unresolved; no permissions were silently changed and no authority check was weakened.

The old CLI can stage candidate files before refusing the versioned locator. The verified invariant is that it cannot replace the active pointers, locator, or service authority.

The generated qualification artifact combines this installer with retained daemon/UI/app inputs. It is not a new product release. Changed-XDG replay, actual cold rollback/recovery, supported uninstall, launcher hardening, and the separate activator remain pending. Keep the PR in draft.

@coderabbitai

coderabbitai Bot commented Sep 13, 2026

Copy link
Copy Markdown

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true

Comment @coderabbitai help to get the list of available commands.

hyperb1iss and others added 12 commits September 13, 2026 10:07
Managed installation needs immutable releases in its data root and a
persistent transaction journal in its state root. Add a split-root store
that retains both directory identities and ancestry under one state lock,
while preserving the existing single-root installer contract.

Validate both roots through bootstrap and every later store operation.
Reject substituted ancestry, changed permissions, overlapping roots and
foreign locks before active-pointer or journal operations proceed.

Co-Authored-By: Nova (GPT-6) <noreply@openai.com>
Permission bits cannot establish per-user installation ownership. Read
owner identity from the retained file handle and require the effective
user to own every bootstrapped installation directory.

Cover replacement-resistant metadata and a foreign-owned directory with
otherwise safe permissions before accepting filesystem authority.
Record installation identity and fixed XDG roots without reconstructing
paths from later environment changes. Reject invalid contracts and root
overlap before filesystem authority can be used for adoption.

Validate directory ownership and physical ancestry under the existing
install lock, including replacement that preserves a leaf inode. Keep
locator publication and activation outside this topology checkpoint.
Persist exact legacy observations and the intended journal digest before
creating managed transaction state. Revalidate the original platform and
sync retained preparation files before publishing the managed locator.

Reuse nonrollback file replacement so an ambiguous directory fsync never
restores old authority. Require an explicit durability retry and reject
unknown locators, unrelated orphan journals, and changed preparation.
…ation

Managed authority handoff must bind the exact intended transaction before
writing its journal. Extract the existing platform preparation path so
callers can persist that binding before entering normal recovery.

Reject pending transactions and foreign locks before preparation. Keep
ordinary install behavior and the existing recovery driver unchanged.
Copied releases can share a digest while identifying different executable
inodes. Bind the prior role through retained historical ancestry and keep
candidate validation tied to the current store during owner proof.

Reject copied or replaced prior authority and restore prior snapshot bytes
from the original retained unit. Exercise same-ID different-inode proofs
and native historical ancestry replacement without changing live services.
Treat the permanent locator as a hint until its identity, journal and
retained roots are verified under the recorded state lock. Confirm the
locator directory barrier before managed transitions and never bootstrap
missing managed state or reacquire the historical lock after election.

Recheck legacy hints under the historical lock to preserve old-then-state
ordering across concurrent adoption. Refuse writable journals and locator
files while allowing ordinary atomic journal replacement during recovery.
Installed trees have immutable modes that differ from extracted release
payloads. Reuse the verified staging path with an explicit installed-source
mode instead of changing permissions or moving the original release.

Validate the source before and after copying, publish only within the
new store filesystem, and verify any existing destination before reuse.
Cover different-device copying and refusal of altered source or target.
Select historical prior authority from its exact persisted executable path,
then validate the original installed tree and executable identity. Keep
current-store and synthetic legacy roles on their existing validators.

Share immutable binding proof with normal platform validation and validate
the complete record after restoring its prior role. Cover copied-inode,
foreign-path, manifest-mode and retained-ancestor refusal.
Restore directory write permission only inside the completed synthetic
fixture so TempDir can remove immutable installed trees. Inspect entries
without following symlinks and keep failed fixtures available for diagnosis.
Retain old then state locks while copying the original release and
preparing recorded roots. Persist the exact initial journal before its
state copy so interrupted preparation resumes the same transaction.

Publish the permanent locator only after fresh prior-platform proof and
durability checks. Cold recovery uses the existing coordinator and keeps
the original inode and public paths available for rollback.
Elect recorded authority before staging and recover pending journals
before attempting a new release. Preserve historical prior bindings only
while rollback or interrupted adoption still requires the original unit.

Restore recorded prior authority once during cold native construction.
Exercise that constructor against retained original and copied releases,
and preserve strict candidate validation before installer bootstrap.
@hyperb1iss
hyperb1iss force-pushed the nova/product-experience-engine branch from a3addfe to 1201eea Compare September 13, 2026 17:12
@hyperb1iss
hyperb1iss force-pushed the nova/managed-install-authority branch from 3fcd441 to 8e7e235 Compare September 13, 2026 17:12
@hyperb1iss
hyperb1iss changed the base branch from nova/product-experience-engine to main September 13, 2026 17:12
The v0.5.1 release added a validate_only flag to InstallReleaseArgs and
made user_skill_files mandatory for candidate manifests after this
branch forked. The rebased legacy-authority fixture no longer compiled,
and the binding fixture's release was rejected as missing the user
skills tree. Both fixtures now match the shape the installer requires.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014466GJcZeEz2om6JDZ3Chm
@hyperb1iss

Copy link
Copy Markdown
Owner Author

Rebased onto main (the v0.5.1 release commit landed while this was open) and retargeted from the now-closed #286 to main. Two fixtures needed repair after the rebase: the legacy authority test gained the new validate_only flag, and the binding fixture's release now ships the user skills tree the v0.5.1 manifest requires. All 17 CLI test binaries pass locally after that (99 lib tests plus the integration suites).

One thing is holding this in draft, and it is the gap the body already names. The new directory policy rejects any group-writable directory (adoption_roots.rs:72 and :81, location.rs:250, store.rs:505) and ensure.rs:123 refuses to normalize a pre-existing 0775 directory. The installer's own directories are fine because durable_ensure_child_directory sets an exact 0755. The problem is the directories Hypercolor creates outside the installer: hypercolor-cli config/mod.rs:126, hypercolor-core config/mod.rs:795, and daemon layout_store.rs:54 all use plain create_dir_all, so on an Ubuntu default umask of 002 they land at 0775. Any user who ran the daemon before running hypercolor install would then fail with InvalidOwner or Unprepared and have no repair path. The positive systemd fixture sidesteps this by chmodding ~/.local to 0700 first, and no test covers umask 002 roots.

Two candidate fixes, either of which would let this merge: normalize uid-owned hypercolor leaf directories to 0755 as an explicit migration step before the policy check, and give those three create_dir_all sites an explicit mode so new installs never produce the shape. Leaving the call to Bliss rather than picking a security posture in a rebase pass.

~ via nova ⚡

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