Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 25 additions & 1 deletion SPEC.md
Original file line number Diff line number Diff line change
Expand Up @@ -1231,8 +1231,32 @@ The self-test is not decoration. It replays both real incidents as positive cont
cosmetic tolerances as negative controls, so the tempting "fix" for a noisy checker — loosening it
until it stops complaining — fails the suite instead of passing quietly.

**DEC-081 — Callers pin a release tag and pass secrets explicitly, never `secrets: inherit`.**
The caller templates used `@main` with `secrets: inherit`, and the reach of that was six times what
it first appeared: **24 repos**, not four — including `agent-zero-new-plugin-template`, so every
future plugin inherited it too.
*Why the tag matters.* `@main` is not merely mutable; it means **every merge to the devkit goes live
in 24 repos' CI immediately and unreviewed**. That is not hypothetical — three fleet-wide regressions
in this SPEC came from exactly that path (DEC-079 closed 18 sync PRs, four already green; DEC-073 left
every consumer's green e2e shipping zero traces; DEC-075 made every Dependabot PR structurally red).
A tag is movable by the same principal who could push `main`, so it is not a security boundary against
an insider — what it changes is the **default**: `main` advances automatically on every merge, a tag
only on a deliberate act. Forgetting to bump fails *safe* (stale but working), never open.
*Why explicit secrets.* `inherit` forwards every repo- and org-level secret, including ones added
later and ones the workflow never declares — so a change to the devkit would execute with the full
secret set of all 24 consumers. The reusable workflow declares exactly three, all `required: false`,
so an explicit list is a strict superset of what it consumes and breaks nothing. It also closes an
unverifiable gap: org-level secrets cannot be enumerated without `admin:org`, and `inherit` would
forward those too. `devkit-sync.caller.yml` already passed secrets explicitly; this brings the other
caller into line rather than inventing a new convention.
*Rated honestly.* Confidentiality risk is **low** — the org has one member, zero forks, no outside
collaborators, and the credential that could leak is a read-only package token plus an App key whose
job disappeared when the devkit went public. The real cost is **integrity and blast radius**: one
unreviewed merge changes CI in 24 repos with no canary and no rollback boundary. Fixed as change
management, not as a breach.

> **Numbering note:** DEC-070–072 were originally introduced as code comments only. They are now
> written up above, so this section documents an unbroken 070–080. The lesson is worth keeping: a
> written up above, so this section documents an unbroken 070–081. The lesson is worth keeping: a
> decision that exists only as a code comment is invisible to anyone reading the SPEC, and all three
> of these were load-bearing (a gate's timeout budget, the image the whole fleet pulls, and a wait
> that was flaking half the runs).
Expand Down
2 changes: 1 addition & 1 deletion templates/devkit-sync.caller.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ permissions:

jobs:
sync:
uses: agent-zero-plugins/agent-zero-plugin-development-testkit/.github/workflows/devkit-sync.yml@main
uses: agent-zero-plugins/agent-zero-plugin-development-testkit/.github/workflows/devkit-sync.yml@v2.1.6
with:
devkit-submodule-path: "tests/_testkit"
secrets:
Expand Down
23 changes: 18 additions & 5 deletions templates/plugin-e2e.caller.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,19 @@
# (.devkit.yml `plugin_dir` → else canonical usr/plugins/<name>), so this
# template is identical across all repos.
#
# Needs the shared sync-App secrets to clone the private devkit submodule, and
# (DEC-055) GHCR_PULL_TOKEN to pull the private fork image. `secrets: inherit`
# forwards all repo secrets, so new shared secrets need no caller change.
# Needs (DEC-055) GHCR_PULL_TOKEN to pull the private fork image; the sync-App
# pair is legacy/optional since the devkit went public (DEC-075).
#
# SECRETS ARE PASSED EXPLICITLY, not via `secrets: inherit` (DEC-081).
# `inherit` forwards EVERY repo- and org-level secret to the reusable workflow —
# including secrets added later and secrets the workflow never declares. The
# reusable workflow declares exactly the three below, all `required: false`, so
# this list is complete. Adding a shared secret later means editing this template
# and re-running `make link-workflows`, which is the same cadence as every other
# devkit asset (devkit-sync.caller.yml already pays exactly this cost).
#
# The `uses:` ref is pinned to a RELEASE TAG, not `@main`. With `@main`, every
# merge to the devkit went live in 24 consumers' CI immediately and unreviewed.
name: plugin-e2e

on:
Expand All @@ -30,7 +40,10 @@ permissions:

jobs:
e2e:
uses: agent-zero-plugins/agent-zero-plugin-development-testkit/.github/workflows/plugin-e2e.yml@main
secrets: inherit
uses: agent-zero-plugins/agent-zero-plugin-development-testkit/.github/workflows/plugin-e2e.yml@v2.1.6
secrets:
GHCR_PULL_TOKEN: ${{ secrets.GHCR_PULL_TOKEN }}
SKILLS_SYNC_APP_CLIENT_ID: ${{ secrets.SKILLS_SYNC_APP_CLIENT_ID }}
SKILLS_SYNC_APP_PRIVATE_KEY: ${{ secrets.SKILLS_SYNC_APP_PRIVATE_KEY }}
with:
capture-all-traces: ${{ inputs.capture_all_traces || false }}
Loading