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
80 changes: 0 additions & 80 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -965,89 +965,11 @@ jobs:
run: bash distribution/sync-manifest.test.sh
- name: Claude-permissions tombstone contract test
run: bash harness/shell/run-tests.sh components/claude-permissions/claude-permissions.test.sh
- name: Plugin-baseline drift-report contract test
run: bash harness/shell/run-tests.sh distribution/check-plugin-baseline.test.sh
- name: Claude-settings target conformance test
run: bash harness/shell/run-tests.sh distribution/check-claude-settings-targets.test.sh
- name: Validate production manifest
run: bash distribution/sync-manifest.sh validate

# Fleet plugin list vs the dotfiles workstation seed. The two lists are
# separate on purpose — the fleet list is the cloud install set and
# first-party only, the seed is workstation enablement and additionally
# carries third-party entries — but a first-party plugin added to the fleet
# list and never reaching the seed is drift nobody sees until someone runs
# the comparator by hand, which is how four ids went missing.
#
# A separate job rather than a step in `distribution`, for the reason the
# pr-convention-lockstep job records: this reads a consumer repository over
# the network, and that non-hermetic dependency must not contaminate the
# hermetic component lane.
plugin-seed-drift:
if: ${{ !(github.event.pull_request.head.repo.full_name == github.repository && (contains(fromJSON('["labeled","unlabeled"]'), github.event.action) || (github.event.action == 'edited' && !github.event.changes.base))) }}
permissions:
contents: read
runs-on: ubuntu-24.04
timeout-minutes: 15
env:
HAS_APP_CREDENTIAL: >-
${{ secrets.STANDARDS_SYNC_APP_CLIENT_ID != ''
&& secrets.STANDARDS_SYNC_APP_PRIVATE_KEY != '' }}
steps:
- name: Check out
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
# dotfiles is private, so the seed is read through the contents API with
# a contents-read token from the standards-sync App — the
# pr-convention-lockstep precedent. No new secret: dotfiles is already a
# sync target, so it is in that installation, and the token is narrowed
# to that one repository. The mint is guarded on the secrets being
# present so an absent credential reaches the fetch tokenless and dies
# there instead of inside the mint action; the fetch then fails and the
# lane goes red. Not a skip either way. Two run kinds see no repository
# secrets: fork pull requests, which carry none by design, and
# Dependabot pull requests, which resolve the same two names from this
# repository's Dependabot secrets. Mirror both there whenever the App
# key rotates, or every Dependabot pull request fails this lane too.
- name: Mint a contents-read App token for the dotfiles seed
id: seed-token
if: env.HAS_APP_CREDENTIAL == 'true'
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
with:
client-id: ${{ secrets.STANDARDS_SYNC_APP_CLIENT_ID }}
private-key: ${{ secrets.STANDARDS_SYNC_APP_PRIVATE_KEY }}
owner: melodic-software
repositories: dotfiles
permission-contents: read
# Only a fleet plugin the seed never names gates, and `-strict` is how
# the comparator says so: exit 3. Plain --compare-seed exits 1 on ANY
# divergence, and the steady state carries deliberate per-plugin seed
# opt-outs plus one seed-only entry, so that code alone would be red on
# every run; grepping the report's prose instead would put the go/no-go
# on a message the script is free to reword, which is the same silent
# green one layer down. Exit 2 — missing jq, unreadable JSON, a seed
# with no claudeSettings.seed.enabledPlugins object — still fails the
# lane, so a broken comparator or a structurally broken seed cannot read
# as "no drift". The report itself streams to the log either way.
- name: Compare the fleet plugin list against the dotfiles seed
env:
GH_TOKEN: ${{ steps.seed-token.outputs.token }}
run: |
set -euo pipefail
seed="$RUNNER_TEMP/dotfiles-claude.json"
gh api -H 'Accept: application/vnd.github.raw+json' \
repos/melodic-software/dotfiles/contents/.chezmoidata/claude.json >"$seed"
bash distribution/check-plugin-baseline.sh --compare-seed-strict "$seed" && rc=0 || rc=$?
case "$rc" in
0 | 1) ;;
3)
echo '::error::the dotfiles seed is missing a plugin the fleet list enables'
exit 1
;;
*) exit "$rc" ;;
esac
Comment thread
kyle-sexton marked this conversation as resolved.

