diff --git a/BUILD-LOG.md b/BUILD-LOG.md index 5f16fc47..746952c1 100644 --- a/BUILD-LOG.md +++ b/BUILD-LOG.md @@ -401,3 +401,29 @@ left them unwritten at the time of work. Ground truth for each entry: the git history (commit subjects/bodies), the Gitea milestone/issue records (#32, #34, #35, #36), and the suite files themselves. No facts were reconstructed from memory alone. + +--- + +## Phase 20: Release self-determination (M16) + +### Entry 20.1 — before + +- Timestamp: 2026-09-03 +- Intended action: The system determines what is installed and aligns itself; the user never manually runs release.sh. `release.sh ensure` (align-or-noop) invoked automatically by human-facing launchers (hello, verify, agent); run-task reports drift without auto-aligning (workers/suites must not trigger builds or model gates mid-automation). +- Reason: Owner direction — intelligence in operation; post-reset pointer loss required a manual release command, which contradicts the self-healing design. +- Expected result: post-reset pointer loss auto-restores via health-gated ensure; drift warns on run-task; aligned state is a no-op; M20 packages/* decision recorded. + +### Entry 20.2 — after + +- Timestamp: 2026-09-03 +- Commands run: ensure with pointer removed (live drift); ensure idempotence; drift-warning demo via RELEASE bump without packaging; full suites. +- Observed result: drift detected (active: none, desired: 0.0.11) -> health-gated activate -> aligned; second ensure no-op; run-task drift warning fired on desired-version bump without packaging; all suites green (24/68/17/14 + verify). +- Failure or correction: + 1. First run-task edit anchor missed (inline comment mismatch) — reapplied with exact text. + 2. RELEASE was temporarily bumped to 0.0.12 without packaging for the drift demo — restored to 0.0.11; state pointer remained aligned. +- M20 decision recorded in ROADMAP.md: v2 adopts packages/* monorepo structure at usurpation (owner, continuity-first). +- Process note (tool races): mechanism confirmed — batching a file write/edit and a dependent bash command in one parallel block runs the bash before the write flushes. Consequences: lost doc updates, stale anchors, one premature grep. Operating rule: dependent calls sequential; every write verified before claimed complete. + +## Result (M16) + +Release self-determination live: the system aligns itself to RELEASE without manual commands. Suites 24/68/17/14 + verify green. diff --git a/docs/plans/ROADMAP.md b/docs/plans/ROADMAP.md index c82e98cf..ba8df490 100644 --- a/docs/plans/ROADMAP.md +++ b/docs/plans/ROADMAP.md @@ -120,10 +120,9 @@ usurpation, v2 content becomes THE next-branch content on mosaicstack/stack — a full refactor-and-replace with a functional system. Not ready yet. -Open decision at usurpation time: whether v2 reorganizes to the stack -monorepo layout (`packages/*` — continuity with existing tooling) or keeps -its flat structure and defines the new convention (clean break). Reference -for either: harvest worktree `~/src/mosaic-stack-worktrees/v2-harvest` +DECIDED (owner, 2026-09-03): v2 reorganizes to the `packages/*` monorepo +structure for continuity with the existing stack tooling and expectations. +Reference: harvest worktree `~/src/mosaic-stack-worktrees/v2-harvest` (branch `next` @ 0db2d19a) and `packages/mosaic` (CLI), `packages/auth`. Usurpation criteria to be defined before any replacement (proposed: functional parity of P0 operations on the v2 runtime + owner sign-off). diff --git a/scripts/agent.sh b/scripts/agent.sh index 787685fb..4f663960 100755 --- a/scripts/agent.sh +++ b/scripts/agent.sh @@ -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. diff --git a/scripts/common.sh b/scripts/common.sh index 8bffd368..23bb503c 100755 --- a/scripts/common.sh +++ b/scripts/common.sh @@ -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 +} diff --git a/scripts/hello.sh b/scripts/hello.sh index becfd479..4e4a30b8 100755 --- a/scripts/hello.sh +++ b/scripts/hello.sh @@ -14,6 +14,7 @@ load_config load_release bootstrap_runtime_dir +ensure_release_aligned # -T: no pseudo-TTY, so stdout is clean model output. # 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 diff --git a/scripts/run-task.sh b/scripts/run-task.sh index 4aac0a08..ff58cfb9 100755 --- a/scripts/run-task.sh +++ b/scripts/run-task.sh @@ -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 diff --git a/scripts/verify.sh b/scripts/verify.sh index 3d057d00..0aff6c85 100755 --- a/scripts/verify.sh +++ b/scripts/verify.sh @@ -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