Skip to content

feat(supporter-loop): contract v2.0 — catch-up + invitation quota queue, sweep-state latch, regenerate order, work budget (#91) - #92

Merged
shojikumaru merged 4 commits into
mainfrom
feat/91-supporter-loop-v2
Sep 8, 2026
Merged

shojikumaru merged 4 commits into
mainfrom
feat/91-supporter-loop-v2

Conversation

@shojikumaru

@shojikumaru shojikumaru commented Sep 8, 2026 •

Copy link
Copy Markdown
Member

Tracked in #91 (leaves #91 open until the MERGED record). Child B of EPIC caty-ai/x-collector#119. Merge order: after caty-ai/x-collector PR #147 (contract v2.0) is on main, before x-collector A3 (caller cron). Also closes the caty-ai/x-collector#141 items 2–4 (watermark, stars in the static check, alert naming — the last one deliberately left as-is).

What

Implements contract v2.0 (caty-ai/x-collector PR #147, candidate e3bdea1) in .github/workflows/supporter-loop-reusable.yml, with fixtures and README:

  • Catch-up (§11 step 4b, act): pass 1 queues projection repairs (identity-free, quota-free); pass 2 invites under Q = max(0, 45 − B) with per-PUT re-evaluation, ≤ 3 min cumulative wait, never-invited-first page order, §3 filter after GET /user/{id}, ok-catchup, two-invitation bound → excluded-unresponsive (automatic catch-up only), gates (a)(b)(c). Record-only rehearsal in decide (would-invite / would-supporters-append, tier key, ledger-only B).
  • Quota + 422 (§14): B = distinct ok* invite records in 24 h (rotation duplicates once) + unledgered pending; invitation-limit 422 → event: whole bundle deferred-quota (green); sweep: one skip/deferred-quota, stop, green; other 422 → error-422. INVITE_LIMIT_PATTERN is one constant + fixtures/workflow/invitation-limit-422.json — provisional; the owner pins the live text before merge (launch gate).
  • Work budget (§11 step 4b): 6 min elapsed / 150 content writes (queued items counted when queued); the deadline also stops the audit / marker / revoke loops (incomplete audit → (d) false → red); steps 5–8 always run.
  • Regeneration (§6.5): GET sha → fresh ledger read → render → PUT, restart from GET on 409/422-CAS; unconditional on every live sweep; tier-1 rows omitted while the invite is undelivered (expired re-arm / unresponsive); ≤ 1 content write/s pacing.
  • State + notifications (§6.6, §9): ledger/<name>.sweep-state.json latch written by act with the ledger PAT; one summary Telegram per sweep; edge-triggered capacity / red-run / deferred-quota alarms; cancelled-run counts listed by decide (actions: read) and handed to act via outputs.
  • Watermark (§11(a)): latest status=success run with event ∈ {schedule, workflow_dispatch}.
  • stars: timeout-minutes: 10; ids in stable first-occurrence page order (no jq unique); guard unchanged. backfill input retired (ok-backfill still a historical success).
  • Static check covers the stars job (rules a / b) with three violating fixtures; four new ledger vocabulary fixtures; test-catchup-model.py and test-live-contract.py (model + extracted live helpers with fail-closed doubles); test-decide.py / test-interface.py / test-model.py extended.

Writer: Codex GPT-6 Astra (brief + continuation, _handoffs/dotgithub-supporter-loop-91-impl-20260908/); commit by alpha; local replication of the selftest workflow green (actionlint, shellcheck, yaml, static check over every fixture with rule letters, ledger fixtures, contents-decode, 7 python suites); the writer's 5 mutations each went red and were restored (REPORT-r1.md).

Labels needed from the owner

  • risk-reviewed on the final head (.github/workflows/ is a risk path) — head e4b97c5 unless the 422 probe adds a commit.
  • Launch gate: owner chose B (2026-09-09) — see the completion record.

完了記録 (Completion record)

candidate SHA: e4b97c5 (r4 = merge candidate; r1 a8c626a → r2 2e04d61 → r3 3182d67 → r4)
implementer: Codex GPT-6 Astra (codex exec --profile astra; brief-r1 + continuation + delta-1/2/3, _handoffs/dotgithub-supporter-loop-91-impl-20260908/); commits by alpha (declared paths only; REPORT-r1..r4 retained in the handoff dir). Alpha inspection = local replication of the selftest workflow on every candidate (actionlint, shellcheck, yaml, static check over every fixture with rule letters, ledger fixtures, contents-decode, all test-*.py) — green on r1–r4.
reviewer: five seats, size H (permission boundary + supporter-facing sends). Seat availability on 2026-09-08: Kimi 403 weekly, GLM 429 (back 09-10), Grok 402; Qwen's first two launches were killed by the sitter supervisor's ~20-min stall rule (no quota issue) and delivered once launched without it. requested → actual:

seat requested actual r1 (a8c626a) delta (r2 2e04d61) confirm (r3 3182d67) confirm (r4 e4b97c5)
Opus opus-5 Claude Opus 5 (code-reviewer Agent; MODEL: claude-opus-5; re-ran suites, static check, mutations vi/vii, own evasion probes) NO-GO — C1 closed-generation exclusion = silent loss (jq-verified), M1 pending match ignores generation, M2 unique_by shadow, M3 parity fixture, N1/N2 GO (C1 re-verified by replaying its counterexample; new NIT tempdir) GO GO (5 own concatenation probes all caught; clean 50/50, violating 269/269)
Gemini gemini-3.8-flash Gemini 3.8 Flash high (agy static packet; r1 self-reported gemini-2.5-pro, launch was --model gemini-3.8-flash-high) GWN — MINOR rehearsal identity failure, 3 NITs GO — GO
Muse muse-spark-1.3 Muse Spark 1.3 (MODEL: muse-spark-1.3) GWN — 2 NITs GO — GO
Codex Sol codex-sol GPT-5.6 Sol (codex exec -m gpt-5.6-sol, MCP disabled; launches 1–3 died on a dead MCP transport / sitter kill) NO-GO — same four items as Opus (independent convergence) GWN (all resolved) — — (same-lineage seat; r3/r4 are test-only + fifth-seat items)
Qwen qwen3.8-max Qwen 3.8 Max (MODEL: qwen3.8-max; no sitter) — — GWN on r3 — MINOR-1 static-check evasion via shell string concatenation; NIT-1..4 GO (MINOR-1 + NIT-1..4 all RESOLVED, resolver verified at check-decide-static.sh:247-305)

Zero open blocking findings. Every finding and its resolution: _handoffs/dotgithub-supporter-loop-91-impl-20260908/review/{record.md,r1/,delta/,r3/,delta/opus-r3.md,delta/opus-r4.md,bconfirm}; writer reports REPORT-r1..r4.md; mutations i–viii red → restored on r4.
identity check: 別モデル(writer = Codex GPT-6 Astra; seats Opus / Gemini / Muse / Qwen heterogeneous; Codex Sol same-lineage). correlated-seats exception (L1-10, 6 fields): scope = PR #92 only; pair = GPT-6 Astra (writer) → GPT-5.6 Sol (seat); reason = on 2026-09-08 every heterogeneous CLI seat was quota-absent at r1 time; approved_by = review_council 2026-08-12 (same-family Codex seat for a Codex writer) — owner ratification bundled with the risk-reviewed label on this PR; date = 2026-09-08; writer condition = Sol wrote nothing in this PR. Note: with Qwen delivering, the panel has four heterogeneous seats + Sol; Sol is not load-bearing for quorum.
CI: green at e4b97c5 (selftest, gitleaks, history-check, publication-gate, repo-state, watch) — verified 2026-09-09 before merge; owner risk-reviewed on e4b97c5.
Launch gate (contract §14): INVITE_LIMIT_PATTERN + fixtures/workflow/invitation-limit-422.json are provisional. Owner decision pending on how to pin the live 422 text: (A) probe — throwaway private repo under the owner + one family-owned account, 51 × PUT /collaborators / DELETE /invitations, paste the sanitized 422 body into the fixture and align the single INVITE_LIMIT_PATTERN assignment (a follow-up commit on this branch, re-running the positive/negative 422 tests; label re-requested after that push); or (B) merge with the provisional pattern and pin on first real occurrence — an unrecognised 422 stays error-422 (red run + Telegram), i.e. fail-closed, never silent (contract §14). Owner decision 2026-09-09: B ("推奨でいこう" + risk-reviewed on e4b97c5) — merge with the provisional pattern; the live text is pinned on first real occurrence (follow-up tracked on caty-ai/x-collector#143). This relaxes the contract §14 "pinned before B merges" clause by owner decision; the fail-closed fallback (error-422, red run, Telegram) of the same clause is what makes it safe.
release: v0.12.0 (shipped reusable workflow behaviour changes = 出荷相当)
previous release: v0.11.6 → https://github.com/caty-ai/.github/releases/tag/v0.11.6 (PR #90, fulfilled 2026-09-07)

Done when → 結果

Done when 項目 結果 証拠
Catch-up (§11 step 4b): candidates, two passes, page order, identity → §3 → PUT, ok-catchup, tier key, line() parameterised, two-invitation bound → excluded-unresponsive PASS catchup / quota / exhausted jq model shared by both jobs; key_namespace; test-catchup-model.py (28 PASS lines incl. closure-then-empty-generation, both-namespace exclusion, tier-enable, page order with duplicates, historical ok-backfill); call-site table (34 sites) in REPORT-r4 verified by Opus/Qwen
Quota: B definition (24 h, duplicates once, pending match per current generation), per-PUT re-evaluation, ≤ 3 min wait, ceiling 45 sweep-only; event 422 → whole bundle deferred-quota; sweep 422 → one skip line, green; other 422 → error-422 PASS quota model + quota_room (three bounds); test-catchup-model.py quota window edges incl. already-1 / old-generation / error / deferred; test-live-contract.py event bundle + sweep stop; mutations ii, vii red
Launch gate: invitation-limit 422 message pinned in a fixture from a live probe before merge N/A by owner decision (B, 2026-09-09): provisional pattern isolated + positive/negative fixtures; live text pinned on first occurrence (x-collector#143) INVITE_LIMIT_PATTERN isolated to one assignment; invitation-limit-422.json + positive/negative tests
Gates (a)(b)(c) for pass 2; unstar cleanup keeps (a)–(e); cancelled runs counted, never gating PASS decide runs queries → outputs cancelled_event/cancelled_schedule; act catch-up gate vs cleanup gate; fixtures 4/2 cancelled runs with gate true
Regeneration: GET sha → fresh ledger read → render → PUT, restart from GET; once per live sweep incl. red-run sweeps; ≤ 1 write/s pacing PASS regenerate() + pace_write(); test-live-contract.py 409/422 restart, concurrent actor, expired omission/restoration (tier-1 only), header failure, queue closure; mutation iv red
State + notifications: sweep-state.json keys exactly per §6.6; one summary per sweep; edge-triggered alarms; record-only rehearsal writes only would-*/skip, never the state file PASS sweep_state() (key set asserted), notifier summary/transition tests, test-decide.py rehearsal fixtures (identity 404 → no line; tier-disabled → nothing; no state write)
stars timeout 10; guard unchanged (400 k) PASS test-interface.py pins; stable first-occurrence order (stable_unique_by, mutation i red)
Fixture list of the Issue (deferred-quota, ok-catchup, §3 reasons, ordering across pages, 2-invitation bound, both-namespace exclusion, quota edges, stop line no self-exclusion, stranded append, two-lane regen conflict, pacing + wait cap, record-only ≡ live parity, sweep-state derivation, interface pins); static check + ledger vocabulary fixtures PASS files listed in REPORT-r4 §fixtures; 4 new ledger fixtures; 6 new static fixtures (3 stars + 3 concatenation); parity fixture executes the real rehearsal and live blocks (Opus M3 / Codex Sol)
README updated; tests + lint green; tag + Release v0.12.0 PASS / PASS / PENDING (at merge) README v2.0 section incl. provisional pattern + owner pin procedure + SUPPORTERS.header.md precondition; selftest green; tag after merge
5-seat review; merge only after x-collector A1 on main PASS / PASS seats table; x-collector PR #147 merged (v0.7.1)

宣言 vs 実 diff(L0-6)

git diff --stat origin/main...e4b97c5: .github/workflows/supporter-loop-reusable.yml, supporter-loop/README.md, supporter-loop/check-decide-static.sh, supporter-loop/fixtures/ledger/ (4 new), supporter-loop/fixtures/static/ (6 new), supporter-loop/fixtures/workflow/ (2 new tests, 1 fixture, 3 updated) — all inside the WIP file set (#91 (comment)); check-ledger.sh unchanged (declared, not needed).
宣言ファイル集合との差分: なし

🤖 Generated with Claude Code

…quota queue, sweep-state latch + summary, regenerate GET→read→render→PUT, work budget, stars timeout 10 (#91)

Implements caty-ai/x-collector contract v2.0 (PR #147, r8 e3bdea1) in the reusable
workflow: standing catch-up (pass 1 projection repairs queued, pass 2 invitations under
Q = max(0, 45 - B) with per-PUT re-evaluation and a <= 3 min cumulative wait), whole-sweep
work budget (6 min / 150 writes incl. queued items, deadline inside steps 2-4 loops),
invitation-limit 422 -> deferred-quota (event: whole bundle; sweep: one skip line, green),
regenerate() GET sha -> fresh ledger read -> render -> PUT with restart-from-GET and the
tier-1 undelivered-invite omission, <= 1 content write/s pacing, ledger/<name>.sweep-state.json
latch with one summary Telegram per sweep and edge-triggered alarms, record-only rehearsal
parity, watermark = schedule|workflow_dispatch success, cancelled-run counts from decide,
stars timeout 10 with stable first-occurrence id order, backfill input retired, static
check covers the stars job (3 violating fixtures), 4 new ledger fixtures, provisional
INVITE_LIMIT_PATTERN pinned by fixture (owner replaces with the live text before merge).

Writer: Codex GPT-6 Astra (brief + continuation); commit by alpha.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ations (C1), pending-to-ledger match per current generation, stable_unique_by rename, decision-level parity fixture, rehearsal identity failure handling (#91)

Seats r1 on a8c626a: Opus NO-GO (C1 + M1-M3), Gemini GWN, Muse GWN. All applied;
mutations i-vii red then restored. Writer: Codex GPT-6 Astra; commit by alpha.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@shojikumaru

Copy link
Copy Markdown
Member Author

Review status (alpha, 2026-09-08): r1 a8c626a → Opus NO-GO (C1 closed-generation exclusion = silent loss, jq-verified) / Gemini GWN / Muse GWN / Codex Sol NO-GO (independently converged with Opus on all four items). delta-1 → r2 2e04d61: Opus GO · Gemini GO · Muse GO · Codex Sol GO-WITH-NITS; mutations i–vii red → restored; Alpha replication of the selftest green; CI green.

Remaining before merge: writer delta-2 (test-only: scratch dirs out of the repo) → r3 · fifth seat (GLM, SEAT-WAIT until 2026-09-10) · launch gate = live invitation-limit 422 text pinned in fixtures/workflow/invitation-limit-422.json (owner probe decision pending) · caty-ai/x-collector PR #147 on main · owner risk-reviewed. Record: _handoffs/dotgithub-supporter-loop-91-impl-20260908/review/record.md.

…elta, Opus nit) (#91)

Five TemporaryDirectory(dir=...) sites in the workflow fixtures now use the system temp
dir, so a crashed test run can never leave supporter-loop/fixtures/workflow/*-scratch
debris inside the tree. Test-only; workflow unchanged. Writer: Codex GPT-6 Astra; commit by alpha.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@shojikumaru

Copy link
Copy Markdown
Member Author

r3 = 3182d67 (test-only, Opus nit: the five TemporaryDirectory(dir=...) sites now use the system temp dir; Alpha replication green; Opus confirm requested). Head is final unless the fifth seat (GLM, 2026-09-10) or the 422 pin changes something.

…ll string concatenation before endpoint/keyword matching (+3 violating fixtures), loud stars-handoff re-validation, exact error code on queued appends, sweep-state keys per contract §6.6 (#91)

Fifth seat Qwen 3.8 Max on 3182d67: GO-WITH-NITS (MINOR-1 + NIT-2/3/4 applied; NIT-1 = REPORT rebuilt). Mutations i-viii red then restored. Writer: Codex GPT-6 Astra; commit by alpha.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@shojikumaru

Copy link
Copy Markdown
Member Author

🏷 ball:human — merge decision requested (alpha, 2026-09-09)

Merge candidate e4b97c5 (r4). Five seats, zero blocking: Opus GO · Gemini GO · Muse GO · Qwen GO · Codex Sol GWN (same-lineage exception, not load-bearing). CI green. Completion record in the PR body.

Owner actions (GitHub, two):

  1. Launch-gate decision — reply with one word here: A = I may probe the live invitation-limit 422 text (throwaway private repo under your account + one family-owned account, 51 × invite/cancel; nothing reaches anyone else), then I pin it in the fixture and re-request the label; B = merge with the provisional pattern and pin on first real occurrence (an unrecognised 422 fails closed: red run + Telegram).
  2. risk-reviewed label on the final head (e4b97c5 for B; a new head after the probe commit for A).

After that: squash → v0.12.0 tag + Release → #91 MERGED, caty-ai/x-collector#141 closed by pointer, x-collector A3 (PR #149) undrafted for its own label.
#92

@shojikumaru shojikumaru added ball:human Waiting on a human decision (owner). Alpha removes it after the decision comment. risk-reviewed Roster human reviewed this head SHA labels Sep 8, 2026
@shojikumaru
shojikumaru merged commit 81539ad into main Sep 8, 2026
6 checks passed
@shojikumaru shojikumaru removed the ball:human Waiting on a human decision (owner). Alpha removes it after the decision comment. label Sep 8, 2026
@shojikumaru
shojikumaru deleted the feat/91-supporter-loop-v2 branch September 8, 2026 18:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

risk-reviewed Roster human reviewed this head SHA

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant