Retire the legacy bifrost-persona-feedback deploy route

The operator ruled "delete the Bifrost lane". This route was that lane's live
trigger: /srv/odin/deploy-manifests/bifrost-persona-feedback runs
`docker compose up persona-feedback`, and the provider actually running on
Yggdrasil is the native bifrost-persona-feedback.service. Firing the route
would start a container next to it.

Following deploy/legacy's own order, the grant goes first, then the dispatch:
- sudoers: drop `deploy bifrost-persona-feedback *`.
- idunn-yggdrasil: drop the target's policy case and its dispatch token, plus
  the Bifrost release-authority check that only it used (requires_bifrost_authority
  and the `idunn validate-release-authority` call). A deploy request that claims
  a Bifrost authority is still refused. The manifest environment keeps
  IDUNN_REQUIRES_BIFROST_AUTHORITY=false, because the installed Epiphany manifest
  checks for it.
- README: record the retirement and the counts. Also record that on 2026-09-30
  the installed actuator matched this file, but the sudoers grant was not
  installed: the idunn user cannot sudo.

Rebinding persona-feedback to a current Idunn binding is a separate follow-up.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Meta Vens 2026-09-30 21:40:21 +02:00
commit 889e23629c
3 changed files with 34 additions and 55 deletions

View file

@ -4,11 +4,16 @@
kept because they are what is *running*, and a migration needs both sides.
`idunn-yggdrasil` is a root shell actuator reached through the narrow sudo grant
in `idunn-yggdrasil.sudoers`. It hardcodes eleven targets, their repositories
in `idunn-yggdrasil.sudoers`. It hardcodes ten targets, their repositories
and their admitted refs, then delegates to executable manifests under
`/srv/odin/deploy-manifests/<target>`. It calls `idunn validate-release-authority`
and `idunn validate-minimum-source-revision` — subcommands the current binary
does not have.
`/srv/odin/deploy-manifests/<target>`. It calls `idunn validate-minimum-source-revision`,
a subcommand the current binary does not have.
Checked on yggdrasil 2026-09-30: `/usr/local/libexec/idunn-yggdrasil` is installed
and matches this file byte for byte (before the retirement below), but
`idunn-yggdrasil.sudoers` is not installed. `sudo -l -U idunn` says the `idunn`
user may not run sudo, so the daemon cannot reach this actuator; only an
operator running it as root can.
## What replaced it
@ -16,10 +21,10 @@ Everything this script does by convention, the current Idunn does by contract:
| Legacy mechanism | Current owner |
|---|---|
| `deployment_target_policy()` — a `case` over eleven target names | one operator binding per target, in `--bindings-dir` |
| `deployment_target_policy()` — a `case` over ten target names | one operator binding per target, in `--bindings-dir` |
| `upstream_ref=refs/heads/...` in shell | `[repository] admitted_ref` |
| `minimum_source_revision_required` | `[repository] minimum_revision` |
| `requires_bifrost_authority` + `--release-authority-store` | `[repository] selection = "signed-release"`, per target |
| `requires_bifrost_authority` + `--release-authority-store` (retired with its only target) | `[repository] selection = "signed-release"`, per target |
| `/srv/odin/deploy-manifests/<target>` shell scripts | recipe `[[steps]]` in the target's own repository |
| `run_manifest_while_holding_authority_locks` | the deployment transaction and process write-lease |
| `flock` on a brake store | `[brakes] deployment_store` and `lifecycle_store` |
@ -45,15 +50,26 @@ This is the real migration scope, read out of `deployment_target_policy()`:
| `heimdall` | GameCult/Heimdall | `main` | restart is `docker compose restart` |
| `epiphany` | GameCult/Epiphany | `codex/epiphany-shakedown-live` | |
| `epiphany-capstone-17` | GameCult/Epiphany | `codex/epiphany-shakedown-live` | second target, same repo |
| `bifrost-persona-feedback` | GameCult/Bifrost | `main` | **only** target requiring Bifrost release authority |
| ~~`bifrost-persona-feedback`~~ | GameCult/Bifrost | `main` | **retired 2026-09-30**, see below |
| `repixelizer` | GameCult/repixelizer | `main` | |
| `streampixels` | GameCult/StreamPixels | `main` | two units, one target |
| `gjallar` | GameCult/Gjallar | `codex/yggdrasil-aggregate-daemon` | |
Seven of eleven are pinned to a non-`main` branch. That is its own problem — the
Seven of the ten live targets are pinned to a non-`main` branch. That is its own problem — the
swarm goal is everyone on `main` and current with each other — and migrating a
target is the moment to fix its ref, not to copy it forward.
## Retired targets
- `bifrost-persona-feedback`, 2026-09-30. Its manifest ran the Bifrost compose lane
(`docker compose up persona-feedback`), while the live provider is the native
`bifrost-persona-feedback.service`, so firing it would have started a container
beside the native unit. The operator ruled "delete the Bifrost lane". Its grant,
dispatch case and the Bifrost release-authority check it alone used are gone; a
deploy request that claims a Bifrost authority is refused. It was the only
caller of `idunn validate-release-authority`. Rebinding persona-feedback to a
current Idunn binding is a separate follow-up.
## When these files die
When every target above has a binding and a recipe, and `idunn serve` owns

