Skip to content

Commit 897f042

Browse files
dmealingclaude
andcommitted
refactor(requirements): dedupe the ledger load, unpick a nested ternary
Simplifier pass over the four gate scripts and the renderer. The strict ledger load plus its "does not load" error report was repeated verbatim in three scripts, down to the `errors.length > 0 || root === undefined` guard — the same duplication this shared module already existed to close for the population walk. It is now `loadLedgerOrReport(dir, gate, detail?)`, with the one caller's extra clause passed rather than the whole block copied. The requirement-test renderer's body assignment had become a NESTED ternary when `retired` got its own text — which this repo's own style notes call out. Same three branches, same order, now an if/else chain, with the comment explaining why the two skipped statuses must not share a body left verbatim. Smaller: check-requirements-vocabulary walked the requirement tree twice, once to validate and once again only to count the L5s; check-requirements-ledger computed its own REPO_ROOT instead of importing the shared one; and the three self-tests' `expect` helpers had drifted into two shapes. Behaviour is unchanged and checked rather than assumed: the harness regenerates byte-identically, all seven gates and self-tests pass, codegen-ts is 1332 pass / 0 fail, the workspace typechecks, and `ci-local.sh --only gates` is green. Every fix from the preceding review pass was re-verified intact — bun_install first in each gate, the round-trip derivation check, the validated --expect-warnings flag, the scaffolder's non-zero bare run, textual --apply, the architectural filter, and esc() on every prose interpolation. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NynrRND6ZUwGvq3ZUTCfxG
1 parent fa18f81 commit 897f042

7 files changed

Lines changed: 59 additions & 44 deletions

File tree

‎scripts/check-requirements-ledger.ts‎

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,8 @@
2626
// it needs a machine-readable verify output, which is a product change and out
2727
// of scope for this ledger.
2828
import { spawnSync } from "node:child_process";
29-
import { fileURLToPath } from "node:url";
30-
import { dirname, resolve } from "node:path";
29+
import { resolve } from "node:path";
30+
import { REPO_ROOT } from "./lib/requirement-vocabulary.js";
3131

3232
/** The one diagnostic code this ledger is expected to emit, and why. */
3333
const EXPECTED_WARNING = "WARN_REQUIREMENT_NOTHING_IMPLEMENTS";
@@ -76,11 +76,11 @@ function expectedWarnings(argv: readonly string[]): number {
7676

7777
const EXPECTED_WARNING_COUNT = expectedWarnings(process.argv.slice(2));
7878

79-
// Resolved against THIS FILE, not the working directory: the self-test runs the
80-
// gate from a temp directory holding a throwaway ledger, so a cwd-relative path
81-
// to the CLI would resolve to nothing there and every case would "fail" for the
82-
// wrong reason.
83-
const REPO_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "..");
79+
// REPO_ROOT (shared with the sibling gates, resolved against THAT FILE rather than the
80+
// working directory) is what makes this safe to resolve here too: the self-test runs
81+
// the gate from a temp directory holding a throwaway ledger, so a cwd-relative path to
82+
// the CLI would resolve to nothing there and every case would "fail" for the wrong
83+
// reason.
8484
const CLI = resolve(REPO_ROOT, "server/typescript/packages/cli/bin/meta.ts");
8585

