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
7 changes: 5 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
.PHONY: validate test rust-check rust-test rust-run-fixture rust-fetch-fixture rust-execute-dry-run-fixture rust-exec-dry-run-fixture rust-apple-m2-dry-run-fixture
.PHONY: validate test validate-lifecycle-contracts rust-check rust-test rust-run-fixture rust-fetch-fixture rust-execute-dry-run-fixture rust-exec-dry-run-fixture rust-apple-m2-dry-run-fixture

validate: test
validate: test validate-lifecycle-contracts
@echo "OK: validate"

test:
python3 -m pip install --user pytest cryptography >/dev/null
PYTHONPATH=src python3 -m pytest -q

validate-lifecycle-contracts:
python3 tools/validate_lifecycle_contracts.py

rust-check:
cd rust/nlboot-client && cargo check

Expand Down
37 changes: 35 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@

`nlboot` is the SourceOS network/live/recovery boot protocol reference implementation and Rust production-client lane.

This repository now contains two layers:
This repository now contains three layers:

- Python reference planner and conformance harness.
- Rust `nlboot-client` usable-MVP lane for planning, artifact fetch/cache, evidence output, gated Linux handoff, and Apple Silicon M2 adapter dry-run proof.
- SourceOS lifecycle contracts for `ReleaseSet`, `BootReleaseSet`, and `LifecycleStateRecord` control-plane objects.

NLBoot is not scoped to one machine. The M2 path is the first-class proof target because it is the first real machine we are proving on. The portable protocol must also support generic UEFI/iPXE, Purism/Linux-first hardware, and VM/bootstrap targets.

Expand All @@ -23,6 +24,7 @@ See:
- `docs/PLATFORM_ADAPTER_MATRIX.md`
- `docs/APPLE_SILICON_M2_ADAPTER_PLAN.md`
- `docs/APPLE_SILICON_M2_ADAPTER_CONTRACT.md`
- `docs/LIFECYCLE_CONTRACTS.md`

## What is implemented now

Expand All @@ -42,6 +44,10 @@ See:
- Apple Silicon M2 dry-run adapter evidence path.
- Dry-run proofs for CI and local validation.
- Refusal records for blocked paths.
- `ReleaseSet` lifecycle contract schema and M2 demo example.
- `BootReleaseSet` lifecycle contract schema and M2 recovery demo example.
- `LifecycleStateRecord` schema and signed-state transition demo example.
- Lifecycle contract validation wired into `make validate`.

## What is still intentionally gated

Expand All @@ -51,7 +57,8 @@ The production client does not yet implement:
- rollback execution;
- real Apple Silicon boot-entry changes;
- host repair actions;
- persistent enrollment-secret storage.
- persistent enrollment-secret storage;
- website/control-plane assignment flows.

Those operations are host mutation and require explicit platform adapters, evidence emission, and review.

Expand Down Expand Up @@ -93,6 +100,31 @@ Those operations are host mutation and require explicit platform adapters, evide
- offline fallback posture
- `execute=false`

## Lifecycle objects

`ReleaseSet` binds the immutable SourceOS system target to user-space closures, agent-space closures, policy bundles, BOM/SBOM refs, signing refs, rollback lineage, and evidence requirements.

`BootReleaseSet` binds a `ReleaseSet` to signed boot artifacts, the signed boot manifest, live/install/recovery channels, platform adapters, authorization requirements, offline fallback, signing refs, and proof requirements.

`LifecycleStateRecord` records state transitions such as build, sign, assign, plan, fetch, load-only, execute, attest, evaluate compliance, and rollback.

Lifecycle contracts and examples:

```text
schemas/release-set.schema.v0.1.json
schemas/boot-release-set.schema.v0.1.json
schemas/lifecycle-state-record.schema.v0.1.json
examples/release_set.m2_demo.json
examples/boot_release_set.m2_demo_recovery.json
examples/lifecycle_state_record.m2_demo_signed.json
```

Lifecycle validation:

```bash
make validate-lifecycle-contracts
```

## Usable MVP flow

Run the generic Linux/Purism/VM local usable-MVP fixture path:
Expand Down Expand Up @@ -147,6 +179,7 @@ A real `kexec --load` path removes `--dry-run` and must run with root or equival

