Companion to felhom-agent v0.60.0 (proof-of-launch destroy gating + restore-
test band-advance). New documentation/runbooks/provisioning.md; campaign doc
updated with the resolution status and the operator manifest-bump follow-up.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Full validation of the felhom pool + scoped-token migration on felhom-pve:
- Track A: complete agent op surface under the scoped token — 16/16 pass
- Track B: vmid origin traced (externally supplied); adversarial collision
probes prove the compensating rollback/teardown attempts to destroy a
guest the job never created (contained only by the pool ACL)
- Track C: containment + 3b-fix intact; Track D: no pool leaks, adopt/
uninstall pool handling correct
- Findings doc: documentation/audits/CAMPAIGN-pool-effects-2026-07-01.md
No code change, no version bump. Demo left as found.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Colleague-safety batch #1+#2 (script-only). Adds a guarded, dry-run-aware
--uninstall local host teardown (guest -> agent -> pveum(ACL,token,user,role)
-> golden(opt-in) -> state file), with ours-check on the /etc/felhom-bootstrap
mount, typed vmid confirm, other-Felhom-guests safe-skip, and idempotent
tolerate-absent. Records customer_id + provisioned_vmid into the install state
(_state_put/_state_get) so uninstall resolves its target automatically. Adds
three provision pre-flight guards: multi-node (--node required), archive-storage
exists, and a RAM-floor WARN. Validated dry-run-only on felhom-pve.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Part A: hub-config operator_email/operator_enabled (committed earlier);
operator + customer email paths proven end-to-end via real Resend sends.
Append the "Findings closed" section to the TESTRUN audit (Part A proof,
F2-a root cause + deploy fix, F2-b fix + live validation, pending §D
supervised reboot) and remove the stale duplicate "Final state" block.
REPORT overwritten. hub/CHANGELOG entry. No hub image change.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0162BnMpUXscPsUB1cU8Tr6K
Operator sets a minimum controller version (FLOOR), per-customer defaulting to a
global floor; the report ACK returns the effective floor + latest_version so the
controller auto-updates to the floor when below it (latest stays the opt-in button).
- store: min_controller_version column + hub_settings global floor + Effective/
Get/SetGlobal/SetMin resolution + config/env DEFAULT_MIN_CONTROLLER_VERSION
- handler: report ACK {min_controller_version, latest_version}; LatestVersionProvider
- web: global floor editor + per-customer override form + Floor column (English)
- tests: floor resolution + ACK + render; override-precedence red-proof verified
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FSZmmSFVzGwEzhYmxbkgBK
Operator-run Day-0 automation for a fresh PVE host: pveum token -> hub
POST /host-enroll (single secret, option C) -> agent config -> felhom-agent
--selftest=provision -> verify. Idempotent/resumable (state.json), --dry-run,
--resume, --force, --preserve-from; --mode dr is a documented 10D stub.
Live-validated on felhom-pve: authorized wipe of demo guest 9201 + full
re-provision from the golden -> controller config-pull + public tunnel HTTP 200
+ host-report of 9201; idempotent --resume no-op. Found+fixed one ordering bug
during the run (token ACL must be applied AFTER token rotation — token-remove
purges the ACL).
Adds scripts/README.md (operator runbook) + scripts/CHANGELOG.md; REPORT
overwritten; spike doc pointer added. No service code changed.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TtXesNa2LGbMmE4DNL6SE7
DR recipe slice (hub half), grounded in SPIKE-dr-recipe-2026-06-16. The hub
receives two additive dr_recipe halves on the existing report paths (agent
storage/guest/PBS on host-report; controller customer/apps on the controller
report), stores them PLAINTEXT in a DEDICATED dr_recipe table keyed by customer
(each half preserves the other), and AssembleDRRecipe stitches them into one
operator-readable recipe (ignore-unknown + version-skew tolerant).
View: a DR-recipe panel on the customer page + GET /customers/{id}/dr-recipe.json
download (operator-auth, no secrets to redact). Plaintext-at-rest is correct —
the recipe is the clean inverse of the retired infra-backup.
Tests: store round-trip (each half preserves the other), assemble-matches-golden,
ignore-unknown + version skew, partial halves, no-secrets sweep. Manifest tag
bumped to v0.13.0.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Live-validated on demo guest 9201 (felhom-flash throwaway): shared/slave mount
propagation delivers host->guest drive swaps live with no restart and no pct
change; bind source = permanent stable dir => inherently C1-immune (clean boot
with drive absent). nsenter fallback non-viable on unprivileged LXC; ro-tmpfs
placeholder is the CAP_DAC_OVERRIDE-proof fail-close. Report-only, no code change.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Live unattended UI exercise of migration/decommission/re-enroll/eject +
feature sweep on shipped controller v0.66.2 / agent v0.32.0 (guest 9201).
No production code changed.
Migration engine solid (all moves checksum-verified identical, sources
cleaned, HDD_PATH flipped, apps healthy). Lifecycle gaps found:
- C1 (critical): decommission/eject leave a stale LXC mp (no pct delete)
-> guest fails pre-start hook on next reboot, ALL apps down. Recovered
live via pct set --delete mp1.
- H1: /api/storage/{disconnect,reconnect,restart-apps} are 404 (unrouted)
though settings.js calls them.
- H2: no UI button for /api/storage/decommission.
- H3: no one-click re-enroll for a decommissioned drive.
- M1: decommissioning the default drive doesn't reassign the default.
- M2: eject does NOT auto-remount (intent-gated like decommission).
- M3: userdata/import/calibre created 755 at enroll; migrate-all doesn't
re-assert setgid on pre-existing dirs (16/17 preserved 2775).
- M4: felhom-usb mount unit had stale UUID; M6: test-email fails (no SMTP).
End-state sane: apps healthy on felhom-flash (now default), both data
drives Aktiv, reboot-safe, sda/PBS/controller untouched.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Report-only spike + one live throwaway FORMAT experiment on /dev/sdc.
No production code, no version bump.
- Q1: raw-disk discovery gap confirmed — Observe AND localapi GET /disks
are both built only from PVE storages (ListStorage/NodeStorage); a
not-yet-storage disk is invisible. proxmox client has no /disks/* method.
- Q3: safety classifier (RoleForRawDevice/SystemDisks/RoleForStorage)
already protects sda/felhom-pbs live, but needs two hardening checks:
whole-disk-granularity enrolled-subtraction + LVM-PV membership.
- Q6: FORMAT path proven end-to-end live — pvesh disks/directory create
-> agent Observe -> controller "Regisztralas" enroll. sdc left as the
clean enrolled felhom-flash B3 migration target. sda/sdb untouched.
- Premise correction: felhom-pbs is remote (no local PBS disk).
- Fork list for the build spec in the audit doc + REPORT.md.
Validated both unvalidated 10D mechanisms: (1) identity-bundle escrow round-trip
via age scrypt+AEAD (recover on a secret-less box, wrong-R fails closed), (2)
Cloudflare tunnel re-establishment — running the recovered token on a new box
routes the hostname there immediately (no DNS change); the old connector is a
hot standby, superseded in routing but not auto-retired -> 10D must rotate the
tunnel/PBS token + retire the stale connector for host-loss security. Redacted;
secrets shredded; live demo untouched.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Agent-only implementation (felhom-agent v0.17.0 escrow.Consume); no hub code
change. 10C done; 10D is the last piece of slice 10.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add DELETE /hosts/{id}/jobs/{job_id} (per-host self-scoped, idempotent) so the
agent clears a job after executing or terminally rejecting it. The hub stores
the operator-signed blobs opaquely (no signing key — cannot forge or open);
the agent verifies + executes. Doc 03 §4/§6/§9 updated (operator-signed path
live; 8C wipe completes; 10B done).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Serve operator intent to authenticated hosts: PUT /admin/hosts/{id}/desired-state
(global key) bumps desired_generation; GET /hosts/{id}/desired-state + /jobs are
per-host self-scoped; the host-report envelope now carries the real generation +
has_signed_ops. New signed_jobs table + store methods. Desired-state stored/served
opaquely (agent owns the schema). Cross-repo golden (envelope + desired-state)
byte-identical with felhom-agent; doc 03 §4/§9 updated.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Update the cross-repo host-report golden byte-identical with felhom-agent
(host.cpu_temp_c). Document GET /host/metrics in doc 03 section 6 and define
slice 9 in the section 9 roadmap. No hub code change / no version bump.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Doc-only spike (no hub code change). Validated on demo-felhom (guest 8200,
torn down): (1) guest->host HTTPS over vmbr0 with fingerprint-pin + bearer +
self-scoping (200/401/403, wrong-pin TLS fail, no firewall rule needed);
(2) config-mount + golden-baked bootstrap unit deploys+runs the controller
(docker login/pull/run v0.34.0) with no pct exec. Verdict: GO to 8A spec.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>