qualia-framework 7.0.0 → 7.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +58 -0
- package/bin/cli.js +6 -1
- package/bin/dep-verify.mjs +328 -0
- package/bin/install.js +9 -0
- package/bin/recall.js +12 -1
- package/bin/report-payload.js +4 -1
- package/bin/runtime-manifest.js +1 -0
- package/bin/state.js +11 -4
- package/bin/trust-score.js +1 -1
- package/hooks/pre-deploy-gate.js +54 -3
- package/hooks/secret-guard.js +162 -0
- package/package.json +4 -1
- package/skills/qualia-build/SKILL.md +8 -1
- package/skills/qualia-recall/SKILL.md +6 -1
- package/skills/qualia-ship/SKILL.md +2 -1
- package/skills/qualia-verify/SKILL.md +11 -4
- package/tests/bin.test.sh +2 -2
- package/tests/dep-verify.test.sh +247 -0
- package/tests/hooks.test.sh +97 -0
- package/tests/install-smoke.test.sh +4 -3
- package/tests/lib.test.sh +4 -4
- package/tests/recall.test.sh +12 -11
- package/tests/run-all.sh +3 -0
- package/tests/runner.js +2 -2
- package/tests/runtime-parity.test.sh +62 -0
- package/tests/secret-guard.test.sh +92 -0
- package/tests/state.test.sh +35 -0
package/tests/lib.test.sh
CHANGED
|
@@ -532,10 +532,10 @@ TMP=$(mktmp)
|
|
|
532
532
|
mkdir -p "$TMP/home/.claude/bin" "$TMP/home/.claude/hooks" "$TMP/home/.claude/knowledge/daily-log" "$TMP/home/.claude/qualia-design" "$TMP/home/.claude/agents" "$TMP/home/.claude/qualia-templates" "$TMP/project"
|
|
533
533
|
echo '{"installed_by":"Test","role":"OWNER","version":"6.3.0","erp":{"enabled":false}}' > "$TMP/home/.claude/.qualia-config.json"
|
|
534
534
|
touch "$TMP/home/.claude/CLAUDE.md" "$TMP/home/.claude/settings.json"
|
|
535
|
-
for f in runtime-manifest.js command-surface.js host-adapters.js state.js qualia-ui.js statusline.js knowledge.js knowledge-flush.js recall.js vault-access.js repo-map.js design-tokens.js batch-plan.js state-ledger.js plan-contract.js contract-runner.js agent-status.js analyze-gate.js verify-panel.js wave-plan.js eval-runner.js branch-hygiene.js last-report.js harness-eval.js trust-score.js agent-runs.js slop-detect.mjs erp-retry.js erp-event.js work-packet.js report-payload.js project-snapshot.js project-sync.js codex-goal.js planning-hygiene.js prune-deprecated.js learning-candidates.js status-snapshot.js security-scan.js auto-report.js; do
|
|
535
|
+
for f in runtime-manifest.js command-surface.js host-adapters.js state.js qualia-ui.js statusline.js knowledge.js knowledge-flush.js recall.js vault-access.js repo-map.js design-tokens.js batch-plan.js state-ledger.js plan-contract.js contract-runner.js agent-status.js analyze-gate.js verify-panel.js wave-plan.js eval-runner.js branch-hygiene.js last-report.js harness-eval.js trust-score.js agent-runs.js slop-detect.mjs dep-verify.mjs erp-retry.js erp-event.js work-packet.js report-payload.js project-snapshot.js project-sync.js codex-goal.js planning-hygiene.js prune-deprecated.js learning-candidates.js status-snapshot.js security-scan.js auto-report.js; do
|
|
536
536
|
touch "$TMP/home/.claude/bin/$f"
|
|
537
537
|
done
|
|
538
|
-
for h in session-start.js auto-update.js branch-guard.js pre-push.js pre-deploy-gate.js migration-guard.js git-guardrails.js stop-session-log.js fawzi-approval-guard.js vercel-account-guard.js env-empty-guard.js supabase-destructive-guard.js; do
|
|
538
|
+
for h in session-start.js auto-update.js branch-guard.js pre-push.js pre-deploy-gate.js migration-guard.js git-guardrails.js stop-session-log.js fawzi-approval-guard.js vercel-account-guard.js env-empty-guard.js supabase-destructive-guard.js secret-guard.js; do
|
|
539
539
|
touch "$TMP/home/.claude/hooks/$h"
|
|
540
540
|
done
|
|
541
541
|
touch "$TMP/home/.claude/knowledge/index.md" "$TMP/home/.claude/knowledge/agents.md" "$TMP/home/.claude/agents/visual-evaluator.md" "$TMP/home/.claude/qualia-guide.md" "$TMP/home/.claude/qualia-templates/help.html"
|
|
@@ -647,10 +647,10 @@ TMP=$(mktmp)
|
|
|
647
647
|
mkdir -p "$TMP/.claude/bin" "$TMP/.claude/hooks" "$TMP/.claude/knowledge/daily-log" "$TMP/.claude/qualia-design" "$TMP/.claude/agents" "$TMP/.claude/qualia-templates" "$TMP/project/.planning"
|
|
648
648
|
echo '{"installed_by":"Test","role":"OWNER","erp":{"enabled":false}}' > "$TMP/.claude/.qualia-config.json"
|
|
649
649
|
touch "$TMP/.claude/CLAUDE.md" "$TMP/.claude/settings.json"
|
|
650
|
-
for f in runtime-manifest.js command-surface.js host-adapters.js state.js qualia-ui.js statusline.js knowledge.js knowledge-flush.js recall.js vault-access.js repo-map.js design-tokens.js batch-plan.js state-ledger.js plan-contract.js contract-runner.js agent-status.js analyze-gate.js verify-panel.js wave-plan.js eval-runner.js branch-hygiene.js last-report.js harness-eval.js trust-score.js agent-runs.js slop-detect.mjs erp-retry.js erp-event.js work-packet.js report-payload.js project-snapshot.js project-sync.js codex-goal.js planning-hygiene.js prune-deprecated.js learning-candidates.js status-snapshot.js security-scan.js auto-report.js; do
|
|
650
|
+
for f in runtime-manifest.js command-surface.js host-adapters.js state.js qualia-ui.js statusline.js knowledge.js knowledge-flush.js recall.js vault-access.js repo-map.js design-tokens.js batch-plan.js state-ledger.js plan-contract.js contract-runner.js agent-status.js analyze-gate.js verify-panel.js wave-plan.js eval-runner.js branch-hygiene.js last-report.js harness-eval.js trust-score.js agent-runs.js slop-detect.mjs dep-verify.mjs erp-retry.js erp-event.js work-packet.js report-payload.js project-snapshot.js project-sync.js codex-goal.js planning-hygiene.js prune-deprecated.js learning-candidates.js status-snapshot.js security-scan.js auto-report.js; do
|
|
651
651
|
touch "$TMP/.claude/bin/$f"
|
|
652
652
|
done
|
|
653
|
-
for h in session-start.js auto-update.js branch-guard.js pre-push.js pre-deploy-gate.js migration-guard.js git-guardrails.js stop-session-log.js fawzi-approval-guard.js vercel-account-guard.js env-empty-guard.js supabase-destructive-guard.js; do
|
|
653
|
+
for h in session-start.js auto-update.js branch-guard.js pre-push.js pre-deploy-gate.js migration-guard.js git-guardrails.js stop-session-log.js fawzi-approval-guard.js vercel-account-guard.js env-empty-guard.js supabase-destructive-guard.js secret-guard.js; do
|
|
654
654
|
touch "$TMP/.claude/hooks/$h"
|
|
655
655
|
done
|
|
656
656
|
touch "$TMP/.claude/knowledge/index.md" "$TMP/.claude/knowledge/agents.md"
|
package/tests/recall.test.sh
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
|
-
# recall.test.sh — bin/recall.js (read-side memory recall
|
|
2
|
+
# recall.test.sh — bin/recall.js (read-side memory recall — OWNER-only command)
|
|
3
3
|
# Run: bash tests/recall.test.sh
|
|
4
4
|
|
|
5
5
|
PASS=0
|
|
@@ -44,7 +44,8 @@ cat > "$VAULT/wiki/_meta/access.md" <<'EOF'
|
|
|
44
44
|
EOF
|
|
45
45
|
|
|
46
46
|
export QUALIA_MEMORY_ROOT="$VAULT"
|
|
47
|
-
|
|
47
|
+
# recall is OWNER-only — every functional test runs as OWNER to reach the logic.
|
|
48
|
+
RUN() { QUALIA_HOME="$HOME_DIR" QUALIA_ROLE=OWNER "$@"; }
|
|
48
49
|
|
|
49
50
|
# ── Invocation guards ──
|
|
50
51
|
RUN $NODE "$RECALL" >/dev/null 2>&1; assert_exit "no query → exit 2" 2 $?
|
|
@@ -55,15 +56,15 @@ OUT=$(QUALIA_ROLE=OWNER RUN $NODE "$RECALL" zebrafish 2>&1); assert_exit "valid
|
|
|
55
56
|
assert_contains "knowledge-layer hit" "$OUT" "learned-patterns.md"
|
|
56
57
|
assert_contains "vault ALL_ROLES hit" "$OUT" "concepts/notes.md"
|
|
57
58
|
|
|
58
|
-
# ──
|
|
59
|
-
OWNER_OUT=$(QUALIA_ROLE=OWNER
|
|
60
|
-
assert_contains "OWNER sees
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
59
|
+
# ── OWNER-only gate (the command itself is restricted, not just vault content) ──
|
|
60
|
+
OWNER_OUT=$(QUALIA_HOME="$HOME_DIR" QUALIA_ROLE=OWNER $NODE "$RECALL" zebrafish --scope vault 2>&1); assert_exit "OWNER → allowed (exit 0)" 0 $?
|
|
61
|
+
assert_contains "OWNER sees vault content" "$OWNER_OUT" "concepts/notes.md"
|
|
62
|
+
QUALIA_HOME="$HOME_DIR" QUALIA_ROLE=EMPLOYEE $NODE "$RECALL" zebrafish >/dev/null 2>&1; assert_exit "EMPLOYEE → refused (exit 3)" 3 $?
|
|
63
|
+
EMP_MSG=$(QUALIA_HOME="$HOME_DIR" QUALIA_ROLE=EMPLOYEE $NODE "$RECALL" zebrafish 2>&1)
|
|
64
|
+
assert_contains "EMPLOYEE refusal names OWNER-only" "$EMP_MSG" "OWNER-only"
|
|
65
|
+
QUALIA_HOME="$HOME_DIR" QUALIA_ROLE=MANAGER $NODE "$RECALL" zebrafish >/dev/null 2>&1; assert_exit "MANAGER → refused (exit 3)" 3 $?
|
|
66
|
+
# unknown role (no QUALIA_ROLE, no config) → fail-closed → refused
|
|
67
|
+
env -u QUALIA_ROLE QUALIA_HOME="$HOME_DIR" $NODE "$RECALL" zebrafish >/dev/null 2>&1; assert_exit "unknown role (fail-closed) → refused (exit 3)" 3 $?
|
|
67
68
|
|
|
68
69
|
# ── JSON shape ──
|
|
69
70
|
JSON=$(QUALIA_ROLE=OWNER RUN $NODE "$RECALL" zebrafish --json 2>&1)
|
package/tests/run-all.sh
CHANGED
package/tests/runner.js
CHANGED
|
@@ -2655,12 +2655,12 @@ describe("install.js", () => {
|
|
|
2655
2655
|
}
|
|
2656
2656
|
});
|
|
2657
2657
|
|
|
2658
|
-
it("
|
|
2658
|
+
it("16 hooks installed (secret-guard added — commit-time secret gate)", () => {
|
|
2659
2659
|
const tmpHome = fs.mkdtempSync(path.join(os.tmpdir(), "qualia-install-"));
|
|
2660
2660
|
try {
|
|
2661
2661
|
runInstall("QS-FAWZI-11", tmpHome);
|
|
2662
2662
|
const hooks = fs.readdirSync(path.join(tmpHome, ".claude", "hooks")).filter(f => f.endsWith(".js"));
|
|
2663
|
-
assert.equal(hooks.length,
|
|
2663
|
+
assert.equal(hooks.length, 16, `expected 16 hooks, got ${hooks.length}: ${hooks.join(", ")}`);
|
|
2664
2664
|
assert.ok(hooks.includes("fawzi-approval-guard.js"), "fawzi-approval-guard.js must be installed");
|
|
2665
2665
|
assert.ok(hooks.includes("pre-compact.js"), "pre-compact.js must be installed (v6.3.2 sidecar-snapshot mechanism)");
|
|
2666
2666
|
assert.ok(hooks.includes("task-write-guard.js"), "task-write-guard.js must be installed (v6.13 R1 guard)");
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Qualia Framework — Claude/Codex hook-registration parity
|
|
3
|
+
#
|
|
4
|
+
# A blocking gate that runs on Claude but not Codex is a silent hole: the
|
|
5
|
+
# framework's gates-over-prompts guarantee only holds if every deterministic
|
|
6
|
+
# guard fires on BOTH supported runtimes. This suite asserts the Codex hook
|
|
7
|
+
# registration in bin/install.js carries the same critical blocking gates as
|
|
8
|
+
# the Claude registration. The regression it guards: task-write-guard.js was
|
|
9
|
+
# registered on Claude (Edit|Write) but omitted on Codex, so off-contract /
|
|
10
|
+
# fabricated-file writes went unblocked on Codex.
|
|
11
|
+
#
|
|
12
|
+
# Run: bash tests/runtime-parity.test.sh
|
|
13
|
+
|
|
14
|
+
PASS=0
|
|
15
|
+
FAIL=0
|
|
16
|
+
INSTALL="$(cd "$(dirname "$0")/.." && pwd)/bin/install.js"
|
|
17
|
+
|
|
18
|
+
pass() { echo " ✓ $1"; PASS=$((PASS + 1)); }
|
|
19
|
+
fail_case() { echo " ✗ $1"; echo " $2"; FAIL=$((FAIL + 1)); }
|
|
20
|
+
|
|
21
|
+
echo "runtime-parity.test.sh — Claude/Codex hook registration parity"
|
|
22
|
+
echo ""
|
|
23
|
+
|
|
24
|
+
if [ ! -f "$INSTALL" ]; then
|
|
25
|
+
fail_case "install.js exists" "$INSTALL not found"
|
|
26
|
+
echo "=== Results: $PASS passed, $FAIL failed ==="
|
|
27
|
+
exit 1
|
|
28
|
+
fi
|
|
29
|
+
|
|
30
|
+
# Extract the Codex hook registration object (const qualiaHooks = { ... };).
|
|
31
|
+
CODEX_BLOCK=$(awk '/const qualiaHooks = \{/{f=1} f{print} f&&/^ \};/{exit}' "$INSTALL")
|
|
32
|
+
|
|
33
|
+
if [ -z "$CODEX_BLOCK" ]; then
|
|
34
|
+
fail_case "locate Codex qualiaHooks block" "could not extract 'const qualiaHooks = { ... }' from install.js"
|
|
35
|
+
echo "=== Results: $PASS passed, $FAIL failed ==="
|
|
36
|
+
exit 1
|
|
37
|
+
fi
|
|
38
|
+
pass "located the Codex qualiaHooks registration block"
|
|
39
|
+
|
|
40
|
+
# Critical blocking gates that MUST be registered on Codex (superset check).
|
|
41
|
+
CRITICAL_GATES="task-write-guard.js branch-guard.js migration-guard.js pre-deploy-gate.js supabase-destructive-guard.js fawzi-approval-guard.js secret-guard.js"
|
|
42
|
+
|
|
43
|
+
for gate in $CRITICAL_GATES; do
|
|
44
|
+
if echo "$CODEX_BLOCK" | grep -q "$gate"; then
|
|
45
|
+
pass "Codex registers $gate"
|
|
46
|
+
else
|
|
47
|
+
fail_case "Codex missing $gate" "blocking gate registered on Claude but absent from the Codex hooks block"
|
|
48
|
+
fi
|
|
49
|
+
done
|
|
50
|
+
|
|
51
|
+
# Specifically pin the regression: task-write-guard must be in the Edit|Write
|
|
52
|
+
# group (where file writes are gated), not merely present somewhere.
|
|
53
|
+
EDIT_GROUP=$(echo "$CODEX_BLOCK" | awk '/matcher: "Edit\|Write"/{f=1} f{print} f&&/\],/{exit}')
|
|
54
|
+
if echo "$EDIT_GROUP" | grep -q "task-write-guard.js"; then
|
|
55
|
+
pass "task-write-guard.js is in the Codex Edit|Write group"
|
|
56
|
+
else
|
|
57
|
+
fail_case "task-write-guard in Edit|Write" "task-write-guard.js not found in the Codex Edit|Write matcher group"
|
|
58
|
+
fi
|
|
59
|
+
|
|
60
|
+
echo ""
|
|
61
|
+
echo "=== Results: $PASS passed, $FAIL failed ==="
|
|
62
|
+
[ "$FAIL" = "0" ]
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Qualia Framework — hooks/secret-guard.js behavior tests
|
|
3
|
+
# The commit-time gate that enforces two constitution non-negotiables:
|
|
4
|
+
# never commit service_role keys; never commit .env files.
|
|
5
|
+
#
|
|
6
|
+
# Run: bash tests/secret-guard.test.sh
|
|
7
|
+
|
|
8
|
+
PASS=0
|
|
9
|
+
FAIL=0
|
|
10
|
+
GUARD="$(cd "$(dirname "$0")/../hooks" && pwd)/secret-guard.js"
|
|
11
|
+
NODE="${NODE:-node}"
|
|
12
|
+
|
|
13
|
+
TMP_DIRS=()
|
|
14
|
+
cleanup() { for d in "${TMP_DIRS[@]}"; do [ -d "$d" ] && rm -rf "$d"; done; }
|
|
15
|
+
trap cleanup EXIT
|
|
16
|
+
mktmp() { local T; T=$(mktemp -d); TMP_DIRS+=("$T"); echo "$T"; }
|
|
17
|
+
pass() { echo " ✓ $1"; PASS=$((PASS + 1)); }
|
|
18
|
+
fail_case() { echo " ✗ $1"; echo " $2"; FAIL=$((FAIL + 1)); }
|
|
19
|
+
|
|
20
|
+
# Build a throwaway git repo with the given file staged; run the guard via a
|
|
21
|
+
# simulated `git commit` PreToolUse payload; echo the exit code.
|
|
22
|
+
run_guard_on_repo() {
|
|
23
|
+
local repo="$1"
|
|
24
|
+
( cd "$repo" && printf '{"tool_input":{"command":"git commit -m wip"}}' | $NODE "$GUARD" >/tmp/sg.out 2>&1 )
|
|
25
|
+
echo $?
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
echo "secret-guard.test.sh — hooks/secret-guard.js behavioral tests"
|
|
29
|
+
echo ""
|
|
30
|
+
|
|
31
|
+
if [ ! -f "$GUARD" ]; then
|
|
32
|
+
fail_case "secret-guard exists" "$GUARD not found"; echo "=== Results: $PASS passed, $FAIL failed ==="; exit 1
|
|
33
|
+
fi
|
|
34
|
+
pass "secret-guard.js exists"
|
|
35
|
+
$NODE --check "$GUARD" 2>/dev/null && pass "secret-guard.js syntax is valid" || fail_case "syntax" "node --check failed"
|
|
36
|
+
|
|
37
|
+
git --version >/dev/null 2>&1 || { echo " - git unavailable, skipping behavioral tests"; echo "=== Results: $PASS passed, $FAIL failed ==="; [ "$FAIL" = 0 ]; exit $?; }
|
|
38
|
+
|
|
39
|
+
init_repo() {
|
|
40
|
+
local R; R=$(mktmp)
|
|
41
|
+
( cd "$R" && git init -q && git config user.email t@t.co && git config user.name t ) 2>/dev/null
|
|
42
|
+
echo "$R"
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
# ── Clean staged file → allow (exit 0) ────────────────────────────────
|
|
46
|
+
R=$(init_repo)
|
|
47
|
+
echo "export const x = 1;" > "$R/app.ts"
|
|
48
|
+
( cd "$R" && git add app.ts ) 2>/dev/null
|
|
49
|
+
RC=$(run_guard_on_repo "$R")
|
|
50
|
+
[ "$RC" = "0" ] && pass "clean staged file → allowed (exit 0)" || fail_case "clean allow" "exit=$RC $(cat /tmp/sg.out)"
|
|
51
|
+
|
|
52
|
+
# ── Staged private key → block (exit 2), value never printed ──────────
|
|
53
|
+
R=$(init_repo)
|
|
54
|
+
printf -- "-----BEGIN RSA PRIVATE KEY-----\nMIIabc123\n-----END RSA PRIVATE KEY-----\n" > "$R/key.pem"
|
|
55
|
+
( cd "$R" && git add key.pem ) 2>/dev/null
|
|
56
|
+
RC=$(run_guard_on_repo "$R")
|
|
57
|
+
if [ "$RC" = "2" ] && grep -q "key.pem" /tmp/sg.out && ! grep -q "MIIabc123" /tmp/sg.out; then
|
|
58
|
+
pass "staged private key → blocked (exit 2), value not printed"
|
|
59
|
+
else
|
|
60
|
+
fail_case "private key block" "exit=$RC out=$(head -c 160 /tmp/sg.out)"
|
|
61
|
+
fi
|
|
62
|
+
|
|
63
|
+
# ── Staged .env file → block ──────────────────────────────────────────
|
|
64
|
+
R=$(init_repo)
|
|
65
|
+
echo "API_KEY=whatever" > "$R/.env"
|
|
66
|
+
( cd "$R" && git add -f .env ) 2>/dev/null
|
|
67
|
+
RC=$(run_guard_on_repo "$R")
|
|
68
|
+
[ "$RC" = "2" ] && grep -q "\.env" /tmp/sg.out && pass "staged .env file → blocked (exit 2)" || fail_case ".env block" "exit=$RC out=$(head -c 160 /tmp/sg.out)"
|
|
69
|
+
|
|
70
|
+
# ── .env.example → allowed (template, not a secret) ──────────────────
|
|
71
|
+
R=$(init_repo)
|
|
72
|
+
echo "API_KEY=" > "$R/.env.example"
|
|
73
|
+
( cd "$R" && git add .env.example ) 2>/dev/null
|
|
74
|
+
RC=$(run_guard_on_repo "$R")
|
|
75
|
+
[ "$RC" = "0" ] && pass ".env.example → allowed (template, not flagged)" || fail_case ".env.example allow" "exit=$RC out=$(head -c 160 /tmp/sg.out)"
|
|
76
|
+
|
|
77
|
+
# ── service_role key assignment → block ──────────────────────────────
|
|
78
|
+
R=$(init_repo)
|
|
79
|
+
echo 'const k = "SUPABASE_SERVICE_ROLE_KEY=abcdef0123456789abcdef0123456789";' > "$R/cfg.ts"
|
|
80
|
+
( cd "$R" && git add cfg.ts ) 2>/dev/null
|
|
81
|
+
RC=$(run_guard_on_repo "$R")
|
|
82
|
+
[ "$RC" = "2" ] && pass "service_role key assignment → blocked (exit 2)" || fail_case "service_role block" "exit=$RC out=$(head -c 160 /tmp/sg.out)"
|
|
83
|
+
|
|
84
|
+
# ── Non-commit command → self-filter exits 0 silently ────────────────
|
|
85
|
+
R=$(init_repo)
|
|
86
|
+
echo "API_KEY=whatever" > "$R/.env"; ( cd "$R" && git add -f .env ) 2>/dev/null
|
|
87
|
+
RC=$( ( cd "$R" && printf '{"tool_input":{"command":"git status"}}' | $NODE "$GUARD" >/dev/null 2>&1 ); echo $? )
|
|
88
|
+
[ "$RC" = "0" ] && pass "non-commit command (git status) → self-filters, exit 0" || fail_case "self-filter" "exit=$RC"
|
|
89
|
+
|
|
90
|
+
echo ""
|
|
91
|
+
echo "=== Results: $PASS passed, $FAIL failed ==="
|
|
92
|
+
[ "$FAIL" = "0" ]
|
package/tests/state.test.sh
CHANGED
|
@@ -1604,6 +1604,41 @@ else
|
|
|
1604
1604
|
fail_case "operate handoff ungated" "out=$OUT"
|
|
1605
1605
|
fi
|
|
1606
1606
|
|
|
1607
|
+
# 64. A5 increment failed-status gap closure (regression: A5 sets status
|
|
1608
|
+
# "failed" on a failed verify, unlike the legacy "verified"+fail path).
|
|
1609
|
+
# Asserts: next_command routes to gap closure (not the /qualia dead-end),
|
|
1610
|
+
# re-plan is allowed from "failed", gap_cycles increments, and the
|
|
1611
|
+
# circuit breaker fires at the limit.
|
|
1612
|
+
TMP=$(make_project)
|
|
1613
|
+
make_valid_plan "$TMP" 1 # plan + contract + evidence for phase 1
|
|
1614
|
+
touch "$TMP/.planning/phase-1-verification.md" # fail verify only needs the file to exist
|
|
1615
|
+
(cd "$TMP" && $NODE "$STATE_JS" migrate >/dev/null 2>&1)
|
|
1616
|
+
A5ID=inc-0001-foundation
|
|
1617
|
+
a5() { (cd "$TMP" && $NODE "$STATE_JS" transition --to "$1" ${2:+--verification "$2"} --id "$A5ID" 2>&1); }
|
|
1618
|
+
a5 planned >/dev/null 2>&1 # ensure planned (no-op if already)
|
|
1619
|
+
a5 built >/dev/null 2>&1
|
|
1620
|
+
VF=$(a5 verified fail)
|
|
1621
|
+
if echo "$VF" | grep -q '"status": "failed"' && echo "$VF" | grep -q '"next_command": "/qualia-plan 1 --gaps"'; then
|
|
1622
|
+
pass "A5 failed verify → status=failed, next_command=/qualia-plan 1 --gaps (no dead-end)"
|
|
1623
|
+
else
|
|
1624
|
+
fail_case "A5 failed next_command" "out=$VF"
|
|
1625
|
+
fi
|
|
1626
|
+
RP1=$(a5 planned)
|
|
1627
|
+
if echo "$RP1" | grep -q '"ok": true' && echo "$RP1" | grep -q '"gap_cycles": 1'; then
|
|
1628
|
+
pass "A5 failed → planned gap closure allowed, gap_cycles=1"
|
|
1629
|
+
else
|
|
1630
|
+
fail_case "A5 replan-from-failed" "out=$RP1"
|
|
1631
|
+
fi
|
|
1632
|
+
a5 built >/dev/null 2>&1; a5 verified fail >/dev/null 2>&1
|
|
1633
|
+
RP2=$(a5 planned)
|
|
1634
|
+
a5 built >/dev/null 2>&1; a5 verified fail >/dev/null 2>&1
|
|
1635
|
+
RP3=$(a5 planned)
|
|
1636
|
+
if echo "$RP2" | grep -q '"gap_cycles": 2' && echo "$RP3" | grep -q "GAP_CYCLE_LIMIT"; then
|
|
1637
|
+
pass "A5 gap_cycles=2 then circuit breaker fires (GAP_CYCLE_LIMIT) at default limit"
|
|
1638
|
+
else
|
|
1639
|
+
fail_case "A5 breaker" "rp2=$RP2 rp3=$RP3"
|
|
1640
|
+
fi
|
|
1641
|
+
|
|
1607
1642
|
# ─── Summary ─────────────────────────────────────────────
|
|
1608
1643
|
echo ""
|
|
1609
1644
|
echo "=== Results: $PASS passed, $FAIL failed ==="
|