Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions docs/contract-additions/e12-ota-backup.md
Original file line number Diff line number Diff line change
@@ -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.
46 changes: 46 additions & 0 deletions docs/contract-additions/sovereign-shell-assembly.md
Original file line number Diff line number Diff line change
@@ -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 <path>` 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)*
56 changes: 56 additions & 0 deletions tools/verify_vendor.py
Original file line number Diff line number Diff line change
@@ -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 <path> or <name> next to this repo) — recomputes
the deterministic `git archive --format=tar <commit>` 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())
22 changes: 22 additions & 0 deletions vendor/hellgraph.lock.json
Original file line number Diff line number Diff line change
@@ -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 <commit>` — 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"
}
Loading