Repository navigation
feat(bag): production readiness Phases 0–1.5 + partial 2 (security hard-close; removes banned nix) - #11
Merged
Conversation
…eck on default build The verdict runner and GitHubBridge were already correct (exit!=0 -> verdict=fail -> non-green status; only :pass is green). The dogfood's 4/32 failures + the demo's 'honest fail' showing pass were a FIXTURE bug: src/estate.zig was used as the 'badly-formatted' negative fixture and had since been formatted clean, so every 'should fail' case silently passed. Generate the dirty fixture at runtime instead, so it cannot rot to clean. Also install zig_fmt.wasm to zig-out/lib/ on the default build (was only built by 'zig build wasm'), so the wasm:// dogfood check is runnable from a fresh build. mix test now 32/32; demo step (2) now an honest VERDICT=fail. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…t wire-ready owned-CI trigger
A 'nix' capability (tag 13) was added 2026-06-14, before the 2026-06-22 estate
Nix ban; 'guix' (tag 4) already existed and mesh-server-1 already carried it, so nix
was a redundant, now-banned duplicate. Removed it across all three layers
(Protocol.idr / Estate.idr / estate.zig / mesh.ex) + the snifs-report test + the doc.
The formal ABI still typechecks (idris2, 4 modules), zig builds, and a check that
requests the removed cap now fails safe ('Unknown capability', no silent pass).
Adds .github/workflows/owned-ci.yml: Option A thin-GHA dispatcher that runs the
dogfood manifest on an owned guix node at 0 further GitHub minutes and reports
verdicts back. It is a no-op until the owner sets BAG_NODE_HOST + BAG_NODE_SSH_KEY.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…pure) Factor the duplicated leaf crypto/key helpers out of the 1309-line main.zig into src/crypto.zig (bytesToHex, hexDecode, readEd25519Seed, hashFileSha256) and add a new crash-safe src/store.zig (atomicWrite: write-tmp+fsync+rename+dir-fsync). CheckBaton method signatures and the BAG-CHECK-BATON envelope/HMAC/ed25519 bytes are unchanged; the methods now call crypto.* internally. store.atomicWrite is provided+tested but not yet wired into Baton.save/freeze. A refAllDecls test block pulls both modules' tests into zig build test. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…laim) Align README.md milestone list with README.adoc and the code: M3 Verified ABI and M4 Guix are Complete (M3 with an honest 'proofs typecheck, not invoked at runtime' caveat). Fix README.adoc broken links (CONTRIBUTING.md/SECURITY.md do not exist) to point at GOVERNANCE/MAINTAINERS/docs/.github. Soften 'Powered by Ephapax' and 'Mobile Wasm Contexts' to match what the code does; reframe Trust Attestation from a future Milestone 3 line to the shipped HMAC-SHA256 + optional ed25519 reality. Add an EXPLAINME Trust-Attestation claim entry, document the closed gaps (negative-fixture rot now generated; duplicate nix capability removed under the Nix ban), and tighten the standing Known-gaps. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…README.adoc Follow-up to 994b9c5 per review brief. EXPLAINME: add the still-open gaps that were missing (static estate manifest; cross-machine Baton transport not yet built), frame the Idris gap as a planned-but-unbuilt build-time conformance gate, and record the fail-closed verdict path as resolved (runner exit!=0 => fail; GitHubBridge maps only :pass->success). README.adoc status table: drop the 'via Zig FFIs' overclaim for M4 (the host shells out to the guix CLI; no FFI exists in src/) and add the 'proofs typecheck, not runtime-invoked' caveat to M3 so the canonical doc matches EXPLAINME. No SPDX/licence lines touched. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add bag/config/{config,dev,test,prod,runtime,releases}.exs and Bag.Config (bag/lib/bag/config.ex). Bag.Config.validate!/0 reads + validates the required prod env (BAG_ATTEST_KEY, GH_TOKEN, BAG_STATE_DIR, plus the Zig host binary; optional BAG_SIGN_KEY_PATH must exist if set) and FAILS CLOSED with a clear message listing every missing item. TRAP 1: validate!/0 is invoked ONLY from config/runtime.exs behind a `config_env() == :prod` guard, so it never runs under :test/:dev — `mix test` and `mix run` work with no env vars set (verified 32/32 with all bag env vars unset). Add a releases: block to mix.exs; `MIX_ENV=prod mix release --overwrite` assembles. Bag.Executor now resolves its binary via Bag.Config.executor_path/0 so BAG_BIN can override.
De-template the 784-line Justfile in place (NOT a rewrite): replace rsr-template project metadata + the ~30 placeholder TODO recipes with real Zig+Elixir+Idris recipes (build/test/fmt/lint/run/release/typecheck/estate-sync/version). TRAP 2: PRESERVE all estate machinery — the import? lines (contractile, init, groove, assess, validate, proofs), assail/maint-assault panic-attack hooks, STATE.a2ml state recipes, git-cliff changelog, session bindings, CRG/cookbook/man. `clean` no longer deletes build/ (estate machinery lives there). Removed the nix-shell recipe (estate Nix ban); kept guix-shell/guix-build. estate-sync diffs the node roster across Estate.idr/estate.zig/nodes.scm and exits non-zero on drift; version is read-only documentation of the four version-bearing files (Justfile, bag/mix.exs, build.zig.zon, .machine_readable/6a2/STATE.a2ml) — version number left at 0.1.0. SPDX/licence header untouched.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…, mandatory ed25519 Closes the forgeable-verdict hole in the Zig host (src/main.zig): - attestKey dev-key fallback DELETED. activeAttestKey requires BAG_ATTEST_KEY present and >= 32 bytes, else exit 78 (no integrity-only default). - ed25519 signing is now MANDATORY on freeze (signOrExit aborts if the signing key is absent/unreadable) — replaces the old fail-open HMAC-only path. - thaw uses checkAttestation: a verdict is accepted ONLY if it is v2, HMAC-valid, carries an ed25519 signature, AND that signature verifies. An unsigned/HMAC-only envelope (the exploit) now returns VERDICT=tampered, exit 3. - BAG-CHECK-BATON bumped v1 -> v2; `mode` (BAG_MODE, default prod) and `key_id` (BAG_ATTEST_KEY_ID, default default) are bound into canonicalString, so a v1 envelope can never produce a matching digest — v1 acceptance dropped cryptographically, not just by policy. - key_id keyring: thaw resolves the verifying key from ~/.config/bag/attest-keys/<key_id> (overlap-window rotation), falling back to BAG_ATTEST_KEY when its id matches. validKeyId rejects path traversal. Verified: zig build + zig build test green (8 new security tests); demo shows unset/short key -> exit 78, tamper/HMAC-only/key_id-traversal -> exit 3. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The Zig host now refuses to freeze an unsigned / non-prod verdict; this closes
the Elixir end so such a verdict can never be POSTed as a green required check.
- Bag.GitHubBridge.report_check refuses to post `success` unless the caller
proves the verdict was attested mode=prod + ed25519:verified via the new
`:attestation` option, returning {:refused, {:unverified_attestation, _}}
WITHOUT calling the runner. Non-green verdicts post as before.
- verdict_attestation/1 parses the authoritative Zig `thaw` output
(mode=… + ed25519:verified) into the gate value — the real verification
primitive the mesh threads through in Phase 3 (thaw-before-report).
- report_sweep threads per-result :attestation; absent ⇒ green refused.
- bag.report surfaces REFUSED posts and exits non-zero (GREEN REFUSED) rather
than crashing on the new return shape.
- Bag.Config min key length 16 → 32, in lock-step with the Zig host so a key one
side accepts and the other rejects can't cause a boot/runtime split-brain.
- test_helper provides a deterministic >=32-byte BAG_ATTEST_KEY + an ephemeral
ed25519 signing key so the integration tests can exercise the fail-closed host.
Verified: cd bag && mix test → 35 tests + 1 doctest, 0 failures.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… store - ci-check-baton.adoc: envelope v1 → v2; mode/key_id fields; attestation is now fail-closed and ed25519-mandatory (freeze exit 78, thaw exit 3); key_id keyring rotation; bridge greens only mode=prod + ed25519:verified. - EXPLAINME.adoc: replace the stale "dev placeholder / signing best-effort" caveat with the fail-closed reality. - docs/secret-store.adoc (new): Phase 1.6 ops procedure — distribute the shared BAG_ATTEST_KEY via age, per-node ed25519 signing key, key_id keyring layout, and zero-downtime overlap-window rotation. (age install is a node prerequisite.) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…-server-1) guix/manifest.scm: the bag-pilot toolchain, pinned to what base Guix 1.5.0 actually ships (zig 0.15.2, elixir 1.19.3, just, age, jq) — verified installed on the Hetzner node. idris2 + wasmtime are NOT in Guix (Idris 1 only; wasmtime unpackaged) → documented as out-of-band acquisition, not needed for the first $0 green check. echidna caps (rust/julia/GNAT-SPARK/z3) deferred to Phase 6. guix/bootstrap.sh: idempotent staged bring-up for Debian 13 + Guix-as-pkgmgr. Corrected against the real bring-up: Debian 13 dropped the `guix` apt package, so it uses the official installer (needs `uidmap`/newgidmap, driven with newlines). Adds a swapfile first (guix pull OOMs on 4GB). `all` skips `pull` since base 1.5.0 suffices. `extra` stubs the idris2/wasmtime acquisition. Verified end-to-end on mesh-server-1 (Debian 13, Hetzner CPX22): Guix 1.5.0 installed, toolchain in, `zig build` produces the host, and ci-baton-demo.sh passes — including the fail-closed exit-78 and the tamper/HMAC-only exit-3 rejections — on real owned compute. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
hyperpolymath
enabled auto-merge (rebase)
June 29, 2026 13:00
hyperpolymath
disabled auto-merge
June 29, 2026 13:01
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
bag-of-actions → production readiness: Phases 0–1.5 + partial Phase 2
Lands the prod-readiness work that had accumulated, unpushed, on
feat/prod-readiness-phase0(rebased onto currentmain, commit series preserved, linear history). Closes the live security holes from the production-readiness audit and removes the bannednixcapability thatmaincurrently ships.What's in it
src/crypto.zig+src/store.zigextracted frommain.zig(pure refactor);mix release+ fail-closedBag.Config(gated to prod boot — test/dev stay green); de-templatedJustfile(realbuild/test/release+estate-syncrosters guard); docs reconciled honestly (anti-overclaim).BAG_ATTEST_KEYmandatory (≥32B) or refuse (exit 78);BAG-CHECK-BATON v2envelope bindingmode+key_id;BAG_MODE(prod-default, fail-closed); mandatory ed25519 signing + verification (no HMAC-only / unsigned acceptance).Bag.GitHubBridgerefuses to post a green status unless the verdict ismode=prod+ed25519:verified— a dev/laptop node can never green a required check (+ 138-line test).guix/manifest.scm+guix/bootstrap.sh(reproducible node toolchain), verified on a realmesh-server-1.nixcapability fromsrc/estate.zig/Protocol.idr/Estate.idr(Guix is canon).maincurrently ships it via the PR feat(ci): report Baton verdicts to GitHub (commit-status bridge) + nix capability #7 bridge merge; this lands the removal.Verified green (rebased tree, run not asserted)
zig build✓ (+zig-out/lib/zig_fmt.wasminstalled)idris2 --typecheck verification/proofs/bag.ipkg✓ (4 modules)cd bag && mix test→ 1 doctest, 35 tests, 0 failures./scripts/ci-baton-demo.sh→ step-0 fail-closed (no key → exit 78), pass/fail/suspended/pass, tamper rejected two ways (hmac + unsigned)just estate-sync→ "all three estate manifests agree"Not in this PR (forward roadmap)
Phase 2-finish (per-node Guix configs, laptop as 2nd node, pilot branch-protection cut-over), Phase 3 (durable store / claim-lease-commit registry / hash-chained ledger + custody chain / SSH-mailbox transport / dynamic roster / conformance gate), Phase 4 (zero-GHA webhook + observability), Phase 5 (Wasm linear-memory snapshot migration), Phase 6 (estate rollout + OSS scaffolding). Plan:
~/.claude/plans/woolly-zooming-gizmo.md.🤖 Generated with Claude Code