Skip to content

v0.9.6 batch — unblock CI, harden release (#18), truth-pass + injection-defense fix - #19

Merged
MountainUnicorn merged 11 commits into
mainfrom
wave-exec-v096
Jun 14, 2026
Merged

v0.9.6 batch — unblock CI, harden release (#18), truth-pass + injection-defense fix#19
MountainUnicorn merged 11 commits into
mainfrom
wave-exec-v096

Conversation

@MountainUnicorn

Copy link
Copy Markdown
Owner

Waves 0–2 of the v1.0 GA execution plan. Turns the red main green, fixes the release tooling, and completes the v0.9.6 truth-pass — including a real injection-defense bug.

Each wave was executed under ADD's own SDLC: RED-first tests where applicable, an independent verifier agent + an agent-to-agent retro per wave, learnings captured to .add/learnings.json (L-035…L-046).

Wave 0 — unblock main

  • C1: rule count (19→20) made compile-derived via {{RULE_COUNT}} so it can't drift again; rule-parity strengthened to cover all prose surfaces (9/9).
  • C3: checkout v5 / setup-python v6 / github-script v8 across all 4 workflows (Node-20 deprecation).

Wave 1 — release tooling

Wave 2 — v0.9.6 truth-pass

  • C5: CONTRIBUTING "three checks"→four; community-PR strategy documented.
  • D1: model-roles.md capability-tier table (Opus 4.8 / Sonnet 4.6 / Haiku 4.5; gpt-5.5 / gpt-5.x-codex).
  • B3: added the missing Codex verify sub-agent (verify.toml + 2 compile.py enumerations + test); 58/58.
  • D3 (security): the unicode-tag-block regex was a broken byte-class with a ~100% false-positive rate (652/652 sampled events benign, 0 attacks). Replaced with a precise pattern (verified to still catch the real attack, AC-028). JSONL audit writer → jq -cn (atomic). .add/security/ gitignored + untracked. Mutation-verified benign-multibyte regression fixture added.

All 15 fixture suites pass; compile --check clean. Version bump to v0.9.6 lands in a follow-up commit on this branch.

🤖 Generated with Claude Code

Wave 0 of the v1.0 GA execution plan.

C1: rule-parity guardrail was red because core/rules/ holds 20 files but the
tree diagram hardcoded 19. Replace the hardcoded count in
runtimes/claude/CLAUDE.md with a {{RULE_COUNT}} placeholder that compile.py
fills from the live core/rules/*.md count, so it can never drift again.
Strengthen test-rule-parity.sh to assert the placeholder is intact and to
read the resolved count from the compiled artifact. Sync prose counts in
CLAUDE.md and README.md (19 -> 20).

C3: bump deprecated CI actions to Node-24 runtimes across all 4 workflows
(checkout v4->v5, setup-python v5->v6, github-script v7->v8).

Guardrails: rule-parity now 6/6; all 14 suites pass; compile --check clean.
Separate verifier + retro agents found three issues in the first Wave 0 pass:

1. Semantic bug: {{RULE_COUNT}} counted ALL core/rules/*.md files but is
   labelled "Auto-loading behavioral rules". It would silently overcount the
   day a rule sets autoload:false. Derive the count from the autoload-filtered
   set instead (same population as {{AUTOLOAD_RULES}}), and count the same way
   in the test.

2. Missed truth-pass surfaces: CONTRIBUTING.md still said 19; README's rules
   table had only 19 rows under a "20" header (model-roles row missing).
   Fixed both; README table now lists all 20 rules.

3. Guardrail coverage: rule-parity only watched the compiled tree diagram.
   Extend it to assert every "N auto-load(ed|ing) behavioral rules" claim in
   CLAUDE.md / README.md / CONTRIBUTING.md matches the real count, so these
   hand-authored surfaces can't drift unnoticed.

rule-parity now 9/9; all 14 suites pass; compile --check clean.
Four learnings from the agent-to-agent retro on the CI-unblock wave:
derive-from-meaning not proxy; guardrails only cover what they read;
when test edits are legitimate; green PR != proven on untriggered paths.
Wave 1 of the v1.0 GA execution plan.

release.sh could exit 0 after pushing the signed tag without ever creating the
GitHub release page (same failure class as F-001 — a command that "succeeds"
without doing the thing). Two fixes:

- Build the gh flags as an array so an absent --draft cannot word-split into a
  silent argument the way the old unquoted $DRAFT_FLAG could.
- After `gh release create`, assert the page exists via `gh release view --json
  url`; treat a missing release as a hard failure and print the exact recovery
  command. The "published" message now only prints after verification.
- Drop the dead TITLE variable; refresh the --dry-run plan.

Add tests/release-tooling/test-release-verify.sh (RED-first regression guard:
asserts the gh release view check follows creation, no unquoted $DRAFT_FLAG,
success message ordering, + shellcheck) and register it in the guardrails
matrix. shellcheck runs in CI (preinstalled on ubuntu-latest).

Release tooling test 3/3; all 15 fixture suites pass; bash -n clean.
Independent verification caught that the first regression test had false
confidence: its `grep | head -1` matched the --dry-run echo lines, not the
real command logic, so it passed even with the entire fix reverted (the exact
"green that doesn't mean what you think" trap that birthed the #18/F-001 bug
class).

Replace the fragile ordering greps with a behavioral harness: mock git/gh/
python3 on PATH, run release.sh end-to-end, and assert the exit code for the
#18 symptom (gh release create "succeeds" but no page exists -> must exit
non-zero), the empty-URL case, and the happy path. Keep anchored static checks
(no unquoted $DRAFT_FLAG; a real non-echo `gh release view` exists) + shellcheck.

Verified the mutation check: reverting the verification block now makes the
test fail 3/5 (previously it stayed green). 5/5 with the fix; all suites pass.
Systemic: 'verify the side effect, never trust the exit code' is now a
twice-proven bug class (F-001 + #18) and a candidate core rule. Plus:
static tests pin structure not behavior; always mutation-check a new
regression test; name the first real release as #18's acceptance test.
…rify agent, injection-defense fixes

Wave 2 of the v1.0 GA execution plan (C5, D1, B3, D3 Phase 0/1).

C5 (CONTRIBUTING): "three CI checks" was stale — there are four workflows now
(compile-drift, schema-check, rule-boundary-check, guardrails). List all four,
and document the community-PR strategy (merge-as-is + co-authored refactor
follow-up) that previously lived only in maintainer memory.

D1 (model-roles): add a concrete capability-tier table mapping role shapes to
the current lineup — Architect: Opus 4.8 / gpt-5.5; Editor: Sonnet 4.6 /
gpt-5.x-codex; Fast: Haiku 4.5 / gpt-5.x-codex-mini — while keeping the
guidance-not-enforcement framing and "prefer latest in each tier".

B3 (Codex verify sub-agent): ADD's 4th role (verify) was missing from the Codex
sub-agent set. Add runtimes/codex/agents/verify.toml (workspace-write, high
reasoning — it runs gates and may write test artifacts) and the two hardcoded
compile.py enumerations that omitted it. Extend the codex-native-skills test to
assert verify.toml. Now emits 5 agents; suite 58/58.

D3 (injection-defense, Phase 0/1):
- SECURITY FIX: the unicode-tag-block regex was a broken byte character-class
  ([\xF3\xA0\x80\x80-\xF3\xA0\x87\xBF]) whose dominant 0x80-0xF3 range matched
  almost any multibyte UTF-8 — ~100% false positives (em-dashes, arrows, box
  characters; 652/652 sampled events were benign, 0 real attacks). Replace with
  (?:\xF3\xA0[\x80\x81][\x80-\xBF]){3,}, which matches exactly U+E0000-E007F as
  the description always claimed. Verified: AC-028 (real tag-channel attack)
  still fires; AC-029 benign control passes; injection suite 11/11.
- BUG: the JSONL audit writer used pretty-printing `jq -n`, producing multi-line
  records that aren't valid JSONL and interleave/corrupt under concurrent hook
  runs. Switch both writers to `jq -cn` (compact, single-line, atomic append).
- HYGIENE: the runtime audit trail .add/security/injection-events.jsonl was
  being committed. Gitignore .add/security/ and untrack it; document the
  local-only audit trail in SECURITY.md's threat model.

All 15 fixture suites pass; compile --check clean.
…tive bug

Wave 2 verification (retro L-3) found the injection suite couldn't detect a
revert of the D3 regex fix: the only negative control was pure ASCII, which
never tripped the OLD broken regex either, so a regression would stay green.

Add benign-multibyte.json — dense with the exact byte classes that caused the
~100% false-positive rate (em-dash, arrow, box-drawing, ≥, ✓, ✅, CJK, emoji)
— and assert it does NOT fire. Mutation-verified: reverting the regex to the
old 0x80-0xF3 byte-class turns this test red (11/12); the fixed regex is 12/12.

Applies the Wave 1 learning (L-041): a regression test must be able to fail.
VERSION 0.9.5 -> 0.9.6; .add/config.json; README badge; migrations.json
plugin_version + a 0.9.5->0.9.6 hop (recording the hop so the migration chain
stays intact — skipping it has silently broken the chain before). CHANGELOG
[0.9.6] section authored from the Wave 0-2 work. Recompiled: plugin.json and
all [ADD vX.Y.Z] / skill-heading substitutions now read v0.9.6.

compile --check clean; validators pass; all 15 suites green.
@MountainUnicorn
MountainUnicorn merged commit 6b9958a into main Jun 14, 2026
19 checks passed
@MountainUnicorn
MountainUnicorn deleted the wave-exec-v096 branch June 14, 2026 20:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

release.sh exits success but skips gh release create when keychain-cached signing succeeds silently

1 participant