Skip to content

Commit c99ecd6

Browse files
committed
feat: bridge Codex v0.5–v0.6 advisory coexistence + smoke-headless checks (3/4)
Phase 3 of the Codex-Game-Studios v0.5–v0.6 bridge: surface installer-safety regressions and command/skill graph integrity through advisory (non-blocking) checks, mirroring upstream's advisory-CI pattern. - audit.sh: new standalone run_coexistence command — runs a real install/uninstall matrix in a temp dir (fresh install, unowned-collision abort, modified-file abort, --replace-modified restore, uninstall missing-state abort, uninstall valid-state clean removal, transactional rollback on forced mid-deploy failure). Auto-picks a model ID from opencode models when available, else relies on validation-SKIP so it runs in model-less CI. chmod-444 rollback path is platform-guarded. - audit.sh: new standalone run_smoke_headless command — command->skill graph integrity (every commands/*.md resolves to an existing skills/<name>/SKILL.md) plus a clear note that model-driven smoke is deferred until a CI model runner exists. - .github/workflows/release-check.yml: coexistence-advisory and smoke-headless-advisory jobs with continue-on-error: true; the existing validate job remains the blocking gate. Verified: bash -n OK; YAML parses; audit.sh all still 0 errors (new commands are standalone, not wired into all); coexistence 9/9 (7 scenarios); smoke-headless 2/2 (77 commands resolve). Not wired into all to keep the blocking gate fast; these are advisory by design.
1 parent 4a6489e commit c99ecd6

2 files changed

Lines changed: 130 additions & 2 deletions

File tree

.github/workflows/release-check.yml

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,3 +39,24 @@ jobs:
3939
- name: Release check (on tag)
4040
if: startsWith(github.ref, 'refs/tags/opencode-v')
4141
run: bash .opencode/audit.sh release --root .
42+
43+
# Advisory (non-blocking) jobs: surface installer-safety regressions early
44+
# without failing the build. Mirror the Codex-Game-Studios advisory pattern.
45+
coexistence-advisory:
46+
runs-on: ubuntu-latest
47+
continue-on-error: true
48+
steps:
49+
- uses: actions/checkout@v4
50+
- uses: actions/setup-python@v5
51+
with:
52+
python-version: '3.x'
53+
- name: Installer coexistence matrix
54+
run: bash .opencode/audit.sh coexistence --root .
55+
56+
smoke-headless-advisory:
57+
runs-on: ubuntu-latest
58+
continue-on-error: true
59+
steps:
60+
- uses: actions/checkout@v4
61+
- name: Headless smoke (command/skill graph)
62+
run: bash .opencode/audit.sh smoke-headless --root .

.opencode/audit.sh

Lines changed: 109 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ command="${1:-all}"
3030
while [ "$#" -gt 0 ]; do
3131
case "$1" in
3232
--root) root="$(cd "$2" && pwd -P)"; shift 2 ;;
33-
all|agents|skills|runtime|config|hooks|smoke|release|closeout|checkpoint|playtest|bug-lifecycle|handoff-review|resume-contract) command="$1"; shift ;;
33+
all|agents|skills|runtime|config|hooks|smoke|release|closeout|checkpoint|playtest|bug-lifecycle|handoff-review|resume-contract|install-safety|coexistence|smoke-headless) command="$1"; shift ;;
3434
*) shift ;;
3535
esac
3636
done
@@ -651,6 +651,111 @@ run_install_safety() {
651651
return $problems
652652
}
653653

