Editing a built-in skill costs two things
The built-in agents and skills are the default chat instructions of every deployment. They live in
src/MeshWeaver.AI/Data/{Agent,Skill}/, ship embedded in MeshWeaver.AI.dll, and are served on every
portal by BuiltInAgentProvider / BuiltInSkillProvider. Editing one is deliberately expensive: two
separate mechanisms record the change, and only one of them is loud.
The two consequences
| What moves | How you find out | |
|---|---|---|
| 1. The module locks | 22 manifest.lock files — every module that bundles src/MeshWeaver.AI carries the file's content hash, so AI, Acp, Anthropic, Chat, Edu, Mcp, Teams, OpenAI, … all move |
scripts/validate-repos.py says manifest.lock is stale (expected moduleVersion …) and names the fix. Loud, immediate, and it looks complete. |
| 2. The content-pack ledger | ONE normalised content hash in src/MeshWeaver.AI.Test/TestData/AiContentPackSync.json |
AiContentPackDriftTest.EveryFile_StillHashesToItsPinnedReconciliationPoint, in src/MeshWeaver.AI.Test — a project whose sources you did not touch |
Why the second one gets missed
Because the first one answers the question you were asking. Measured on Plugins#2147 (2026-09-19):
editing Data/Skill/recycle-after-deploy.md moved 22 locks. Regenerating them, re-running
validate-repos.py and reading ✓ 315 node(s) valid across 72 repo(s) is a complete, green,
self-consistent answer to "what else does this change touch?" — and it is wrong, because the ledger
is not a manifest and validate-repos.py does not read it.
Nothing else points at it either. The edited file is markdown; no NodeType recompiles; no project
under src/ references it in a using; and the suite that pins it is selected by the module closure,
so on a PR it runs after the locks are already green.
This is the fleet's recurring shape, not a local surprise: the VISIBLE consequence is not the denominator. The same reasoning error lands as a sweep's
count: 0over partitions it could not read, and as a green gate that checked a literal roster instead of the folder.
What to actually do
Run src/MeshWeaver.AI.Test whenever you touch src/MeshWeaver.AI/Data/** — not because your diff
compiles into it, but because it is where the decision is recorded.
dotnet test src/MeshWeaver.AI.Test/MeshWeaver.AI.Test.csproj -c Release --no-build \
-p:MeshWeaverRoot=<platform checkout> --filter-class "*AiContentPackDriftTest"
The guard hands you the replacement hash in its own failure message, which is why this costs two minutes once you know to look:
Drifted: Skill/recycle-after-deploy.md: pinned fd781d68214e… →
re-pin "core" to "0830a07f60c37f557efa43101a0713fc229fc075d62f4da45fbfad7fdf9365d7"
total: 15, failed: 1, succeeded: 14
Paste that hash into the entry's core field and say in its note what changed. The note is the
point of the ledger, not paperwork around it: the rule the test enforces is that an edit to a built-in
agent or skill is a recorded decision rather than an accident, and a re-pin with no note satisfies
the hash while defeating the reason the hash exists.
Re-run and require the positive signal — total: 15, failed: 0, succeeded: 15. An exit code alone is
not evidence here: the suite exits 0 for a project this worktree never built.
What the ledger is NOT
It is not a duplicate of the manifest locks, and the two hashes are different by construction: the
lock carries the file's raw content hash, the ledger a normalised one (state: PackAbsent on every
entry since the duplicate Agent/ + Skill/ packages were consolidated away in 2026-08). So a green
lock cannot imply a green ledger, and the ledger's core hash cannot be read off a manifest.lock.
Reading the red that tells you — a guard's self-test looks exactly like its verdict
A related trap, because it is how the same PR nearly mis-read a different guard. Most guards in
scripts/ run their --self-test before their verdict, and the self-test drives deliberately broken
fixtures — which print as ##[error] lines in the job log. Grepping the log for ##[error] and
reading the first hit therefore quotes a FIXTURE. Measured 2026-09-19 on the resolver drift guard:
the log carries 2 code line(s) differ from a fixture above the real verdict of 32 code line(s) differ (76 raw).
Ask the job which STEP failed instead — one call, and a fixture cannot forge it:
gh api "repos/Systemorph/MeshWeaver.Plugins/actions/jobs/<id>" \
--jq '.steps[]|select(.conclusion=="failure")|.name'
Then read the log from that step. The step name is also the better thing to quote in a PR body or a handover, because it says which invariant broke rather than which number was largest.