Skip to content

feat(runner-policy): admit role-named secrets and register ci-workflows v0.51.0 intake - #811

Merged
kyle-sexton merged 1 commit into
mainfrom
feat/runner-policy-intake-v0.51.0
Oct 19, 2026
Merged

kyle-sexton merged 1 commit into
mainfrom
feat/runner-policy-intake-v0.51.0

Conversation

@kyle-sexton

Copy link
Copy Markdown
Member

No related issue: runner-policy contract for the claude-code-plugins intake move onto ci-workflows' run-claude-cli launcher (melodic-software/ci-workflows#784)

Hold: a human merges this.

Summary

claude-code-plugins' intake now calls ci-workflows intake-triage.yml at v0.51.0, which runs on the run-claude-cli launcher funded by the automations role. The caller passes the role's two secrets, AUTOMATIONS_CLAUDE_API_KEY and AUTOMATIONS_CLAUDE_CODE_OAUTH_TOKEN. The same-name rule for privileged contracts (from #140) refused that mapping, so the contract could not load.

Fix

  • Security control change, owner-approved 2026-10-19. A contract with allowedCallerPermissions may map a secret to its own name, as before, or to its funding role's prefixed name, NAME: ${{ secrets.<ROLE>_NAME }}. The prefixed form is allowed only when:

    • the contract declares a literal role (lower kebab);
    • role is in its allowedInputs;
    • the caller passes role: as that exact literal.

    Any other rename is still refused, as are expressions and other prefixes. The role is a reviewed literal, never an expression. This lets several funding roles share one repository.

  • Registers intake-triage.yml@f129f4c142d00ec3feacbf2b9102d327088504bf (v0.51.0) with role: automations. It is the v0.49.0 entry plus the role input, with the one OAuth secret replaced by the role's API key and subscription token. Permissions and routing are unchanged. The v0.49.0 entry stays until the caller repins.

  • policy.schema.json gains the optional role property, and the README documents the rule.

Verification

node --test components/runner-policy/*.test.mjs: 319/319 pass. The new tests:

  • a declared role's prefixed secret is accepted;
  • another role's prefix is refused, and so is a prefix with no declared role;
  • a declared role missing from allowedInputs is refused;
  • a caller passing another role is refused;
  • the v0.51.0 entry equals the v0.49.0 entry plus the role, its input and its secrets.

Related

🤖 Generated with Claude Code

…ws v0.51.0 intake

A contract with allowedCallerPermissions still maps each secret to its own
name, or now, when it declares a funding role, to that role's prefixed name
(<ROLE>_NAME, role upper-snake). The role must be in allowedInputs and the
caller must pass it as that exact literal; every other rename is refused.
Role-named secrets let several funding roles share one repository.

Registers intake-triage at the v0.51.0 tag f129f4c with role automations:
the v0.49.0 entry plus the role input and the automations role's API key
and subscription token. The v0.49.0 entry stays until the caller repins.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 19, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-19T02:38:05.367533Z 0bcc13d PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@github-actions

Copy link
Copy Markdown

claude-security-review reviewed 0bcc13dface6be3dd7805ab1f7ecdb26de638b6b: 0 finding(s), 0 as inline comments.

Security review: PR #811 at 0bcc13d

No security findings.

I read the diff (policy.json, policy.schema.json, runner-policy.mjs, tests, README) and the surrounding reusableWorkflowStatus enforcement path in runner-policy.mjs.

Role-named secrets. The check does not let a caller choose a secret name.

  • validatePolicy accepts a role-prefixed secret expression only when the contract itself declares role. The prefix comes from that role, upper-snaked. Any other rename is still refused.
  • The caller side is exactCanonicalMap(job.secrets, contract.allowedSecrets, …). It requires the caller's secrets: block to match the reviewed contract exactly, so the policy file fixes which secret expression a caller can pass.
  • A contract that declares role must list role in allowedInputs. The caller must pass inputs.role as a strict !== literal match. An expression, such as ${{ inputs.x }} or ${{ github.event.* }}, cannot satisfy it.
  • The role is added to reviewedContractSurface. A Dependabot SHA bump that changes the role is therefore not treated as an unchanged surface, and the README keeps secret-capable runner-input contracts out of the Dependabot auto-approval extension.

New intake-triage contract. It pins a 40-character SHA. It keeps the exact caller permissions (contents: read, issues: write). It maps only the two AUTOMATIONS_* secrets by whole named expression. It adds no secrets: inherit or wildcard path.

Residual note, not a finding. A role prefix lets a reviewed contract name any <ROLE>_<NAME> secret. That is bounded by human review of policy.json, and the same review already gates the existing same-name mapping. I did not independently confirm that SHA f129f4c… is the ci-workflows v0.51.0 tag. The sandbox blocked the gh api lookup. The PR marks sign-off as pending.

No instruction-surface deletions: no rule, hook or allowlist was removed or softened. I did not consult the instruction exception register, because no standing instruction was deleted.

@github-actions

Copy link
Copy Markdown

claude-review reviewed 0bcc13dface6be3dd7805ab1f7ecdb26de638b6b: 0 finding(s), 0 as inline comments.

No findings. I read the full diff, the PR context, and the surrounding runner-policy.mjs code. I did not run the tests myself. The PR's "319/319 pass" is author-claimed and unverified by me.

  • Validation: validatePolicy accepts a secret mapped to its own name or to ${{ secrets.<ROLE>_NAME }}. The prefixed form is accepted only when a literal role is declared and role is in allowedInputs. Every other rename is still refused, and the generic whole-expression check still applies afterwards.
  • Caller check: reusableWorkflowStatus requires inputs.role to equal the contract's role. An expression or another role therefore fails the strict comparison. The check runs after the secrets and allowedInputs checks.
  • Auto-approval: reviewedContractSurface now includes role, so a Dependabot bump cannot change a contract's role without a human review. Contracts with allowedCallerPermissions are still declined for auto-approval.
  • Schema: role is constrained to lower kebab, so toUpperCase().replaceAll("-", "_") cannot produce a collision.
  • Policy entry: the v0.51.0 intake-triage entry matches the README text and the equality test against the v0.49.0 entry, and the v0.49.0 entry is kept.
  • Tests: the new tests cover the accepted prefixed secret, another role's prefix, a prefix with no declared role, a declared role missing from allowedInputs, and a caller passing the wrong role.
  • PR body: the body follows the contract (No related issue: line with a reason, plus Summary, Fix, Verification and Related sections).
  • Closing issues: the PR closes none, so there are no acceptance criteria to check.
  • Rules: the only path-scoped rule file, .claude/rules/pr-body-contract.md, covers PR bodies, not the changed files.

@kyle-sexton
kyle-sexton merged commit cb0ec4a into main Oct 19, 2026
22 checks passed
@kyle-sexton
kyle-sexton deleted the feat/runner-policy-intake-v0.51.0 branch October 19, 2026 02:47
kyle-sexton added a commit that referenced this pull request Oct 21, 2026
…829)

Refs: melodic-software/ci-workflows#818
Refs: melodic-software/ci-workflows#819

## Summary

Registers ci-workflows
[v0.56.0](https://github.com/melodic-software/ci-workflows/releases/tag/v0.56.0)
(`1bb8953644d16f21d10718962c6702f01ea3308a`) contracts for
`intake-triage.yml` and `intake-label-needs-triage.yml`.
[claude-code-plugins#8800](melodic-software/claude-code-plugins#8800)
repins both intake callers to that SHA, and its runner-policy gate fails
with `runner-target-contract: the reusable workflow path@SHA has no
reviewed runner-input contract (auto-approval declined: ... carries a
reviewed allowedCallerPermissions grant; its steps cannot be proven
unchanged by this surface diff, so auto-approval is declined)` for both.

**Owner sign-off pending.** The README rollout line says "sign-off
pending"; replace it with "signed off by hand on <date>" when you sign
off.

## Fix

- `components/runner-policy/policy.json`:
- `intake-triage.yml@1bb8953`: a verbatim copy of the v0.55.0 entry
(`3a2c748`).
- `intake-label-needs-triage.yml@1bb8953`: a verbatim copy of the
v0.42.0 entry (`182aca3`), the newest reviewed SHA for that file.
- `components/runner-policy/README.md`: the v0.56.0 rollout line,
sign-off pending.
- `components/runner-policy/runner-policy.test.mjs`: a test that both
v0.56.0 entries equal their source entries and that they are the only
two keys at that SHA.

### Byte-identity evidence

Git blob SHA of each file, from `gh api
repos/melodic-software/ci-workflows/contents/.github/workflows/<file>?ref=<sha>
--jq .sha`:

| File | `182aca3` | `3a2c748` (v0.55.0) | `1bb8953` (v0.56.0) |
|---|---|---|---|
| `intake-triage.yml` | `fa4e86f8` | `d79b7e9e` | `d79b7e9e` |
| `intake-label-needs-triage.yml` | `8d274a66` | `8d274a66` | `8d274a66`
|

`gh api repos/melodic-software/ci-workflows/compare/3a2c748...1bb8953`
is one commit
([ci-workflows#836](melodic-software/ci-workflows#836))
touching nine files, all under `.github/actions/pr-require-checks/` plus
`README.md`; neither intake file is among them. Against `182aca3`, the
compare lists `intake-triage.yml` as modified (that file's reviewed SHA
is `3a2c748`, not `182aca3`) and not `intake-label-needs-triage.yml`.

No input, secret, caller permission, or `runs-on` routing changes.

## Verification

- `npm run test:runner-policy`: 323 pass, 0 fail, including `✔ both
intake contracts at the v0.56.0 tag copy their reviewed entries forward
verbatim`. Adding an input to the new `intake-label-needs-triage` entry
makes that test fail.
- `npm run lint:runner-policy`: `Runner policy passed.`
- Against the claude-code-plugins#8800 worktree
(`--repository-visibility public`, worktree not modified):
- with its vendored policy: the two `runner-target-contract` failures
above, and no others;
- with `--policy` set to this branch's `policy.json`: `Runner policy
passed.`;
- with this branch's `runner-policy.mjs` and `policy.json`: `Runner
policy passed.`
- Every other ci-workflows reusable that #8800 calls stays on a SHA that
already has a contract (`pr-review` and `pr-review-security` at v0.48.0,
`maintenance-check-links-online` at v0.55.0). The other v0.56.0 pins are
composite actions, which need no contract.
- lefthook pre-commit (typos, editorconfig, markdownlint, gitleaks,
biome): clean.

## Related

-
[claude-code-plugins#8800](melodic-software/claude-code-plugins#8800)
passes its runner-policy gate once this merges and syncs.
- Precedent: #828 (v0.55.0 verbatim copies), #811, #802.

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

https://claude.ai/code/session_01Tje1j8KEYaYSxYe8RgJ3Lw

---------

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

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant