feat(release): self-determination - ensure at launch, drift warnings, M20 packages/* decision (#38)
- release.sh ensure: aligned no-op; drift -> package-if-needed + health-gated activate (M3 gate-then-flip, automated) - ensure_release_aligned in common.sh: invoked by hello/verify/agent; MOSAIC_ENSURE_SKIP guards recursion; run-task warns on drift without auto-aligning (workers/suites never trigger builds or model gates) - ROADMAP: M20 decision recorded - v2 adopts packages/* monorepo at usurpation - BUILD-LOG Phase 20 + tool-race process note Live-verified: post-reset pointer loss auto-restored via health-gated ensure; drift warning fires on desired-version bump; idempotent no-op on aligned state. Closes #38
This commit is contained in:
@@ -40,6 +40,7 @@ case "$NAME" in *[!A-Za-z0-9._-]*|'') echo "agent: invalid agent name" >&2; exit
|
||||
load_config
|
||||
load_release
|
||||
bootstrap_runtime_dir
|
||||
ensure_release_aligned
|
||||
|
||||
# Onboarding gate (M16): a TUI agent cannot launch without a user profile.
|
||||
# The onboarding wizard runs automatically here - the TTY is already yours.
|
||||
|
||||
@@ -55,3 +55,19 @@ bootstrap_runtime_dir() {
|
||||
# auto-invoked by agent.sh) creates it from the template.
|
||||
mkdir -p "$MOSAIC_DEV_DIR/user"
|
||||
}
|
||||
|
||||
# M16 release self-determination: launchers call this after load_release.
|
||||
# Cheap no-op when the active pointer matches the desired RELEASE; on drift
|
||||
# it delegates to `release.sh ensure` (package if needed, health-gated
|
||||
# activate). The health gate's own task run sets MOSAIC_ENSURE_SKIP so the
|
||||
# ensure never re-enters itself.
|
||||
ensure_release_aligned() {
|
||||
[ -n "${MOSAIC_ENSURE_SKIP:-}" ] && return 0
|
||||
local pointer="$MOSAIC_DEV_DIR/state/active.json"
|
||||
local active=""
|
||||
if [ -f "$pointer" ]; then
|
||||
active="$(node -e 'try{const p=JSON.parse(require("fs").readFileSync(process.argv[1],"utf8"));process.stdout.write(p.release||"")}catch{}' "$pointer" 2>/dev/null)"
|
||||
fi
|
||||
[ "$active" = "$MOSAIC_RELEASE" ] && return 0
|
||||
MOSAIC_ENSURE_SKIP=1 scripts/release.sh ensure
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ load_config
|
||||
load_release
|
||||
|
||||
bootstrap_runtime_dir
|
||||
ensure_release_aligned
|
||||
|
||||
# -T: no pseudo-TTY, so stdout is clean model output.
|
||||
# </dev/null: detach stdin. Pi's print mode reads piped stdin until EOF;
|
||||
|
||||
+20
-1
@@ -140,10 +140,29 @@ cmd_rollback() {
|
||||
activate "$prev_release" "$prev" "rollback"
|
||||
}
|
||||
|
||||
cmd_ensure() {
|
||||
# M16 release self-determination: the system determines what is installed
|
||||
# and aligns itself. Aligned (pointer == RELEASE, image present) -> no-op.
|
||||
# Drifted -> package if needed, then health-gated activate.
|
||||
local pointer="$MOSAIC_DEV_DIR/state/active.json"
|
||||
local active=""
|
||||
if [ -f "$pointer" ]; then
|
||||
active="$(node -e 'try{const p=JSON.parse(require("fs").readFileSync(process.argv[1],"utf8"));process.stdout.write(p.release||"")}catch{}' "$pointer")"
|
||||
fi
|
||||
if [ "$active" = "$MOSAIC_RELEASE" ] && image_exists "$MOSAIC_IMAGE_TAG"; then
|
||||
echo "release: aligned at $MOSAIC_RELEASE ($MOSAIC_IMAGE_TAG)"
|
||||
return 0
|
||||
fi
|
||||
echo "release: drift (active: ${active:-none}, desired: $MOSAIC_RELEASE) - aligning"
|
||||
image_exists "$MOSAIC_IMAGE_TAG" || cmd_package
|
||||
activate "$MOSAIC_RELEASE" "$MOSAIC_IMAGE_TAG" "activate"
|
||||
}
|
||||
|
||||
case "${1:-}" in
|
||||
package) cmd_package ;;
|
||||
activate) shift; cmd_activate "$@" ;;
|
||||
rollback) cmd_rollback ;;
|
||||
status) cmd_status ;;
|
||||
*) echo "usage: scripts/release.sh package | activate [--fault-injection] | rollback | status" >&2; exit 4 ;;
|
||||
ensure) shift; cmd_ensure "$@" ;;
|
||||
*) echo "usage: scripts/release.sh package | activate [--fault-injection] | rollback | status | ensure" >&2; exit 4 ;;
|
||||
esac
|
||||
|
||||
@@ -14,6 +14,13 @@ load_config
|
||||
load_release # compose requires MOSAIC_IMAGE_TAG; task runs are release-scoped too
|
||||
bootstrap_runtime_dir
|
||||
|
||||
# Workers never auto-align releases (no builds or model gates mid-automation);
|
||||
# drift is reported so the operator or a human-facing launcher can align.
|
||||
if [ -f "$MOSAIC_DEV_DIR/state/active.json" ]; then
|
||||
ACTIVE_REL="$(node -e 'try{const p=JSON.parse(require("fs").readFileSync(process.argv[1],"utf8"));process.stdout.write(p.release||"")}catch{}' "$MOSAIC_DEV_DIR/state/active.json")"
|
||||
[ "$ACTIVE_REL" = "$MOSAIC_RELEASE" ] || echo "run-task: note: release drift (active: ${ACTIVE_REL:-none}, desired: $MOSAIC_RELEASE) - run scripts/release.sh ensure to align" >&2
|
||||
fi
|
||||
|
||||
if [ ! -f "$MOSAIC_DEV_DIR/user/USER.md" ]; then
|
||||
echo "run-task: note: user profile not onboarded - continuing without user context (scripts/onboard.sh)" >&2
|
||||
fi
|
||||
|
||||
@@ -30,6 +30,7 @@ IMAGE="$MOSAIC_IMAGE_TAG"
|
||||
# Ensure the configured data root exists (host-owned) before the mount,
|
||||
# otherwise Docker would auto-create a root-owned directory.
|
||||
bootstrap_runtime_dir
|
||||
ensure_release_aligned
|
||||
|
||||
# 1. Build the image only if it is not already present.
|
||||
if ! docker image inspect "$IMAGE" >/dev/null 2>&1; then
|
||||
|
||||
Reference in New Issue
Block a user