Skip to content

Enable §3 issues-as-state (dual-write) + §7 votable health by default, + read-only/gh-api hardening#550

Merged
aaronjmars merged 3 commits into
mainfrom
feat/state-health-default-on
Jun 25, 2026
Merged

Enable §3 issues-as-state (dual-write) + §7 votable health by default, + read-only/gh-api hardening#550
aaronjmars merged 3 commits into
mainfrom
feat/state-health-default-on

Conversation

@aaronjmars

Copy link
Copy Markdown
Owner

Makes §3 and §7 on by default, implemented safely, plus the read-only/gh api and ensure()-race hardening. Builds on #548.

§3 issues-as-state — ON by default (dual-write transition)

STATE_BACKEND is now tri-state:

value behaviour
dual (default, unset) append each run event to the pinned aeon:cron-state Issue AND keep committing cron-state.json — readers unchanged, zero staleness risk, Issue store exercised live
issues append + skip the file-commit/rebase loop (churn-free end state; the gated materialize step handles the reader cutover)
file legacy file-only escape hatch

This is hardening item #3 (the dual-write transition): the file stays authoritative, so even total failure of the Issue path can't corrupt or stale the readers. Append failures always fall through to the file. Graduate to STATE_BACKEND=issues to drop the commit churn once dual-write is proven.

§7 votable health Issues — ON by default

Flipped to default-on (HEALTH_ISSUES != '0'; opt out with =0). Silent on clean runs, so a healthy fleet still creates zero Issues — only regressions auto-open a votable per-skill Issue.

Hardening

  1. read-only ✕ gh api — documented in skill_mode.sh: read-only excludes all gh (incl. GET reads) by design; GitHub reads should use WebFetch/curl on api.github.com or stay write. The two affected skills (github-trending, security-digest) use gh api only as a fallback, so they degrade gracefully.
  2. ensure() race — pre-created the pinned aeon:cron-state Issue (aeon:cron-state #549) so ensure() always finds it and never races to create a duplicate under concurrency.
  3. materialize-failure safety — addressed by the dual-write default above (file remains the safety net).
  4. notify.sh live delivery — to be confirmed in the canary (next step), not a code change.

Safety / testing

  • Both workflows valid YAML; tri-state logic traced (unset→dual→append+keep-file; issues→append+skip-file; file→legacy). materialize + commit-drop fire only on explicit issues, so dual keeps the file fresh.
  • skill_mode suite still green.
  • Default-path change: every run now does one extra gh issue comment (append) — the file path is otherwise unchanged.

🤖 Generated with Claude Code

aaronjmars and others added 3 commits June 25, 2026 13:47
§3 issues-as-state is now ON by default as a safe dual-write transition. STATE_BACKEND
becomes tri-state: dual (default) appends each run event to the pinned aeon:cron-state
Issue AND keeps committing cron-state.json — readers unchanged, zero staleness risk,
while the Issue store is exercised live; issues = append + skip the file/rebase loop
(churn-free end state, gated materialize handles the reader cutover); file = legacy
escape hatch. Append failures always fall through to the file path.

§7 votable health Issues is ON by default (opt out with HEALTH_ISSUES=0). It is silent
on clean runs, so a healthy fleet still creates zero issues; regressions auto-open a
votable per-skill Issue.

Default run path now writes to the Issue store; the pinned issue is pre-created
(#549) so ensure() never races. Graduate to STATE_BACKEND=issues once dual-write is
proven to drop the commit churn.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…eads)

read-only drops all gh because gh is a write vector and Bash(gh:*) is all-or-nothing —
even gh api GET reads are excluded. A read-only skill needing GitHub data should use
WebFetch/curl on api.github.com or stay write. Notes the two graceful degraders
(github-trending, security-digest — gh api is a fallback behind a curl/WebFetch primary).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…n issues

The issues-as-state ledger (`aeon:cron-state`) is machine-managed and append-only,
so there's no reason for it to sit in the repo's open-issues list forever. GitHub
allows commenting on and reading a *closed* issue (only locking blocks comments),
so the ledger works just as well closed — invisibly.

- `ensure` now searches `--state all` (finds the ledger whether open or closed)
- on creation it closes the issue immediately; appends still land on it
- aeon manages the lifecycle on-demand: first run creates+closes it, no human pin

Verified live against GitHub: create+close, append to closed issue, fold, and
dedup re-ensure all pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@aaronjmars aaronjmars merged commit b7573fb into main Jun 25, 2026
2 checks passed
@aaronjmars aaronjmars deleted the feat/state-health-default-on branch June 25, 2026 18:16
aaronjmars added a commit that referenced this pull request Jun 25, 2026
… 0 disabled it (#552)

#550 made §7 votable-health "default-on" via `if: ... && vars.HEALTH_ISSUES != '0'`.
But GitHub Actions `if:` comparisons do numeric coercion: an UNSET repo var is null,
and `null != '0'` evaluates as `0 != 0` → false. So with HEALTH_ISSUES unset (the
default), the step was SKIPPED on every run — §7 was silently default-OFF, the exact
opposite of the intent.

Caught on a fork canary: the analyzer correctly scored a deliberately-bad skill 1 +
`empty_output`, yet no health issue was filed because the step never ran.

Fix: gate the step only on `always() && steps.skill.outputs.name != ''` (proven by the
sibling "Update cron state" step) and move the toggle into bash —
`if [ "${{ vars.HEALTH_ISSUES }}" = "0" ]; then exit 0; fi` — a string compare with no
coercion, matching how STATE_BACKEND is handled. Verified on a fork: re-dispatch then
opened `health: <skill>` issue #5 with the regression comment.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
aaronjmars added a commit that referenced this pull request Jun 25, 2026
…runner (#553)

Two bugs the fork canary surfaced on the chain path (chains are unused on prod
today, so these were latent):

1. Missing `issues: write` — the §3 issues-as-state append (added in #548/#550)
   calls the Issues API via state_store.sh. chain-runner only had contents+actions
   write, so the append 403'd: chain run-events never reached the ledger Issue
   (dual mode masked it via the file path; `issues` mode would have lost them).

2. Sub-run discovery regex `test("skill: ${skill}(")` — the literal '(' is an
   unterminated regex group (jq throws "end pattern with unmatched parenthesis")
   and only appears in run titles when a skill has a var ("skill: digest (x)").
   Var-less steps ("skill: heartbeat") never matched, so dispatch_skill timed out
   and the chain aborted at step 1. Fixed to `test("skill: ${skill}( |$)")` —
   matches name-then-space (var) or name-then-end (no var).

Verified end-to-end on a fork: a 2-step heartbeat chain now completes green —
both sub-runs discovered + awaited, and "chain state appended to issue #N
(backend=dual)" succeeds.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
aaronjmars added a commit that referenced this pull request Jun 25, 2026
…554)

The hardening features shipped in #548/#550 were undocumented. Add operator/author
docs for the three user-facing ones:

- README "It heals itself": votable health (`HEALTH_ISSUES`, `health: <skill>`
  issues, 👍/👎 to set repair priority).
- README Advanced: "Capability tiers (read-only skills)" (`mode:` frontmatter) and
  "Durable state without the churn" (`STATE_BACKEND` dual/issues/file).
- docs/CAPABILITIES.md: a "Runtime enforcement: the `mode:` write tier" section —
  the runtime gate that complements the install-time `capabilities:` taxonomy.
- docs/CORE.md + docs/status.md: votable-health notes in the self-healing loop and
  the public /status/ health page.

No code changes — documentation only.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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.

1 participant