feat(release): recursion guard for the health gate; run-task drift warning; M20 packages/* decision recorded (#39)

- release.sh health gate runs with MOSAIC_ENSURE_SKIP=1: the gated task run
  cannot re-enter release self-determination
- run-task.sh warns on release drift instead of silently using a stale image
- ROADMAP: M20 decision recorded (packages/* monorepo at usurpation,
  continuity-first); restructure sequenced as M20 phase 1

Closes #39
This commit is contained in:
2026-09-03 15:58:46 -05:00
parent 9051ad179b
commit 9fd16b9739
16 changed files with 440 additions and 31 deletions
+26 -4
View File
@@ -22,6 +22,7 @@ MISSION=""
WORKSPACE=""
SESSION=""
TOOLS=""
SKILLS=""
while [ $# -gt 0 ]; do
case "$1" in
@@ -29,6 +30,7 @@ while [ $# -gt 0 ]; do
--workspace) WORKSPACE="${2:?}"; shift 2 ;;
--session) SESSION="${2:?}"; shift 2 ;;
--tools) TOOLS="${2:?}"; shift 2 ;;
--skills) SKILLS="${2:?}"; shift 2 ;;
--help|-h) sed -n '2,12p' "$0"; exit 0 ;;
*) NAME="$1"; shift ;;
esac
@@ -57,6 +59,8 @@ fi
AGENTS_DIR="${MOSAIC_AGENTS_DIR:-agents}"
ROLE=""
DEFCAPS=""
AGENT_DEF_SKILLS=""
AGENT_DEF_SKILLS=""
if [ -f "$AGENTS_DIR/$NAME/agent.json" ]; then
DEFAULTS_FILE="$(mktemp)"
node -e '
@@ -73,13 +77,14 @@ if (p.capabilities !== undefined) {
if (!Array.isArray(p.capabilities.tools) || p.capabilities.tools.some(t => !/^[a-z]+$/.test(t))) process.exit(2);
tools = p.capabilities.tools.join(",");
}
fs.writeFileSync(process.argv[2], "AGENT_DEF_ROLE=" + (p.role || "") + "\nAGENT_DEF_CAPS=" + tools + "\n");
fs.writeFileSync(process.argv[2], "AGENT_DEF_ROLE=" + (p.role || "") + "\nAGENT_DEF_CAPS=" + tools + "\nAGENT_DEF_SKILLS=" + ((p.skills && Array.isArray(p.skills)) ? p.skills.join(",") : "") + "\n");
' "$AGENTS_DIR/$NAME/agent.json" "$DEFAULTS_FILE" || { rm -f "$DEFAULTS_FILE"; echo "agent: invalid agent definition" >&2; exit 2; }
AGENT_DEF_ROLE=""; AGENT_DEF_CAPS=""
AGENT_DEF_ROLE=""; AGENT_DEF_CAPS=""; AGENT_DEF_SKILLS=""
while IFS= read -r line; do
case "$line" in
AGENT_DEF_ROLE=*) AGENT_DEF_ROLE="${line#AGENT_DEF_ROLE=}" ;;
AGENT_DEF_CAPS=*) AGENT_DEF_CAPS="${line#AGENT_DEF_CAPS=}" ;;
AGENT_DEF_ROLE=*) AGENT_DEF_ROLE="${line#AGENT_DEF_ROLE=}" ;;
AGENT_DEF_CAPS=*) AGENT_DEF_CAPS="${line#AGENT_DEF_CAPS=}" ;;
AGENT_DEF_SKILLS=*) AGENT_DEF_SKILLS="${line#AGENT_DEF_SKILLS=}" ;;
esac
done < "$DEFAULTS_FILE"
rm -f "$DEFAULTS_FILE"
@@ -106,6 +111,23 @@ export MOSAIC_INTERACTIVE=1
if [ -z "$TOOLS" ] && [ -n "$DEFCAPS" ]; then TOOLS="$DEFCAPS"; fi
export MOSAIC_TOOLS="${TOOLS:+$TOOLS}"
# Skills (M17): seat definition may declare skill names; each must be
# enabled in <dataRoot>/skills-enabled or the launch refuses - a silently
# under-equipped seat is the failure mode this prevents.
SKILLS_LIST="${SKILLS:-$AGENT_DEF_SKILLS}"
if [ -n "$SKILLS_LIST" ]; then
mkdir -p "$MOSAIC_DEV_DIR/skills-enabled"
RESOLVED=""
OLDIFS=$IFS; IFS=','
for s in $SKILLS_LIST; do
case "$s" in *[!A-Za-z0-9._-]*|'') echo "agent: invalid skill name: '$s'" >&2; exit 2;; esac
[ -d "$MOSAIC_DEV_DIR/skills-enabled/$s" ] || { echo "agent: skill '$s' is declared but not enabled (scripts/skill.sh activate $s)" >&2; exit 1; }
RESOLVED="${RESOLVED:+$RESOLVED,}/var/lib/mosaic/skills-enabled/$s"
done
IFS=$OLDIFS
export MOSAIC_SKILLS="$RESOLVED"
fi
if [ -n "$MISSION" ]; then
[ -r "$MISSION" ] || { echo "agent: mission file not readable: $MISSION" >&2; exit 4; }
mkdir -p "$MOSAIC_DEV_DIR/agent-missions"