@biffo/cli 0.301.28 → 0.302.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +368 -114
- package/package.json +1 -1
- package/scripts/verify.sh +23 -0
package/dist/index.js
CHANGED
|
@@ -11965,8 +11965,8 @@ async function runOwnershipCheck(argv) {
|
|
|
11965
11965
|
const { stdout } = await execa("git", ["diff", "--cached", "--name-status"], { cwd: root });
|
|
11966
11966
|
({ changed: changedFiles, deleted: deletedFiles } = parseNameStatus(stdout));
|
|
11967
11967
|
if (messageFile) {
|
|
11968
|
-
const { readFileSync:
|
|
11969
|
-
if (
|
|
11968
|
+
const { readFileSync: readFileSync46, existsSync: existsSync56 } = await import("fs");
|
|
11969
|
+
if (existsSync56(messageFile)) commitMessage = readFileSync46(messageFile, "utf8");
|
|
11970
11970
|
}
|
|
11971
11971
|
} else {
|
|
11972
11972
|
const base = process.env["GITHUB_BASE_REF"] ?? args[0];
|
|
@@ -12066,9 +12066,253 @@ ${BOLD}If the divergence is deliberate${OFF}
|
|
|
12066
12066
|
process.exit(1);
|
|
12067
12067
|
}
|
|
12068
12068
|
|
|
12069
|
-
// src/lib/
|
|
12070
|
-
import {
|
|
12069
|
+
// src/lib/distribution-inventory.ts
|
|
12070
|
+
import { existsSync as existsSync43, readFileSync as readFileSync34 } from "fs";
|
|
12071
12071
|
import { join as join47 } from "path";
|
|
12072
|
+
var INVENTORY_FILENAME = "distribution-inventory.json";
|
|
12073
|
+
function loadDistributionInventory(root) {
|
|
12074
|
+
const path = join47(root, INVENTORY_FILENAME);
|
|
12075
|
+
if (!existsSync43(path)) {
|
|
12076
|
+
throw new Error(
|
|
12077
|
+
`${INVENTORY_FILENAME} not found at ${root} -- expected it beside core-manifest.json`
|
|
12078
|
+
);
|
|
12079
|
+
}
|
|
12080
|
+
return JSON.parse(readFileSync34(path, "utf8"));
|
|
12081
|
+
}
|
|
12082
|
+
function validateInventory(inventory) {
|
|
12083
|
+
const violations = [];
|
|
12084
|
+
const seenIds = /* @__PURE__ */ new Set();
|
|
12085
|
+
for (const entry of inventory.entries) {
|
|
12086
|
+
if (seenIds.has(entry.id)) {
|
|
12087
|
+
violations.push({
|
|
12088
|
+
entryId: entry.id,
|
|
12089
|
+
rule: "duplicate-id",
|
|
12090
|
+
detail: "id reused by another entry"
|
|
12091
|
+
});
|
|
12092
|
+
}
|
|
12093
|
+
seenIds.add(entry.id);
|
|
12094
|
+
const channel = inventory.channels[entry.channel];
|
|
12095
|
+
if (!channel) {
|
|
12096
|
+
violations.push({
|
|
12097
|
+
entryId: entry.id,
|
|
12098
|
+
rule: "unknown-channel",
|
|
12099
|
+
detail: `channel "${entry.channel}" is not declared in inventory.channels`
|
|
12100
|
+
});
|
|
12101
|
+
continue;
|
|
12102
|
+
}
|
|
12103
|
+
if (entry.status === "detected" && (!channel.detectorCommand || entry.channel === "none")) {
|
|
12104
|
+
violations.push({
|
|
12105
|
+
entryId: entry.id,
|
|
12106
|
+
rule: "detected-with-no-detector",
|
|
12107
|
+
detail: `status "detected" but channel "${entry.channel}" declares no detectorCommand`
|
|
12108
|
+
});
|
|
12109
|
+
}
|
|
12110
|
+
if (entry.status === "unregistered" && !channel.detectorCommand) {
|
|
12111
|
+
violations.push({
|
|
12112
|
+
entryId: entry.id,
|
|
12113
|
+
rule: "unregistered-with-no-mechanism",
|
|
12114
|
+
detail: `status "unregistered" claims a mechanism exists to register with, but channel "${entry.channel}" declares no detectorCommand -- this should be "unverified" instead`
|
|
12115
|
+
});
|
|
12116
|
+
}
|
|
12117
|
+
if (entry.status === "unverified" && entry.channel !== "none") {
|
|
12118
|
+
violations.push({
|
|
12119
|
+
entryId: entry.id,
|
|
12120
|
+
rule: "unverified-on-live-channel",
|
|
12121
|
+
detail: `status "unverified" but channel "${entry.channel}" has a real detector -- this is "unregistered" (mechanism exists, artifact not added to it), not "unverified" (no mechanism at all)`
|
|
12122
|
+
});
|
|
12123
|
+
}
|
|
12124
|
+
if ((entry.status === "unregistered" || entry.status === "unverified") && !entry.gapReason) {
|
|
12125
|
+
violations.push({
|
|
12126
|
+
entryId: entry.id,
|
|
12127
|
+
rule: "gap-with-no-reason",
|
|
12128
|
+
detail: `status "${entry.status}" requires a gapReason explaining what is missing and why`
|
|
12129
|
+
});
|
|
12130
|
+
}
|
|
12131
|
+
if (entry.evidence.length === 0) {
|
|
12132
|
+
violations.push({
|
|
12133
|
+
entryId: entry.id,
|
|
12134
|
+
rule: "no-evidence",
|
|
12135
|
+
detail: "evidence must name at least one issue or file"
|
|
12136
|
+
});
|
|
12137
|
+
}
|
|
12138
|
+
if (entry.targets.length === 0) {
|
|
12139
|
+
violations.push({
|
|
12140
|
+
entryId: entry.id,
|
|
12141
|
+
rule: "no-targets",
|
|
12142
|
+
detail: "targets must name at least one destination"
|
|
12143
|
+
});
|
|
12144
|
+
}
|
|
12145
|
+
for (const assertion of entry.remoteContentAssertions ?? []) {
|
|
12146
|
+
if (!assertion.repo || !assertion.path || !assertion.ref) {
|
|
12147
|
+
violations.push({
|
|
12148
|
+
entryId: entry.id,
|
|
12149
|
+
rule: "incomplete-remote-content-assertion",
|
|
12150
|
+
detail: `remoteContentAssertions entry missing repo/path/ref: ${JSON.stringify(assertion)}`
|
|
12151
|
+
});
|
|
12152
|
+
}
|
|
12153
|
+
if ((assertion.mustContain?.length ?? 0) === 0 && (assertion.mustNotContain?.length ?? 0) === 0) {
|
|
12154
|
+
violations.push({
|
|
12155
|
+
entryId: entry.id,
|
|
12156
|
+
rule: "empty-remote-content-assertion",
|
|
12157
|
+
detail: `remoteContentAssertions entry for ${assertion.repo}/${assertion.path} states neither mustContain nor mustNotContain -- checks nothing`
|
|
12158
|
+
});
|
|
12159
|
+
}
|
|
12160
|
+
}
|
|
12161
|
+
}
|
|
12162
|
+
return violations;
|
|
12163
|
+
}
|
|
12164
|
+
function checkRemoteContentAssertions(inventory, fetchedContent) {
|
|
12165
|
+
const violations = [];
|
|
12166
|
+
for (const entry of inventory.entries) {
|
|
12167
|
+
for (const assertion of entry.remoteContentAssertions ?? []) {
|
|
12168
|
+
const key = `${assertion.repo}
|
|
12169
|
+
${assertion.path}
|
|
12170
|
+
${assertion.ref}`;
|
|
12171
|
+
const content = fetchedContent.get(key);
|
|
12172
|
+
if (content === void 0) {
|
|
12173
|
+
violations.push({
|
|
12174
|
+
entryId: entry.id,
|
|
12175
|
+
assertion,
|
|
12176
|
+
rule: "fetch-failed",
|
|
12177
|
+
detail: `no fetched content supplied for ${key.replace(/\n/g, " @ ")}`
|
|
12178
|
+
});
|
|
12179
|
+
continue;
|
|
12180
|
+
}
|
|
12181
|
+
if (content === null) {
|
|
12182
|
+
violations.push({
|
|
12183
|
+
entryId: entry.id,
|
|
12184
|
+
assertion,
|
|
12185
|
+
rule: "fetch-failed",
|
|
12186
|
+
detail: `fetching ${assertion.repo}/${assertion.path}@${assertion.ref} failed`
|
|
12187
|
+
});
|
|
12188
|
+
continue;
|
|
12189
|
+
}
|
|
12190
|
+
for (const needle of assertion.mustContain ?? []) {
|
|
12191
|
+
if (!content.includes(needle)) {
|
|
12192
|
+
violations.push({
|
|
12193
|
+
entryId: entry.id,
|
|
12194
|
+
assertion,
|
|
12195
|
+
rule: "missing-required-substring",
|
|
12196
|
+
detail: `${assertion.repo}/${assertion.path}@${assertion.ref} no longer contains "${needle}" -- the entry's gapReason claims this is (still) the current state`
|
|
12197
|
+
});
|
|
12198
|
+
}
|
|
12199
|
+
}
|
|
12200
|
+
for (const needle of assertion.mustNotContain ?? []) {
|
|
12201
|
+
if (content.includes(needle)) {
|
|
12202
|
+
violations.push({
|
|
12203
|
+
entryId: entry.id,
|
|
12204
|
+
assertion,
|
|
12205
|
+
rule: "contains-forbidden-substring",
|
|
12206
|
+
detail: `${assertion.repo}/${assertion.path}@${assertion.ref} still contains "${needle}" -- the entry's gapReason claims this was removed/changed`
|
|
12207
|
+
});
|
|
12208
|
+
}
|
|
12209
|
+
}
|
|
12210
|
+
}
|
|
12211
|
+
}
|
|
12212
|
+
return violations;
|
|
12213
|
+
}
|
|
12214
|
+
async function fetchRemoteContentViaGh(repo, path, ref, execCommand) {
|
|
12215
|
+
const result = await execCommand("gh", [
|
|
12216
|
+
"api",
|
|
12217
|
+
`repos/${repo}/contents/${path}?ref=${ref}`,
|
|
12218
|
+
"--jq",
|
|
12219
|
+
".content"
|
|
12220
|
+
]);
|
|
12221
|
+
if (result.exitCode !== 0) return null;
|
|
12222
|
+
const base64 = result.stdout.trim();
|
|
12223
|
+
if (!base64) return null;
|
|
12224
|
+
try {
|
|
12225
|
+
return Buffer.from(base64, "base64").toString("utf8");
|
|
12226
|
+
} catch {
|
|
12227
|
+
return null;
|
|
12228
|
+
}
|
|
12229
|
+
}
|
|
12230
|
+
|
|
12231
|
+
// src/scripts/check-distribution-inventory.ts
|
|
12232
|
+
async function runDistributionInventoryCheck(root) {
|
|
12233
|
+
const repoRoot = root ?? (await execa("git", ["rev-parse", "--show-toplevel"])).stdout.trim();
|
|
12234
|
+
const inventory = loadDistributionInventory(repoRoot);
|
|
12235
|
+
const violations = validateInventory(inventory);
|
|
12236
|
+
console.log(
|
|
12237
|
+
`distribution-inventory: examined ${inventory.entries.length} inventory entr(ies) across ${Object.keys(inventory.channels).length} declared channel(s) in distribution-inventory.json (${violations.length} schema violation(s))`
|
|
12238
|
+
);
|
|
12239
|
+
if (violations.length === 0) {
|
|
12240
|
+
console.log("\u2713 distribution-inventory: every entry is well-formed and internally consistent");
|
|
12241
|
+
return;
|
|
12242
|
+
}
|
|
12243
|
+
console.error(
|
|
12244
|
+
`\u2717 distribution-inventory: ${violations.length} schema violation(s) \u2014 an entry is malformed, half-classified, or inconsistent with the channel it names:
|
|
12245
|
+
`
|
|
12246
|
+
);
|
|
12247
|
+
for (const v of violations) {
|
|
12248
|
+
console.error(` ${v.entryId} [${v.rule}]: ${v.detail}`);
|
|
12249
|
+
}
|
|
12250
|
+
process.exit(1);
|
|
12251
|
+
}
|
|
12252
|
+
|
|
12253
|
+
// src/scripts/check-distribution-remote-state.ts
|
|
12254
|
+
import { join as join48 } from "path";
|
|
12255
|
+
async function ghExecCommand(file, args) {
|
|
12256
|
+
const result = await execa(file, args, { reject: false });
|
|
12257
|
+
return { stdout: String(result.stdout ?? ""), exitCode: result.exitCode ?? null };
|
|
12258
|
+
}
|
|
12259
|
+
async function runDistributionRemoteStateCheck(root) {
|
|
12260
|
+
const repoRoot = root ?? (await execa("git", ["rev-parse", "--show-toplevel"])).stdout.trim();
|
|
12261
|
+
const inventory = loadDistributionInventory(repoRoot);
|
|
12262
|
+
const assertions = [];
|
|
12263
|
+
for (const entry of inventory.entries) {
|
|
12264
|
+
for (const assertion of entry.remoteContentAssertions ?? []) {
|
|
12265
|
+
assertions.push({ entryId: entry.id, assertion });
|
|
12266
|
+
}
|
|
12267
|
+
}
|
|
12268
|
+
console.log(
|
|
12269
|
+
`distribution-remote-state: examined ${assertions.length} remote content assertion(s) declared across ${inventory.entries.length} inventory entries (${join48(repoRoot, "distribution-inventory.json")})`
|
|
12270
|
+
);
|
|
12271
|
+
if (assertions.length === 0) {
|
|
12272
|
+
console.log("\u2713 distribution-remote-state: nothing declared to check");
|
|
12273
|
+
return;
|
|
12274
|
+
}
|
|
12275
|
+
const fetched = /* @__PURE__ */ new Map();
|
|
12276
|
+
let fetchFailures = 0;
|
|
12277
|
+
for (const { assertion } of assertions) {
|
|
12278
|
+
const key = `${assertion.repo}
|
|
12279
|
+
${assertion.path}
|
|
12280
|
+
${assertion.ref}`;
|
|
12281
|
+
if (fetched.has(key)) continue;
|
|
12282
|
+
const content = await fetchRemoteContentViaGh(
|
|
12283
|
+
assertion.repo,
|
|
12284
|
+
assertion.path,
|
|
12285
|
+
assertion.ref,
|
|
12286
|
+
ghExecCommand
|
|
12287
|
+
);
|
|
12288
|
+
fetched.set(key, content);
|
|
12289
|
+
if (content === null) fetchFailures += 1;
|
|
12290
|
+
}
|
|
12291
|
+
if (fetchFailures > 0) {
|
|
12292
|
+
console.error(
|
|
12293
|
+
`\u2717 distribution-remote-state: could not fetch ${fetchFailures} of ${fetched.size} distinct remote file(s) -- no BIFFO_GITHUB_TOKEN, network failure, or the file/ref no longer exists. Cannot tell whether the remaining claims hold.`
|
|
12294
|
+
);
|
|
12295
|
+
process.exit(2);
|
|
12296
|
+
}
|
|
12297
|
+
const violations = checkRemoteContentAssertions(inventory, fetched);
|
|
12298
|
+
if (violations.length === 0) {
|
|
12299
|
+
console.log(
|
|
12300
|
+
`\u2713 distribution-remote-state: all ${assertions.length} assertion(s) match real content`
|
|
12301
|
+
);
|
|
12302
|
+
return;
|
|
12303
|
+
}
|
|
12304
|
+
console.error(
|
|
12305
|
+
`\u2717 distribution-remote-state: ${violations.length} assertion(s) no longer match real content -- the affected entry's gapReason is stale (#1570/#1816):`
|
|
12306
|
+
);
|
|
12307
|
+
for (const v of violations) {
|
|
12308
|
+
console.error(` ${v.entryId} [${v.rule}]: ${v.detail}`);
|
|
12309
|
+
}
|
|
12310
|
+
process.exit(1);
|
|
12311
|
+
}
|
|
12312
|
+
|
|
12313
|
+
// src/lib/eventbridge-log-permission-guard.ts
|
|
12314
|
+
import { readFileSync as readFileSync35, readdirSync as readdirSync20, statSync as statSync12 } from "fs";
|
|
12315
|
+
import { join as join49 } from "path";
|
|
12072
12316
|
var SKIP_DIRS2 = /* @__PURE__ */ new Set(["node_modules", ".git", ".terraform", ".worktrees", "dist"]);
|
|
12073
12317
|
var EVENT_TARGET_TYPE = "aws_cloudwatch_event_target";
|
|
12074
12318
|
var LOG_RESOURCE_POLICY_TYPE = "aws_cloudwatch_log_resource_policy";
|
|
@@ -12145,7 +12389,7 @@ function walkTerraformFiles(root) {
|
|
|
12145
12389
|
return;
|
|
12146
12390
|
}
|
|
12147
12391
|
for (const entry of entries) {
|
|
12148
|
-
const p =
|
|
12392
|
+
const p = join49(dir, entry);
|
|
12149
12393
|
let st;
|
|
12150
12394
|
try {
|
|
12151
12395
|
st = statSync12(p);
|
|
@@ -12188,7 +12432,7 @@ function auditEventBridgeLogPermissions(root) {
|
|
|
12188
12432
|
let rawEventTargetCount = 0;
|
|
12189
12433
|
let rawLogPolicyCount = 0;
|
|
12190
12434
|
for (const file of files) {
|
|
12191
|
-
const text =
|
|
12435
|
+
const text = readFileSync35(file, "utf8");
|
|
12192
12436
|
rawEventTargetCount += countRawResourceDeclarations(text, EVENT_TARGET_TYPE);
|
|
12193
12437
|
rawLogPolicyCount += countRawResourceDeclarations(text, LOG_RESOURCE_POLICY_TYPE);
|
|
12194
12438
|
eventTargetBlocks.push(...findResourceBlocks(text, file, EVENT_TARGET_TYPE));
|
|
@@ -12278,8 +12522,8 @@ async function runEventBridgeLogPermissionCheck() {
|
|
|
12278
12522
|
}
|
|
12279
12523
|
|
|
12280
12524
|
// src/scripts/check-instance-adoption.ts
|
|
12281
|
-
import { existsSync as
|
|
12282
|
-
import { join as
|
|
12525
|
+
import { existsSync as existsSync44 } from "fs";
|
|
12526
|
+
import { join as join50 } from "path";
|
|
12283
12527
|
async function runInstanceAdoptionCheck(opts = {}) {
|
|
12284
12528
|
if (!opts.instanceDir) {
|
|
12285
12529
|
console.error(
|
|
@@ -12287,7 +12531,7 @@ async function runInstanceAdoptionCheck(opts = {}) {
|
|
|
12287
12531
|
);
|
|
12288
12532
|
process.exit(2);
|
|
12289
12533
|
}
|
|
12290
|
-
if (!
|
|
12534
|
+
if (!existsSync44(opts.instanceDir)) {
|
|
12291
12535
|
console.error(
|
|
12292
12536
|
`\u2717 instance-adoption guard: --instance-dir ${opts.instanceDir} does not exist \u2014 cannot tell whether it is adopted, and that is not the same as a clean pass.`
|
|
12293
12537
|
);
|
|
@@ -12295,7 +12539,7 @@ async function runInstanceAdoptionCheck(opts = {}) {
|
|
|
12295
12539
|
}
|
|
12296
12540
|
const root = (await execa("git", ["rev-parse", "--show-toplevel"])).stdout.trim();
|
|
12297
12541
|
const theirsDir = opts.theirsDir ?? root;
|
|
12298
|
-
const instanceLabel = opts.instance ??
|
|
12542
|
+
const instanceLabel = opts.instance ?? join50(opts.instanceDir).split("/").filter(Boolean).pop();
|
|
12299
12543
|
const report = checkInstanceAdoption(theirsDir, opts.instanceDir);
|
|
12300
12544
|
console.log(
|
|
12301
12545
|
`examined ${report.examinedInstances} instance (${instanceLabel}) against ${report.registeredPairs} registered pair(s), ${report.applicablePairs} applicable to this instance, against template tree ${theirsDir}`
|
|
@@ -12317,12 +12561,12 @@ async function runInstanceAdoptionCheck(opts = {}) {
|
|
|
12317
12561
|
}
|
|
12318
12562
|
|
|
12319
12563
|
// src/lib/lambda-output-guard.ts
|
|
12320
|
-
import { readFileSync as
|
|
12321
|
-
import { join as
|
|
12564
|
+
import { readFileSync as readFileSync37 } from "fs";
|
|
12565
|
+
import { join as join52 } from "path";
|
|
12322
12566
|
|
|
12323
12567
|
// src/lib/terraform-input-guard.ts
|
|
12324
|
-
import { existsSync as
|
|
12325
|
-
import { join as
|
|
12568
|
+
import { existsSync as existsSync45, readdirSync as readdirSync21, readFileSync as readFileSync36, statSync as statSync13 } from "fs";
|
|
12569
|
+
import { join as join51 } from "path";
|
|
12326
12570
|
var GUARDED_SUBCOMMANDS = [
|
|
12327
12571
|
"init",
|
|
12328
12572
|
"plan",
|
|
@@ -12336,7 +12580,7 @@ function stripComments2(source) {
|
|
|
12336
12580
|
return source.split("\n").map((line) => line.replace(/(^|\s)#.*$/, "$1")).join("\n");
|
|
12337
12581
|
}
|
|
12338
12582
|
function vendoredPluginServiceDirs(repoRoot) {
|
|
12339
|
-
const servicesDir =
|
|
12583
|
+
const servicesDir = join51(repoRoot, "services");
|
|
12340
12584
|
const result = /* @__PURE__ */ new Set();
|
|
12341
12585
|
let entries;
|
|
12342
12586
|
try {
|
|
@@ -12345,9 +12589,9 @@ function vendoredPluginServiceDirs(repoRoot) {
|
|
|
12345
12589
|
return result;
|
|
12346
12590
|
}
|
|
12347
12591
|
for (const entry of entries) {
|
|
12348
|
-
const full =
|
|
12349
|
-
if (!
|
|
12350
|
-
if (
|
|
12592
|
+
const full = join51(servicesDir, entry);
|
|
12593
|
+
if (!existsSync45(full) || !statSync13(full).isDirectory()) continue;
|
|
12594
|
+
if (existsSync45(join51(full, "biffo.plugin.json"))) {
|
|
12351
12595
|
result.add(entry);
|
|
12352
12596
|
}
|
|
12353
12597
|
}
|
|
@@ -12369,7 +12613,7 @@ function findWorkflowFiles(repoRoot) {
|
|
|
12369
12613
|
if (entry === ".github" && relative11.startsWith("services/") && vendoredPluginDirs.has(relative11.slice("services/".length))) {
|
|
12370
12614
|
continue;
|
|
12371
12615
|
}
|
|
12372
|
-
const full =
|
|
12616
|
+
const full = join51(dir, entry);
|
|
12373
12617
|
const rel = relative11 ? `${relative11}/${entry}` : entry;
|
|
12374
12618
|
let isDir;
|
|
12375
12619
|
try {
|
|
@@ -12419,7 +12663,7 @@ function checkWorkflowSource(file, rawSource) {
|
|
|
12419
12663
|
}
|
|
12420
12664
|
function checkTerraformInput(repoRoot) {
|
|
12421
12665
|
return findWorkflowFiles(repoRoot).flatMap(
|
|
12422
|
-
(file) => checkWorkflowSource(file,
|
|
12666
|
+
(file) => checkWorkflowSource(file, readFileSync36(join51(repoRoot, file), "utf8"))
|
|
12423
12667
|
);
|
|
12424
12668
|
}
|
|
12425
12669
|
|
|
@@ -12477,7 +12721,7 @@ function checkWorkflowSource2(file, rawSource) {
|
|
|
12477
12721
|
}
|
|
12478
12722
|
function checkLambdaOutput(repoRoot) {
|
|
12479
12723
|
return findWorkflowFiles(repoRoot).flatMap(
|
|
12480
|
-
(file) => checkWorkflowSource2(file,
|
|
12724
|
+
(file) => checkWorkflowSource2(file, readFileSync37(join52(repoRoot, file), "utf8"))
|
|
12481
12725
|
);
|
|
12482
12726
|
}
|
|
12483
12727
|
|
|
@@ -12505,8 +12749,8 @@ async function runLambdaOutputCheck() {
|
|
|
12505
12749
|
}
|
|
12506
12750
|
|
|
12507
12751
|
// src/scripts/check-migration-body-change.ts
|
|
12508
|
-
import { existsSync as
|
|
12509
|
-
import { join as
|
|
12752
|
+
import { existsSync as existsSync46 } from "fs";
|
|
12753
|
+
import { join as join53 } from "path";
|
|
12510
12754
|
|
|
12511
12755
|
// src/lib/migration-body-change-guard.ts
|
|
12512
12756
|
function checkMigrationBodyChangeMarkers(diffs) {
|
|
@@ -12572,7 +12816,7 @@ async function runMigrationBodyChangeCheck(argv) {
|
|
|
12572
12816
|
);
|
|
12573
12817
|
return;
|
|
12574
12818
|
}
|
|
12575
|
-
if (!
|
|
12819
|
+
if (!existsSync46(join53(root, MIGRATIONS_VERSIONS_DIR))) {
|
|
12576
12820
|
console.log(`\u2713 migration body-change guard: no ${MIGRATIONS_VERSIONS_DIR} in this repo.`);
|
|
12577
12821
|
return;
|
|
12578
12822
|
}
|
|
@@ -12674,7 +12918,7 @@ ${BOLD2}What to do${OFF2}
|
|
|
12674
12918
|
}
|
|
12675
12919
|
|
|
12676
12920
|
// src/scripts/check-orphan-ratchet.ts
|
|
12677
|
-
import { existsSync as
|
|
12921
|
+
import { existsSync as existsSync47 } from "fs";
|
|
12678
12922
|
import { basename as basename3 } from "path";
|
|
12679
12923
|
function reportOrphan(entry, manifest) {
|
|
12680
12924
|
console.error(` ${entry.path}`);
|
|
@@ -12697,7 +12941,7 @@ function reportOrphan(entry, manifest) {
|
|
|
12697
12941
|
}
|
|
12698
12942
|
}
|
|
12699
12943
|
async function runOrphanRatchetCheck(opts = {}) {
|
|
12700
|
-
if (opts.instanceDir !== void 0 && !
|
|
12944
|
+
if (opts.instanceDir !== void 0 && !existsSync47(opts.instanceDir)) {
|
|
12701
12945
|
console.error(
|
|
12702
12946
|
`\u2717 orphan-ratchet guard: --instance-dir ${opts.instanceDir} does not exist \u2014 cannot tell whether it carries unsanctioned files, and that is not the same as a clean pass.`
|
|
12703
12947
|
);
|
|
@@ -12739,8 +12983,8 @@ baseline was ${String(ratchet.baseline)}, now ${String(ratchet.count)}. See biff
|
|
|
12739
12983
|
}
|
|
12740
12984
|
|
|
12741
12985
|
// src/scripts/check-pipe-trap.ts
|
|
12742
|
-
import { readFileSync as
|
|
12743
|
-
import { join as
|
|
12986
|
+
import { readFileSync as readFileSync38, readdirSync as readdirSync22 } from "fs";
|
|
12987
|
+
import { join as join54, relative as relative9 } from "path";
|
|
12744
12988
|
|
|
12745
12989
|
// src/lib/pipe-trap-guard.ts
|
|
12746
12990
|
var STATUS_BEARING = [
|
|
@@ -12836,7 +13080,7 @@ function findPipeTraps(source) {
|
|
|
12836
13080
|
function shellFiles(root) {
|
|
12837
13081
|
const out = [];
|
|
12838
13082
|
for (const dir of ["scripts", ".githooks"]) {
|
|
12839
|
-
const full =
|
|
13083
|
+
const full = join54(root, dir);
|
|
12840
13084
|
let entries;
|
|
12841
13085
|
try {
|
|
12842
13086
|
entries = readdirSync22(full, { withFileTypes: true });
|
|
@@ -12846,7 +13090,7 @@ function shellFiles(root) {
|
|
|
12846
13090
|
for (const entry of entries) {
|
|
12847
13091
|
if (!entry.isFile()) continue;
|
|
12848
13092
|
if (dir === "scripts" && !entry.name.endsWith(".sh")) continue;
|
|
12849
|
-
out.push(
|
|
13093
|
+
out.push(join54(full, entry.name));
|
|
12850
13094
|
}
|
|
12851
13095
|
}
|
|
12852
13096
|
return out;
|
|
@@ -12862,7 +13106,7 @@ async function runPipeTrapCheck() {
|
|
|
12862
13106
|
process.exit(1);
|
|
12863
13107
|
}
|
|
12864
13108
|
const findings = files.flatMap(
|
|
12865
|
-
(file) => findPipeTraps(
|
|
13109
|
+
(file) => findPipeTraps(readFileSync38(file, "utf8")).map(
|
|
12866
13110
|
(t) => `${relative9(root, file)}:${t.line} ${t.text}
|
|
12867
13111
|
${t.reason}`
|
|
12868
13112
|
)
|
|
@@ -12879,8 +13123,8 @@ async function runPipeTrapCheck() {
|
|
|
12879
13123
|
}
|
|
12880
13124
|
|
|
12881
13125
|
// src/lib/plugin-allowlist-convention.ts
|
|
12882
|
-
import { readFileSync as
|
|
12883
|
-
import { join as
|
|
13126
|
+
import { readFileSync as readFileSync39 } from "fs";
|
|
13127
|
+
import { join as join55 } from "path";
|
|
12884
13128
|
var COMPUTE_MAIN_TF = "modules/cloud/aws/compute/main.tf";
|
|
12885
13129
|
var PLUGIN_TEMPLATE_MAIN_TF = "modules/plugins/_template/main.tf";
|
|
12886
13130
|
var ALLOWLIST_MAIN_TF = "modules/cloud/aws/plugin-allowlist/main.tf";
|
|
@@ -12891,7 +13135,7 @@ var PLUGIN = "<plugin>";
|
|
|
12891
13135
|
var ACCOUNT = "<account>";
|
|
12892
13136
|
function read(repoRoot, relative11) {
|
|
12893
13137
|
try {
|
|
12894
|
-
return
|
|
13138
|
+
return readFileSync39(join55(repoRoot, relative11), "utf8");
|
|
12895
13139
|
} catch {
|
|
12896
13140
|
throw new Error(`plugin-allowlist drift guard: cannot read ${relative11}`);
|
|
12897
13141
|
}
|
|
@@ -13012,32 +13256,32 @@ async function runPluginAllowlistConventionCheck() {
|
|
|
13012
13256
|
}
|
|
13013
13257
|
|
|
13014
13258
|
// src/scripts/check-plugin-collisions.ts
|
|
13015
|
-
import { existsSync as
|
|
13016
|
-
import { join as
|
|
13259
|
+
import { existsSync as existsSync49 } from "fs";
|
|
13260
|
+
import { join as join57 } from "path";
|
|
13017
13261
|
|
|
13018
13262
|
// src/lib/plugin-collision-guard.ts
|
|
13019
|
-
import { existsSync as
|
|
13020
|
-
import { join as
|
|
13263
|
+
import { existsSync as existsSync48, readdirSync as readdirSync23, statSync as statSync14 } from "fs";
|
|
13264
|
+
import { join as join56 } from "path";
|
|
13021
13265
|
var PYTEST_SPECIAL = /* @__PURE__ */ new Set(["conftest.py"]);
|
|
13022
13266
|
var IGNORED_DIRS = /* @__PURE__ */ new Set([".venv", "node_modules", "__pycache__", ".git", "dist", "build"]);
|
|
13023
13267
|
function subdirectories(dir) {
|
|
13024
|
-
if (!
|
|
13268
|
+
if (!existsSync48(dir)) return [];
|
|
13025
13269
|
return readdirSync23(dir).filter((entry) => {
|
|
13026
13270
|
if (IGNORED_DIRS.has(entry) || entry.startsWith(".")) return false;
|
|
13027
13271
|
try {
|
|
13028
|
-
return statSync14(
|
|
13272
|
+
return statSync14(join56(dir, entry)).isDirectory();
|
|
13029
13273
|
} catch {
|
|
13030
13274
|
return false;
|
|
13031
13275
|
}
|
|
13032
13276
|
});
|
|
13033
13277
|
}
|
|
13034
13278
|
function regularPackagesOf(pluginDir2) {
|
|
13035
|
-
return subdirectories(pluginDir2).filter((name) =>
|
|
13279
|
+
return subdirectories(pluginDir2).filter((name) => existsSync48(join56(pluginDir2, name, "__init__.py"))).sort();
|
|
13036
13280
|
}
|
|
13037
13281
|
function bareTestModulesOf(pluginDir2) {
|
|
13038
|
-
const testsDir =
|
|
13039
|
-
if (!
|
|
13040
|
-
if (
|
|
13282
|
+
const testsDir = join56(pluginDir2, "tests");
|
|
13283
|
+
if (!existsSync48(testsDir)) return [];
|
|
13284
|
+
if (existsSync48(join56(testsDir, "__init__.py"))) return [];
|
|
13041
13285
|
return readdirSync23(testsDir).filter((f) => f.endsWith(".py") && !PYTEST_SPECIAL.has(f)).sort();
|
|
13042
13286
|
}
|
|
13043
13287
|
function findCollisions(servicesDir, pluginDirs) {
|
|
@@ -13046,7 +13290,7 @@ function findCollisions(servicesDir, pluginDirs) {
|
|
|
13046
13290
|
const gather = (kind, namesOf) => {
|
|
13047
13291
|
const claims = /* @__PURE__ */ new Map();
|
|
13048
13292
|
for (const plugin of plugins) {
|
|
13049
|
-
for (const name of namesOf(
|
|
13293
|
+
for (const name of namesOf(join56(servicesDir, plugin))) {
|
|
13050
13294
|
claims.set(name, [...claims.get(name) ?? [], plugin]);
|
|
13051
13295
|
}
|
|
13052
13296
|
}
|
|
@@ -13084,8 +13328,8 @@ function formatCollisions(collisions) {
|
|
|
13084
13328
|
// src/scripts/check-plugin-collisions.ts
|
|
13085
13329
|
async function runPluginCollisionCheck() {
|
|
13086
13330
|
const root = (await execa("git", ["rev-parse", "--show-toplevel"])).stdout.trim();
|
|
13087
|
-
const servicesDir =
|
|
13088
|
-
if (!
|
|
13331
|
+
const servicesDir = join57(root, "services");
|
|
13332
|
+
if (!existsSync49(servicesDir)) {
|
|
13089
13333
|
console.log("\u2713 plugin collision guard: no services/ directory \u2014 nothing to compare");
|
|
13090
13334
|
return;
|
|
13091
13335
|
}
|
|
@@ -13102,8 +13346,8 @@ async function runPluginCollisionCheck() {
|
|
|
13102
13346
|
}
|
|
13103
13347
|
|
|
13104
13348
|
// src/lib/plugin-terraform-guard.ts
|
|
13105
|
-
import { existsSync as
|
|
13106
|
-
import { dirname as dirname10, join as
|
|
13349
|
+
import { existsSync as existsSync50, readFileSync as readFileSync40, readdirSync as readdirSync24 } from "fs";
|
|
13350
|
+
import { dirname as dirname10, join as join58, relative as relative10, sep as sep4 } from "path";
|
|
13107
13351
|
var SKIP_DIRS3 = /* @__PURE__ */ new Set(["node_modules", ".git", ".worktrees", "dist", ".venv", "__pycache__"]);
|
|
13108
13352
|
var PLUGIN_MANIFEST_FILE2 = "biffo.plugin.json";
|
|
13109
13353
|
function findPluginManifests(root) {
|
|
@@ -13118,9 +13362,9 @@ function findPluginManifests(root) {
|
|
|
13118
13362
|
for (const entry of entries) {
|
|
13119
13363
|
if (entry.isDirectory()) {
|
|
13120
13364
|
if (SKIP_DIRS3.has(entry.name)) continue;
|
|
13121
|
-
walk2(
|
|
13365
|
+
walk2(join58(dir, entry.name));
|
|
13122
13366
|
} else if (entry.isFile() && entry.name === PLUGIN_MANIFEST_FILE2) {
|
|
13123
|
-
found.push(relative10(root,
|
|
13367
|
+
found.push(relative10(root, join58(dir, entry.name)).split(sep4).join("/"));
|
|
13124
13368
|
}
|
|
13125
13369
|
}
|
|
13126
13370
|
};
|
|
@@ -13130,7 +13374,7 @@ function findPluginManifests(root) {
|
|
|
13130
13374
|
function readSubscriptions(absManifestPath) {
|
|
13131
13375
|
let parsed;
|
|
13132
13376
|
try {
|
|
13133
|
-
parsed = JSON.parse(
|
|
13377
|
+
parsed = JSON.parse(readFileSync40(absManifestPath, "utf8"));
|
|
13134
13378
|
} catch {
|
|
13135
13379
|
return null;
|
|
13136
13380
|
}
|
|
@@ -13145,14 +13389,14 @@ function readSubscriptions(absManifestPath) {
|
|
|
13145
13389
|
}
|
|
13146
13390
|
function checkPluginTerraform(root) {
|
|
13147
13391
|
const violations = [];
|
|
13148
|
-
const coreManifest =
|
|
13392
|
+
const coreManifest = existsSync50(join58(root, CORE_MANIFEST_FILE)) ? readCoreManifest(root) : null;
|
|
13149
13393
|
for (const manifest of findPluginManifests(root)) {
|
|
13150
13394
|
if (coreManifest && !isTemplateOwned(manifest, coreManifest)) continue;
|
|
13151
|
-
const absManifest =
|
|
13395
|
+
const absManifest = join58(root, manifest);
|
|
13152
13396
|
const subscriptions = readSubscriptions(absManifest);
|
|
13153
13397
|
if (subscriptions === null) continue;
|
|
13154
13398
|
const pluginDir2 = dirname10(absManifest);
|
|
13155
|
-
if (
|
|
13399
|
+
if (existsSync50(join58(pluginDir2, "terraform"))) continue;
|
|
13156
13400
|
const relPluginDir = relative10(root, pluginDir2).split(sep4).join("/");
|
|
13157
13401
|
violations.push({
|
|
13158
13402
|
manifest,
|
|
@@ -13183,12 +13427,12 @@ async function runPluginTerraformCheck() {
|
|
|
13183
13427
|
}
|
|
13184
13428
|
|
|
13185
13429
|
// src/scripts/check-plugin-tool-supply.ts
|
|
13186
|
-
import { existsSync as
|
|
13187
|
-
import { join as
|
|
13430
|
+
import { existsSync as existsSync52 } from "fs";
|
|
13431
|
+
import { join as join60 } from "path";
|
|
13188
13432
|
|
|
13189
13433
|
// src/lib/plugin-tool-supply-audit.ts
|
|
13190
|
-
import { existsSync as
|
|
13191
|
-
import { join as
|
|
13434
|
+
import { existsSync as existsSync51, readFileSync as readFileSync41, readdirSync as readdirSync25, statSync as statSync15 } from "fs";
|
|
13435
|
+
import { join as join59 } from "path";
|
|
13192
13436
|
|
|
13193
13437
|
// src/lib/openrouter-model-snapshot.ts
|
|
13194
13438
|
var OPENROUTER_MODEL_SNAPSHOT_FETCHED_AT = "2026-08-10T06:39:01Z";
|
|
@@ -13605,7 +13849,7 @@ function listDirs(root) {
|
|
|
13605
13849
|
}
|
|
13606
13850
|
return entries.filter((e) => {
|
|
13607
13851
|
try {
|
|
13608
|
-
return statSync15(
|
|
13852
|
+
return statSync15(join59(root, e)).isDirectory();
|
|
13609
13853
|
} catch {
|
|
13610
13854
|
return false;
|
|
13611
13855
|
}
|
|
@@ -13621,7 +13865,7 @@ function walkFiles2(root, accept, skipDir) {
|
|
|
13621
13865
|
return;
|
|
13622
13866
|
}
|
|
13623
13867
|
for (const entry of entries) {
|
|
13624
|
-
const p =
|
|
13868
|
+
const p = join59(dir, entry);
|
|
13625
13869
|
let st;
|
|
13626
13870
|
try {
|
|
13627
13871
|
st = statSync15(p);
|
|
@@ -13647,14 +13891,14 @@ function pluginPythonFiles(pluginDir2) {
|
|
|
13647
13891
|
);
|
|
13648
13892
|
}
|
|
13649
13893
|
function pluginTerraformFiles(pluginDir2) {
|
|
13650
|
-
const tfDir =
|
|
13894
|
+
const tfDir = join59(pluginDir2, "terraform");
|
|
13651
13895
|
let entries;
|
|
13652
13896
|
try {
|
|
13653
13897
|
entries = readdirSync25(tfDir);
|
|
13654
13898
|
} catch {
|
|
13655
13899
|
return [];
|
|
13656
13900
|
}
|
|
13657
|
-
return entries.filter((e) => e.endsWith(".tf")).map((e) =>
|
|
13901
|
+
return entries.filter((e) => e.endsWith(".tf")).map((e) => join59(tfDir, e)).sort();
|
|
13658
13902
|
}
|
|
13659
13903
|
function extractManifestTools(manifestText) {
|
|
13660
13904
|
let parsed;
|
|
@@ -13906,8 +14150,8 @@ function isSnapshotStale(fetchedAt, now) {
|
|
|
13906
14150
|
function normalizeModelId(id) {
|
|
13907
14151
|
return id.endsWith(":online") ? id.slice(0, -":online".length) : id;
|
|
13908
14152
|
}
|
|
13909
|
-
var CONFIG_PY_PATH =
|
|
13910
|
-
var ORCHESTRATION_SCHEMA_PATH =
|
|
14153
|
+
var CONFIG_PY_PATH = join59("services", "api", "src", "api", "config.py");
|
|
14154
|
+
var ORCHESTRATION_SCHEMA_PATH = join59(
|
|
13911
14155
|
"services",
|
|
13912
14156
|
"api",
|
|
13913
14157
|
"src",
|
|
@@ -13919,10 +14163,10 @@ function auditDeclaredModelIds(repoRoot, options = {}) {
|
|
|
13919
14163
|
const knownModelIds = options.knownModelIds ?? OPENROUTER_MODEL_IDS;
|
|
13920
14164
|
const snapshotFetchedAt = options.snapshotFetchedAt ?? OPENROUTER_MODEL_SNAPSHOT_FETCHED_AT;
|
|
13921
14165
|
const now = options.now ?? /* @__PURE__ */ new Date();
|
|
13922
|
-
const configPath =
|
|
13923
|
-
const orchestrationPath =
|
|
13924
|
-
const configMissing = !
|
|
13925
|
-
const orchestrationSchemaMissing = !
|
|
14166
|
+
const configPath = join59(repoRoot, CONFIG_PY_PATH);
|
|
14167
|
+
const orchestrationPath = join59(repoRoot, ORCHESTRATION_SCHEMA_PATH);
|
|
14168
|
+
const configMissing = !existsSync51(configPath);
|
|
14169
|
+
const orchestrationSchemaMissing = !existsSync51(orchestrationPath);
|
|
13926
14170
|
const knownSet = new Set(knownModelIds);
|
|
13927
14171
|
const snapshotEmpty = knownModelIds.length === 0;
|
|
13928
14172
|
const snapshotStale = isSnapshotStale(snapshotFetchedAt, now);
|
|
@@ -13940,13 +14184,13 @@ function auditDeclaredModelIds(repoRoot, options = {}) {
|
|
|
13940
14184
|
};
|
|
13941
14185
|
let settingsBlind = false;
|
|
13942
14186
|
if (!configMissing) {
|
|
13943
|
-
const settingsFields = extractSettingsModelFields(
|
|
14187
|
+
const settingsFields = extractSettingsModelFields(readFileSync41(configPath, "utf8"));
|
|
13944
14188
|
if (settingsFields.length === 0) settingsBlind = true;
|
|
13945
14189
|
for (const { field, value } of settingsFields) record(`${CONFIG_PY_PATH}#${field}`, value);
|
|
13946
14190
|
}
|
|
13947
14191
|
let curatedFieldsBlind = false;
|
|
13948
14192
|
if (!orchestrationSchemaMissing) {
|
|
13949
|
-
const curated = extractCuratedModelFields(
|
|
14193
|
+
const curated = extractCuratedModelFields(readFileSync41(orchestrationPath, "utf8"));
|
|
13950
14194
|
if (curated.rawFieldCount > 0 && curated.fields.every((f) => f.defaultValue === null && f.optionValues.length === 0)) {
|
|
13951
14195
|
curatedFieldsBlind = true;
|
|
13952
14196
|
}
|
|
@@ -13993,7 +14237,7 @@ function auditDeclaredModelIds(repoRoot, options = {}) {
|
|
|
13993
14237
|
function discoverPluginDirs(pluginsRoot) {
|
|
13994
14238
|
return listDirs(pluginsRoot).filter((name) => {
|
|
13995
14239
|
try {
|
|
13996
|
-
return statSync15(
|
|
14240
|
+
return statSync15(join59(pluginsRoot, name, "biffo.plugin.json")).isFile();
|
|
13997
14241
|
} catch {
|
|
13998
14242
|
return false;
|
|
13999
14243
|
}
|
|
@@ -14006,8 +14250,8 @@ function auditPluginToolSupply(pluginsRoot) {
|
|
|
14006
14250
|
let terraformBlind = false;
|
|
14007
14251
|
let totalDeclaredTools = 0;
|
|
14008
14252
|
for (const name of pluginNames) {
|
|
14009
|
-
const pluginDir2 =
|
|
14010
|
-
const manifestText =
|
|
14253
|
+
const pluginDir2 = join59(pluginsRoot, name);
|
|
14254
|
+
const manifestText = readFileSync41(join59(pluginDir2, "biffo.plugin.json"), "utf8");
|
|
14011
14255
|
const manifest = extractManifestTools(manifestText);
|
|
14012
14256
|
if (manifest.parseError) {
|
|
14013
14257
|
findings.push({
|
|
@@ -14025,13 +14269,13 @@ function auditPluginToolSupply(pluginsRoot) {
|
|
|
14025
14269
|
totalDeclaredTools += manifest.tools.length;
|
|
14026
14270
|
const pySources = pluginPythonFiles(pluginDir2).map((f) => ({
|
|
14027
14271
|
file: f,
|
|
14028
|
-
text:
|
|
14272
|
+
text: readFileSync41(f, "utf8")
|
|
14029
14273
|
}));
|
|
14030
14274
|
const resolver = buildSymbolResolver(pySources);
|
|
14031
14275
|
const registry = extractToolRegistryEntries(pySources, resolver);
|
|
14032
14276
|
if (registry.rawToolDefinitionCount > 0 && registry.entries.length === 0) registryBlind = true;
|
|
14033
14277
|
const tfFiles = pluginTerraformFiles(pluginDir2);
|
|
14034
|
-
const tfText = tfFiles.map((f) =>
|
|
14278
|
+
const tfText = tfFiles.map((f) => readFileSync41(f, "utf8")).join("\n");
|
|
14035
14279
|
const terraform = extractTerraformEnvKeys(tfText);
|
|
14036
14280
|
if (terraform.rawMarkerCount > 0 && terraform.resolvedBlockCount === 0) terraformBlind = true;
|
|
14037
14281
|
for (const toolName of manifest.tools) {
|
|
@@ -14105,7 +14349,7 @@ function auditPluginToolSupply(pluginsRoot) {
|
|
|
14105
14349
|
requiredEnvVars: envResult.envVars,
|
|
14106
14350
|
missingEnvVars: anyWired ? [] : envResult.envVars,
|
|
14107
14351
|
status: anyWired ? "ok" : "missing-env",
|
|
14108
|
-
detail: anyWired ? `${entry.predicate}() is satisfiable: at least one of ${JSON.stringify(envResult.envVars)} is wired in Terraform` : `${entry.predicate}() reads ${JSON.stringify(envResult.envVars)} \u2014 NONE of these are wired by any environment_variables block under ${
|
|
14352
|
+
detail: anyWired ? `${entry.predicate}() is satisfiable: at least one of ${JSON.stringify(envResult.envVars)} is wired in Terraform` : `${entry.predicate}() reads ${JSON.stringify(envResult.envVars)} \u2014 NONE of these are wired by any environment_variables block under ${join59(pluginDir2, "terraform")}, so this deployment can never supply it`
|
|
14109
14353
|
});
|
|
14110
14354
|
}
|
|
14111
14355
|
}
|
|
@@ -14138,8 +14382,8 @@ function auditPluginToolSupply(pluginsRoot) {
|
|
|
14138
14382
|
async function runPluginToolSupplyCheck() {
|
|
14139
14383
|
const root = (await execa("git", ["rev-parse", "--show-toplevel"])).stdout.trim();
|
|
14140
14384
|
let allOk = true;
|
|
14141
|
-
const pluginsRoot =
|
|
14142
|
-
if (!
|
|
14385
|
+
const pluginsRoot = join60(root, "services", "_plugins");
|
|
14386
|
+
if (!existsSync52(pluginsRoot)) {
|
|
14143
14387
|
console.log("\u2713 plugin tool-supply guard: no services/_plugins/ \u2014 nothing to audit");
|
|
14144
14388
|
} else {
|
|
14145
14389
|
const report = auditPluginToolSupply(pluginsRoot);
|
|
@@ -14169,8 +14413,8 @@ async function runPluginToolSupplyCheck() {
|
|
|
14169
14413
|
console.log(`\u2713 plugin tool-supply guard: ${report.summary}`);
|
|
14170
14414
|
}
|
|
14171
14415
|
}
|
|
14172
|
-
const servicesApiRoot =
|
|
14173
|
-
if (!
|
|
14416
|
+
const servicesApiRoot = join60(root, "services", "api");
|
|
14417
|
+
if (!existsSync52(servicesApiRoot)) {
|
|
14174
14418
|
console.log("\u2713 plugin model-id guard: no services/api/ \u2014 nothing to audit");
|
|
14175
14419
|
} else {
|
|
14176
14420
|
const modelReport = auditDeclaredModelIds(root);
|
|
@@ -14344,10 +14588,10 @@ async function runReleaseSubjectCheck(argv) {
|
|
|
14344
14588
|
}
|
|
14345
14589
|
|
|
14346
14590
|
// src/scripts/check-shared-file-reduction.ts
|
|
14347
|
-
import { readFileSync as
|
|
14591
|
+
import { readFileSync as readFileSync43 } from "fs";
|
|
14348
14592
|
|
|
14349
14593
|
// src/lib/shared-file-reduction-guard.ts
|
|
14350
|
-
import { readFileSync as
|
|
14594
|
+
import { readFileSync as readFileSync42 } from "fs";
|
|
14351
14595
|
var LEAF_TEST_CALLS = /* @__PURE__ */ new Set(["it", "test"]);
|
|
14352
14596
|
var SUITE_CALLS = /* @__PURE__ */ new Set(["describe", "suite"]);
|
|
14353
14597
|
var TEST_FILE_PATTERN = /\.(test|spec)\.(ts|tsx|mts|cts|js|jsx|mjs|cjs)$/;
|
|
@@ -14474,7 +14718,7 @@ function formatReductionReport(report) {
|
|
|
14474
14718
|
// src/scripts/check-shared-file-reduction.ts
|
|
14475
14719
|
function readStdin() {
|
|
14476
14720
|
try {
|
|
14477
|
-
return
|
|
14721
|
+
return readFileSync43(0, "utf8");
|
|
14478
14722
|
} catch {
|
|
14479
14723
|
return "";
|
|
14480
14724
|
}
|
|
@@ -14491,15 +14735,15 @@ function pairsFromTsv(tsv) {
|
|
|
14491
14735
|
const [target, existingPath, incomingPath] = fields;
|
|
14492
14736
|
pairs.push({
|
|
14493
14737
|
target,
|
|
14494
|
-
existing:
|
|
14495
|
-
incoming:
|
|
14738
|
+
existing: readFileSync43(existingPath, "utf8"),
|
|
14739
|
+
incoming: readFileSync43(incomingPath, "utf8")
|
|
14496
14740
|
});
|
|
14497
14741
|
}
|
|
14498
14742
|
return pairs;
|
|
14499
14743
|
}
|
|
14500
14744
|
function loadAccepted(manifestPath) {
|
|
14501
14745
|
if (!manifestPath) return {};
|
|
14502
|
-
const parsed = JSON.parse(
|
|
14746
|
+
const parsed = JSON.parse(readFileSync43(manifestPath, "utf8"));
|
|
14503
14747
|
return parsed.acceptedReductions ?? {};
|
|
14504
14748
|
}
|
|
14505
14749
|
async function runSharedFileReductionCheck(args) {
|
|
@@ -14507,13 +14751,13 @@ async function runSharedFileReductionCheck(args) {
|
|
|
14507
14751
|
let accepted;
|
|
14508
14752
|
try {
|
|
14509
14753
|
if (args.pairs) {
|
|
14510
|
-
pairs = pairsFromTsv(args.pairs === "-" ? readStdin() :
|
|
14754
|
+
pairs = pairsFromTsv(args.pairs === "-" ? readStdin() : readFileSync43(args.pairs, "utf8"));
|
|
14511
14755
|
} else if (args.target && args.existing && args.incoming) {
|
|
14512
14756
|
pairs = [
|
|
14513
14757
|
{
|
|
14514
14758
|
target: args.target,
|
|
14515
|
-
existing:
|
|
14516
|
-
incoming:
|
|
14759
|
+
existing: readFileSync43(args.existing, "utf8"),
|
|
14760
|
+
incoming: readFileSync43(args.incoming, "utf8")
|
|
14517
14761
|
}
|
|
14518
14762
|
];
|
|
14519
14763
|
} else {
|
|
@@ -14547,12 +14791,12 @@ async function runSharedFileReductionCheck(args) {
|
|
|
14547
14791
|
}
|
|
14548
14792
|
|
|
14549
14793
|
// src/scripts/check-skeleton-drift.ts
|
|
14550
|
-
import { existsSync as
|
|
14551
|
-
import { join as
|
|
14794
|
+
import { existsSync as existsSync53, readdirSync as readdirSync27 } from "fs";
|
|
14795
|
+
import { join as join62 } from "path";
|
|
14552
14796
|
|
|
14553
14797
|
// src/lib/skeleton-drift-guard.ts
|
|
14554
|
-
import { readFileSync as
|
|
14555
|
-
import { join as
|
|
14798
|
+
import { readFileSync as readFileSync44, readdirSync as readdirSync26, statSync as statSync16 } from "fs";
|
|
14799
|
+
import { join as join61 } from "path";
|
|
14556
14800
|
var isWorkflow = (rel) => rel.startsWith(".github/workflows/") && (rel.endsWith(".yml") || rel.endsWith(".yaml"));
|
|
14557
14801
|
var isRootLayout = (rel) => rel.endsWith("src/app/layout.tsx");
|
|
14558
14802
|
var uncommented = (contents) => contents.split("\n").filter((line) => !/^\s*(\/\/|\/\*|\*)/.test(line)).join("\n");
|
|
@@ -14616,7 +14860,7 @@ function walk(dir, base = dir) {
|
|
|
14616
14860
|
}
|
|
14617
14861
|
for (const entry of entries) {
|
|
14618
14862
|
if (entry === ".venv" || entry === "node_modules" || entry === ".git") continue;
|
|
14619
|
-
const abs =
|
|
14863
|
+
const abs = join61(dir, entry);
|
|
14620
14864
|
let isDir;
|
|
14621
14865
|
try {
|
|
14622
14866
|
isDir = statSync16(abs).isDirectory();
|
|
@@ -14638,7 +14882,7 @@ function auditSkeleton(skeletonRoot, name, rules = SKELETON_RULES) {
|
|
|
14638
14882
|
if (!rule.appliesTo(rel)) continue;
|
|
14639
14883
|
let contents;
|
|
14640
14884
|
try {
|
|
14641
|
-
contents =
|
|
14885
|
+
contents = readFileSync44(join61(skeletonRoot, rel), "utf8");
|
|
14642
14886
|
} catch {
|
|
14643
14887
|
continue;
|
|
14644
14888
|
}
|
|
@@ -14667,23 +14911,23 @@ function formatViolations2(violations) {
|
|
|
14667
14911
|
|
|
14668
14912
|
// src/scripts/check-skeleton-drift.ts
|
|
14669
14913
|
function discoverSkeletons(root) {
|
|
14670
|
-
const skeletonsDir =
|
|
14914
|
+
const skeletonsDir = join62(root, "_skeletons");
|
|
14671
14915
|
let entries;
|
|
14672
14916
|
try {
|
|
14673
14917
|
entries = readdirSync27(skeletonsDir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
|
|
14674
14918
|
} catch {
|
|
14675
14919
|
return [];
|
|
14676
14920
|
}
|
|
14677
|
-
return entries.filter((name) =>
|
|
14921
|
+
return entries.filter((name) => existsSync53(join62(skeletonsDir, name, ".github", "workflows", "ci.yml"))).sort();
|
|
14678
14922
|
}
|
|
14679
14923
|
async function runSkeletonDriftCheck() {
|
|
14680
14924
|
const root = (await execa("git", ["rev-parse", "--show-toplevel"])).stdout.trim();
|
|
14681
14925
|
const skeletons = discoverSkeletons(root);
|
|
14682
14926
|
let filesConsidered = 0;
|
|
14683
14927
|
for (const name of skeletons) {
|
|
14684
|
-
const skeletonRoot =
|
|
14928
|
+
const skeletonRoot = join62(root, "_skeletons", name);
|
|
14685
14929
|
filesConsidered += findWorkflowFiles(skeletonRoot).length;
|
|
14686
|
-
if (
|
|
14930
|
+
if (existsSync53(join62(skeletonRoot, "apps", "frontend", "src", "app", "layout.tsx"))) {
|
|
14687
14931
|
filesConsidered += 1;
|
|
14688
14932
|
}
|
|
14689
14933
|
}
|
|
@@ -14697,7 +14941,7 @@ async function runSkeletonDriftCheck() {
|
|
|
14697
14941
|
process.exit(1);
|
|
14698
14942
|
}
|
|
14699
14943
|
const violations = skeletons.flatMap(
|
|
14700
|
-
(name) => auditSkeleton(
|
|
14944
|
+
(name) => auditSkeleton(join62(root, "_skeletons", name), name)
|
|
14701
14945
|
);
|
|
14702
14946
|
if (violations.length > 0) {
|
|
14703
14947
|
console.error("\u2717 Skeleton-drift guard: drift found between this repo and its scaffolding\n");
|
|
@@ -14733,7 +14977,7 @@ async function runTerraformInputCheck() {
|
|
|
14733
14977
|
|
|
14734
14978
|
// src/commands/check.ts
|
|
14735
14979
|
var checkCommand = new Command24("check").description(
|
|
14736
|
-
"Repo guards (ownership, release subject, plugin terraform, plugin collisions, eventbridge-log-permissions, plugin-tool-supply, core-direct-paths, instance-adoption, orphan-ratchet, cognito-invite-template, lambda-output, pipe-trap, codeql-suppression, skeleton-drift, terraform-input, plugin-allowlist-convention, migration-body-change) run in CI and git hooks, plus out-of-band audits (branch protection, plugin-staleness)"
|
|
14980
|
+
"Repo guards (ownership, release subject, plugin terraform, plugin collisions, eventbridge-log-permissions, plugin-tool-supply, core-direct-paths, instance-adoption, orphan-ratchet, cognito-invite-template, lambda-output, pipe-trap, codeql-suppression, skeleton-drift, terraform-input, plugin-allowlist-convention, migration-body-change, distribution-inventory, distribution-remote-state) run in CI and git hooks, plus out-of-band audits (branch protection, plugin-staleness)"
|
|
14737
14981
|
);
|
|
14738
14982
|
checkCommand.command("ownership").description("Refuse changes to template-owned paths in an instance (#370)").argument("[base]", "Base branch to diff against; defaults to $GITHUB_BASE_REF").option("--staged <messageFile>", "Check staged changes instead of a branch diff (commit hook)").allowExcessArguments(true).action(async () => {
|
|
14739
14983
|
await runOwnershipCheck(rawArgsAfter("ownership"));
|
|
@@ -14796,6 +15040,16 @@ checkCommand.command("instance-adoption").description(
|
|
|
14796
15040
|
).action(async (opts) => {
|
|
14797
15041
|
await runInstanceAdoptionCheck(opts);
|
|
14798
15042
|
});
|
|
15043
|
+
checkCommand.command("distribution-inventory").description(
|
|
15044
|
+
"Refuse a distribution-inventory.json entry that is malformed, half-classified, or inconsistent with the channel it names (#1570) -- validateInventory's own schema rules, self-checkable and network-free. Contrast distribution-remote-state below, its sibling, which needs a real cross-repo token to check a NAMED REMOTE repo's actual content and runs only on a schedule."
|
|
15045
|
+
).action(async () => {
|
|
15046
|
+
await runDistributionInventoryCheck();
|
|
15047
|
+
});
|
|
15048
|
+
checkCommand.command("distribution-remote-state").description(
|
|
15049
|
+
"Refuse a distribution-inventory.json gapReason restating a REMOTE repo's content as current fact once that content has actually changed (#1816) -- fetches every declared remoteContentAssertion via `gh api` and compares against real content, generalising the one-off #1807 wording-regex guard to any entry that declares one. Needs a real cross-repo token (BIFFO_GITHUB_TOKEN in CI); exits 2 (cannot tell) rather than passing when a fetch fails."
|
|
15050
|
+
).action(async () => {
|
|
15051
|
+
await runDistributionRemoteStateCheck();
|
|
15052
|
+
});
|
|
14799
15053
|
checkCommand.command("orphan-ratchet").description(
|
|
14800
15054
|
"Refuse an instance-written file under a template-owned path with no sanctioned carve-out (#1026), reusing planCoreUpgrade's classify() and checkOrphanRatchet \u2014 previously reachable only from inside `biffo core upgrade`, so drift was discovered only ~90 core versions later by the upgrade it blocks (#1714). --instance-dir defaults to a SELF-CHECK (this repo's own tree, unlike instance-adoption above) that can only ever report zero \u2014 see the entrypoint's own doc comment for why that default exists anyway. Pass a real instance checkout for a check that can actually find something."
|
|
14801
15055
|
).option(
|
|
@@ -14878,8 +15132,8 @@ function rawArgsAfter(subcommand) {
|
|
|
14878
15132
|
}
|
|
14879
15133
|
|
|
14880
15134
|
// src/commands/doctor.ts
|
|
14881
|
-
import { existsSync as
|
|
14882
|
-
import { join as
|
|
15135
|
+
import { existsSync as existsSync54, readFileSync as readFileSync45 } from "fs";
|
|
15136
|
+
import { join as join63, resolve as resolve20 } from "path";
|
|
14883
15137
|
import chalk21 from "chalk";
|
|
14884
15138
|
import { Command as Command25 } from "commander";
|
|
14885
15139
|
|
|
@@ -15274,10 +15528,10 @@ async function runDoctorFixBranches(cwd, facts, deps = { git: new GitAdapter(),
|
|
|
15274
15528
|
return reapAllBareBranches(cwd, facts.branches, facts.worktrees, facts.currentBranch, deps);
|
|
15275
15529
|
}
|
|
15276
15530
|
function readLocalCoreVersion(cwd) {
|
|
15277
|
-
const path =
|
|
15278
|
-
if (!
|
|
15531
|
+
const path = join63(cwd, INSTANCE_CORE_FILE);
|
|
15532
|
+
if (!existsSync54(path)) return null;
|
|
15279
15533
|
try {
|
|
15280
|
-
return extractVersionField(
|
|
15534
|
+
return extractVersionField(readFileSync45(path, "utf8"));
|
|
15281
15535
|
} catch {
|
|
15282
15536
|
return null;
|
|
15283
15537
|
}
|
|
@@ -15297,10 +15551,10 @@ function extractVersionField(contents) {
|
|
|
15297
15551
|
return match?.[1] ?? null;
|
|
15298
15552
|
}
|
|
15299
15553
|
function readFossil(cwd) {
|
|
15300
|
-
const path =
|
|
15301
|
-
if (!
|
|
15554
|
+
const path = join63(cwd, CORE_VERSION_FILE);
|
|
15555
|
+
if (!existsSync54(path)) return null;
|
|
15302
15556
|
try {
|
|
15303
|
-
const value =
|
|
15557
|
+
const value = readFileSync45(path, "utf8").trim();
|
|
15304
15558
|
return value === "" ? null : value;
|
|
15305
15559
|
} catch {
|
|
15306
15560
|
return null;
|
|
@@ -15829,13 +16083,13 @@ import { fileURLToPath as fileURLToPath6 } from "url";
|
|
|
15829
16083
|
import { Command as Command27 } from "commander";
|
|
15830
16084
|
|
|
15831
16085
|
// src/lib/packaged-scripts.ts
|
|
15832
|
-
import { existsSync as
|
|
15833
|
-
import { dirname as dirname11, join as
|
|
16086
|
+
import { existsSync as existsSync55 } from "fs";
|
|
16087
|
+
import { dirname as dirname11, join as join64 } from "path";
|
|
15834
16088
|
function findPackagedScript(startDir, relativePath) {
|
|
15835
16089
|
let dir = startDir;
|
|
15836
16090
|
for (; ; ) {
|
|
15837
|
-
const candidate =
|
|
15838
|
-
if (
|
|
16091
|
+
const candidate = join64(dir, relativePath);
|
|
16092
|
+
if (existsSync55(candidate)) return candidate;
|
|
15839
16093
|
const parent = dirname11(dir);
|
|
15840
16094
|
if (parent === dir) return null;
|
|
15841
16095
|
dir = parent;
|
package/package.json
CHANGED
package/scripts/verify.sh
CHANGED
|
@@ -1413,6 +1413,16 @@ if [ -f scripts/biffo.sh ]; then
|
|
|
1413
1413
|
run_check plugin-tool-supply sh scripts/biffo.sh check plugin-tool-supply
|
|
1414
1414
|
run_check core-direct-paths sh scripts/biffo.sh check core-direct-paths
|
|
1415
1415
|
run_check orphan-ratchet sh scripts/biffo.sh check orphan-ratchet
|
|
1416
|
+
# #1714 second remediation: the INSTANCE-mode caller, distinct from the
|
|
1417
|
+
# self-check above. It clones a real template tree pinned to
|
|
1418
|
+
# biffo.core.json's own version and compares against it -- but this repo
|
|
1419
|
+
# never carries biffo.core.json (it is the template, not an instance), so
|
|
1420
|
+
# the script itself detects that and exits 0 as a genuine skip (see its own
|
|
1421
|
+
# doc comment) rather than attempting a comparison it has no tree for. Kept
|
|
1422
|
+
# unconditional, exactly like the line above, for the same reason: ci.yml's
|
|
1423
|
+
# exact command text must appear in `verify.sh --list`'s output or the
|
|
1424
|
+
# parity test (verify-parity.test.ts, #1773) reports it uncategorised.
|
|
1425
|
+
run_check orphan-ratchet-instance sh scripts/check-orphan-ratchet-instance.sh
|
|
1416
1426
|
run_check cognito-invite sh scripts/biffo.sh check cognito-invite-template
|
|
1417
1427
|
run_check lambda-output sh scripts/biffo.sh check lambda-output
|
|
1418
1428
|
run_check pipe-trap sh scripts/biffo.sh check pipe-trap
|
|
@@ -1421,6 +1431,7 @@ if [ -f scripts/biffo.sh ]; then
|
|
|
1421
1431
|
run_check claim-invocation sh scripts/biffo.sh check claim-invocation
|
|
1422
1432
|
run_check terraform-input sh scripts/biffo.sh check terraform-input
|
|
1423
1433
|
run_check plugin-allowlist sh scripts/biffo.sh check plugin-allowlist-convention
|
|
1434
|
+
run_check distribution-inventory sh scripts/biffo.sh check distribution-inventory
|
|
1424
1435
|
else
|
|
1425
1436
|
skip biffo-guards "no scripts/biffo.sh in this repo"
|
|
1426
1437
|
fi
|
|
@@ -1452,6 +1463,18 @@ fi
|
|
|
1452
1463
|
[ -f scripts/allocate-module-number.test.sh ] &&
|
|
1453
1464
|
run_check allocate-module-number sh scripts/allocate-module-number.test.sh
|
|
1454
1465
|
|
|
1466
|
+
# check-orphan-ratchet-instance.sh's own self-test (#1714, second
|
|
1467
|
+
# remediation): real throwaway git fixtures standing in for "the template at
|
|
1468
|
+
# a pinned version" and "a live instance", exercising the wrapper end to end
|
|
1469
|
+
# via THIS repo's own already-built local CLI through tsx (no mocking of
|
|
1470
|
+
# planCoreUpgrade/classify()). Needs cli/node_modules/.bin/tsx -- present
|
|
1471
|
+
# once `pnpm install` has run in this checkout, same as every other
|
|
1472
|
+
# JS-backed check above; the test itself fails with a clear "run pnpm
|
|
1473
|
+
# install" message rather than a cryptic tsx-not-found if it hasn't.
|
|
1474
|
+
# Measured here: ~1.6s.
|
|
1475
|
+
[ -f scripts/check-orphan-ratchet-instance.test.sh ] &&
|
|
1476
|
+
run_check orphan-ratchet-instance-selftest sh scripts/check-orphan-ratchet-instance.test.sh
|
|
1477
|
+
|
|
1455
1478
|
# The append-only corpus guard (#778). CI runs it in Release Guards, and it was
|
|
1456
1479
|
# invisible to the parity test until #897 widened the harvester -- it is neither
|
|
1457
1480
|
# `pnpm`, `uv`, `terraform` nor `sh scripts/`, so the guard whose property is
|