View file

@ -96,14 +96,12 @@ deployment_target_policy() {
daemon_id=yggdrasil-odin
repository_full_name=GameCult/Odin
upstream_ref=refs/heads/codex/ygg-idunn-independent-bootstrap
requires_bifrost_authority=false
manifest_takes_action=true
;;
codex-connector)
daemon_id=yggdrasil-codex-connector
repository_full_name=GameCult/CodexConnector
upstream_ref=refs/heads/codex/ghostlight-release-binding
requires_bifrost_authority=false
minimum_source_revision_required=true
manifest_takes_action=true
;;
@ -111,7 +109,6 @@ deployment_target_policy() {
daemon_id=yggdrasil-ghostlight
repository_full_name=GameCult/Ghostlight
upstream_ref=refs/heads/codex/ghostlight-dungeon-mvp
requires_bifrost_authority=false
minimum_source_revision_required=true
manifest_takes_action=true
;;
@ -119,52 +116,39 @@ deployment_target_policy() {
daemon_id=yggdrasil-voidbot
repository_full_name=GameCult/VoidBot
upstream_ref=refs/heads/main
requires_bifrost_authority=false
manifest_takes_action=true
;;
heimdall)
daemon_id=yggdrasil-heimdall
repository_full_name=GameCult/Heimdall
upstream_ref=refs/heads/main
requires_bifrost_authority=false
;;
epiphany-capstone-17)
daemon_id=yggdrasil-epiphany-capstone-17
repository_full_name=GameCult/Epiphany
upstream_ref=refs/heads/codex/epiphany-shakedown-live
requires_bifrost_authority=false
manifest_takes_action=true
;;
epiphany)
daemon_id=yggdrasil-epiphany
repository_full_name=GameCult/Epiphany
upstream_ref=refs/heads/codex/epiphany-shakedown-live
requires_bifrost_authority=false
manifest_takes_action=true
;;
bifrost-persona-feedback)
daemon_id=yggdrasil-bifrost-persona-feedback
repository_full_name=GameCult/Bifrost
upstream_ref=refs/heads/main
requires_bifrost_authority=true
;;
repixelizer)
daemon_id=yggdrasil-repixelizer
repository_full_name=GameCult/repixelizer
upstream_ref=refs/heads/main
requires_bifrost_authority=false
;;
streampixels)
daemon_id=yggdrasil-streampixels
repository_full_name=GameCult/StreamPixels
upstream_ref=refs/heads/main
requires_bifrost_authority=false
;;
gjallar)
daemon_id=yggdrasil-gjallar
repository_full_name=GameCult/Gjallar
upstream_ref=refs/heads/codex/yggdrasil-aggregate-daemon
requires_bifrost_authority=false
;;
*) echo "deployment target has no root actuator policy: $target" >&2; exit 1 ;;
esac
@ -190,19 +174,10 @@ require_signed_deployment_grant() {
*) echo "signed deployment request is not for root target $target" >&2; exit 1 ;;
esac
case "$requires_bifrost_authority" in
true)
[ -n "$release_authority_id" ] || { echo "missing Bifrost release authority id" >&2; exit 1; }
case "$release_authority_sha256" in
*[!0-9a-f]*|'') echo "invalid or missing Bifrost release authority digest" >&2; exit 1 ;;
esac
[ "${#release_authority_sha256}" -eq 64 ] || { echo "Bifrost release authority digest must be 64 lowercase hex characters" >&2; exit 1; }
;;
false)
[ -z "$release_authority_id" ] || { echo "source-observed release cannot claim Bifrost authority id" >&2; exit 1; }
[ -z "$release_authority_sha256" ] || { echo "source-observed release cannot claim Bifrost authority digest" >&2; exit 1; }
;;
esac
# No target carries Bifrost release authority: its only one, bifrost-persona-feedback,
# was retired on 2026-09-30. A request that claims one is refused.
[ -z "$release_authority_id" ] || { echo "source-observed release cannot claim Bifrost authority id" >&2; exit 1; }
[ -z "$release_authority_sha256" ] || { echo "source-observed release cannot claim Bifrost authority digest" >&2; exit 1; }
require_root_owned_resolved_executable "$idunn_provision"
require_root_owned_regular_path "$brake_store"
@ -234,18 +209,6 @@ require_signed_deployment_grant() {
--daemon "$daemon_id" \
--source-revision "$source_commit"
fi
if [ "$requires_bifrost_authority" = true ]; then
require_root_owned_resolved_executable /usr/local/bin/idunn
/usr/local/bin/idunn validate-release-authority \
--store /var/lib/gamecult/idunn/yggdrasil.keepalive.cc \
--release-authority-store /srv/bifrost/state/repository-release-authority.cc \
--repository-full-name "$repository_full_name" \
--upstream-ref "$upstream_ref" \
--source-revision "$source_commit" \
--release-authority-id "$release_authority_id" \
--release-authority-sha256 "$release_authority_sha256"
fi
}
manifest_group_exists() {
@ -355,7 +318,7 @@ case "$action:$target" in
run_manifest_while_holding_authority_locks "$manifest" restart
exit $?
;;
deploy:voidbot|deploy:epiphany|deploy:epiphany-capstone-17|deploy:odin|deploy:codex-connector|deploy:ghostlight|deploy:heimdall|deploy:repixelizer|deploy:streampixels|deploy:bifrost-persona-feedback|deploy:gjallar)
deploy:voidbot|deploy:epiphany|deploy:epiphany-capstone-17|deploy:odin|deploy:codex-connector|deploy:ghostlight|deploy:heimdall|deploy:repixelizer|deploy:streampixels|deploy:gjallar)
manifest="/srv/odin/deploy-manifests/$target"
require_root_owned_regular_path "$manifest"
[ -x "$manifest" ] || { echo "root actuator manifest is not executable: $manifest" >&2; exit 1; }
@ -368,7 +331,8 @@ case "$action:$target" in
export BIFROST_RELEASE_AUTHORITY_ID="$release_authority_id"
export BIFROST_RELEASE_AUTHORITY_SHA256="$release_authority_sha256"
export IDUNN_DEPLOYMENT_REQUEST_ID="$deployment_request_id"
export IDUNN_REQUIRES_BIFROST_AUTHORITY="$requires_bifrost_authority"
# Installed manifests (Epiphany's) still require this to read false.
export IDUNN_REQUIRES_BIFROST_AUTHORITY=false
if [ "$manifest_takes_action" = true ]; then
run_manifest_while_holding_authority_locks "$manifest" "$action"
exit $?

