diff --git a/docs/contract-additions/e12-ota-backup.md b/docs/contract-additions/e12-ota-backup.md new file mode 100644 index 0000000..2280cf8 --- /dev/null +++ b/docs/contract-additions/e12-ota-backup.md @@ -0,0 +1,50 @@ +# E12 — Sovereign OTA + Snapshot/Backup (Software Update + Time Machine) + +**Gap #5 of the [feature-gaps-zero campaign](https://github.com/SourceOS-Linux/sourceos-spec/issues/267).** +Replaces two macOS subsystems at once — **Software Update** (OTA) and **Time Machine / +Migration Assistant** (backup/restore) — with a signed, reproducible, rollbackable +generation model. No vendor cloud; every update and every restore is consent-gated and +receipted, and a failed verify rolls back fail-closed. + +## Update model — signed atomic generations +An update is a new **generation** of the reproducible image tree (Guix generations / +Nix profiles), never an in-place mutation. It follows the [B₁₁ change loop](../surfaces/b11-life-mirror.html) +exactly: + +| Step | What | Fail-closed rule | +|---|---|---| +| **A · stage** | fetch a signed image; verify signature + provenance (SLSA/in-toto) | unsigned or unverifiable ⇒ refuse, never staged | +| **B · verify** | boot-test the generation; run the eval gate; the twin re-mirrors the new surface set | any check red ⇒ **auto-rollback** to the last sealed generation | +| **Q · commit** | owner consent (QR / present-owner) commits the generation as default-boot | no consent ⇒ hold on the current generation | + +The base is sealed by **measured boot** (PureBoot/Heads + dm-verity) — the Atzilut +seal in the framework. A tripwire trip *during* an update drives the automaton to +SAFE-HALT and hands recovery to the present owner. + +## Backup model — generations to a sovereign store +- **Snapshot:** each generation + user-space state snapshots to a sovereign store + (prophet-backups / MinIO via restic/borg), encrypted, agent-scoped, region-tolerated + (GDPR Ch. V — same residency rule as the consent plane). +- **Restore / migrate:** restoring is *booting a prior generation* — Migration Assistant + parity without a vendor account. Diff any two generations before committing a restore. + +## Governance +An OTA is `purpose = administer` (commit) / `ship` (publish an image); a restore is +`operate`. All are consent-gated + emit an `AutonomyAdmissionReceipt` bound to the +generation hash. The runtime enforcement is the same [consent plane](./e11-consent-receipts-ux.md); +the surface is the E11 pane (an update shows as a pending consent with its warrant). + +## Superiority over macOS +- **Reproducible + rollbackable** — every update is a content-addressed generation you can + diff and revert; Software Update is neither. +- **Receipted** — each update/restore is a sealed receipt with its warrant; macOS shows a spinner. +- **No vendor cloud** — backups live in *your* sovereign store, not iCloud. + +## Done-definition +Signed atomic update-tree + measured-boot attestation + snapshot/restore of a generation, +all consent-gated + receipted; passes the **update seam** purple-team test (no path commits +an unsigned, unverified, or un-consented generation as default-boot). + +## Non-goals +Not a package manager (that's Guix/Nix underneath) and not a general file-sync product — +E12 is specifically the *update + backup/restore generation lifecycle* and its governance. diff --git a/docs/contract-additions/sovereign-shell-assembly.md b/docs/contract-additions/sovereign-shell-assembly.md new file mode 100644 index 0000000..92d7446 --- /dev/null +++ b/docs/contract-additions/sovereign-shell-assembly.md @@ -0,0 +1,46 @@ +# Sovereign Shell Assembly — how the OS is built from the parts + +The build index for the SociOS/SourceOS agentic shell: every component the +[feature-gaps-zero campaign](https://github.com/SourceOS-Linux/sourceos-spec/issues/267) +produced, its owning repo, and where it plugs into the [source-os](https://github.com/SourceOS-Linux/source-os) +image build. Read with the [census](./macos-replacement-matrix.md) (the *what*) and the +[integrated-agent-native-stack contract](./integrated-agent-native-stack.md) (the *how*). + +## Components (built this campaign) +| Component | Repo | Kind | Integration point | +|---|---|---|---| +| Consent plane (spaces/purposes/gate) | agent-standards · policy-fabric · goose-guard · ontogenesis | runtime + ontology | enforced at 3 surfaces; TCC replacement | +| lampstand (parse + router) | sourceos-shell `tools/lampstand_*` | service logic | Spotlight replacement; routes to IR + consent | +| MeshTransfer (E3) | sourceos-shell `tools/mesh_transfer` | schema + gate | Continuity/AirDrop; `sourceos-shell` mesh service | +| netwatch (System Graph) | TurtleTerm `turtle-netwatch` | agent | Activity Monitor replacement; ingests to hellgraph | +| Canonical surfaces (7) | sourceos-spec `docs/surfaces/` | UI + tokens | E11 pane, B₁₁ automaton, launcher, turn-witness, framework, genesis/flywheel | +| Surface feeds + producer | sourceos-spec `docs/surfaces/data` + `tools/build_surface_feed.py` | live-state seam | surfaces read feeds; `provenance` LIVE/SAMPLE | +| E12 OTA + backup | (spec) `e12-ota-backup.md` | generation lifecycle | Software Update + Time Machine replacement | + +## Vendored dependencies (consumed, not forked) +Pinned by release + verified by `tools/verify_vendor.py` (recomputes the deterministic +`git archive` sha512). A pin that can't be verified is a build failure. + +| Dep | Pin | Why vendored | Lock | +|---|---|---|---| +| **hellgraph** (System Graph) | `v0.4.45` (`dbe854f`) · MIT | not our lane — netwatch ingests into it; surface feeds read from it | `vendor/hellgraph.lock.json` | + +> Rule: hellgraph is **consumed vendored**, never modified here. Bump the pin by +> re-running `verify_vendor.py --checkout ` against a new release tag. + +## Build order (bottom-up) +1. **Base** — measured boot (Heads + dm-verity) seals the image (Atzilut). *(source-os)* +2. **Substrate** — vendored hellgraph (System Graph) + telemetry. *(vendored + source-os)* +3. **Governance** — consent plane loaded; taints/tolerations active (fail-closed). *(agent-standards/policy-fabric)* +4. **Shell services** — lampstand, mesh transfer, netwatch, goose. *(sourceos-shell/TurtleTerm)* +5. **Surfaces** — E11/B₁₁/launcher/turn-witness wired to feeds. *(sourceos-spec/docs/surfaces)* +6. **Update lifecycle** — E12 OTA + backup generations. *(source-os)* + +Each layer is fail-closed and receipted; a layer that can't verify the one below it +does not come up. This is the buildable spine referenced by the census and E12. + +## What remains to a bootable image +- Package the shell components into the source-os workstation profile (a NixOS/Guix + module) + a contract test that the flake evaluates. *(source-os; verified by `nix build`)* +- E12 update-tree implementation + measured-boot attestation. *(source-os)* +- Local model serving (E2/E6) for the Noetica concierge. *(agent-machine)* diff --git a/tools/verify_vendor.py b/tools/verify_vendor.py new file mode 100644 index 0000000..bef72c6 --- /dev/null +++ b/tools/verify_vendor.py @@ -0,0 +1,56 @@ +#!/usr/bin/env python3 +"""Verify vendored dependency pins. + +Validates every vendor/*.lock.json has the required shape, and — when a local +checkout is available (--checkout or next to this repo) — recomputes +the deterministic `git archive --format=tar ` sha512 and asserts it matches +the pinned integrity hash. Vendored deps are consumed, never floating: a pin that +can't be verified is a failure, not a warning. +""" +from __future__ import annotations +import argparse, hashlib, json, subprocess, sys +from pathlib import Path + +REQUIRED = ["name", "repo", "reason", "pin", "integrity"] + + +def _archive_sha512(checkout: Path, commit: str) -> str: + tar = subprocess.run(["git", "-C", str(checkout), "archive", "--format=tar", commit], + check=True, stdout=subprocess.PIPE).stdout + return hashlib.sha512(tar).hexdigest() + + +def verify(lock: dict, checkout: Path | None) -> list[str]: + errs = [f"missing key {k!r}" for k in REQUIRED if k not in lock] + if errs: + return errs + if checkout and checkout.exists(): + got = _archive_sha512(checkout, lock["pin"]["commit"]) + want = lock["integrity"]["git_archive_tar"] + if got != want: + errs.append(f"integrity mismatch: got {got[:16]}… want {want[:16]}…") + return errs + + +def main(argv=None) -> int: + ap = argparse.ArgumentParser() + ap.add_argument("--vendor-dir", type=Path, default=Path(__file__).resolve().parents[1] / "vendor") + ap.add_argument("--checkout", type=Path, help="local checkout to recompute the hash against") + a = ap.parse_args(argv) + locks = sorted(a.vendor_dir.glob("*.lock.json")) + if not locks: + print("no vendor locks found", file=sys.stderr); return 1 + bad = 0 + for lp in locks: + lock = json.loads(lp.read_text()) + errs = verify(lock, a.checkout) + if errs: + bad += 1; print(f"FAIL {lp.name}: {'; '.join(errs)}") + else: + hv = " (hash verified)" if a.checkout and a.checkout.exists() else " (shape ok; hash not checked — no checkout)" + print(f"OK {lp.name} → {lock['name']} @ {lock['pin'].get('tag', lock['pin']['commit'][:12])}{hv}") + return 1 if bad else 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/vendor/hellgraph.lock.json b/vendor/hellgraph.lock.json new file mode 100644 index 0000000..30e8bad --- /dev/null +++ b/vendor/hellgraph.lock.json @@ -0,0 +1,22 @@ +{ + "name": "hellgraph", + "repo": "https://github.com/SocioProphet/hellgraph", + "reason": "Consumed vendored, not forked: hellgraph is the System Graph substrate that netwatch ingests into and that the B11/E11 surface feeds read. It is not our lane to modify — we pin a release and verify integrity.", + "pin": { + "tag": "v0.4.45", + "commit": "dbe854faf5b8f53a484fd164ba6f84328b5dd24b", + "tree": "4161b5f614691e1248a303137ea833a4f9bf97df", + "released_on_main": true + }, + "integrity": { + "algo": "sha512", + "git_archive_tar": "5f892c91e892206fd9b266202c3a4960528211fd593c79e64ea7ffaf8cc9ab5f29ec73bb5c06c09fc26acc866f13bf3ac12375ac8ac7d76f64e6e5d406ff7421", + "note": "sha512 of `git archive --format=tar ` — deterministic for the commit; recompute with tools/verify_vendor.py" + }, + "consumers": [ + "docs/surfaces/data (surface feed producer, build_surface_feed.py)", + "TurtleTerm/turtle-netwatch (System Graph ingest)", + "sourceos-shell (System Graph consumption)" + ], + "vendored_at": "2026-08-03" +}