Design packet · for review

PolymerOS — Update & Build Orchestration

How a compartmentalized, nix-built OS ships fast security fixes and feature bumps to new and existing users — without rebuilding everything, and without breaking running compartments.

Status: DESIGN — under review by the design agent, implementation by the build agents · source: notes/update-system-design.md

The problem

Five principles

P1

Two lanes: batched baseline + fast-track security overlay

The baseline lane is the whole-OS bump — fresh nixpkgs pin, full gauntlet, channel-promoted, cadenced. The security lane overrides just the vulnerable package on the current baseline (the cosmic-comp override technique), rebuilds only that sub-closure, re-signs, and ships as 0.0.5 → 0.0.5.1+sec. Nix means patching one CVE never requires a full pin bump.

P2

A change rebuilds only the artifacts whose closure contains it

Shippable artifacts = the core OS image + each catalog VM (firefox, OSE, …), each its own closure. The trigger graph is computed from closures, not guessed:

Coalesce changes per window; a rebuilt path (patched openssl) is built once and substituted everywhere. No blanket rebuilds, ever.

P3

Fast-track by exposure — compartmentalization is the mitigation

Tier 0 (TCB): kernel, VMM (cloud-hypervisor/crosvm), bond broker, verity/crypto, signing, compositor surface → always fast-track. Tier 1 (parsers/crypto/edge): openssl, image/font/media parsers → fast-track on high severity. Tier 2 (compartmentalized apps): FreeCAD, editors → batch; a bug is trapped in its VM behind the IOMMU. The isolation boundary earns the right to hold Tier 2.

P4

The immutable store means VMs don't break on core updates

Store paths are content-addressed and immutable: an update creates a new path; the old one persists until GC. A VM is a pinned closure — lean (shares present paths) and stable (its exact paths never move). Updating a VM = a new generation (atomic switch, roll back to the prior one whose paths still exist); each generation is a gcroot that keeps its paths. A host security overlay just makes a patched path available; each VM adopts it on its own schedule, with rollback — never force-broken.

P5

Test gating — it will break subtly, so catch it

No artifact ships until its test suite passes (never narrow the check). Core OS gauntlet: compile, headless boot, compartment-role checks, presence socket, trust rings, scene_fails=0, PLUS bond-grant flow and apply-then-rollback. Per-VM smoke test: its own done-criterion (firefox launches + renders; FreeCAD/KiCad open). Channel soak IS the extended test: chaos → alpha → … catches what the gauntlet misses before stable users see it.

The orchestrator

watch upstreams (per artifact) + security feed │ trigger ──▶ compute affected artifacts (closure diff) │ fabric build (dedup shared paths, coalesce window) │ per-artifact test suite ──▶ fail: stop, don't ship │ pass publish: core OS → channel · VM → catalog │ auto BOM + diff + latest.json (release-bom tooling)

Delivery to existing users is the A/B verity image: a security point release lands on the inactive partition, atomic switch, one reboot rolls back. The client polls latest.json for its channel and marks +sec vs feature. New users always pull the latest image with all overlays folded in — patched on first boot.

Open decisions (Colby)

Review asks

Design agentideas & criticism

Where does this break? Especially: the coalescing-window vs security-urgency tension; whether per-VM generations + rollback is the right UX or too much machinery; failure modes of the closure-computed trigger graph; and any update-experience angles we're missing for a non-expert user.

Build agentsimplementation targets

Hammer out: (1) the closure→artifacts trigger-graph query (given a store path, which shippable artifacts contain it); (2) the security-watch lane (pin diff + CVE feed → candidates); (3) the security-overlay builder + gauntlet gate; (4) per-artifact smoke-test harness. Reuse the existing release-bom / latest.json / gauntlet tooling.