Skip to content

fix(installer): make Runtime Pack install reversible - #71

Open
div0-space wants to merge 19 commits into
deprivatize/vibecraftedfrom
fix/recovery-runtime
Open

fix(installer): make Runtime Pack install reversible#71
div0-space wants to merge 19 commits into
deprivatize/vibecraftedfrom
fix/recovery-runtime

Conversation

@div0-space

Copy link
Copy Markdown
Member

Stacked on #70. Do not merge before the base PR.

Summary

  • carry the shared installer and its import closure inside the signed Runtime Pack
  • make AppDelegate delegate hydration and reset to that installer
  • record closed ownership receipts with hashes and collision backups
  • fail closed before teardown on modified files or tampered receipt paths
  • preserve Vibecrafted.app while resetting owned runtime/config/state surfaces

Verification so far

  • make unified-product-contract-gate: 625 passed, 18 skipped; core 89 passed, 1 skipped; self-test PASS; wheel install PASS
  • make check
  • make semgrep
  • unsigned Xcode Debug build
  • portable build and checksum verification from c7eca02

Blacky fresh-onboarding runtime verification remains in progress; final Linux tarball smoke and PR-check observation will follow after implementation stabilizes.

…ys dead

release-rehearsal caught it: the manifest and the wave's test both named
scripts/runtime_paths.py, but this line deliberately retired that PEP 562
shim (a2469bb — control-plane sync calls the module; one module object
per truth). My earlier manifest addition followed the stale test instead
of the repo. Manifest entry and test expectations now agree with the
retire decision; the git-drift parametrize row points at a file that
exists.

Gates: distribution manifest suite 107 passed.

Authored-By: claude <agents@vetcoders.io>
session_id: 97e159e7-6b2b-466a-95ba-55c33e4c3191
time: 2026-08-24T15:50:01+02:00
runtime: claude-code
…t a build-host name

The payload anonymity gate walks from the checkout root up to the first
directory every machine has. That list carried /tmp and /private/var but
not /private/tmp — the realpath of /tmp on macOS. Building from a
scratchpad worktree under /private/tmp therefore poisoned the forbidden
literal set with the generic /private/tmp itself, and the gate FATALed on
ten committed occurrences of that string (pytest fixtures, keychain
examples, tmp-normalization docs) in eight shipped files. Both the
in-build assert and release-rehearsal reproduced it on
Vibecrafted_4.2.4-20260824-c7485299-portable.tar.gz.

- scripts/lib/payload-hygiene.sh: add /private/tmp to
  _PAYLOAD_HYGIENE_GENERIC_ROOTS; a checkout at /private/tmp/x/repo now
  forbids /private/tmp/x (still host-specific), never the generic root.
- tests/tui/test_payload_hygiene.py: pin both directions — /private/tmp/solo
  yields no workshop, /private/tmp/scratch/repo yields /private/tmp/scratch.

Authored-By: claude <agents@vetcoders.io>
session_id: 97e159e7-6b2b-466a-95ba-55c33e4c3191
time: 2026-08-24T15:58:00+02:00
runtime: claude-code
…ld host scratch root

The rebuilt tarball at df5e706 passed the generic-root fix and the gate
promptly earned its keep: two shipped files still carried the literal
/private/tmp/claude-501/... — the vc-aicx skill (EN and PL) documented the
background-task output path with the operator's real uid-scoped scratch
directory baked in. PLAN_23 (unshipped, but on the deprivatize branch)
carried three more /tmp/claude-501/ mentions plus a /Users/tester
placeholder the host-paths gate refuses on principle.

All now use /tmp/claude-<uid>/<project>/... and ~/vc-workspace/... shapes;
loct find --regex 'claude-501' over 1213 of 1213 indexed files returns
zero hits.

Authored-By: claude <agents@vetcoders.io>
session_id: 97e159e7-6b2b-466a-95ba-55c33e4c3191
time: 2026-08-24T16:08:00+02:00
runtime: claude-code
… Swift 6 strict concurrency