View file

@ -1,7 +1,7 @@
# Narrow root actuator grant for the Idunn Yggdrasil deployment/continuity path.
# One line per live target. codex-connector and gjallar were retired 2026-09-06;
# their grants are removed here first, because the privileged half of a retired
# target must not outlive its consumer.
# One line per live target. codex-connector and gjallar were retired 2026-09-06,
# and bifrost-persona-feedback on 2026-09-30; their grants are removed here first,
# because the privileged half of a retired target must not outlive its consumer.
Cmnd_Alias IDUNN_YGGDRASIL_DEPLOY = \
/usr/local/libexec/idunn-yggdrasil deploy odin *, \
/usr/local/libexec/idunn-yggdrasil deploy ghostlight *, \
@ -9,7 +9,6 @@ Cmnd_Alias IDUNN_YGGDRASIL_DEPLOY = \
/usr/local/libexec/idunn-yggdrasil deploy heimdall *, \
/usr/local/libexec/idunn-yggdrasil deploy epiphany-capstone-17 *, \
/usr/local/libexec/idunn-yggdrasil deploy epiphany *, \
/usr/local/libexec/idunn-yggdrasil deploy bifrost-persona-feedback *, \
/usr/local/libexec/idunn-yggdrasil deploy repixelizer *, \
/usr/local/libexec/idunn-yggdrasil deploy streampixels *
Cmnd_Alias IDUNN_YGGDRASIL_RESTART = \