654+
run_coexistence() {
655+
printf '\n── Coexistence / Installer Matrix ───────────────────────────\n'
656+
printf ' (advisory — runs a real install/uninstall matrix in a temp dir)\n'
657+
local inst="$root/.opencode/install.sh"
658+
local uninst="$root/.opencode/uninstall.sh"
659+
[ -f "$inst" ] && [ -f "$uninst" ] || { fail "install.sh/uninstall.sh not found"; return 1; }
660+
661+
local model=""
662+
if command -v opencode >/dev/null 2>&1; then model="$(opencode models 2>/dev/null | head -1)"
663+
elif [ -x "$HOME/.opencode/bin/opencode" ]; then model="$("$HOME/.opencode/bin/opencode" models 2>/dev/null | head -1)"; fi
664+
[ -n "$model" ] || model="local/test"
665+
local MF="--tier-opus $model --tier-sonnet $model --tier-haiku $model --primary $model"
666+
667+
local base T out rc
668+
base="$(mktemp -d "${TMPDIR:-/tmp}/ccgs-coex.XXXXXX")" || { fail "could not create temp dir"; return 1; }
669+
670+
# S1 fresh install
671+
T="$base/s1"; mkdir -p "$T"
672+
rc=0; out="$(bash "$inst" $MF "$T" 2>&1)" || rc=$?
673+
if [ "$rc" -eq 0 ] && [ -f "$T/.opencode/install-state.json" ]; then pass "S1 fresh install + state"; else fail "S1 fresh install (rc=$rc)"; fi
674+
675+
# S2 unowned collision -> preflight abort, no mutation
676+
T="$base/s2"; mkdir -p "$T/.github"; printf 'USER\n' > "$T/.github/CODEOWNERS"
677+
rc=0; out="$(printf 'y\n' | bash "$inst" $MF "$T" 2>&1)" || rc=$?
678+
if [ "$rc" -ne 0 ] && printf '%s' "$out" | grep -qi 'Preflight conflict'; then pass "S2 unowned collision abort"; else fail "S2 collision abort (rc=$rc)"; fi
679+
if [ -f "$T/.opencode/install-state.json" ]; then fail "S2 mutated target despite abort"; else pass "S2 no mutation"; fi
680+
681+
# S3 modified-file -> preflight abort, then S4 --replace-modified
682+
T="$base/s3"; mkdir -p "$T"
683+
bash "$inst" $MF "$T" >/dev/null 2>&1 || true
684+
printf 'USER-EDIT\n' > "$T/.opencode/VERSION"
685+
rc=0; out="$(bash "$inst" $MF "$T" 2>&1)" || rc=$?
686+
if [ "$rc" -ne 0 ] && printf '%s' "$out" | grep -qi 'Locally-modified'; then pass "S3 modified-file abort"; else fail "S3 modified abort (rc=$rc)"; fi
687+
rc=0; out="$(bash "$inst" $MF --replace-modified "$T" 2>&1)" || rc=$?
688+
if [ "$rc" -eq 0 ] && [ "$(cat "$T/.opencode/VERSION" 2>/dev/null)" = "$(cat "$root/.opencode/VERSION")" ]; then pass "S4 --replace-modified restore"; else fail "S4 replace-modified (rc=$rc)"; fi
689+
690+
# S5 uninstall missing-state -> abort, no removal
691+
T="$base/s5"; mkdir -p "$T"
692+
bash "$inst" $MF "$T" >/dev/null 2>&1 || true
693+
local agents_before
694+
agents_before="$(ls "$T/.opencode/agents"/*.md 2>/dev/null | wc -l | tr -d ' ')"
695+
rm -f "$T/.opencode/install-state.json"
696+
rc=0; out="$(bash "$uninst" "$T" 2>&1)" || rc=$?
697+
if [ "$rc" -ne 0 ] && printf '%s' "$out" | grep -qi 'cannot uninstall safely'; then pass "S5 uninstall missing-state abort"; else fail "S5 missing-state abort (rc=$rc)"; fi
698+
if [ "$(ls "$T/.opencode/agents"/*.md 2>/dev/null | wc -l | tr -d ' ')" = "$agents_before" ]; then pass "S5 no files removed"; else fail "S5 files removed despite abort"; fi
699+
700+
# S6 uninstall valid-state -> clean removal
701+
T="$base/s6"; mkdir -p "$T"
702+
bash "$inst" $MF "$T" >/dev/null 2>&1 || true
703+
rc=0; out="$(bash "$uninst" "$T" 2>&1)" || rc=$?
704+
if [ "$rc" -eq 0 ] && [ ! -f "$T/.opencode/install-state.json" ] && [ ! -f "$T/.opencode/agents/creative-director.md" ]; then pass "S6 uninstall valid-state clean"; else fail "S6 valid-state uninstall (rc=$rc)"; fi
705+
706+
# S7 transactional rollback on forced mid-deploy failure
707+
T="$base/s7"; mkdir -p "$T"
708+
bash "$inst" $MF "$T" >/dev/null 2>&1 || true
709+
printf 'MY-MOD\n' > "$T/.opencode/.gitignore"
710+
rm -f "$T/.opencode/README.md"
711+
if chmod 444 "$T/.opencode/uninstall.sh" 2>/dev/null; then
712+
local a_before a_after
713+
a_before="$(cat "$T/.opencode/.gitignore")"
714+
rc=0; out="$(bash "$inst" $MF --replace-modified "$T" 2>&1)" || rc=$?
715+
chmod 644 "$T/.opencode/uninstall.sh" 2>/dev/null || true
716+
a_after="$(cat "$T/.opencode/.gitignore" 2>/dev/null)"
717+
if [ "$rc" -ne 0 ] && printf '%s' "$out" | grep -qi 'rolling back' && [ "$a_after" = "$a_before" ] && [ ! -f "$T/.opencode/README.md" ]; then
718+
pass "S7 transactional rollback (restore + remove)"
719+
else
720+
fail "S7 rollback (rc=$rc)"
721+
fi
722+
else
723+
pass "S7 rollback: SKIP (chmod 444 unsupported)"
724+
fi
725+
726+
rm -rf "$base" 2>/dev/null || true
727+
return 0
728+
}
729+
730+
run_smoke_headless() {
731+
printf '\n── Headless Smoke (non-model) ──────────────────────────────\n'
732+
printf ' (advisory — command/skill graph integrity; model-driven smoke deferred)\n'
733+
local problems=0 cmds=0 broken=0
734+
735+
# command -> skill graph integrity
736+
local c ref skill_path
737+
for c in "$root"/.opencode/commands/*.md; do
738+
[ -f "$c" ] || continue
739+
cmds=$((cmds + 1))
740+
ref="$(grep -oE '@\.opencode/skills/[^/]+/SKILL\.md' "$c" 2>/dev/null | head -1)"
741+
if [ -z "$ref" ]; then
742+
fail "$(basename "$c" .md): no @skill reference"
743+
broken=$((broken + 1)); problems=1
744+
continue
745+
fi
746+
skill_path="${ref#@}"
747+
if [ ! -f "$root/$skill_path" ]; then
748+
fail "$(basename "$c" .md): skill $ref missing"
749+
broken=$((broken + 1)); problems=1
750+
fi
751+
done
752+
[ "$broken" -eq 0 ] && pass "$cmds commands resolve to an existing skill"
753+
754+
# model-driven boot is intentionally deferred (needs model + API access)
755+
pass "model-driven boot: DEFERRED (wire explicitly when a CI model runner exists)"
756+
return $problems
757+
}
758+
654759
case "$command" in
655760
all)
656761
run_agents
@@ -678,10 +783,12 @@ case "$command" in
678783
runtime) run_runtime ;;
679784
config) run_config ;;
680785
install-safety) run_install_safety ;;
786+
coexistence) run_coexistence ;;
787+
smoke-headless) run_smoke_headless ;;
681788
hooks) run_hooks ;;
682789
smoke) run_smoke ;;
683790
release) run_release ;;
684-
*) printf 'Unknown command: %s\nAvailable: all, agents, skills, closeout, checkpoint, playtest, bug-lifecycle, handoff-review, resume-contract, runtime, config, install-safety, hooks, smoke, release\n' "$command" >&2; exit 2 ;;
791+
*) printf 'Unknown command: %s\nAvailable: all, agents, skills, closeout, checkpoint, playtest, bug-lifecycle, handoff-review, resume-contract, runtime, config, install-safety, coexistence, smoke-headless, hooks, smoke, release\n' "$command" >&2; exit 2 ;;
685792
esac
686793

687794
printf '\n── Result: %d error(s) ──\n' "$errors"

0 commit comments

Comments
 (0)