exec-bit:
if: ${{ !(github.event.pull_request.head.repo.full_name == github.repository && (contains(fromJSON('["labeled","unlabeled"]'), github.event.action) || (github.event.action == 'edited' && !github.event.changes.base))) }}
permissions:
Expand Down Expand Up @@ -1252,7 +1174,6 @@ jobs:
- cloud-environment
- mcp-launcher
- distribution
- plugin-seed-drift
- lefthook
- exec-bit
- machine-specific-paths
Expand Down Expand Up @@ -1321,7 +1242,6 @@ jobs:
${{ needs.harness.result }}
${{ needs.cloud-environment.result }}
${{ needs.distribution.result }}
${{ needs.plugin-seed-drift.result }}
${{ needs.lefthook.result }}
${{ needs.exec-bit.result }}
${{ needs.machine-specific-paths.result }}
Expand Down
45 changes: 22 additions & 23 deletions components/cloud-environment/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,19 +60,22 @@ its next cache rebuild (see [Update lifecycle](#update-lifecycle)).
## Plugin install

Before the repo bootstrap, a generic, data-driven stage installs plugins from
one settings-shaped file: **the fleet list**,
[`fleet-plugins.json`](fleet-plugins.json) beside this script, the one place
the organization's cloud plugin set is declared. The script fetches it from
the same `raw.githubusercontent.com` path the bootstrap fetches this script
from, writes it into the snapshot at `/opt/melodic-fleet-plugins.json`
one settings-shaped file: **the fleet list**. The script fetches the
melodic-software marketplace catalog,
[`.claude-plugin/marketplace.json`](https://github.com/melodic-software/claude-code-plugins/blob/main/.claude-plugin/marketplace.json)
in claude-code-plugins, and enables every plugin whose catalog entry leaves
`defaultEnabled` unset or sets it `true`; any other value leaves the plugin
off. It writes the list into the snapshot at
`/opt/melodic-fleet-plugins.json`
(falling back to `/tmp/melodic-fleet-plugins.json` with a logged `WARN` when
`/opt` is unwritable, mirroring the stamp), and installs it. Every snapshot
gets the fleet whatever repo it was built for, and the snapshot stays
repo-agnostic.

A repo's own `.claude/settings.json` carries only the deltas it declares
beyond the fleet (an extra marketplace, a plugin beyond the fleet, or a
`false` opt-out, which project scope applies over the user-scope install).
beyond the fleet (an extra marketplace, a `true` opt-in to an off-by-default
plugin, or a `false` opt-out, which project scope applies over the user-scope
install).
Those are not installed by this stage: the
[repo bootstrap](#repo-bootstrap-handoff) that runs next applies them as an
overlay on top of this list, from the snapshot copy (see the
Expand All @@ -88,17 +91,13 @@ add`, skipping ones already registered) and every `enabledPlugins` entry set
to `true` is installed (`claude plugin install <id> --scope user -y`,
skipping ones already installed). Every step is best-effort with a `WARN`
line to the log; the whole stage skips cleanly when the `claude` CLI or `jq`
is unavailable, and a failed fleet fetch installs no plugins that build (the
next rebuild fetches the list again).

The fleet list is settings-shaped on purpose: `jq` expressions written for a
repo's settings file read it unchanged, and
[`distribution/check-plugin-baseline.sh`](../../distribution/check-plugin-baseline.sh)
uses it as the baseline every repo and the dotfiles seed are compared
against. Every entry in it is `true`; a `false` is a per-repo decision and
belongs in that repo's own file. To add a plugin to the fleet, add its entry
here in byte order (`setup.test.sh` checks both), then force a snapshot
rebuild (see [Update lifecycle](#update-lifecycle)).
is unavailable, and a failed catalog fetch installs no plugins that build (the
next rebuild fetches the catalog again).

The fleet list is settings-shaped so the repo bootstrap reads it with the
same `jq` expressions it uses on a repo's settings file. To add a plugin to or
remove one from the cloud fleet, change its `defaultEnabled` in the catalog,
then force a snapshot rebuild (see [Update lifecycle](#update-lifecycle)).

The timing is load-bearing: Claude Code builds its plugin registry at
process start and never re-reads it, so only installs already in the
Expand All @@ -121,7 +120,7 @@ unattended-loop `allow` grants
([claude-code-plugins#3172](https://github.com/melodic-software/claude-code-plugins/issues/3172)).

The floor is fetched from the same `raw.githubusercontent.com` host as the
fleet list, and the merge follows the dotfiles template:
plugin catalog, and the merge follows the dotfiles template:
`claudePermissions.allow` and `.deny` are unioned into the file's
`permissions.allow` and `permissions.deny`, then the floor's `withdraw`
tombstones are removed from `allow`. The file is never overwritten. Every
Expand Down Expand Up @@ -232,12 +231,12 @@ the snapshot.
- Rollback: environments keep booting from their cached snapshot until
rebuilt, so reverting the commit and forcing a rebuild restores the prior
state; in an emergency the bootstrap can pin a commit SHA in the raw URL
instead of `main`. The pin covers this script only: the fleet list and the
permission floor are still fetched from `main`.
instead of `main`. The pin covers this script only: the plugin catalog and
the permission floor are still fetched from `main`.

The scope boundary holds as elsewhere in this repository: this component owns
the shared environment baseline, which includes the fleet plugin list and the
composition of the fleet permission floor.
the shared environment baseline, which includes deriving the fleet plugin
list and composing the fleet permission floor.
Repo-specific dependencies and plugin deltas belong to each repo's committed
`.claude/cloud-bootstrap.sh` and `.claude/settings.json` (templates in the
fleet guide above), never to this script.
89 changes: 0 additions & 89 deletions components/cloud-environment/fleet-plugins.json

This file was deleted.

Loading