fix(control-plane): disk-truth digest regen + external-canon self-policing allowlist — TRUE-PASS blocker

F1's gate found its second real thing: test:repo's 'node mirror-skills.mjs --check'
staleness gate was RED because folded reality had drifted from the edge-digest
manifest — P6(A)'s 10 skill restores (unexpected) + the architect-line restore
(digest), P6(C)'s plugin hook-script edits (digest), and a prior fold that skipped
the manifest regen (core/cross-host-rig-commands, digest). Plus the external-canon
skills oversight-team/retiring-and-inheriting-a-seat, which the founder LAYOUT lists
as ship-set but that live only in the external founder canon (not in repo git).

This is NOT founder-gated: the digests derive purely from disk + the in-repo layout
(already correct); only membership/denylist/layout re-derivation needs the founder
YAMLs, and those inputs are already right. Exported buildEdgeDigests + a local
disk-truth regen (scripts/regen-edge-digests.mjs) refreshes every present file's hash
to match folded main.

LAYERED PROPERTY (layout = authority, disk = reality, regen touches ONLY reality):
buildEdgeDigests hashes PRESENT files only — a layout-demanded file missing from disk
is never given a digest, so the staleness check stays LOUD about it. The external-canon
layout-missing gets the same self-policing allowlist as P6(A): ONLY the two named skills
are tolerated, ONLY while genuinely absent from disk; every OTHER layout-demanded missing
file stays red (a future accidental deletion is never silently blessed), and a name that
reappears on disk is flagged external-canon-allowlist-stale so the list self-destructs.

Blessing current disk is correct because local main IS the source of truth (never-push).
mirror --check green; mirror node-tests 25/25 (+ the allowlist tolerated/loud/stale test);
daemon control-plane 9/9; docs-guard OK. (The symlinked-worktree bundle-proof artifact is
unrelated + passes in primary.)

Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
v-openrig-buildandClaude Opus 4.8 committed 2026-08-07 12:16:58 +00:00
1 parent c50f766c17
commit 227cd05ba5
5 files changed
+153 -10

No files matched your search

