mirror of
https://github.com/mvschwarz/openrig.git
synced 2026-11-08 17:31:56 -10:00
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:
1 parent
1c7ec2065b
commit
5de3b994fc
7 files changed
+279
-78
No files matched your search
@@ -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,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));
|
||||
}
|
||||
});
|
||||
@@ -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();
|
||||
@@ -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 });
|
||||
}
|
||||
});
|
||||
Reference in new issue
Block a user