```bash
make validate
make validate-lifecycle-contracts
make rust-check
make rust-test
make rust-run-fixture
Expand Down
114 changes: 114 additions & 0 deletions docs/LIFECYCLE_CONTRACTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
# NLBoot lifecycle contracts

NLBoot now carries the contract layer that connects the executable boot/recovery client to the SourceOS control plane.

The Rust client already proves signed manifest validation, token validation, artifact fetch/cache, evidence output, gated Linux kexec dry-run, and Apple Silicon M2 adapter dry-run. The lifecycle contracts define the higher-level objects that the website/control plane must build, sign, assign, and verify.

## Contract objects

| Object | Schema | Purpose |
|---|---|---|
| `ReleaseSet` | `schemas/release-set.schema.v0.1.json` | Signed SourceOS lifecycle release binding system target, user closures, agent closures, policy, BOM, rollback, and evidence. |
| `BootReleaseSet` | `schemas/boot-release-set.schema.v0.1.json` | Bootable/recovery release binding `ReleaseSet` to signed boot manifest, boot artifacts, platform adapters, authorization, offline fallback, signing, and proof requirements. |
| `LifecycleStateRecord` | `schemas/lifecycle-state-record.schema.v0.1.json` | Control-plane transition record for build/sign/assign/plan/fetch/load/execute/attest/compliance/rollback states. |

Examples:

```text
examples/release_set.m2_demo.json
examples/boot_release_set.m2_demo_recovery.json
examples/lifecycle_state_record.m2_demo_signed.json
```

Validation:

```bash
make validate-lifecycle-contracts
make validate
```

## State design

The lifecycle path is intentionally explicit:

```text
DraftProfile
-> ResolvedBOM
-> Built
-> Signed
-> Assigned
-> Planned
-> Fetched
-> Loaded
-> Executed
-> Attested
-> Compliant / Noncompliant
-> RollbackAvailable
-> RolledBack
```

Each transition emits or references a `LifecycleStateRecord`.

## BootReleaseSet and NLBoot

`BootReleaseSet` does not replace `SignedBootManifest` or `BootPlan`.

It binds them to the SourceOS control-plane lifecycle:

```text
ReleaseSet
-> BootReleaseSet
-> SignedBootManifest
-> EnrollmentToken
-> BootPlan
-> fetch/cache evidence
-> adapter evidence
-> fingerprint/compliance/rollback evidence
```

## M2 proof target

The Apple Silicon M2 path remains first-class but not exclusive.

`BootReleaseSet` supports platform adapters including:

- `apple-silicon-m2`
- `linux-kexec`
- `uefi-ipxe`
- `purism-linux`
- `vm-bootstrap`

The M2 adapter remains dry-run only until reviewed platform-specific host mutation exists. The contract requires evidence for proposed visible entries:

```text
SourceOS
SourceOS Recovery/Installer
```

## Safety invariants

- Unsigned fallback is forbidden.
- One-time enrollment token is required for boot/recovery authorization.
- Device claim is required.
- Last-known-good fallback is required for recovery posture.
- Host mutation is explicit and evidence-backed.
- Reboot paths require explicit acknowledgement in executor paths.
- Signing records do not mutate host state.
- Boot/recovery action must emit plan, fetch, adapter, and fingerprint evidence.

## Website/control-plane responsibility

The website/control plane must eventually own:

- profile selection;
- BOM resolution;
- ReleaseSet creation and signing;
- BootReleaseSet creation and signing;
- enrollment token issuance;
- device assignment;
- lifecycle transition records;
- artifact hosting;
- compliance dashboard;
- rollback assignment.

NLBoot owns portable boot planning, artifact fetch/cache/evidence, and platform adapter execution boundaries.
71 changes: 71 additions & 0 deletions examples/boot_release_set.m2_demo_recovery.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
{
"schemaVersion": "v0.1",
"kind": "BootReleaseSet",
"bootReleaseSetId": "urn:srcos:boot-release-set:m2-demo-recovery-2026-04-26",
"baseReleaseSetRef": "urn:srcos:release-set:m2-demo-2026-04-26",
"bootMode": "recovery",
"signedManifestRef": "urn:srcos:boot-manifest:m2-demo-recovery-2026-04-26",
"channels": [
"recovery",
"rollback",
"rescue"
],
"artifacts": {
"kernelRef": "urn:srcos:artifact:m2-demo-recovery-kernel-sha256-0f3b6d7f",
"initrdRef": "urn:srcos:artifact:m2-demo-recovery-initrd-sha256-08f4c82e",
"rootfsRef": "urn:srcos:artifact:m2-demo-recovery-rootfs-sha256-5f4dcc3b",
"artifactMapRef": "examples/artifact_map.recovery.json",
"cachePolicyRef": "urn:srcos:nlboot-cache-policy:m2-demo-last-known-good"
},
"platformAdapters": [
{
"adapter": "apple-silicon-m2",
"mode": "dry-run",
"status": "dry-run-only",
"entryRefs": [
"urn:srcos:boot-entry:m2-demo-sourceos",
"urn:srcos:boot-entry:m2-demo-sourceos-recovery-installer"
],
"requiresHostMutation": true,
"requiresRebootAck": false
},
{
"adapter": "linux-kexec",
"mode": "load-only",
"status": "implemented",
"entryRefs": [],
"requiresHostMutation": true,
"requiresRebootAck": true
}
],
"authorization": {
"enrollmentTokenRequired": true,
"oneTimeUseRequired": true,
"deviceClaimRequired": true,
"purpose": "recover"
},
"offlineFallback": {
"lastKnownGoodRequired": true,
"allowUnsignedFallback": false,
"fallbackCacheRef": "urn:srcos:nlboot-cache:last-known-good:m2-demo"
},
"signing": {
"signerRef": "urn:srcos:key:sourceos-release-root",
"signatureRef": "urn:srcos:signature:m2-demo-recovery-2026-04-26",
"signatureAlgorithm": "rsa-pss-sha256",
"cryptoProfile": "fips-140-3-compatible"
},
"proofs": {
"emitBootPlan": true,
"emitFetchRecord": true,
"emitAdapterRecord": true,
"emitFingerprint": true,
"requiredEvidenceKinds": [
"BootPlan",
"ArtifactCacheRecord",
"AdapterPlanRecord",
"BootEntryRecord",
"LifecycleStateRecord"
]
}
}
42 changes: 42 additions & 0 deletions examples/lifecycle_state_record.m2_demo_signed.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
{
"schemaVersion": "v0.1",
"kind": "LifecycleStateRecord",
"recordId": "urn:srcos:lifecycle-state-record:m2-demo-release-set-signed",
"capturedAt": "2026-04-26T14:35:00Z",
"subjectRef": "urn:srcos:control-plane:local-m2-demo",
"objectRef": "urn:srcos:release-set:m2-demo-2026-04-26",
"objectKind": "ReleaseSet",
"fromState": "built",
"toState": "signed",
"transition": {
"name": "sign",
"allowed": true,
"reason": "ReleaseSet signed by SourceOS release root for M2 demo lifecycle proof.",
"refusalRef": null
},
"proofs": {
"required": [
"bomHash",
"policyHash",
"signatureRef"
],
"present": [
"urn:srcos:bom:m2-demo-2026-04-26",
"sha256:release-set-policy-demo",
"urn:srcos:signature:m2-demo-release-set-2026-04-26"
],
"missing": []
},
"policy": {
"policyRef": "urn:srcos:policy-bundle:m2-demo-standard",
"policyHash": "sha256:release-set-policy-demo",
"approvalRequired": true,
"approvalRef": "urn:srcos:approval:m2-demo-release-sign"
},
"sideEffects": {
"hostMutation": false,
"diskWrite": false,
"reboot": false,
"networkFetch": false
}
}
64 changes: 64 additions & 0 deletions examples/release_set.m2_demo.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
{
"schemaVersion": "v0.1",
"kind": "ReleaseSet",
"releaseSetId": "urn:srcos:release-set:m2-demo-2026-04-26",
"channel": "demo",
"description": "M2 demo ReleaseSet binding SourceOS immutable system target, user profile closures, agent profile closures, policy, BOM, signing, rollback, and evidence requirements.",
"system": {
"systemPlane": "ostree-silverblue",
"targetRef": "urn:srcos:ostree-commit:m2-demo-sourceos-silverblue-gnome-2026-04-26",
"updateModel": "ostree-rebase",
"mutationPolicy": "requires-policy"
},
"userSpace": {
"profileRefs": [
"urn:srcos:experience-profile:macos-like-gnome"
],
"closureRefs": [
"urn:srcos:nix-closure:user-macos-like-gnome-demo"
],
"experienceProfileRef": "urn:srcos:experience-profile:macos-like-gnome"
},
"agentSpace": {
"profileRefs": [
"urn:srcos:agent-profile:default-devtools"
],
"closureRefs": [
"urn:srcos:nix-closure:agent-default-devtools-demo"
],
"defaultIsolation": "container",
"policyCanUpgradeIsolation": true
},
"policy": {
"policyBundleRef": "urn:srcos:policy-bundle:m2-demo-standard",
"policyHash": "sha256:release-set-policy-demo",
"approvalRequired": true,
"guardrailRefs": [
"urn:socioprophet:guardrail-fabric:sourceos-m2-demo"
]
},
"bom": {
"bomRef": "urn:srcos:bom:m2-demo-2026-04-26",
"bomHash": "sha256:bom-demo",
"sbomRef": "urn:srcos:sbom:m2-demo-2026-04-26"
},
"signing": {
"signerRef": "urn:srcos:key:sourceos-release-root",
"signatureRef": "urn:srcos:signature:m2-demo-release-set-2026-04-26",
"signatureAlgorithm": "rsa-pss-sha256",
"cryptoProfile": "fips-140-3-compatible"
},
"rollback": {
"previousReleaseSetRefs": [
"urn:srcos:release-set:m2-demo-previous-known-good"
],
"rollbackAllowed": true,
"lastKnownGoodRequired": true
},
"evidence": {
"emitFingerprint": true,
"emitVerificationRecord": true,
"emitRollbackRecord": true,
"agentPlaneEvidenceRef": "urn:socioprophet:agentplane:evidence:release-set-m2-demo"
}
}
Loading
Loading