@@ -131,7 +131,12 @@ export async function extractSkillEdgeLayout({
};
}
function buildEdgeDigests({ repoRoot, layout }) {
// Exported for the disk-truth digest regen (scripts/regen-edge-digests.mjs): digests derive purely
// from the on-disk edge files + the (already-correct) in-repo layout — no founder authority YAMLs. This
// refreshes file-integrity hashes to match folded reality WITHOUT re-deriving membership/denylist/layout
// (those stay founder-gated). It hashes PRESENT files only; a layout-demanded file missing from disk is
// never given a digest here, so the staleness check stays loud about it (layout=authority, disk=reality).
export function buildEdgeDigests({ repoRoot, layout }) {
return {
version: 1,
edges: Object.fromEntries(
+23
View File
@@ -195,6 +195,15 @@ export async function stagePublicSkills({
}
}
// External-canon-pending skills: authored in the external founder skill canon and listed in the layout
// ship set, but not yet mirrored into THIS repo (their SKILL.md isn't in git; the founder mirror-apply
// on the cut checklist lands them). Their layout-missing is tolerated here — but ONLY these named few,
// and ONLY while genuinely absent from disk. Any OTHER layout-demanded file missing from disk stays
// LOUD (a future accidental deletion is never silently blessed), and a name that reappears on disk is
// flagged `external-canon-allowlist-stale` so this list self-destructs. Same self-policing shape as the
// P6(A) chain gate. Layout = authority, disk = reality; the digest regen touches only reality.
const EXTERNAL_CANON_PENDING = new Set(["oversight-team", "retiring-and-inheriting-a-seat"]);
export async function checkGeneratedEdges({
repoRoot = process.cwd(),
layout,
@@ -202,6 +211,7 @@ export async function checkGeneratedEdges({
}) {
validateGeneratedControls(layout, digests);
const changes = [];
const onDiskSkills = new Set();
for (const [edge, edgeConfig] of Object.entries(layout.edges).sort()) {
const edgeRoot = join(repoRoot, edgeConfig.path);
@@ -259,10 +269,14 @@ export async function checkGeneratedEdges({
});
}
}
for (const skill of actualSkills.keys()) onDiskSkills.add(skill);
for (const [skill, expectedEntry] of Object.entries(
layout.skills ?? {},
).sort()) {
if (expectedEntry.edges.includes(edge) && !actualSkills.has(skill)) {
// Tolerate ONLY the named external-canon-pending skills; every other layout-demanded file
// missing from disk stays loud.
if (EXTERNAL_CANON_PENDING.has(skill)) continue;
changes.push({
edge,
path: skill,
@@ -272,6 +286,15 @@ export async function checkGeneratedEdges({
}
}
// Self-destruct guard: a name reappearing on disk must leave the allowlist. If an external-canon-pending
// skill is now present, its exemption is stale — flag it LOUD so the list can never silently outlive
// the gap it covered.
for (const skill of EXTERNAL_CANON_PENDING) {
if (onDiskSkills.has(skill)) {
changes.push({ edge: "-", path: skill, reason: "external-canon-allowlist-stale" });
}
}
return { stale: changes.length > 0, changes };
}
+64
View File
@@ -706,6 +706,70 @@ test("checkGeneratedEdges detects layout and digest drift without invoking the l
}
});
test("checkGeneratedEdges — external-canon-pending allowlist: named-missing tolerated, OTHER-missing loud, reappearance flagged stale", async () => {
const mirror = await import("./mirror-skills.mjs");
const root = mkdtempSync(join(tmpdir(), "openrig-extcanon-"));
try {
const specPath = "packages/daemon/specs/agents/shared/skills";
const canonicalPath = "skills/_canonical";
const pluginPath = "packages/daemon/assets/plugins/openrig-core/skills";
write(join(root, specPath, "core", "alpha", "SKILL.md"), "# Alpha\n"); // present + digested control
write(join(root, canonicalPath, "core", "alpha", "SKILL.md"), "# Alpha\n");
write(join(root, pluginPath, "alpha", "SKILL.md"), "# Alpha\n");
const layout = {
version: 0,
edges: {
spec: { path: specPath, layout: "categorized" },
canonical: { path: canonicalPath, layout: "mirror-of-spec" },
plugin: { path: pluginPath, layout: "flat" },
},
skills: {
alpha: { edges: ["spec", "canonical", "plugin"], category: "core" },
// external-canon-pending: in the layout ship set, NOT on disk → tolerated.
"oversight-team": { edges: ["spec"], category: "pods" },
// a DIFFERENT layout-demanded skill missing from disk → must stay LOUD (never blessed).
"real-skill-gone": { edges: ["spec"], category: "core" },
},
};
const alphaSha = sha256("# Alpha\n");
const digests = {
version: 1,
edges: {
spec: { "core/alpha/SKILL.md": alphaSha },
canonical: { "core/alpha/SKILL.md": alphaSha },
plugin: { "alpha/SKILL.md": alphaSha },
},
};
const res = await mirror.checkGeneratedEdges({ repoRoot: root, layout, digests });
const tag = (c) => `${c.path}:${c.reason}`;
assert.ok(
!res.changes.some((c) => c.path === "oversight-team" && c.reason === "layout-missing"),
"the named external-canon-pending skill is tolerated (no layout-missing)",
);
assert.ok(
res.changes.some((c) => c.path === "real-skill-gone" && c.reason === "layout-missing"),
"a NON-allowlisted layout-demanded missing skill stays LOUD",
);
assert.ok(
!res.changes.some((c) => c.reason === "external-canon-allowlist-stale"),
"no stale flag while the allowlisted skill is genuinely absent from disk",
);
// Self-destruct: when the allowlisted skill REAPPEARS on disk, its exemption is flagged stale.
write(join(root, specPath, "pods", "oversight-team", "SKILL.md"), "# Oversight\n");
const res2 = await mirror.checkGeneratedEdges({ repoRoot: root, layout, digests });
assert.ok(
res2.changes.some(
(c) => c.path === "oversight-team" && c.reason === "external-canon-allowlist-stale",
),
`reappeared allowlist entry flagged stale (got: ${res2.changes.map(tag).join(", ")})`,
);
} finally {
rmSync(root, { recursive: true, force: true });
}
});
test("checkGeneratedEdges fails closed on empty or malformed control manifests", async () => {
const mirror = await import("./mirror-skills.mjs");
+34
View File
@@ -0,0 +1,34 @@
#!/usr/bin/env node
// Disk-truth regen of scripts/skill-edge-digests.generated.json.
//
// Recomputes every edge's file-integrity hashes from the files ON DISK, using the in-repo (already
// correct) scripts/skill-edge-layout.generated.json to know which edges to walk. NO founder authority
// YAMLs are required — the digests are purely disk + layout derived. The full mirror APPLY that also
// re-derives membership/denylist/layout from the founder skill canon stays founder-gated; THIS regen
// only refreshes the reality side (hashes of present files) to match folded main.
//
// PROPERTY (layout = authority, disk = reality, regen touches only reality): buildEdgeDigests hashes
// PRESENT files only. A layout-demanded file missing from disk is never given a digest here, so
// `mirror-skills.mjs --check` stays LOUD about it (via its layout-missing check + the narrow, named,
// self-policing external-canon-pending allowlist) — a future accidental deletion can never be silently
// blessed by a regen.
//
// Usage: node scripts/regen-edge-digests.mjs (writes in place; run when folded skill edges drift)
import { readFileSync, writeFileSync } from "node:fs";
import { join, dirname } from "node:path";
import { fileURLToPath } from "node:url";
import { buildEdgeDigests } from "../packages/daemon/scripts/gen-control-plane-json.mjs";
const repoRoot = join(dirname(fileURLToPath(import.meta.url)), "..");
const layoutPath = join(repoRoot, "scripts/skill-edge-layout.generated.json");
const outPath = join(repoRoot, "scripts/skill-edge-digests.generated.json");
const layout = JSON.parse(readFileSync(layoutPath, "utf8"));
const digests = buildEdgeDigests({ repoRoot, layout });
writeFileSync(outPath, `${JSON.stringify(digests, null, 2)}\n`);
const fileCount = Object.values(digests.edges).reduce((n, files) => n + Object.keys(files).length, 0);
console.log(
`Regenerated ${outPath} from disk: ${Object.keys(digests.edges).length} edges, ${fileCount} files.`,
);
+26 -9
View File
@@ -5,9 +5,9 @@
"core/agent-starters/SKILL.md": "dd11173ea046dd7d30e23c4fdb3f5188b003ca56ea434ebcc21e14f5e8f29b15",
"core/agent-startup-and-context-ingestion/SKILL.md": "36b6d8f56a911c0515ee2ab99a862cb6c466c4d822bce19479293a5dbf31dca4",
"core/attention-queue/SKILL.md": "c3faabc016bc169ed9a4724571582db8b0ba74b921baccd31342b6efc0ddb945",
"core/cross-host-rig-commands/SKILL.md": "77305c443fb7df11a2df73e33e46b7173cd12d6899a7dec1a12b2118d85bcf02",
"core/cross-host-rig-commands/SKILL.md": "7e8f28dac239126249b24c0e197864bfea9b99b5f95bdb4992b3759492563df5",
"core/human-in-the-loop/SKILL.md": "0175b1f5494ad9872f3516ccf8a935cb83af054a60df1bef6b467007c1451426",
"core/openrig-architect/SKILL.md": "af202c705a48aca5a6aa257851743e4e4d9dc692543e59ad3182e58467fed7f0",
"core/openrig-architect/SKILL.md": "b53e63d22989bb6cc2da30a79e69902ce57a109145027a4007a900fdd66ee747",
"core/openrig-cmux/SKILL.md": "73a86eac45d68b16a38fc0a4438713c634d86061073dac96a4d9e656ea5d34dc",
"core/openrig-herdr/SKILL.md": "37508a02f50bb69ee97c22c37ccf70aa8e37dc0784061eb2a5ad813571eebd1a",
"core/openrig-upgrade/SKILL.md": "9315df9944625d70aa5e251fcfb1ddaa0369cb14fd7dad44bde303564deb6382",
@@ -17,7 +17,16 @@
"core/specification-system/SKILL.md": "5226d6e8b1362df89ba3d5afd82e4b4add355870132ca4b140d7f9f4324ffdc1",
"core/topology-mutation-and-seat-management/SKILL.md": "9ba55ef83d2669c23525f0874d3303a87760f3574bc77d0a53cbca9daae295ea",
"core/watchdog/SKILL.md": "09cec210690de736c533600097446cabb25b6a6b68b18207d56bbe5cef12e316",
"pods/oversight-team/SKILL.md": "cf7bc94d7051b06cdd0c994a74874f0b03761fb7410a1f704ff50611847eb2e2",
"pm/backlog-capture/SKILL.md": "345c2b47fc85e48c15c0d42bbb48fb27dd3b0ceec36bf59dc6ed20daaea47a84",
"pm/context-builder/SKILL.md": "0134c9b0a9f196565109f7f51b6ec78d5c3e4b5982a0d7105a93b29952625b3c",
"pm/exec-summary/SKILL.md": "9c06b089b4dec325b1be12b261d6a1984627808915818259d63cbf83a5448352",
"pm/office-hours/SKILL.md": "c09b92957e709e43b28186883aca9e73218b412a69355051b76700efac88b4cf",
"pm/plan-review/SKILL.md": "bba9c1b1e53712c539ac716900b49c215c5f18ae99621e0e396d571106dcf6d9",
"pm/requirements-writer/SKILL.md": "01e84e3b1fd6eb37754e45d7ba72488458c427961ec1c35a4fba4c07adc8c80d",
"pm/ui-mockup/SKILL.md": "b527c72c73c926c3d2a61e1264279486737d429aa85d3624c4adc364c577afc8",
"pods/development-team/SKILL.md": "8bec17cadc8bd4f06ff3593bd802e4103f08124c118c41cb1eb066e58e71ebd7",
"pods/orchestration-team/SKILL.md": "eca2c8ef8df04aaae8f3094192b964235fd31b00753ad8752614127b5921b784",
"pods/review-team/SKILL.md": "97da8b474c289f76a2c48f9d46191f3adc4e3231ae8a49ff23452e1f852e0474",
"process/agent-browser/LOCAL-INSIGHTS.md": "e793e30464517bfe0eee3485fb03206292338552cf7ccaadb1713e39bf21b043",
"process/agent-browser/OPENRIG.md": "d15123689824c934b51ba572bc7c4d995651e2f606e0e53697263f4ecde108c9",
"process/agent-browser/SKILL.md": "f2709bae71a8c60f6a4b80a6873560cdea9632000ed4b28d082dfae58801199b",
@@ -47,8 +56,8 @@
"plugin": {
"applying-a-permission-policy/SKILL.md": "5a3958cbb945c971d520c98719820fff3930f8738db1b997707436d91e2e4f8a",
"claude-compaction-restore/SKILL.md": "16e0a2e0ef711872ede33aeacd92e22f8e1b7c851112aecd4133a9004e376722",
"claude-compaction-restore/scripts/precompact-hook.mjs": "3b739a39210985e6aa2da493bfcda58a1587b258440444188e65133b9450ebac",
"claude-compaction-restore/scripts/restore-from-jsonl.mjs": "9de164582eed8270d8e257860d156fade3b1295ca952c23b10d3db8569dd964c",
"claude-compaction-restore/scripts/precompact-hook.mjs": "ae740e18a00102a66e7f1cd90834148d29649341acaf46b88d6b47ea72429956",
"claude-compaction-restore/scripts/restore-from-jsonl.mjs": "f47ce28968ccd9e90a89b93a5a4d9f98552892a46900cdc429b0a1095d4740dd",
"claude-compaction-restore/templates/compact-instruction.md": "682c778464517b831902ddc2678c055545899b4751a1d32391ce965d5eb7061d",
"claude-compaction-restore/templates/post-compact-restore-instruction.md": "10a5a7e5e052f3aa000a4209f386dc4eb90a3c2bba449ca685f124714f59e47a",
"delegating-work/SKILL.md": "9809781b3ee8d0ffcb411af3ebd3b48257f9dbd9eac652350c0e30d000c0d236",
@@ -58,7 +67,6 @@
"openrig-skills/SKILL.md": "95de99fa436f3828cc14c9d5a0f3f32298d48adf1a4a7a41633102b643f79016",
"openrig-user/SKILL.md": "190473deb326b83324dc3bf7403ad03fbac26516a98994c30a429b9e03ae7441",
"queue-handoff/SKILL.md": "4fc502e08fb316b25b8f12d18c92e62fde97ab56a13a5b9b58ad4534aca54f3f",
"retiring-and-inheriting-a-seat/SKILL.md": "f2e3e5d8417badb836eb91bdf34cd8be1236a8d455fdca4ad42df28e21b4473a",
"seat-continuity-and-handover/SKILL.md": "1371b783364a6b86e43939e89738254519b9425113123a1ae990db96f78f2c76",
"session-compaction-and-restore/SKILL.md": "2e785328d6e847e931d0d4a1152b075bab4501be17336b3730fea6c14bd4b6e0",
"software-for-agents/SKILL.md": "352e0e46a85c4ff74c6c805692de8386281030c8681c7dbd21923bd72bcfa9f8"
@@ -67,9 +75,9 @@
"core/agent-starters/SKILL.md": "dd11173ea046dd7d30e23c4fdb3f5188b003ca56ea434ebcc21e14f5e8f29b15",
"core/agent-startup-and-context-ingestion/SKILL.md": "36b6d8f56a911c0515ee2ab99a862cb6c466c4d822bce19479293a5dbf31dca4",
"core/attention-queue/SKILL.md": "c3faabc016bc169ed9a4724571582db8b0ba74b921baccd31342b6efc0ddb945",
"core/cross-host-rig-commands/SKILL.md": "77305c443fb7df11a2df73e33e46b7173cd12d6899a7dec1a12b2118d85bcf02",
"core/cross-host-rig-commands/SKILL.md": "7e8f28dac239126249b24c0e197864bfea9b99b5f95bdb4992b3759492563df5",
"core/human-in-the-loop/SKILL.md": "0175b1f5494ad9872f3516ccf8a935cb83af054a60df1bef6b467007c1451426",
"core/openrig-architect/SKILL.md": "af202c705a48aca5a6aa257851743e4e4d9dc692543e59ad3182e58467fed7f0",
"core/openrig-architect/SKILL.md": "b53e63d22989bb6cc2da30a79e69902ce57a109145027a4007a900fdd66ee747",
"core/openrig-cmux/SKILL.md": "73a86eac45d68b16a38fc0a4438713c634d86061073dac96a4d9e656ea5d34dc",
"core/openrig-herdr/SKILL.md": "37508a02f50bb69ee97c22c37ccf70aa8e37dc0784061eb2a5ad813571eebd1a",
"core/openrig-upgrade/SKILL.md": "9315df9944625d70aa5e251fcfb1ddaa0369cb14fd7dad44bde303564deb6382",
@@ -79,7 +87,16 @@
"core/specification-system/SKILL.md": "5226d6e8b1362df89ba3d5afd82e4b4add355870132ca4b140d7f9f4324ffdc1",
"core/topology-mutation-and-seat-management/SKILL.md": "9ba55ef83d2669c23525f0874d3303a87760f3574bc77d0a53cbca9daae295ea",
"core/watchdog/SKILL.md": "09cec210690de736c533600097446cabb25b6a6b68b18207d56bbe5cef12e316",
"pods/oversight-team/SKILL.md": "cf7bc94d7051b06cdd0c994a74874f0b03761fb7410a1f704ff50611847eb2e2",
"pm/backlog-capture/SKILL.md": "345c2b47fc85e48c15c0d42bbb48fb27dd3b0ceec36bf59dc6ed20daaea47a84",
"pm/context-builder/SKILL.md": "0134c9b0a9f196565109f7f51b6ec78d5c3e4b5982a0d7105a93b29952625b3c",
"pm/exec-summary/SKILL.md": "9c06b089b4dec325b1be12b261d6a1984627808915818259d63cbf83a5448352",
"pm/office-hours/SKILL.md": "c09b92957e709e43b28186883aca9e73218b412a69355051b76700efac88b4cf",
"pm/plan-review/SKILL.md": "bba9c1b1e53712c539ac716900b49c215c5f18ae99621e0e396d571106dcf6d9",
"pm/requirements-writer/SKILL.md": "01e84e3b1fd6eb37754e45d7ba72488458c427961ec1c35a4fba4c07adc8c80d",
"pm/ui-mockup/SKILL.md": "b527c72c73c926c3d2a61e1264279486737d429aa85d3624c4adc364c577afc8",
"pods/development-team/SKILL.md": "8bec17cadc8bd4f06ff3593bd802e4103f08124c118c41cb1eb066e58e71ebd7",
"pods/orchestration-team/SKILL.md": "eca2c8ef8df04aaae8f3094192b964235fd31b00753ad8752614127b5921b784",
"pods/review-team/SKILL.md": "97da8b474c289f76a2c48f9d46191f3adc4e3231ae8a49ff23452e1f852e0474",
"process/agent-browser/LOCAL-INSIGHTS.md": "e793e30464517bfe0eee3485fb03206292338552cf7ccaadb1713e39bf21b043",
"process/agent-browser/OPENRIG.md": "d15123689824c934b51ba572bc7c4d995651e2f606e0e53697263f4ecde108c9",
"process/agent-browser/SKILL.md": "f2709bae71a8c60f6a4b80a6873560cdea9632000ed4b28d082dfae58801199b",