First `make dmg` after the fp-wave merge was also the first time grok's
native-notifications code (0a398d0) met the app build gate: Swift 6
refuses `static let shared` on a non-Sendable class
(#MutableGlobalVariable), killing the DMG leg at SwiftCompile.

@mainactor would be a lie — handleIpcEvent fires on whatever thread posts
IpcEvent from the FFI. The house idiom for callback carriers is already in
this AppDelegate (EventObserver: @unchecked Sendable), and the invariant
holds: `started` and `presentWindow` are written only on the main thread
during launch; cross-thread callbacks read, never mutate. Declared
@unchecked Sendable with that invariant documented on the class.

Authored-By: claude <agents@vetcoders.io>
session_id: 97e159e7-6b2b-466a-95ba-55c33e4c3191
time: 2026-08-24T16:40:00+02:00
runtime: claude-code
Moves native App hydration behind the same receipt-owning installer used by CLI and source channels. The signed Runtime Pack now carries the installer closure, records exact owned files and backups, and rejects conflicts or tampered receipt paths before teardown.

The App remains an optional transport overlay: uninstall preserves the application while removing only proven runtime, config, state, cache, and launcher ownership so first-run testing can begin again.

Authored-By: codex <agents@vetcoders.io>

session_id: 01a03396-782e-7703-93b5-84e7659ddb28

time: 2026-08-24T21:08:57+02:00

runtime: codex
Copilot AI lite review requested due to automatic review settings August 24, 2026 19:25

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR makes the signed/offline “Runtime Pack” install path reversible by embedding the shared installer (and its import closure) into the Runtime Pack, delegating native app runtime hydration/reset to that installer, and switching uninstall to a receipt-driven, fail-closed workflow.

Changes:

  • Embed scripts/vetcoders_install.py + minimal import closure inside the Runtime Pack and include them in required-hash/product-contract checks.
  • Add runtime-install / runtime-uninstall modes to the shared installer with a hashed ownership receipt and pre-teardown drift/tamper checks.
  • Update the macOS app to call the embedded installer for install/uninstall instead of implementing install logic in Swift; adjust tests/docs to match.

Reviewed changes

Copilot reviewed 24 out of 24 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
vibecrafted-core/vibecrafted_core/skills/vc-aicx/SKILL.md Update tmp-path example to /private/tmp/claude-<uid>/… form.
vibecrafted-core/vibecrafted_core/skills/pl/vc-aicx/SKILL.md Polish mirror of the same tmp-path example update.
vibecrafted-core/vibecrafted_core/product_contract.py Expand runtime required-hash set to include installer/import-closure scripts.
vibecrafted-core/vibecrafted_core/deck/vibecrafted Make uninstall prefer the Runtime Pack’s bundled Python/installer when available.
vibecrafted-core/tests/test_runtime_receipt.py Update runtime receipt fixture/hash-count expectations for new required files.
vibecrafted-app/shell-agent/app/Vibecrafted/NotificationManager.swift Mark as @unchecked Sendable with documentation of threading assumptions.
vibecrafted-app/shell-agent/app/Vibecrafted/AppDelegate.swift Delegate runtime install/uninstall to embedded installer; add --uninstall flag handling.
vibecrafted-app/docs/plans/PLAN_23_AGENT_OPERATOR_DASHBOARD.md Normalize repo/tmp path literals away from user-specific values.
tests/tui/test_uv_bootstrap.py Add scripts/installer_brand.py to required-file checks.
tests/tui/test_unified_app_contract.py Update assertions to ensure AppDelegate uses embedded installer; validate embedded script presence.
tests/tui/test_release_contract.py Shift terminal-policy/entry assertions from AppDelegate to installer script.
tests/tui/test_payload_hygiene.py Add coverage for /private/tmp handling as /tmp realpath on macOS.
tests/tui/test_keys.py Switch wrapper-verb parity check from Swift parsing to installer constant _RUNTIME_WRAPPER_VERBS.
tests/tui/test_installer_uninstall.py Add round-trip install/uninstall tests and fail-closed conflict/tamper coverage for receipt mode.
tests/tui/test_installer_doctor.py Expand runtime-generation fixture sources to include embedded installer/import-closure scripts.
tests/tui/test_install_bootstrap.py Add scripts/installer_brand.py to required-file checks.
tests/tui/test_distribution_manifest.py Update included payload list and mutation tests to reflect new required scripts.
scripts/vibecrafted Same change as deck copy: prefer bundled installer Python when uninstalling from a Runtime Pack generation.
scripts/vetcoders_install.py Implement receipt-based runtime-install/runtime-uninstall, add required-hash entries, and integrate runtime-uninstall into legacy uninstall.
scripts/lib/payload-hygiene.sh Treat /private/tmp as a generic root for topmost-host-root logic on macOS.
scripts/distribution_manifest.py Include scripts/installer_brand.py in distribution include list.
scripts/build-vibecrafted-release.sh Install embedded installer/import-closure scripts into the Runtime Pack at build time.
install.sh Add scripts/installer_brand.py to the bootstrap required-file set.
docs/installer/REQUIRED-SET.md Update docs to distinguish receipt-based Runtime Pack uninstall vs legacy discovery uninstall and list new required surfaces.
Suppressed comments (1)

scripts/vetcoders_install.py:14625

  • runtime-uninstall drift detection skips any owned path that has been replaced with a symlink (or other non-regular file), so an attacker or accidental change can bypass the “fail closed before teardown” check. Treat any existing owned path that is a symlink or not a regular file as a conflict.
    conflicts = [
        raw_path
        for raw_path, installed_hash in sorted(owned_files.items())
        if (path := Path(raw_path)).is_file()
        and not path.is_symlink()
        and _sha256_path(path) != installed_hash
    ]

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +14408 to +14412
terminal_theme = product_config / "terminal-theme.toml"
if not terminal_theme.exists():
shutil.copy2(generation / "config/vc-terminal/themes/dark.toml", terminal_theme)
receipt["owned_dirs"].append(str(terminal_theme))
terminal_policy = generation / "config/vc-terminal/vibecrafted.toml"
Comment on lines +327 to +330
var environment = ProcessInfo.processInfo.environment
environment["PYTHONNOUSERSITE"] = "1"
environment["PYTHONDONTWRITEBYTECODE"] = "1"
process.environment = environment
Blacky runtime verification proved the previous teardown could delete the generation while App, terminal, shell, and vc-frame processes kept running on unlinked files. Census canonical product executables and runtime shell scripts by stable Darwin birth identity and argv, exclude the installer ancestry, and re-prove each PID before signaling.

Treat an unlocked persistent supervisor.lock as historical state rather than a live service, while retaining fail-closed behavior for held, foreign, unreadable, or symlinked locks. macOS 27 remote-login EPERM uses a strict absolute-ps fallback only for the conservative ancestor exclusion set.

Authored-By: codex <agents@vetcoders.io>

session_id: 01a03396-782e-7703-93b5-84e7659ddb28

time: 2026-08-24T21:40:14+02:00

runtime: codex
Copilot AI review requested due to automatic review settings August 24, 2026 19:47

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 24 out of 24 changed files in this pull request and generated no new comments.

Suppressed comments (2)

Previously missed (1) — in code that hasn't changed since the last review.

scripts/vetcoders_install.py:14803

  • Conflict detection for owned_files ignores cases where an owned file has been replaced with a symlink or a non-regular file. That’s still local modification and should fail closed before teardown; otherwise uninstall will silently unlink the symlink and proceed.
        if (path := Path(raw_path)).is_file()
        and not path.is_symlink()
        and _sha256_path(path) != installed_hash
    ]

