-
Notifications
You must be signed in to change notification settings - Fork 1
159 lines (153 loc) · 7.79 KB
/
Copy pathplugin-drift-ledger.yml
File metadata and controls
159 lines (153 loc) · 7.79 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
# Plugin drift ledger (nexus-ut7ek).
#
# marketplace.json pins plugins[].source.ref to an immutable release tag, so
# Claude Code loads hooks / commands / skills / agents from THAT TAG, never from
# the working tree. Guard changes on develop are therefore INERT in every live
# session until a release ships. On 2026-07-25 three guards were merged, closed
# as "mechanized", and protecting nothing -- a subagent then ran `git stash -u`
# straight past a guard that explicitly covers `stash`.
#
# The ledger (conexus/PENDING_RELEASE.md) requires that drift be DECLARED. This
# workflow is what makes that requirement real on the PR path.
#
# WHY ITS OWN WORKFLOW rather than a job in ci.yml:
# * The drift tests need the pinned TAG resolvable. ci.yml's `test` job checks
# out shallow with no tags, deliberately (nexus-dhs30 -- "every push must
# not pay for a history fetch"). So the tests SKIPPED in the only CI job
# that collected them: the guard against believed-live-but-inert code was
# itself believed-CI-mechanized and was not. Found by review, not by tests.
# * Re-adding tags to `test` would undo that same-day cost decision for the
# whole matrix.
# * A job in ci.yml cannot use native per-path filtering; ci.yml's existing
# dorny/paths-filter pattern needs `fetch-depth: 0`, which would cost more
# than this entire job. Workflow-level `paths:` is free.
#
# COST: runs ONLY when the plugin surface or the pin itself changes. Depth-1
# checkout plus a fetch of EXACTLY the pinned tags (not fetch-depth:0, not
# fetch-tags -- both pull far more than needed). One test file. No matrix, no
# native build, no service, no PG.
name: plugin drift ledger
on:
push:
branches: [develop]
paths: &surface
- 'conexus/hooks/**'
- 'conexus/commands/**'
- 'conexus/skills/**'
- 'conexus/agents/**'
- 'conexus/resources/**'
- 'conexus/PENDING_RELEASE.md'
- 'sn/hooks/**'
- 'sn/.mcp.json'
- 'conexus/.mcp.json'
- '.claude-plugin/marketplace.json'
- 'tests/test_plugin_release_drift_ledger.py'
- '.github/workflows/plugin-drift-ledger.yml'
pull_request:
branches: [main, develop]
paths: *surface
concurrency:
group: plugin-drift-ledger-${{ github.ref }}
cancel-in-progress: true
jobs:
ledger:
name: pinned-tag freshness
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- name: Fetch exactly the pinned release tags
env:
# The proof's fallback target before the anchored tag exists is the
# cut PR's HEAD COMMIT (RDR-197 anchoring rule, nexus-a2wmi.4).
# actions/checkout resolves the merge ref at depth 1 on
# pull_request, so the head sha is not in the checkout unless
# fetched explicitly.
PR_HEAD_SHA: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || '' }}
run: |
set -euo pipefail
# RDR-197 (nexus-a2wmi.4): the channel's checks need MORE than the
# pinned refs — the BASE CLIENT TAG (blind-checkout sentinel and the
# wheel-surface proof's range base; in the both-anchored state no
# pinned ref is a client tag, so without this it is never fetched).
# NOT `fetch-tags: true` on the checkout: release.yml:58-65 records
# that a depth-1 checkout's fetch-tags does NOT deliver tags
# (release.yml chose fetch-depth: 0, unaffordable here — see the
# COST header). Explicit shallow tag fetch instead; if it proves not
# to deliver the tag objects in practice, widen THIS fetch and
# record which form was needed.
git fetch --depth=1 --tags --force origin
if [ -n "${PR_HEAD_SHA:-}" ]; then
git fetch --depth=1 origin "$PR_HEAD_SHA"
fi
refs=$(python3 -c "
import json
d = json.load(open('.claude-plugin/marketplace.json'))
print(' '.join(sorted({p['source']['ref'] for p in d.get('plugins', [])
if isinstance(p.get('source'), dict) and p['source'].get('ref')})))
")
echo "pinned refs: $refs"
# A marketplace with no pinned ref is a defect, not a free pass.
test -n "$refs"
version=$(python3 -c "
import tomllib
print(tomllib.load(open('pyproject.toml','rb'))['project']['version'])
")
version_re=$(printf '%s' "$version" | sed 's/\./\\./g')
for ref in $refs; do
if git fetch --depth=1 origin "refs/tags/$ref:refs/tags/$ref"; then
continue
fi
# RELEASE WINDOW (first hit at the 7.0.0 cut): on the release
# branch the pin names the ABOUT-TO-BE-CUT tag — created only
# after the PR merges — so ref==v<pyproject version> with the
# tag absent is the documented sequencing, not drift. The
# in-repo tests (_in_release_window) apply the same contract:
# in the window the ledger must be EMPTY, and that IS checked.
# The ANCHORED form plugin-v<pyproject>-<n> is the same
# sequencing on a plugin-cut PR (RDR-197 invariant W; this site
# runs on the cut PR by trigger, so it needs the shape check,
# not the branch check — the in-repo per-plugin predicate
# applies all four window conditions). Any other missing tag is
# still a hard failure.
if [ "$ref" = "v$version" ]; then
echo "release window: $ref == v<pyproject> and not yet cut — tolerated"
elif printf '%s' "$ref" | grep -qE "^plugin-v${version_re}-[1-9][0-9]*$"; then
echo "plugin release window: $ref is the anchored form for v$version and not yet cut — tolerated"
else
echo "pinned ref $ref does not exist and is NOT this release's own tag" >&2
exit 1
fi
done
git tag -l
- uses: astral-sh/setup-uv@v7
- name: Run the ledger contract
env:
# Taglessness is a FAILURE here, never a skip. If the fetch step above
# silently breaks, a skip would report green having checked nothing --
# precisely the bug this ledger exists to catch, one level up.
NX_REQUIRE_PLUGIN_DRIFT_CHECK: "1"
# The autouse engine-substrate fixture (nexus-i711w) routes EVERY
# test to the engine unless told otherwise. This minimal runner has
# no jar/PG by design (see the COST header), so without the opt-out
# all 12 tests skip at fixture setup and the job reports green
# having checked nothing -- the require-flag above cannot help,
# because it lives inside the test bodies and a fixture-level skip
# preempts it. Observed live (nexus-05m1i): run 31223172021 on
# develop@599e4980 ended "12 skipped in 0.23s", SUCCESS, while that
# very commit carried undeclared SKILL.md drift. These tests are
# pure git/YAML; they need no substrate anywhere.
NX_TEST_T2_SUBSTRATE: "none"
run: |
set -euo pipefail
uv run pytest tests/test_plugin_release_drift_ledger.py -q -rs 2>&1 \
| tee pytest-drift-ledger.txt
# Non-vacuity belt (nexus-05m1i): on this gate runner a skip is
# NEVER acceptable -- the taglessness skip already hard-fails via
# NX_REQUIRE_PLUGIN_DRIFT_CHECK, so any skip that still appears
# means a NEW vacuity mechanism (fixture- or collection-level)
# crept in around the flag. Fail loudly instead of green-nothing.
if grep -qE '[0-9]+ skipped' pytest-drift-ledger.txt; then
echo "VACUITY: drift-ledger tests skipped on the gate runner -- " \
"the job proved nothing. See nexus-05m1i." >&2
exit 1
fi