Skip to content

feat(managed-files-guard): ship an advisory hosted-only caller for the fleet - #511

Merged
kyle-sexton merged 6 commits into
mainfrom
cursor/managed-files-guard-caller-77c4
Sep 2, 2026
Merged

feat(managed-files-guard): ship an advisory hosted-only caller for the fleet#511
kyle-sexton merged 6 commits into
mainfrom
cursor/managed-files-guard-caller-77c4

Conversation

@kyle-sexton

Copy link
Copy Markdown
Contributor

Closes #496

Summary

Ships the first hop of the managed-files-guard rollout: a new sync-managed component, managed-files-guard-caller, that lands an advisory .github/workflows/managed-files-guard.yml on every hosted-only-eligible manifest target. The caller runs melodic-software/ci-workflows .github/actions/managed-files-guard on each consumer PR and fails when a sync-managed file was hand-edited, closing the seam ADR 0007 asked us to keep binary.

This hop is deliberately scoped:

  • Advisory. The lane is not in ci-status and is not a required check. Nothing is promoted here.
  • Hosted-only. The job runs on ubuntu-24.04 with no select-runner indirection. Targets enrolled in selector-routed runner policy (dotfiles, github-iac, medley, provisioning) are excluded and get a selector-routed sibling in the second hop.
  • standards-ref: main during soak, per the action's own input docs. This is a workflow input into a different repo, so the pin-comment convention does not apply and it is intentionally not SHA-pinned in this hop.

Targets managing the caller (confirmed against distribution/sync-manifest.yml, none invented): melodic-software/.github, agent-plugins, ci-runner, claude-code-plugins, claude-code-proxy, codex-plugins, cursor-plugins. melodic-software/ci-workflows is declared locally-owned because it already runs the guard as a job inside its own ci.yml.

Fix

New component components/managed-files-guard/

  • managed-files-guard.yml — the caller. on: pull_request, permissions: contents: read, canonical concurrency block, single job on ubuntu-24.04 with timeout-minutes: 10, actions/checkout SHA-pinned (persist-credentials: false, fetch-depth: 0 so the guard can diff base..head), then uses: melodic-software/ci-workflows/.github/actions/managed-files-guard@3b2f4eab5b4bb58a150e400613350ede37742ee8 # 3b2f4ea 2026-08-30 with standards-ref: main.
  • README.md — records advisory soak, hosted-only first hop, excluded selector-routed targets and their removal trigger, the ci-workflows locally-owned reason, standards-ref: main until soak completes, the pin-advance path, the checkout-pin/Dependabot interaction as an expected soak finding class, and the live consumer list.
  • managed-files-guard.test.sh — component contract test (see Verification).

Pin. 3b2f4eab5b4bb58a150e400613350ede37742ee8 is re-verified as the latest commit touching the action path on ci-workflows main as of 2026-09-02. The latest release, v0.17.2 (2026-08-21), predates that fix, so the pin has no matching release tag and uses the convention's fallback form # <short-sha> <date>.

Re-pin cascade (components/claude-lanes/, .github/workflows/claude-lanes-repin.yml)

  • The caller file is added to EXTRA_CALLER_FILES in repin-callers.sh, so it advances with the fleet when a release postdates the pin.
  • It is not added to REPIN_TARGETS in repin-policy-lockstep.mjs: that file's kind system expresses reusable-workflow contracts (workflow_call surface + policy.json entry). A composite action has neither, so a REPIN_TARGETS entry would lie about kind. Reason recorded in the component README.
  • New release fence: because the fleet release is currently older than this pin, an unfenced cascade would have rewritten 3b2f4ea (2026-08-30) back to v0.17.2 (2026-08-21). repin::resolve now emits the release commit date alongside tag and SHA; repin::apply skips any fallback-form pin whose recorded date is later than the release date and reports it as a notice instead of a rewrite. The workflow passes the date through and adds components/managed-files-guard to the PR add-paths.

Manifest and repo wiring

  • distribution/sync-manifest.yml — new managed-files-guard-caller component mapping components/managed-files-guard/managed-files-guard.yml.github/workflows/managed-files-guard.yml; managed on the seven targets above; locally-owned on ci-workflows.
  • distribution/README.md — records the caller as the second documented exception to "callers are consumer-owned", alongside the claude-lanes callers.
  • .github/workflows/ci.yml — runs the new component test in the actionlint job, adds the caller to the zizmor and pin-comment-convention scan paths.
  • components/runner-policy/runner-policy.test.mjs — asserts the caller audits clean on every target that manages it and is not admissible on a selector-enrolled private consumer. No approvedReusableWorkflowContracts entry was added: composite actions are not SHA-allowlisted and a hosted-only caller on a public/un-enrolled repo does not need one.

Not touched: ci-status, required-check configuration, repin-policy-lockstep.mjs.