scripts/vetcoders_install.py:14590

  • terminal-theme.toml is a regular file but it’s being recorded under owned_dirs, so it won’t be hash-verified for drift and can be removed without triggering the conflict fail-closed behavior. Record it as an owned_files entry (hash) instead.
    terminal_theme = product_config / "terminal-theme.toml"
    if not terminal_theme.exists():
        shutil.copy2(generation / "config/vc-terminal/themes/dark.toml", terminal_theme)
        receipt["owned_dirs"].append(str(terminal_theme))
    terminal_policy = generation / "config/vc-terminal/vibecrafted.toml"

Decode installer paths as filesystem URLs so AppKit can spawn the bundled terminal. Resolve server binaries, site assets, and launchd environment from the active runtime generation.

Authored-By: codex <agents@vetcoders.io>

session_id: 01a03396-78e0-74e0-94c2-9adc16febc5c

time: 2026-08-24T22:15:54+02:00

runtime: interactive
Copilot AI review requested due to automatic review settings August 24, 2026 20:20

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 27 out of 27 changed files in this pull request and generated no new comments.

Suppressed comments (1)

scripts/vetcoders_install.py:14589

  • terminal-theme.toml is recorded under owned_dirs (and not hashed), so runtime-uninstall will not detect local edits to this managed file before teardown/removal. This breaks the “fail closed on locally modified managed files” guarantee and can silently delete operator changes to the theme.
    terminal_theme = product_config / "terminal-theme.toml"
    if not terminal_theme.exists():
        shutil.copy2(generation / "config/vc-terminal/themes/dark.toml", terminal_theme)
        receipt["owned_dirs"].append(str(terminal_theme))

