feat(framework): P4 (2/2) — TS installer parity + fixtures + fail-closed init
Some checks failed
ci/woodpecker/pr/ci Pipeline failed
ci/woodpecker/push/ci Pipeline was successful

Mirror install.sh's upgrade-safe migration in the npm/TS install path and lock it
with CI-gating fixtures:
- file-adapter.ts: FRAMEWORK_OWNED_FILES (overwrite, backup-once to
  .pre-constitution.bak) vs USER_SEEDED_FILES (seed-if-absent); CONSTITUTION.md
  added to preserve; reconcile mirrors reconcile_framework_files() in install.sh
- file-adapter.test.ts: 5 migration fixtures (overwrite framework-owned + backup,
  idempotent backup-once, preserve SOUL/credentials, preserve user-seeded TOOLS);
  updated the prior "preserve AGENTS" test that P4 intentionally inverts
- mosaic-init: fail-closed persona — --name is REQUIRED in --non-interactive mode
  (no silent agent named "Assistant"); verified exit 1 + clear error
- verify-sanitized.sh self-test: prove the identity scan actually covers
  *.yaml/*.service config formats (Claude review ticket)

Both installers now behave identically. install.sh fixture suite 14/14 green;
gate green; mosaic-init fail-closed verified.

Refs #542

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-21 17:56:01 -05:00
parent d2d0279e92
commit 1064427c6d
4 changed files with 79 additions and 18 deletions

View File

@@ -274,6 +274,13 @@ detect_existing_config
echo "[mosaic-init] Generating SOUL.md — agent identity contract"
echo ""
# Fail-closed persona: in non-interactive mode the agent NAME must be supplied
# explicitly (--name) — never silently ship an agent named "Assistant".
if [[ $NON_INTERACTIVE -eq 1 && -z "$AGENT_NAME" ]]; then
echo "[mosaic-init] ERROR: --name (agent name) is required in non-interactive mode." >&2
exit 1
fi
prompt_if_empty AGENT_NAME "What name should agents use" "Assistant"
prompt_if_empty ROLE_DESCRIPTION "Agent role description" "execution partner and visibility engine"

View File

@@ -53,9 +53,15 @@ _selftest() {
local tmp; tmp="$(mktemp -d)" || return 1
printf 'contact jason.woltje at jarvis-brain (PDA-friendly)\n' > "$tmp/planted.md"
printf 'X="${VAR:-$HOME/src/whatever/x.json}"\n' > "$tmp/planted.sh"
printf 'name: jason-woltje\n' > "$tmp/planted.yaml"
printf '[Service]\nUser=jarvis\n' > "$tmp/planted.service"
local rc=0
grep -qIEi "$DENYLIST" "$tmp/planted.md" || { echo "✗ SELF-TEST: identity denylist regex broken" >&2; rc=1; }
grep -qIE "$STRUCTURAL_SH" "$tmp/planted.sh" || { echo "✗ SELF-TEST: structural regex broken" >&2; rc=1; }
# Prove the identity scan covers the config formats it claims to (yaml/service/etc).
local n_ext
n_ext=$(find "$tmp" -type f \( -name '*.yaml' -o -name '*.service' \) -print0 | xargs -0 -r grep -lIEi "$DENYLIST" 2>/dev/null | wc -l)
[[ "$n_ext" -eq 2 ]] || { echo "✗ SELF-TEST: identity scan does not cover .yaml/.service extensions" >&2; rc=1; }
rm -rf "$tmp"; return $rc
}
_selftest || exit 2