Admission evidence (docs/component-lifecycle.md)

  • Live consumer. Seven manifest targets manage the file on merge; ci-workflows already runs the same action locally, proving the action end to end.
  • Owner. melodic-software/standards owns the caller shape and the pin; melodic-software/ci-workflows owns the action's behavior.
  • Delivery boundary. Sync-managed file at .github/workflows/managed-files-guard.yml. The action resolves the manifest from standards-ref: main, so target roster and component changes take effect without re-syncing the caller.
  • Alternatives considered. (a) Consumer-owned callers — rejected, the guard exists precisely because hand-edited callers drift. (b) Folding the guard into ci-status now — rejected, it must soak advisory first. (c) A single selector-routed caller for all targets — rejected for this hop, hosted-only targets must not take a runner-policy dependency they do not have.
  • Operational fit. contents: read only, hosted runner, 10-minute timeout, canonical concurrency. Failure mode is a red advisory check on the consumer PR with the guard's own diagnostic; rollback is removing the target from the manifest's managed list.
  • Update path. Action pin advances via claude-lanes-repin (EXTRA_CALLER_FILES, fenced so it never moves backwards). actions/checkout pin advances with sibling workflows in this repo. standards-ref gets SHA-pinned when soak completes (operator-hosted follow-up, not this PR).
  • Verification. Below.

Verification

Run locally on the final branch state (GIT_CONFIG_GLOBAL=/dev/null so a host insteadOf rewrite does not leak into the materialization tests):