Copilot AI review requested due to automatic review settings August 24, 2026 20:24

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 27 out of 27 changed files in this pull request and generated no new comments.

Suppressed comments (2)

scripts/vetcoders_install.py:14589

  • terminal-theme.toml is being tracked in owned_dirs, so it is not hashed and won’t be included in the modified-file conflict check during runtime-uninstall. If the operator edits the theme after install, uninstall can silently delete it instead of failing closed as intended for receipted ownership.
    terminal_theme = product_config / "terminal-theme.toml"
    if not terminal_theme.exists():
        shutil.copy2(generation / "config/vc-terminal/themes/dark.toml", terminal_theme)
        receipt["owned_dirs"].append(str(terminal_theme))

vibecrafted-app/shell-agent/app/Vibecrafted/AppDelegate.swift:356

  • The app runs the bundled installer under the inherited process environment. To reduce the risk of Python environment injection (e.g., PYTHONPATH/startup hooks) when executing a signed installer, run the bundled interpreter in isolated mode.
    let process = Process()
    let output = Pipe()
    let errors = Pipe()
    process.executableURL = python
    process.arguments = [installer.path] + arguments
    var environment = ProcessInfo.processInfo.environment
    environment["PYTHONNOUSERSITE"] = "1"
    environment["PYTHONDONTWRITEBYTECODE"] = "1"
    process.environment = environment

Projects one active release into the CLI, App, and agent discovery views while preserving operator collisions through a checkpointed receipt. Adds deterministic interrupted-install reset and refuses drift, injected receipt paths, or symlinked projection ancestors. Exposes live server health, reconcile, diagnostics, About, and Help in the native tray.

Authored-By: codex <agents@vetcoders.io>

session_id: 01a03595-0d19-7943-af2a-0a5eff007ac3

time: 2026-08-24T23:37:36+02:00

runtime: vc-terminal
Makes project.yml the sole Xcode project authority, ignores the generated .xcodeproj, and makes the release fail closed if generated output is tracked again. This removes XcodeGen-version churn that dirtied the source tree after a successful build and blocked provenance signing.

Authored-By: codex <agents@vetcoders.io>

session_id: 01a03595-0d19-7943-af2a-0a5eff007ac3

time: 2026-08-24T23:56:02+02:00