8686
function main(): number {

‎scripts/check-requirements-vocabulary.ts‎

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -35,13 +35,13 @@
3535
* statement about portability, not a licence to leave the vocabulary unpromised — an
3636
* adopter can author `view.textarea` today, so something must say what it is for.
3737
*/
38-
import { MetaDataLoader } from "../server/typescript/packages/metadata/src/index.js";
3938
import { collectAddressedRequirements } from "../server/typescript/packages/cli/src/lib/requirement-check.js";
4039
import {
4140
DEFAULT_LEDGER_DIR,
4241
LEVEL_ATTR,
4342
LEVEL_SUBTYPE,
4443
derivationProblems,
44+
loadLedgerOrReport,
4545
participatesInVocabularyLink,
4646
population,
4747
splitName,
@@ -65,17 +65,14 @@ async function main(): Promise<number> {
6565
return 1;
6666
}
6767

68-
const { root, errors } = await MetaDataLoader.fromDirectory(LEDGER_DIR, { strict: true });
69-
if (errors.length > 0 || root === undefined) {
70-
console.error("requirements-vocabulary: the ledger does not load.\n");
71-
for (const e of errors) console.error(` ${e.code ?? ""} ${e.message}`);
72-
return 1;
73-
}
68+
const root = await loadLedgerOrReport(LEDGER_DIR, "requirements-vocabulary");
69+
if (root === undefined) return 1;
7470

7571
const claimed = new Map<string, string>(); // type.subType -> requirement address
7672
const problems: string[] = [];
73+
const addressed = collectAddressedRequirements(root);
7774

78-
for (const { node, path } of collectAddressedRequirements(root)) {
75+
for (const { node, path } of addressed) {
7976
// Architectural requirements are object-independent and name no subtype (0.23.0
8077
// made them levellable, so one can legally sit at L4).
8178
if (!participatesInVocabularyLink(node)) continue;
@@ -127,7 +124,7 @@ async function main(): Promise<number> {
127124
return 1;
128125
}
129126

130-
const l5 = collectAddressedRequirements(root)
127+
const l5 = addressed
131128
.filter((r) => participatesInVocabularyLink(r.node) && r.node.level() === LEVEL_ATTR).length;
132129
console.log(
133130
`requirements-vocabulary: OK — ${pop.size} registered subtype(s), each promised by exactly ` +

‎scripts/generate-requirement-harness.ts‎

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -36,13 +36,13 @@
3636
*/
3737
import { mkdirSync, writeFileSync, readFileSync, existsSync } from "node:fs";
3838
import { join } from "node:path";
39-
import { MetaDataLoader } from "../server/typescript/packages/metadata/src/index.js";
4039
import { collectAddressedRequirements } from "../server/typescript/packages/cli/src/lib/requirement-check.js";
4140
import {
4241
DEFAULT_LEDGER_DIR,
4342
LEVEL_ATTR,
4443
LEVEL_SUBTYPE,
4544
REPO_ROOT,
45+
loadLedgerOrReport,
4646
participatesInVocabularyLink,
4747
} from "./lib/requirement-vocabulary.js";
4848

@@ -188,12 +188,8 @@ const renderers: Record<string, { file: string; render: (ps: Promise_[]) => stri
188188

189189
async function main(): Promise<number> {
190190
const check = process.argv.includes("--check");
191-
const { root, errors } = await MetaDataLoader.fromDirectory(DEFAULT_LEDGER_DIR, { strict: true });
192-
if (errors.length > 0 || root === undefined) {
193-
console.error("requirement-harness: the ledger does not load.\n");
194-
for (const e of errors) console.error(` ${e.code ?? ""} ${e.message}`);
195-
return 1;
196-
}
191+
const root = await loadLedgerOrReport(DEFAULT_LEDGER_DIR, "requirement-harness");
192+
if (root === undefined) return 1;
197193
const promises = collect(root);
198194
if (promises.length === 0) {
199195
console.error("requirement-harness: the ledger declares no L4 or L5 promise — nothing to scaffold.\n");

‎scripts/lib/requirement-vocabulary.ts‎

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import { readdirSync, readFileSync } from "node:fs";
1010
import { fileURLToPath } from "node:url";
1111
import { dirname, join, resolve } from "node:path";
12+
import { MetaDataLoader, type MetaRoot } from "../../server/typescript/packages/metadata/src/index.js";
1213
import {
1314
REQUIREMENT_SUBTYPE_ARCHITECTURAL,
1415
} from "../../server/typescript/packages/metadata/src/core/requirement/requirement-constants.js";
@@ -27,6 +28,31 @@ export const DEFAULT_LEDGER_DIR = join(REPO_ROOT, "metaobjects");
2728
export const LEVEL_SUBTYPE = 4;
2829
export const LEVEL_ATTR = 5;
2930

31+
/**
32+
* Load the ledger under `dir` in strict mode, reporting load errors the same way
33+
* everywhere and returning `undefined` so the caller can exit non-zero.
34+
*
35+
* Repeated identically (down to the `errors.length > 0 || root === undefined` guard)
36+
* across `check-requirements-vocabulary.ts`, `scaffold-metamodel-entry.ts` and
37+
* `generate-requirement-harness.ts` before this was pulled out — the same duplication
38+
* this module already exists to close for `population()`. `gate` names the caller for
39+
* the printed message; `detail` is one caller's extra clause ("; fix it before
40+
* scaffolding from it") appended before the trailing period.
41+
*/
42+
export async function loadLedgerOrReport(
43+
dir: string,
44+
gate: string,
45+
detail = "",
46+
): Promise<MetaRoot | undefined> {
47+
const { root, errors } = await MetaDataLoader.fromDirectory(dir, { strict: true });
48+
if (errors.length > 0 || root === undefined) {
49+
console.error(`${gate}: the ledger does not load${detail}.\n`);
50+
for (const e of errors) console.error(` ${e.code ?? ""} ${e.message}`);
51+
return undefined;
52+
}
53+
return root;
54+
}
55+
3056
export interface Entry { key: string; attrs: Set<string> }
3157

3258
/**

‎scripts/scaffold-metamodel-entry.ts‎

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -32,13 +32,13 @@
3232
*/
3333
import { readFileSync, writeFileSync, existsSync } from "node:fs";
3434
import { join } from "node:path";
35-
import { MetaDataLoader } from "../server/typescript/packages/metadata/src/index.js";
3635
import { collectAddressedRequirements } from "../server/typescript/packages/cli/src/lib/requirement-check.js";
3736
import {
3837
DEFAULT_LEDGER_DIR,
3938
DEFAULT_MANIFEST,
4039
DEFAULT_SPEC_DIR,
4140
LEVEL_SUBTYPE,
41+
loadLedgerOrReport,
4242
participatesInVocabularyLink,
4343
population,
4444
splitName,
@@ -65,7 +65,6 @@ function parseArgs(argv: string[]): Args {
6565
};
6666
}
6767

68-
6968
/**
7069
* Append one entry to a provider file's `types` array by editing TEXT, not by
7170
* re-serialising the document. Returns undefined when the array's end cannot be
@@ -89,12 +88,8 @@ async function main(): Promise<number> {
8988
const pop = population(specDir, manifest);
9089
const types = typeAxis(pop);
9190

92-
const { root, errors } = await MetaDataLoader.fromDirectory(ledgerDir, { strict: true });
93-
if (errors.length > 0 || root === undefined) {
94-
console.error("scaffold-metamodel: the ledger does not load; fix it before scaffolding from it.\n");
95-
for (const e of errors) console.error(` ${e.code ?? ""} ${e.message}`);
96-
return 1;
97-
}
91+
const root = await loadLedgerOrReport(ledgerDir, "scaffold-metamodel", "; fix it before scaffolding from it");
92+
if (root === undefined) return 1;
9893

9994
// A requirement naming vocabulary nothing registers is the SIGNAL, not an error.
10095
const wanted: { key: string; path: string; title: string }[] = [];

‎scripts/test-requirements-ledger.ts‎

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -79,13 +79,9 @@ metadata.root:
7979
`;
8080

8181
let failures = 0;
82-
function expect(label: string, condition: boolean, detail: string): void {
83-
if (condition) {
84-
console.log(` ok ${label}`);
85-
} else {
86-
console.error(` FAIL ${label}\n${detail}`);
87-
failures++;
88-
}
82+
function expect(label: string, ok: boolean, detail: string): void {
83+
if (ok) console.log(` ok ${label}`);
84+
else { console.error(` FAIL ${label}\n${detail}`); failures++; }
8985
}
9086

9187
console.log("test-requirements-ledger:");

‎server/typescript/packages/codegen-ts/src/templates/requirement-test.ts‎

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -124,15 +124,20 @@ export function renderRequirementTest(a: RequirementTestArgs): string {
124124
// to write it "when this becomes live" instructs them to revive the capability —
125125
// inverting the one guardrail 0.24.2 restored `retired` for. The repo's own harness
126126
// generator already branches here; the shipped renderer now does too.
127-
const body = skipped
128-
? a.view.status === REQUIREMENT_STATUS_RETIRED
129-
? ` // Retired: this capability was deliberately removed and must not be rebuilt.\n` +
130-
` // If you assert anything here, assert that it STAYS removed.`
131-
: ` // Intended, not built. Write the assertion when this becomes live.`
132-
: ` expect.unreachable(\n` +
127+
let body: string;
128+
if (!skipped) {
129+
body =
130+
` expect.unreachable(\n` +
133131
` "unimplemented requirement stub: ${testName} — " +\n` +
134132
` "replace this with an assertion that fails when: ${forStringLiteral(a.counterexample)}",\n` +
135133
` );`;
134+
} else if (a.view.status === REQUIREMENT_STATUS_RETIRED) {
135+
body =
136+
` // Retired: this capability was deliberately removed and must not be rebuilt.\n` +
137+
` // If you assert anything here, assert that it STAYS removed.`;
138+
} else {
139+
body = ` // Intended, not built. Write the assertion when this becomes live.`;
140+
}
136141

137142
return (
138143
`// ${GENERATED_HEADER}.\n` +

0 commit comments

Comments
 (0)