Suite Result
components/managed-files-guard/managed-files-guard.test.sh 81 pass, 0 fail — shape, pins, standards-ref, manifest wiring, materialization on all 7 targets is byte-identical and actionlint-clean, ci-workflows receives no caller
components/claude-lanes/repin-callers.test.sh 110 pass, 0 fail — includes new date emission, ahead-of-release fence (older / same-day / newer), usage errors
components/claude-lanes/claude-lanes.test.sh 32 pass, 0 fail
components/runner-policy/runner-policy.test.mjs 274 pass, 0 fail — includes the two new admission-boundary tests
components/concurrency-policy (node --test) 24 pass, 0 fail
components/pin-comment-convention/pin-comment-convention.test.sh 34 pass, 0 fail
distribution/sync-manifest.sh validate Manifest valid: 35 components, 12 targets
actionlint on the caller and all .github/workflows/*.yml clean
shellcheck on repin-callers.sh and the new test clean

Pin re-verified via gh api repos/melodic-software/ci-workflows/commits?path=.github/actions/managed-files-guard&sha=main: latest is 3b2f4eab5b4bb58a150e400613350ede37742ee8 (2026-08-30); latest release v0.17.2 (2026-08-21).

Related

  • Roll managed-files-guard out to every sync target so a downstream hand-edit of a managed file fails its PR #496 — the rollout issue this closes (first hop).
  • docs/adr/0007-keep-the-managed-file-seam-binary.md — the decision this enforces.
  • melodic-software/ci-workflows .github/actions/managed-files-guard — the action being called.
  • Second hop (selector-routed sibling for dotfiles, github-iac, medley, provisioning), soak monitoring, promotion into ci-status, and SHA-pinning standards-ref remain operator-hosted after this PR. No new tracking issue was filed.

…kwards

Enumerate the managed-files-guard caller component among the extra caller
files the daily re-pin rewrites, and teach apply to leave a fallback-form
pin alone when its recorded commit date is after the release commit's:
such a pin names a commit the release cannot contain, so rewriting it would
move the pin behind the fix it was taken for. resolve now emits the release
commit date; the workflow passes it through and commits the new component
directory.
…e fleet

Add the managed-files-guard-caller component: a thin pull_request workflow
that calls the ci-workflows managed-files-guard composite action on the
approved hosted label, pinned by full SHA to the commit that closed the
guard's fail-open (3b2f4eab, fallback pin-comment form since no release
carries it yet), with standards-ref: main for the soak. Manage it for the
seven hosted-only-eligible targets, hold it locally-owned in ci-workflows
(which already runs the action from its own tree), and defer the four
selector-enrolled private targets to a selector-routed sibling. Advisory:
no ci-status change anywhere.

The contract test asserts the caller shape against the parsed YAML, the
manifest wiring and target boundary, and byte-exact materialization
through the real engine; CI runs it in the actionlint job and adds the
component file to the pin-comment and zizmor scans.
…boundary

The caller must audit clean on every target that manages it under that
target's hosted-only inventory (claude-code-plugins executes the gate), and
must not be admitted to a selector-enrolled private consumer, which is the
property the second-hop exclusion rests on.
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 2, 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-09-02T05:22:55.576846Z c7bf9c6 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.

@claude

claude Bot commented Sep 2, 2026

Copy link
Copy Markdown

Claude finished @kyle-sexton's task in 3m 44s —— View job


Review complete

  • Gather context (PR body, changed files, custom instructions)
  • Run review (the /review:code-review skill invocation errored in this environment — Execute skill: review:code-review with no further output — so I performed the equivalent manual review directly against origin/main...HEAD, reading every changed file and cross-checking cross-file claims)
  • Check for inline findings
  • Post summary

No inline findings — I didn't find a defect I'm confident enough in to anchor to a line.

What I checked

  • components/managed-files-guard/managed-files-guard.yml — shape matches the README/PR-body claims: pull_request trigger, contents: read only, canonical concurrency block, single hosted job (ubuntu-24.04, 10 min timeout), full-history credential-free checkout, action pinned by full 40-char SHA in the fallback comment form.
  • Manifest wiring (distribution/sync-manifest.yml) — the 7 targets the PR body claims manage the caller (melodic-software/.github, agent-plugins, ci-runner, claude-code-plugins, claude-code-proxy, codex-plugins, cursor-plugins) are exactly the 7 that do; the 4 selector-routed exclusions (dotfiles, github-iac, medley, provisioning) correctly have no managed-files-guard-caller entry; ci-workflows is locally-owned as claimed.
  • components/claude-lanes/repin-callers.sh — the new downgrade fence (repin::ahead_of / the rewrite/ahead split in repin::apply) is correct: strict > comparison so a same-day pin still advances, ISO date strings compare correctly lexically, the all-ahead path cleanly no-ops with changed=false, and a malformed release date is rejected before any file is touched. repin-callers.test.sh covers all of these (ahead, same-day, all-ahead, bad-date, usage-error) with concrete assertions on file bytes and GITHUB_OUTPUT, not just exit codes.
  • components/runner-policy/runner-policy.test.mjs additions — the two new tests (audits clean on every managing target; rejected on a selector-enrolled private consumer) match the exclusion boundary described in the README.
  • Cross-file consistency.github/workflows/ci.yml (component test wiring, zizmor paths, pin-comment-convention scan paths), .github/workflows/claude-lanes-repin.yml (date output threaded through, add-paths, PR body text), distribution/README.md, and components/managed-files-guard/README.md all cross-reference each other consistently — same target list, same rationale, no stale prose.
  • Doc links resolve: docs/component-lifecycle.md and docs/adr/0007-keep-the-managed-file-seam-binary.md both exist.

One thing worth a sanity check, not a blocker

repin::resolve fetches the release commit's committer date via commit.committer.date for the ahead-of-release comparison, and the comment explains that choice deliberately. The fallback pin comments themselves (e.g. # 3b2f4ea 2026-08-30 in the new guard caller) are hand-authored, though, and nothing pins down whether those dates are committer or author date — pin-comment-convention only format-checks the fallback date, it doesn't verify provenance against the actual commit (pin-comment-patterns.sh). If a future hand-authored fallback comment used author date instead of committer date for a commit where the two diverge (rebase/squash), the fence's comparison would be comparing two different date bases. Given the PR's own comment shows this distinction was already considered deliberately, I'd treat this as a "worth a note in the README's pin-advance section" possibility rather than a real defect — not asking for a change.

Not independently re-verified

  • The upstream ci-workflows facts (latest release v0.17.2, latest commit on the action path 3b2f4eab5b4bb58a150e400613350ede37742ee8, ci-workflows#530 fixing the fail-open bug) — no network access in this environment to call gh api.
  • Actual actionlint/shellcheck/node --test runs — I read the diffs and cross-checked logic rather than re-executing the suites the PR body already reports as green.

Everything else — the manifest, the fence logic, the test coverage, and the cross-references between the four docs touched — checked out consistent and correct.

Branch

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c7bf9c6514

ℹ️ 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".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread components/claude-lanes/repin-callers.sh Outdated
@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown

Claude has reviewed this PR 1 time. The lane skips further automatic reviews after 5; deleting this comment resets the count.

cursoragent and others added 2 commits September 2, 2026 05:42
Day-truncated pin-comment dates treat a same-UTC-day fallback pin as
contained even when the release predates it. apply now asks GitHub's
compare API whether the pinned SHA is an ancestor of the release SHA
(ahead/diverged stays put; behind/identical rewrites; lookup failure is
fatal). Dates remain argv/provenance only.

Also scope old-sha extraction to ci-workflows uses: lines so a sibling
actions/checkout pin is not reported as a ci-workflows SHA.

Co-authored-by: ksextonmelodic <ksextonmelodic@gmail.com>
The assignment already runs under set -e, so a failed compare exits
without an || condition that disables errexit inside the function.

Co-authored-by: ksextonmelodic <ksextonmelodic@gmail.com>
@kyle-sexton
kyle-sexton merged commit 2ad65dd into main Sep 2, 2026
46 checks passed
@kyle-sexton
kyle-sexton deleted the cursor/managed-files-guard-caller-77c4 branch September 2, 2026 05:48
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.

Roll managed-files-guard out to every sync target so a downstream hand-edit of a managed file fails its PR

2 participants