runtime: vc-terminal
Stages pinned Loctree, AICX, PRView, and ScreenScribe payloads into the immutable Runtime Pack and rejects incomplete packs before launcher publication.

Authored-By: codex <agents@vetcoders.io>
session_id: 01a03595-0d19-7943-af2a-0a5eff007ac3
time: 2026-08-25T00:36:00+02:00
runtime: vc-terminal
Pins AICX v0.12.5 to commit ced57997 and applies Rust plus native compiler path remaps because the published release binaries retain their CI builder home.

Authored-By: codex <agents@vetcoders.io>
session_id: 01a03595-0d19-7943-af2a-0a5eff007ac3
time: 2026-08-25T00:57:00+02:00
runtime: vc-terminal
Build PRView against static OpenSSL on macOS and reject carrier binaries that retain non-system dynamic library dependencies. This keeps the mandatory PRView foundation runnable on clean customer machines.

Authored-By: codex <agents@vetcoders.io>

session_id: 01a03595-0d19-7943-af2a-0a5eff007ac3

time: 2026-08-25T01:27:00+02:00

runtime: vc-terminal
Publishes one manifest-bound Runtime Pack, canonicalizes vc-frame configuration, and makes doctor validate the active generation rather than legacy path projections.

Authored-By: codex <agents@vetcoders.io>

session_id: 01a03595-0d19-7943-af2a-0a5eff007ac3

time: 2026-08-25T02:43:10+02:00

runtime: vc-terminal
Ship a dedicated Agents workspace tab with New agent and voc doors. Keep interactive init/resume inside the current vc-frame tab and project live Agent faces from vc-frame pane truth.

Authored-By: codex <agents@vetcoders.io>

session_id: 01a03595-0d19-7943-af2a-0a5eff007ac3

time: 2026-08-25T04:57:47+02:00

runtime: vc-terminal
Resolves the installed adjacent libexec binary before ambient candidates and rejects shell wrappers as native vc-frame providers. Adds executable regressions for the installed layout and recursive data-root shadow.

Authored-By: codex <agents@vetcoders.io>

session_id: 01a03396-78e0-74e0-94c2-9adc16febc5c

time: 2026-08-25T05:24:01+02:00

runtime: vc-terminal
Treat cleanup of the disposable Foundations staging tree as best-effort so a macOS metadata race cannot replace a successful carrier build with ENOTEMPTY. Lock the release contract with a focused regression assertion.

Authored-By: codex <agents@vetcoders.io>

session_id: 01a03396-782e-7703-93b5-84e7659ddb28

time: 2026-08-25T05:41:42+02:00

runtime: vc-terminal
Make spawn.py the single provider policy normalizer used by headless launches, interactive CLI entrypoints, and Agent Workspaces. The complete provider/runtime/permission matrix fails closed on unsupported semantics and reports host capability separately.

Authored-By: codex <agents@vetcoders.io>

session_id: 01a036f1-7603-7b43-a4d7-021030c00153

time: 2026-08-25T05:50:00+02:00

runtime: headless
Rename the tray actions to the product-facing VC names, keep server status first, and route both Quit actions through a control-plane active-lane warning including worktree-backed runs.

Authored-By: codex <agents@vetcoders.io>

session_id: 01a036f1-7603-7b43-a4d7-021030c00153

time: 2026-08-25T05:52:00+02:00

runtime: headless
Copilot AI review requested due to automatic review settings August 25, 2026 03:56

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 57 out of 59 changed files in this pull request and generated 1 comment.

Files not reviewed (1)
  • vibecrafted-app/shell-agent/app/Vibecrafted.xcodeproj/project.xcworkspace/contents.xcworkspacedata: Generated file

Comment on lines +185 to +190
def configure(self) -> None:
curses.curs_set(0)
curses.noecho()
curses.cbreak()
self.window.keypad(True)
self.window.timeout(500)
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.

2 participants