Skip to content

ci(intake-triage): skip a credit-stopped key with the check-key-credit gate - #996

Draft
kyle-sexton wants to merge 1 commit into
mainfrom
ci/intake-key-credit-gate
Draft

kyle-sexton wants to merge 1 commit into
mainfrom
ci/intake-key-credit-gate

Conversation

@kyle-sexton

@kyle-sexton kyle-sexton commented Nov 5, 2026 •

Copy link
Copy Markdown
Member

Refs: melodic-software/claude-code-plugins#13586

Summary

intake-triage.yml now has the check-key-credit gate that pr-review-cli.yml already runs. When the key's last credit stop is inside the window, the intake triage skips the key and goes straight to the subscription leg, so no request is spent on a key that will be refused.

Hold: merge only after the reusable's one caller job grants actions: read. That grant waits on melodic-software/standards#949, which adds actions: read to this reusable's runner-policy contract; a claude-code-plugins follow-up then adds the grant to its intake-triage caller job. The gate needs that permission, and a called workflow can hold only what its caller grants, so merging this first would break that caller. A code search for ci-workflows/.github/workflows/intake-triage.yml across melodic-software found no other workflow caller. The do-not-merge label holds it.

Fix

  • New check-key-credit job. It holds no secret and only actions: read. It runs for every event, so the claude job's skip still means only that the author gate refused.
  • The claude job's credential step treats a credit-stopped key as absent (key-skipped=true). The subscription job then triages, and its lane record carries fallback reason credit-stop.
  • After its own credit stop, the key leg scans and uploads the gate's marker, the same way pr-review-cli.yml does.
  • New input key-credit-window-hours, default 1.
  • The usage example in the header now shows actions: read.

Verification

  • node --test .github/scripts/*.test.cjs: 913 tests, 913 pass.
  • New test: the gate's job, the skip branch of the credential step (run in bash), and the marker upload.
  • The parity test still holds the subscription job equal to the claude job, apart from the named key-only steps.
  • zizmor --offline: no findings.

Related

Draft: held until the intake-triage caller grants actions: read

🤖 Generated with Claude Code

…t gate

intake-triage.yml gets the gate pr-review-cli.yml already runs. A
check-key-credit job with no secret reads the key's credit history; the
claude job treats a key whose credit stop is inside
key-credit-window-hours (1 hour by default) as absent, so the
subscription job triages with fallback reason credit-stop, and the key
leg uploads the gate's marker after its own credit stop. The claude job
still runs, so its skip stays the author gate's alone. The gate reads
markers with actions: read, which each caller job must now grant.

Refs: melodic-software/claude-code-plugins#13586

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kyle-sexton kyle-sexton added the do-not-merge Hard merge gate: do not merge while applied. label Nov 5, 2026
kyle-sexton added a commit to melodic-software/standards that referenced this pull request Nov 5, 2026
…949)

Refs: melodic-software/claude-code-plugins#13586

## Summary

This adds `actions: read` to the reviewed `allowedCallerPermissions` of
the `intake-triage.yml` contract in
`components/runner-policy/policy.json`. After the change, a caller job
that grants `actions: read` passes `runner-policy`.

## Fix

-
[melodic-software/ci-workflows#996](melodic-software/ci-workflows#996)
adds the `check-key-credit` gate to `intake-triage.yml`;
`pr-review-cli.yml` already has it. The gate lists the repository's
credit-stop markers with `actions: read`. A called workflow holds only
the permissions its caller job grants, so every caller job must grant
`actions: read`.
- Until this contract lists that permission, `runner-policy` rejects the
claude-code-plugins caller: "reusable workflow caller permissions has
unapproved properties". That rejection is the lint-repo failure on
[melodic-software/claude-code-plugins#13608](melodic-software/claude-code-plugins#13608).
- `actions: read` adds no write access and no token beyond the job's own
`GITHUB_TOKEN`.
- Callers float on `@main`, so `approvedShas` stays as it is.

## Verification

- `node --test components/runner-policy/runner-policy.test.mjs`: 226
tests, 226 pass.

## Related

Merge order:
1. This PR, then sync it to the callers.
2. The claude-code-plugins caller grants `actions: read`. That change
was taken out of #13608 until this PR syncs, and goes back in
afterwards.
3. ci-workflows#996, held with `do-not-merge` until step 2 lands.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
kyle-sexton added a commit to melodic-software/standards that referenced this pull request Nov 6, 2026
…r a migration (#950)

Refs: melodic-software/claude-code-plugins#13586

## Summary

The runner-policy contract matches a caller's permissions against
`allowedCallerPermissions` exactly. standards#949 added `actions: read`
to the intake-triage contract, so every caller must now grant it. The
claude-code-plugins caller cannot add it before the sync, because the
old synced contract rejects it, and the sync PR cannot be hand-edited
(check-managed-files). Under a required `ci-status` that is a deadlock.

This PR lets a contract list several exact maps, so a permission change
lands expand/contract:

1. This PR: accept a list of exact maps, and list the old and new
intake-triage maps.
2. The sync PR, plus the caller PR that grants `actions: read`.
3. A follow-up that drops the old intake-triage map.
4. melodic-software/ci-workflows#996.

## Fix

- `runner-policy.mjs`: `allowedCallerPermissions` is one exact map or a
non-empty list of exact maps. A caller passes when its effective
permissions equal any one listed map exactly; each map is still matched
exactly, so the ceiling stays exact. With more than one map, the failure
names every allowed map (`must be exactly one of {...}, {...}`). The
single-map form keeps its existing per-scope wording. The config checks
(at least one write scope, satisfies `minimumCallerPermissions`) run per
map and name the failing index.
- `policy.schema.json`: `allowedCallerPermissions` is `oneOf` a
permission map or a `minItems: 1`, `uniqueItems` array of permission
maps.
- `policy.json`: intake-triage lists `{contents: read, issues: write}`
and `{actions: read, contents: read, issues: write}`. No other contract
changes.
- `README.md`: "Contract revision lockstep" item 3 describes the
expand/contract order for a caller permission change; the contract
description admits the list form.

## Verification

- `npm run test:runner-policy`: 232 pass, 0 fail. New cases: a caller
matching the first map passes; matching the second passes; matching
neither fails with a message listing both; a superset, subset or access
drift of either map fails; the single-map form passes on an exact match
and keeps its old messages; the schema rejects an empty list; every
listed map must carry a write scope.
- A mutant that checks only the first listed map fails the "matching
either map passes" case.
- `npm run lint:runner-policy`: passed.
- biome 2.6.3 `ci` on `components/runner-policy`: clean. markdownlint on
the README: clean.

## Related

- #949
- melodic-software/claude-code-plugins#13630
- melodic-software/ci-workflows#996

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

do-not-merge Hard merge gate: do not merge while applied.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant