refactor(scope): one canonical attestation-lineage derivation (Slice-18 follow-on)

The duplicated CLI/daemon builders extract to ONE canonical source
(packages/cli/src/lib/scope/attestation-lineage.ts) carried across the
barred cli->daemon import boundary by the repo's mirror/codegen convention:
scripts/sync-scope-lineage.mjs emits the daemon's .generated mirror
(--check drift mode + node:test guard incl. the mutate-canonical->both-
surfaces-change demonstration). Both surfaces now import their package-
local module; the parity pin is reworked to hold canonical==generated
plus consumption, so the drift class dies structurally.

Co-Authored-By: Claude Fable 5 <[email protected]>
This commit is contained in:
v-openrig-buildandClaude Fable 5 committed 2026-08-05 00:30:13 +00:00
1 parent 1c7ec2065b
commit 5de3b994fc
7 files changed
+279 -78

No files matched your search

+1 -29
View File
@@ -11,6 +11,7 @@ import path from "node:path";
import { execFileSync } from "node:child_process";
import { Command } from "commander";
import { DaemonClient } from "../client.js";
import { attestationLineage, type AttestationLineage } from "../lib/scope/attestation-lineage.js";
import { getDaemonStatus, getDaemonUrl } from "../daemon-lifecycle.js";
import { realDeps } from "./daemon.js";
@@ -1037,35 +1038,6 @@ function extractFrontmatterRaw(content: string): string | null {
return match ? match[1]! : null;
}
// OPR.0.5.0.18 — amendment lineage from frontmatter: the re-stamp verb writes
// `approved-spec-priors` / `approved-priors` atomically beside the current
// stamp, so the (filesystem-local) audit can show lineage without DB access.
// Returns undefined for never-amended slices (first-approve output unchanged).
interface AttestationLineage {
spec?: { by: string; at: string; priors: number };
delivery?: { by: string; at: string; priors: number };
}
function attestationLineage(frontmatterRaw: string | null): AttestationLineage | undefined {
if (!frontmatterRaw) return undefined;
const read = (key: string): string | null => {
const m = new RegExp(`^${key}\\s*:\\s*(.+)$`, "m").exec(frontmatterRaw);
return m ? m[1]!.trim().replace(/^["']|["']$/g, "") : null;
};
const lineage: AttestationLineage = {};
for (const [scope, fields] of [
["spec", { by: "approved-spec-by", at: "approved-spec-at", priors: "approved-spec-priors" }],
["delivery", { by: "approved-by", at: "approved-at", priors: "approved-priors" }],
] as const) {
const priorsRaw = read(fields.priors);
const priors = priorsRaw !== null ? Number(priorsRaw) : NaN;
if (Number.isFinite(priors) && priors > 0) {
lineage[scope] = { by: read(fields.by) ?? "?", at: read(fields.at) ?? "?", priors };
}
}
return Object.keys(lineage).length > 0 ? lineage : undefined;
}
function directoryHasEntries(dir: string): boolean {
try {
return fs.readdirSync(dir).some((entry) => !entry.startsWith("."));
@@ -0,0 +1,38 @@
// OPR.0.5.0.18 — the CANONICAL amendment-lineage derivation (ONE source of truth).
//
// The re-stamp verb (`rig scope … approve --re-approve`) writes
// `approved-spec-priors` / `approved-priors` atomically beside the current stamp,
// so a filesystem-local audit can show lineage without DB access. Returns
// undefined for never-amended scopes (first-approve output unchanged).
//
// SHARED ACROSS PACKAGES BY GENERATION: the daemon audit route consumes the
// byte-identical body via packages/daemon/src/domain/scope/
// attestation-lineage.generated.ts, emitted by `node scripts/sync-scope-lineage.mjs`
// (the CLI→daemon import direction is barred, so the repo's mirror/codegen
// convention carries it). EDIT THIS FILE ONLY, then run the sync — the
// scope-lineage-parity pin fails if the two ever diverge.
export interface AttestationLineage {
spec?: { by: string; at: string; priors: number };
delivery?: { by: string; at: string; priors: number };
}
export function attestationLineage(frontmatterRaw: string | null): AttestationLineage | undefined {
if (!frontmatterRaw) return undefined;
const read = (key: string): string | null => {
const m = new RegExp(`^${key}\\s*:\\s*(.+)$`, "m").exec(frontmatterRaw);
return m ? m[1]!.trim().replace(/^["']|["']$/g, "") : null;
};
const lineage: AttestationLineage = {};
for (const [scope, fields] of [
["spec", { by: "approved-spec-by", at: "approved-spec-at", priors: "approved-spec-priors" }],
["delivery", { by: "approved-by", at: "approved-at", priors: "approved-priors" }],
] as const) {
const priorsRaw = read(fields.priors);
const priors = priorsRaw !== null ? Number(priorsRaw) : NaN;
if (Number.isFinite(priors) && priors > 0) {
lineage[scope] = { by: read(fields.by) ?? "?", at: read(fields.at) ?? "?", priors };
}
}
return Object.keys(lineage).length > 0 ? lineage : undefined;
}
@@ -0,0 +1,44 @@
// GENERATED FILE — DO NOT EDIT.
// Emitted by scripts/sync-scope-lineage.mjs from the canonical source:
// packages/cli/src/lib/scope/attestation-lineage.ts
// Edit the canonical file, then run: node scripts/sync-scope-lineage.mjs
// The scope-lineage-parity vitest pin fails if this mirror drifts.
// OPR.0.5.0.18 — the CANONICAL amendment-lineage derivation (ONE source of truth).
//
// The re-stamp verb (`rig scope … approve --re-approve`) writes
// `approved-spec-priors` / `approved-priors` atomically beside the current stamp,
// so a filesystem-local audit can show lineage without DB access. Returns
// undefined for never-amended scopes (first-approve output unchanged).
//
// SHARED ACROSS PACKAGES BY GENERATION: the daemon audit route consumes the
// byte-identical body via packages/daemon/src/domain/scope/
// attestation-lineage.generated.ts, emitted by `node scripts/sync-scope-lineage.mjs`
// (the CLI→daemon import direction is barred, so the repo's mirror/codegen
// convention carries it). EDIT THIS FILE ONLY, then run the sync — the
// scope-lineage-parity pin fails if the two ever diverge.
export interface AttestationLineage {
spec?: { by: string; at: string; priors: number };
delivery?: { by: string; at: string; priors: number };
}
export function attestationLineage(frontmatterRaw: string | null): AttestationLineage | undefined {
if (!frontmatterRaw) return undefined;
const read = (key: string): string | null => {
const m = new RegExp(`^${key}\\s*:\\s*(.+)$`, "m").exec(frontmatterRaw);
return m ? m[1]!.trim().replace(/^["']|["']$/g, "") : null;
};
const lineage: AttestationLineage = {};
for (const [scope, fields] of [
["spec", { by: "approved-spec-by", at: "approved-spec-at", priors: "approved-spec-priors" }],
["delivery", { by: "approved-by", at: "approved-at", priors: "approved-priors" }],
] as const) {
const priorsRaw = read(fields.priors);
const priors = priorsRaw !== null ? Number(priorsRaw) : NaN;
if (Number.isFinite(priors) && priors > 0) {
lineage[scope] = { by: read(fields.by) ?? "?", at: read(fields.at) ?? "?", priors };
}
}
return Object.keys(lineage).length > 0 ? lineage : undefined;
}
+1 -28
View File
@@ -1,4 +1,5 @@
import { Hono } from "hono";
import { attestationLineage, type AttestationLineage } from "../domain/scope/attestation-lineage.generated.js";
import * as fs from "node:fs";
import * as path from "node:path";
import { classifyScopeItem, type ScopeAuditResult } from "../domain/scope/scope-audit.js";
@@ -36,34 +37,6 @@ function listProofArtifactsForAudit(proofDir: string): Array<{ path: string; fro
}
// OPR.0.5.0.18 — amendment lineage from frontmatter (parity with the CLI
// audit builder): the atomic re-stamp writes `approved-spec-priors` /
// `approved-priors` beside the current stamp; surfaced only when > 0.
interface AttestationLineage {
spec?: { by: string; at: string; priors: number };
delivery?: { by: string; at: string; priors: number };
}
function attestationLineage(frontmatterRaw: string | null): AttestationLineage | undefined {
if (!frontmatterRaw) return undefined;
const read = (key: string): string | null => {
const m = new RegExp(`^${key}\\s*:\\s*(.+)$`, "m").exec(frontmatterRaw);
return m ? m[1]!.trim().replace(/^["']|["']$/g, "") : null;
};
const lineage: AttestationLineage = {};
for (const [scope, fields] of [
["spec", { by: "approved-spec-by", at: "approved-spec-at", priors: "approved-spec-priors" }],
["delivery", { by: "approved-by", at: "approved-at", priors: "approved-priors" }],
] as const) {
const priorsRaw = read(fields.priors);
const priors = priorsRaw !== null ? Number(priorsRaw) : NaN;
if (Number.isFinite(priors) && priors > 0) {
lineage[scope] = { by: read(fields.by) ?? "?", at: read(fields.at) ?? "?", priors };
}
}
return Object.keys(lineage).length > 0 ? lineage : undefined;
}
export function scopeAuditRoutes(): Hono {
const app = new Hono();
@@ -1,12 +1,17 @@
// OPR.0.5.0.18 — CROSS-SURFACE PARITY PIN (guard-carried requirement).
// OPR.0.5.0.18 — CROSS-SURFACE PARITY PIN (guard-carried requirement; extraction landed).
//
// The CLI (`packages/cli/src/commands/scope.ts`) and the daemon audit route
// (`packages/daemon/src/routes/scope-audit.ts`) each carry a `attestationLineage`
// builder that reads the amendment lineage (current attestation + prior-count) from a
// spec's frontmatter. They are DUPLICATED and byte-identical today — but were UNPINNED,
// so a fix to one surface could silently drift from the other. This pin fails loudly the
// moment the two builders diverge, until the durable fix (extract to ONE shared builder)
// lands. It is TEST-ONLY: no production change, no extraction this cycle.
// The amendment-lineage derivation now has ONE canonical source:
// packages/cli/src/lib/scope/attestation-lineage.ts (canonical, hand-edited)
// packages/daemon/src/domain/scope/attestation-lineage.generated.ts
// (GENERATED verbatim-body mirror — emitted by scripts/sync-scope-lineage.mjs;
// the cli→daemon import direction is barred, so the repo's mirror/codegen
// convention carries the body across the package boundary)
//
// This pin holds the whole chain: canonical == generated (function body byte-wise),
// AND both consuming surfaces import their package-local module rather than carrying
// a private copy — so a mutation of the canonical fn changes BOTH surfaces together
// (run `node scripts/sync-scope-lineage.mjs`), and the old silent-drift class is
// structurally dead. If generated drifts (hand-edit without the sync), this fails.
import { it, expect } from "vitest";
import { readFileSync } from "node:fs";
import { fileURLToPath } from "node:url";
@@ -31,19 +36,48 @@ function extractAttestationLineage(src: string): string {
return src.slice(start, i).replace(/\r\n/g, "\n").trim();
}
it("the CLI and daemon attestationLineage builders stay byte-identical (drift pin)", () => {
const here = dirname(fileURLToPath(import.meta.url));
const root = join(here, "..", "..", ".."); // packages/daemon/test -> repo root
const cliSrc = readFileSync(join(root, "packages/cli/src/commands/scope.ts"), "utf8");
const daemonSrc = readFileSync(join(root, "packages/daemon/src/routes/scope-audit.ts"), "utf8");
const here = dirname(fileURLToPath(import.meta.url));
const root = join(here, "..", "..", ".."); // packages/daemon/test -> repo root
const cliFn = extractAttestationLineage(cliSrc);
const daemonFn = extractAttestationLineage(daemonSrc);
it("canonical and generated attestationLineage bodies are byte-identical (the sync carried them)", () => {
const canonical = readFileSync(join(root, "packages/cli/src/lib/scope/attestation-lineage.ts"), "utf8");
const generated = readFileSync(join(root, "packages/daemon/src/domain/scope/attestation-lineage.generated.ts"), "utf8");
// Both builders must be present…
expect(cliFn.length, "CLI attestationLineage not found").toBeGreaterThan(0);
expect(daemonFn.length, "daemon attestationLineage not found").toBeGreaterThan(0);
// …and byte-identical. If this fails, one surface changed without the other — reconcile
// them (or extract to a single shared builder, which is the durable fix).
expect(daemonFn).toBe(cliFn);
const canonicalFn = extractAttestationLineage(canonical);
const generatedFn = extractAttestationLineage(generated);
expect(canonicalFn.length, "canonical attestationLineage not found").toBeGreaterThan(0);
expect(generatedFn.length, "generated attestationLineage not found").toBeGreaterThan(0);
expect(generatedFn).toBe(canonicalFn);
// The generated file must declare its provenance loudly (the repo's generated-file rule).
expect(generated).toMatch(/GENERATED[\s\S]*DO NOT EDIT/i);
expect(generated).toContain("scripts/sync-scope-lineage.mjs");
});
it("both surfaces CONSUME the shared derivation — no private builder copies remain", () => {
const cliSurface = readFileSync(join(root, "packages/cli/src/commands/scope.ts"), "utf8");
const daemonSurface = readFileSync(join(root, "packages/daemon/src/routes/scope-audit.ts"), "utf8");
// Neither surface defines its own copy any more…
expect(cliSurface).not.toContain("function attestationLineage(");
expect(daemonSurface).not.toContain("function attestationLineage(");
// …both import their package-local module.
expect(cliSurface).toMatch(/from "\.\.\/lib\/scope\/attestation-lineage\.js"/);
expect(daemonSurface).toMatch(/from "\.\.\/domain\/scope\/attestation-lineage\.generated\.js"/);
});
it("the shared derivation behaves identically through BOTH package-local modules (behavioral belt)", async () => {
const cli = await import(join(root, "packages/cli/src/lib/scope/attestation-lineage.ts"));
const daemon = await import(join(root, "packages/daemon/src/domain/scope/attestation-lineage.generated.ts"));
const fixtures = [
null,
"id: X",
"approved-spec-by: a@r\napproved-spec-at: t1\napproved-spec-priors: 2",
"approved-by: b@r\napproved-at: t2\napproved-priors: 1\napproved-spec-by: a@r\napproved-spec-at: t1\napproved-spec-priors: 3",
"approved-spec-by: a@r\napproved-spec-at: t1\napproved-spec-priors: 0",
'approved-spec-by: "q@r"\napproved-spec-at: t\napproved-spec-priors: not-a-number',
];
for (const fm of fixtures) {
expect(daemon.attestationLineage(fm)).toEqual(cli.attestationLineage(fm));
}
});
+63
View File
@@ -0,0 +1,63 @@
// OPR.0.5.0.18 — sync the CANONICAL attestation-lineage derivation into the daemon's
// generated mirror (the repo's mirror/codegen convention: the CLI→daemon import
// direction is barred, so the body crosses the package boundary by generation).
//
// node scripts/sync-scope-lineage.mjs # (re)write the generated mirror
// node scripts/sync-scope-lineage.mjs --check # exit 1 on drift, write nothing
//
// Canonical: packages/cli/src/lib/scope/attestation-lineage.ts (EDIT THIS ONE)
// Generated: packages/daemon/src/domain/scope/attestation-lineage.generated.ts
//
// The vitest-level scope-lineage-parity pin enforces the same invariant on every
// test run; this script is the WRITE path (and the CI-style --check).
import { readFileSync, writeFileSync, mkdirSync, existsSync } from "node:fs";
import { dirname, join, resolve } from "node:path";
import { fileURLToPath } from "node:url";
const REPO_ROOT = process.env.SYNC_LINEAGE_ROOT
? resolve(process.env.SYNC_LINEAGE_ROOT)
: resolve(dirname(fileURLToPath(import.meta.url)), "..");
export const CANONICAL = "packages/cli/src/lib/scope/attestation-lineage.ts";
export const GENERATED = "packages/daemon/src/domain/scope/attestation-lineage.generated.ts";
const HEADER = `// GENERATED FILE — DO NOT EDIT.
// Emitted by scripts/sync-scope-lineage.mjs from the canonical source:
// ${CANONICAL}
// Edit the canonical file, then run: node scripts/sync-scope-lineage.mjs
// The scope-lineage-parity vitest pin fails if this mirror drifts.
`;
export function renderGenerated(canonicalContent) {
return HEADER + canonicalContent;
}
function main() {
const check = process.argv.includes("--check");
const canonicalPath = join(REPO_ROOT, CANONICAL);
const generatedPath = join(REPO_ROOT, GENERATED);
const canonical = readFileSync(canonicalPath, "utf8");
const expected = renderGenerated(canonical);
if (check) {
const actual = existsSync(generatedPath) ? readFileSync(generatedPath, "utf8") : null;
if (actual !== expected) {
console.error(
`DRIFT: ${GENERATED} does not match the canonical derivation.\n` +
`Re-run: node scripts/sync-scope-lineage.mjs`,
);
process.exit(1);
}
console.log("sync-scope-lineage: in sync.");
return;
}
mkdirSync(dirname(generatedPath), { recursive: true });
writeFileSync(generatedPath, expected, "utf8");
console.log(`sync-scope-lineage: wrote ${GENERATED}`);
}
main();
+77
View File
@@ -0,0 +1,77 @@
// OPR.0.5.0.18 — sync-scope-lineage guard: the generated daemon mirror stays in
// lockstep with the canonical CLI derivation, drift dies loudly, and a canonical
// mutation propagates to BOTH surfaces through one sync run (the structural kill
// of the old copy-drift class).
import { test } from "node:test";
import assert from "node:assert/strict";
import { execFileSync } from "node:child_process";
import * as fs from "node:fs";
import * as os from "node:os";
import * as path from "node:path";
const REPO_ROOT = path.resolve(import.meta.dirname, "..");
const SCRIPT = path.join(REPO_ROOT, "scripts/sync-scope-lineage.mjs");
const CANONICAL = "packages/cli/src/lib/scope/attestation-lineage.ts";
const GENERATED = "packages/daemon/src/domain/scope/attestation-lineage.generated.ts";
function run(args, env) {
try {
const stdout = execFileSync(process.execPath, [SCRIPT, ...args], {
env: { ...process.env, ...env },
encoding: "utf8",
});
return { code: 0, stdout };
} catch (err) {
return { code: err.status ?? 1, stdout: String(err.stdout ?? ""), stderr: String(err.stderr ?? "") };
}
}
function makeTmpRoot() {
const root = fs.mkdtempSync(path.join(os.tmpdir(), "sync-lineage-"));
for (const rel of [CANONICAL, GENERATED]) {
const target = path.join(root, rel);
fs.mkdirSync(path.dirname(target), { recursive: true });
fs.copyFileSync(path.join(REPO_ROOT, rel), target);
}
return root;
}
test("the repo is in sync (--check passes on the committed tree)", () => {
const r = run(["--check"]);
assert.equal(r.code, 0, r.stderr ?? "");
});
test("a hand-edited generated mirror is DRIFT: --check exits 1 naming the fix", () => {
const root = makeTmpRoot();
try {
fs.appendFileSync(path.join(root, GENERATED), "\n// vandalized\n");
const r = run(["--check"], { SYNC_LINEAGE_ROOT: root });
assert.equal(r.code, 1);
assert.match(r.stderr ?? "", /DRIFT/);
assert.match(r.stderr ?? "", /sync-scope-lineage\.mjs/);
} finally {
fs.rmSync(root, { recursive: true, force: true });
}
});
test("a canonical mutation propagates to BOTH surfaces in one sync (the drift class is structurally dead)", () => {
const root = makeTmpRoot();
try {
const canonicalPath = path.join(root, CANONICAL);
fs.writeFileSync(
canonicalPath,
fs.readFileSync(canonicalPath, "utf8").replace("priors > 0", "priors > 0 /* mutated */"),
"utf8",
);
// Before the sync: the mirror does NOT carry the mutation (drift exists).
assert.equal(run(["--check"], { SYNC_LINEAGE_ROOT: root }).code, 1);
// One sync run: the mirror now carries it — both surfaces changed together.
assert.equal(run([], { SYNC_LINEAGE_ROOT: root }).code, 0);
const generated = fs.readFileSync(path.join(root, GENERATED), "utf8");
assert.match(generated, /priors > 0 \/\* mutated \*\//);
assert.equal(run(["--check"], { SYNC_LINEAGE_ROOT: root }).code, 0);
} finally {
fs.rmSync(root, { recursive: true, force: true });
}
});