@biffo/cli 0.303.1 → 0.303.3

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 CHANGED
@@ -10920,6 +10920,246 @@ Already accepted? List it in docs/ADR/${ALLOWLIST_FILENAME} instead of leaving t
10920
10920
  console.log("\u2713 ADR numbering guard: OK");
10921
10921
  }
10922
10922
 
10923
+ // src/lib/api-gateway-integration-guard.ts
10924
+ import { readFileSync as readFileSync30, readdirSync as readdirSync16, statSync as statSync9 } from "fs";
10925
+ import { join as join41 } from "path";
10926
+ var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", ".terraform", ".worktrees", "dist"]);
10927
+ var MODULE_TYPE = "module";
10928
+ var INTEGRATION_TYPE = "aws_apigatewayv2_integration";
10929
+ function lineNumber(text, index) {
10930
+ let count = 1;
10931
+ for (let i = 0; i < index && i < text.length; i++) {
10932
+ if (text[i] === "\n") count += 1;
10933
+ }
10934
+ return count;
10935
+ }
10936
+ function balancedBraceSpan(text, openIndex) {
10937
+ let depth = 0;
10938
+ for (let i = openIndex; i < text.length; i++) {
10939
+ if (text[i] === "{") depth += 1;
10940
+ else if (text[i] === "}") {
10941
+ depth -= 1;
10942
+ if (depth === 0) return i;
10943
+ }
10944
+ }
10945
+ return null;
10946
+ }
10947
+ function findResourceBlocks(text, file, resourceType) {
10948
+ const blocks = [];
10949
+ const pattern = new RegExp(`resource\\s+"${resourceType}"\\s+"([\\w-]+)"\\s*\\{`, "g");
10950
+ let m;
10951
+ while ((m = pattern.exec(text)) !== null) {
10952
+ const name = m[1];
10953
+ const line = lineNumber(text, m.index);
10954
+ const openIndex = m.index + m[0].length - 1;
10955
+ const closeIndex = balancedBraceSpan(text, openIndex);
10956
+ if (closeIndex === null) {
10957
+ blocks.push({ file, line, type: resourceType, name, body: null });
10958
+ continue;
10959
+ }
10960
+ blocks.push({
10961
+ file,
10962
+ line,
10963
+ type: resourceType,
10964
+ name,
10965
+ body: text.slice(openIndex + 1, closeIndex)
10966
+ });
10967
+ }
10968
+ return blocks;
10969
+ }
10970
+ function findModuleBlocks(text, file) {
10971
+ const blocks = [];
10972
+ const pattern = /module\s+"([\w-]+)"\s*\{/g;
10973
+ let m;
10974
+ while ((m = pattern.exec(text)) !== null) {
10975
+ const name = m[1];
10976
+ const line = lineNumber(text, m.index);
10977
+ const openIndex = m.index + m[0].length - 1;
10978
+ const closeIndex = balancedBraceSpan(text, openIndex);
10979
+ if (closeIndex === null) {
10980
+ blocks.push({ file, line, type: MODULE_TYPE, name, body: null });
10981
+ continue;
10982
+ }
10983
+ blocks.push({
10984
+ file,
10985
+ line,
10986
+ type: MODULE_TYPE,
10987
+ name,
10988
+ body: text.slice(openIndex + 1, closeIndex)
10989
+ });
10990
+ }
10991
+ return blocks;
10992
+ }
10993
+ function countRawResourceDeclarations(text, resourceType) {
10994
+ const needle = resourceType === MODULE_TYPE ? 'module "' : `resource "${resourceType}"`;
10995
+ return text.split(needle).length - 1;
10996
+ }
10997
+ function walkTerraformFiles(root) {
10998
+ const out = [];
10999
+ const walk2 = (dir) => {
11000
+ let entries;
11001
+ try {
11002
+ entries = readdirSync16(dir);
11003
+ } catch {
11004
+ return;
11005
+ }
11006
+ for (const entry of entries) {
11007
+ const p = join41(dir, entry);
11008
+ let st;
11009
+ try {
11010
+ st = statSync9(p);
11011
+ } catch {
11012
+ continue;
11013
+ }
11014
+ if (st.isDirectory()) {
11015
+ if (SKIP_DIRS.has(entry)) continue;
11016
+ walk2(p);
11017
+ continue;
11018
+ }
11019
+ if (entry.endsWith(".tf")) out.push(p);
11020
+ }
11021
+ };
11022
+ walk2(root);
11023
+ return out.sort();
11024
+ }
11025
+ function isApiGatewaySource(body) {
11026
+ const m = /source\s*=\s*"([^"]+)"/.exec(body);
11027
+ if (!m) return false;
11028
+ return /(^|\/)modules\/cloud\/aws\/api-gateway\/?$/.test(m[1].trim());
11029
+ }
11030
+ function protectedFunctionModuleName(body) {
11031
+ const m = /lambda_function_arn\s*=\s*module\.([\w-]+)\.function_arn\b/.exec(body);
11032
+ return m ? m[1] : null;
11033
+ }
11034
+ function integrationUriOf(body) {
11035
+ const m = /integration_uri\s*=\s*(.+)/.exec(body);
11036
+ if (!m) return null;
11037
+ return m[1].trim().replace(/^"\$\{(.+)\}"$/, "$1").replace(/^"(.+)"$/, "$1").trim();
11038
+ }
11039
+ function auditApiGatewayIntegrations(root) {
11040
+ const files = walkTerraformFiles(root);
11041
+ if (files.length === 0) {
11042
+ throw new Error(
11043
+ `auditApiGatewayIntegrations: no .tf files found under ${root} \u2014 the guard cannot verify anything against input it cannot see. This is a hard failure, not "0 violations": fix the path, do not treat an empty scan as a clean pass.`
11044
+ );
11045
+ }
11046
+ const moduleBlocks = [];
11047
+ const integrationBlocks = [];
11048
+ let rawModuleCount = 0;
11049
+ let rawIntegrationCount = 0;
11050
+ for (const file of files) {
11051
+ const text = readFileSync30(file, "utf8");
11052
+ rawModuleCount += countRawResourceDeclarations(text, MODULE_TYPE);
11053
+ rawIntegrationCount += countRawResourceDeclarations(text, INTEGRATION_TYPE);
11054
+ moduleBlocks.push(...findModuleBlocks(text, file));
11055
+ integrationBlocks.push(...findResourceBlocks(text, file, INTEGRATION_TYPE));
11056
+ }
11057
+ const unterminatedBlocks = [...moduleBlocks, ...integrationBlocks].filter((b) => b.body === null);
11058
+ const moduleBlind = rawModuleCount > 0 && moduleBlocks.length === 0;
11059
+ const integrationBlind = rawIntegrationCount > 0 && integrationBlocks.length === 0;
11060
+ const protectedLambdas = [];
11061
+ for (const block of moduleBlocks) {
11062
+ if (block.body === null) continue;
11063
+ if (!isApiGatewaySource(block.body)) continue;
11064
+ if (block.file.split("/").includes("_skeletons")) continue;
11065
+ const functionModuleName = protectedFunctionModuleName(block.body);
11066
+ if (functionModuleName === null) continue;
11067
+ protectedLambdas.push({
11068
+ functionModuleName,
11069
+ gatewayModuleName: block.name,
11070
+ file: block.file,
11071
+ line: block.line
11072
+ });
11073
+ }
11074
+ const protectedNames = new Set(protectedLambdas.map((p) => p.functionModuleName));
11075
+ const integrations = [];
11076
+ for (const block of integrationBlocks) {
11077
+ if (block.body === null) continue;
11078
+ integrations.push({
11079
+ file: block.file,
11080
+ line: block.line,
11081
+ name: block.name,
11082
+ integrationUri: integrationUriOf(block.body)
11083
+ });
11084
+ }
11085
+ const violations = [];
11086
+ for (const integration of integrations) {
11087
+ if (integration.integrationUri === null) continue;
11088
+ const m = /^module\.([\w-]+)\.function_arn$/.exec(integration.integrationUri);
11089
+ if (!m) continue;
11090
+ const targetModuleName = m[1];
11091
+ if (!protectedNames.has(targetModuleName)) continue;
11092
+ const owner = protectedLambdas.find((p) => p.functionModuleName === targetModuleName);
11093
+ violations.push({
11094
+ file: integration.file,
11095
+ line: integration.line,
11096
+ integrationName: integration.name,
11097
+ targetModuleName,
11098
+ reason: `aws_apigatewayv2_integration.${integration.name} targets module.${targetModuleName}.function_arn (unqualified), but module.${owner?.gatewayModuleName ?? "<gateway>"} already grants that Lambda's "live" alias \u2014 not the unqualified function \u2014 invoke access (#1747). This integration will fail closed with a generic API Gateway 500 at runtime, invisible to terraform plan/apply on either side. Use module.${owner?.gatewayModuleName ?? "<gateway>"}.lambda_integration_uri instead (biffo-template#1900).`
11099
+ });
11100
+ }
11101
+ const ok = !moduleBlind && !integrationBlind && unterminatedBlocks.length === 0 && violations.length === 0;
11102
+ const summary = `scanned ${files.length} .tf file(s) under ${root}; ${moduleBlocks.length} module block(s) found (${protectedLambdas.length} api-gateway instance(s) fronting an alias-qualified Lambda), ${integrationBlocks.length} ${INTEGRATION_TYPE} block(s) found; ${violations.length} mis-qualified, ${unterminatedBlocks.length} unterminated.`;
11103
+ return {
11104
+ filesScanned: files.length,
11105
+ moduleBlocksFound: moduleBlocks.length,
11106
+ integrationBlocksFound: integrationBlocks.length,
11107
+ protectedLambdas,
11108
+ integrations,
11109
+ unterminatedBlocks,
11110
+ violations,
11111
+ moduleBlind,
11112
+ integrationBlind,
11113
+ ok,
11114
+ summary
11115
+ };
11116
+ }
11117
+
11118
+ // src/scripts/check-api-gateway-integration.ts
11119
+ async function runApiGatewayIntegrationCheck() {
11120
+ const root = (await execa("git", ["rev-parse", "--show-toplevel"])).stdout.trim();
11121
+ let report;
11122
+ try {
11123
+ report = auditApiGatewayIntegrations(root);
11124
+ } catch (err) {
11125
+ const message = err instanceof Error ? err.message : String(err);
11126
+ if (message.includes("no .tf files found under")) {
11127
+ console.log(
11128
+ `\xB7 API Gateway integration-qualifier guard: not applicable \u2014 ${message} This repo carries no Terraform at all, which is expected for a non-instance satellite. Skipping.`
11129
+ );
11130
+ return;
11131
+ }
11132
+ console.error("\u2717 API Gateway integration-qualifier guard: could not run\n");
11133
+ console.error(message);
11134
+ process.exit(1);
11135
+ }
11136
+ console.log(`scanned ${report.filesScanned} .tf file(s) under ${root}`);
11137
+ if (!report.ok) {
11138
+ console.error("\u2717 API Gateway integration-qualifier guard: mis-qualified integration(s) found\n");
11139
+ if (report.moduleBlind) {
11140
+ console.error(
11141
+ ' BLIND (module): raw source contains `module "..."` blocks but the block extractor found none \u2014 the extractor broke, this is not evidence there are no modules.'
11142
+ );
11143
+ }
11144
+ if (report.integrationBlind) {
11145
+ console.error(
11146
+ " BLIND (aws_apigatewayv2_integration): raw source contains this resource type but the block extractor found none \u2014 the extractor broke, this is not evidence there are no integrations."
11147
+ );
11148
+ }
11149
+ for (const b of report.unterminatedBlocks) {
11150
+ console.error(
11151
+ ` UNTERMINATED ${b.file}:${b.line} ${b.type} "${b.name}" never closes \u2014 cannot verify what it targets or grants.`
11152
+ );
11153
+ }
11154
+ for (const v of report.violations) {
11155
+ console.error(` MIS-QUALIFIED ${v.file}:${v.line} ${v.reason}`);
11156
+ }
11157
+ console.error("\nSee biffo-template#1900.");
11158
+ process.exit(1);
11159
+ }
11160
+ console.log(`\u2713 API Gateway integration-qualifier guard: ${report.summary}`);
11161
+ }
11162
+
10923
11163
  // src/scripts/check-branch-protection.ts
10924
11164
  import { Octokit as Octokit2 } from "@octokit/rest";
10925
11165
 
@@ -11180,18 +11420,18 @@ async function runBranchProtectionCheck(explicitRepo, options = {}) {
11180
11420
  }
11181
11421
 
11182
11422
  // src/lib/claim-invocation-parity.ts
11183
- import { existsSync as existsSync40, readFileSync as readFileSync30, readdirSync as readdirSync16 } from "fs";
11184
- import { join as join41 } from "path";
11423
+ import { existsSync as existsSync40, readFileSync as readFileSync31, readdirSync as readdirSync17 } from "fs";
11424
+ import { join as join42 } from "path";
11185
11425
  function distributedAgentsDocs(root) {
11186
11426
  const docs = [];
11187
- const own = join41(root, "AGENTS.md");
11188
- if (existsSync40(own)) docs.push({ path: "AGENTS.md", text: readFileSync30(own, "utf8") });
11189
- const skeletons = join41(root, "_skeletons");
11427
+ const own = join42(root, "AGENTS.md");
11428
+ if (existsSync40(own)) docs.push({ path: "AGENTS.md", text: readFileSync31(own, "utf8") });
11429
+ const skeletons = join42(root, "_skeletons");
11190
11430
  if (existsSync40(skeletons)) {
11191
- for (const name of readdirSync16(skeletons).sort()) {
11192
- const abs = join41(skeletons, name, "AGENTS.md");
11431
+ for (const name of readdirSync17(skeletons).sort()) {
11432
+ const abs = join42(skeletons, name, "AGENTS.md");
11193
11433
  if (!existsSync40(abs)) continue;
11194
- docs.push({ path: `_skeletons/${name}/AGENTS.md`, text: readFileSync30(abs, "utf8") });
11434
+ docs.push({ path: `_skeletons/${name}/AGENTS.md`, text: readFileSync31(abs, "utf8") });
11195
11435
  }
11196
11436
  }
11197
11437
  return docs;
@@ -11324,12 +11564,12 @@ async function runClaimInvocationCheck() {
11324
11564
 
11325
11565
  // src/scripts/check-codeql-suppression.ts
11326
11566
  import { existsSync as existsSync41 } from "fs";
11327
- import { join as join43, relative as relative8 } from "path";
11567
+ import { join as join44, relative as relative8 } from "path";
11328
11568
 
11329
11569
  // src/lib/codeql-suppression-guard.ts
11330
- import { readdirSync as readdirSync17, readFileSync as readFileSync31, statSync as statSync9 } from "fs";
11331
- import { join as join42 } from "path";
11332
- var SKIP_DIRS = /* @__PURE__ */ new Set([
11570
+ import { readdirSync as readdirSync18, readFileSync as readFileSync32, statSync as statSync10 } from "fs";
11571
+ import { join as join43 } from "path";
11572
+ var SKIP_DIRS2 = /* @__PURE__ */ new Set([
11333
11573
  ".git",
11334
11574
  ".worktrees",
11335
11575
  "node_modules",
@@ -11354,20 +11594,20 @@ function walkSourceFiles(root) {
11354
11594
  const walk2 = (dir) => {
11355
11595
  let entries;
11356
11596
  try {
11357
- entries = readdirSync17(dir);
11597
+ entries = readdirSync18(dir);
11358
11598
  } catch {
11359
11599
  return;
11360
11600
  }
11361
11601
  for (const entry of entries) {
11362
- const p = join42(dir, entry);
11602
+ const p = join43(dir, entry);
11363
11603
  let st;
11364
11604
  try {
11365
- st = statSync9(p);
11605
+ st = statSync10(p);
11366
11606
  } catch {
11367
11607
  continue;
11368
11608
  }
11369
11609
  if (st.isDirectory()) {
11370
- if (SKIP_DIRS.has(entry)) continue;
11610
+ if (SKIP_DIRS2.has(entry)) continue;
11371
11611
  walk2(p);
11372
11612
  continue;
11373
11613
  }
@@ -11385,7 +11625,7 @@ function countSourceFiles(root) {
11385
11625
  function sweepCodeqlSuppressionComments(root) {
11386
11626
  const hits = [];
11387
11627
  for (const path of walkSourceFiles(root)) {
11388
- const text = readFileSync31(path, "utf8");
11628
+ const text = readFileSync32(path, "utf8");
11389
11629
  for (const line of findCodeqlSuppressionComments(text)) {
11390
11630
  hits.push({ path, line, text: text.split("\n")[line - 1] ?? "" });
11391
11631
  }
@@ -11396,7 +11636,7 @@ function sweepCodeqlSuppressionComments(root) {
11396
11636
  // src/scripts/check-codeql-suppression.ts
11397
11637
  async function runCodeqlSuppressionCheck() {
11398
11638
  const root = (await execa("git", ["rev-parse", "--show-toplevel"])).stdout.trim();
11399
- const scanRoot = join43(root, "cli", "src");
11639
+ const scanRoot = join44(root, "cli", "src");
11400
11640
  if (!existsSync41(scanRoot)) {
11401
11641
  console.log(
11402
11642
  "\u2014 codeql-suppression guard: skipped \u2014 no cli/src in this repo, so there is no CLI source to scan."
@@ -11420,9 +11660,13 @@ async function runCodeqlSuppressionCheck() {
11420
11660
  console.log("\u2713 codeql-suppression guard: no dead `codeql[...]` suppression comment found");
11421
11661
  }
11422
11662
 
11663
+ // src/scripts/check-cognito-invite-template.ts
11664
+ import { existsSync as existsSync42 } from "fs";
11665
+ import { join as join46 } from "path";
11666
+
11423
11667
  // src/lib/cognito-invite-template-guard.ts
11424
- import { readdirSync as readdirSync18, readFileSync as readFileSync32, statSync as statSync10 } from "fs";
11425
- import { join as join44 } from "path";
11668
+ import { readdirSync as readdirSync19, readFileSync as readFileSync33, statSync as statSync11 } from "fs";
11669
+ import { join as join45 } from "path";
11426
11670
  var REQUIRED_INVITE_MEMBERS = ["email_subject", "email_message", "sms_message"];
11427
11671
  var REQUIRED_INVITE_PLACEHOLDERS = ["{username}", "{####}"];
11428
11672
  var PLACEHOLDER_MEMBERS = ["email_message", "sms_message"];
@@ -11499,18 +11743,18 @@ function findModuleTerraformFiles(repoRoot) {
11499
11743
  const walk2 = (dir, relative11) => {
11500
11744
  let entries;
11501
11745
  try {
11502
- entries = readdirSync18(dir);
11746
+ entries = readdirSync19(dir);
11503
11747
  } catch {
11504
11748
  return;
11505
11749
  }
11506
11750
  for (const entry of entries) {
11507
11751
  if (entry === "node_modules" || entry === ".git" || entry === ".worktrees" || entry === ".venv")
11508
11752
  continue;
11509
- const full = join44(dir, entry);
11753
+ const full = join45(dir, entry);
11510
11754
  const rel = `${relative11}/${entry}`;
11511
11755
  let isDir;
11512
11756
  try {
11513
- isDir = statSync10(full).isDirectory();
11757
+ isDir = statSync11(full).isDirectory();
11514
11758
  } catch {
11515
11759
  continue;
11516
11760
  }
@@ -11521,12 +11765,12 @@ function findModuleTerraformFiles(repoRoot) {
11521
11765
  }
11522
11766
  }
11523
11767
  };
11524
- walk2(join44(repoRoot, "modules"), "modules");
11768
+ walk2(join45(repoRoot, "modules"), "modules");
11525
11769
  return found.sort();
11526
11770
  }
11527
11771
  function checkCognitoInviteTemplates(repoRoot) {
11528
11772
  return findModuleTerraformFiles(repoRoot).flatMap(
11529
- (file) => checkInviteTemplateSource(file, readFileSync32(join44(repoRoot, file), "utf8"))
11773
+ (file) => checkInviteTemplateSource(file, readFileSync33(join45(repoRoot, file), "utf8"))
11530
11774
  );
11531
11775
  }
11532
11776
 
@@ -11536,6 +11780,12 @@ async function runCognitoInviteTemplateCheck() {
11536
11780
  const files = findModuleTerraformFiles(root);
11537
11781
  console.log(`audited ${files.length} .tf file(s) under modules/ under ${root}`);
11538
11782
  if (files.length === 0) {
11783
+ if (!existsSync42(join46(root, "modules"))) {
11784
+ console.log(
11785
+ `\xB7 Cognito invite template guard: not applicable \u2014 no modules/ directory under ${root} \u2014 this is not a template/instance tree (a satellite repo never carries the template-owned Terraform modules). Skipping.`
11786
+ );
11787
+ return;
11788
+ }
11539
11789
  console.error(
11540
11790
  "\u2717 Cognito invite template guard: found 0 .tf files under modules/ \u2014 this looks like a broken scan, not a clean repo. Refusing to report success over zero input."
11541
11791
  );
@@ -11554,11 +11804,11 @@ async function runCognitoInviteTemplateCheck() {
11554
11804
  }
11555
11805
 
11556
11806
  // src/scripts/check-core-direct-paths.ts
11557
- import { join as join46 } from "path";
11807
+ import { join as join48 } from "path";
11558
11808
 
11559
11809
  // src/lib/core-direct-paths-audit.ts
11560
- import { existsSync as existsSync42, readFileSync as readFileSync33, readdirSync as readdirSync19, statSync as statSync11 } from "fs";
11561
- import { join as join45 } from "path";
11810
+ import { existsSync as existsSync43, readFileSync as readFileSync34, readdirSync as readdirSync20, statSync as statSync12 } from "fs";
11811
+ import { join as join47 } from "path";
11562
11812
  var EXTERNAL_BASE_IDENTIFIERS = ["CORE_API_URL"];
11563
11813
  var API_ROUTE_PREFIX = "/api/v1";
11564
11814
  var TEST_FILE_SUFFIXES = [".test.ts", ".test.tsx", ".spec.ts", ".spec.tsx"];
@@ -11605,7 +11855,7 @@ function* templateLiterals(text) {
11605
11855
  i = j + 1;
11606
11856
  }
11607
11857
  }
11608
- function lineNumber(text, index) {
11858
+ function lineNumber2(text, index) {
11609
11859
  let count = 1;
11610
11860
  for (let i = 0; i < index && i < text.length; i++) {
11611
11861
  if (text[i] === "\n") count += 1;
@@ -11673,7 +11923,7 @@ function extractCoreDirectPaths(rawText, file, externalBases = EXTERNAL_BASE_IDE
11673
11923
  if (touchesConcatenation(text, start - 1, end + 1)) {
11674
11924
  found.push({
11675
11925
  file,
11676
- line: lineNumber(text, start),
11926
+ line: lineNumber2(text, start),
11677
11927
  raw,
11678
11928
  normalized: null,
11679
11929
  unresolvedReason: "built with string concatenation (+) \u2014 the extracted literal is a fragment, not the whole path",
@@ -11684,7 +11934,7 @@ function extractCoreDirectPaths(rawText, file, externalBases = EXTERNAL_BASE_IDE
11684
11934
  if (isNestedTemplateLiteralArtifact(rest)) {
11685
11935
  found.push({
11686
11936
  file,
11687
- line: lineNumber(text, start),
11937
+ line: lineNumber2(text, start),
11688
11938
  raw,
11689
11939
  normalized: null,
11690
11940
  unresolvedReason: "contains a template literal nested inside its own interpolation \u2014 this extractor's scanner would otherwise truncate at the inner backtick, producing a corrupted path rather than a real mismatch",
@@ -11695,7 +11945,7 @@ function extractCoreDirectPaths(rawText, file, externalBases = EXTERNAL_BASE_IDE
11695
11945
  const normalized = stripQueryString(resolveInterpolations(rest));
11696
11946
  found.push({
11697
11947
  file,
11698
- line: lineNumber(text, start),
11948
+ line: lineNumber2(text, start),
11699
11949
  raw,
11700
11950
  normalized,
11701
11951
  unresolvedReason: null,
@@ -11717,15 +11967,15 @@ function walkFiles(root, accept, skipDir) {
11717
11967
  const walk2 = (dir) => {
11718
11968
  let entries;
11719
11969
  try {
11720
- entries = readdirSync19(dir);
11970
+ entries = readdirSync20(dir);
11721
11971
  } catch {
11722
11972
  return;
11723
11973
  }
11724
11974
  for (const entry of entries) {
11725
- const p = join45(dir, entry);
11975
+ const p = join47(dir, entry);
11726
11976
  let st;
11727
11977
  try {
11728
- st = statSync11(p);
11978
+ st = statSync12(p);
11729
11979
  } catch {
11730
11980
  continue;
11731
11981
  }
@@ -11752,7 +12002,7 @@ function auditFrontendExtraction(frontendSrcDir, externalBases = EXTERNAL_BASE_I
11752
12002
  const extracted = [];
11753
12003
  let rawTotal = 0;
11754
12004
  for (const file of files) {
11755
- const text = readFileSync33(file, "utf8");
12005
+ const text = readFileSync34(file, "utf8");
11756
12006
  rawTotal += countRawExternalOccurrences(text, externalBases);
11757
12007
  extracted.push(...extractCoreDirectPaths(text, file, externalBases));
11758
12008
  }
@@ -11801,7 +12051,7 @@ function auditCoreRouteExtraction(apiSrcDir) {
11801
12051
  const prefixSet = /* @__PURE__ */ new Set();
11802
12052
  let rawApiRouterCount = 0;
11803
12053
  for (const file of files) {
11804
- const text = readFileSync33(file, "utf8");
12054
+ const text = readFileSync34(file, "utf8");
11805
12055
  const extraction = extractCoreRoutePrefixes(text);
11806
12056
  rawApiRouterCount += extraction.rawApiRouterCount;
11807
12057
  for (const p of extraction.prefixes) prefixSet.add(normalizePrefix(p));
@@ -11817,10 +12067,10 @@ function pathMatchesAnyCorePrefix(normalized, corePrefixes, apiRoutePrefix = API
11817
12067
  }
11818
12068
  function resolveSiblingCoreSrc(params) {
11819
12069
  const { estateDir, sibling } = params;
11820
- const configPath = join45(estateDir, sibling, "biffo.sibling.json");
12070
+ const configPath = join47(estateDir, sibling, "biffo.sibling.json");
11821
12071
  let raw;
11822
12072
  try {
11823
- raw = readFileSync33(configPath, "utf8");
12073
+ raw = readFileSync34(configPath, "utf8");
11824
12074
  } catch (err) {
11825
12075
  throw new Error(
11826
12076
  `cannot resolve ${sibling}'s core: ${configPath} does not exist or is unreadable (${err.message}) -- refusing to guess which core serves this sibling.`
@@ -11840,8 +12090,8 @@ function resolveSiblingCoreSrc(params) {
11840
12090
  `cannot resolve ${sibling}'s core: ${configPath} has no non-empty "core_project" field.`
11841
12091
  );
11842
12092
  }
11843
- const coreApiSrcDir = join45(estateDir, coreProject, "services", "api", "src");
11844
- if (!existsSync42(coreApiSrcDir)) {
12093
+ const coreApiSrcDir = join47(estateDir, coreProject, "services", "api", "src");
12094
+ if (!existsSync43(coreApiSrcDir)) {
11845
12095
  throw new Error(
11846
12096
  `cannot resolve ${sibling}'s core: biffo.sibling.json names core_project "${coreProject}", but ${coreApiSrcDir} does not exist -- the instance is missing from this estate checkout, not merely unmatched. Refusing to silently skip ${sibling} and shrink the audit's denominator.`
11847
12097
  );
@@ -11884,7 +12134,7 @@ function auditSiblingCoreDirectPaths(params) {
11884
12134
  async function runCoreDirectPathsCheck(opts = {}) {
11885
12135
  const root = (await execa("git", ["rev-parse", "--show-toplevel"])).stdout.trim();
11886
12136
  const sibling = opts.sibling ?? "sibling-template (self-check)";
11887
- const frontendSrcDir = opts.frontendSrc ?? join46(root, "_skeletons", "sibling-template", "apps", "frontend", "src");
12137
+ const frontendSrcDir = opts.frontendSrc ?? join48(root, "_skeletons", "sibling-template", "apps", "frontend", "src");
11888
12138
  let coreApiSrcDir;
11889
12139
  let coreProject = null;
11890
12140
  if (opts.coreSrc) {
@@ -11900,7 +12150,7 @@ async function runCoreDirectPathsCheck(opts = {}) {
11900
12150
  coreApiSrcDir = resolution.coreApiSrcDir;
11901
12151
  coreProject = resolution.coreProject;
11902
12152
  } else {
11903
- coreApiSrcDir = join46(root, "services", "api", "src");
12153
+ coreApiSrcDir = join48(root, "services", "api", "src");
11904
12154
  }
11905
12155
  const report = auditSiblingCoreDirectPaths({ sibling, frontendSrcDir, coreApiSrcDir });
11906
12156
  console.log(
@@ -11965,8 +12215,8 @@ async function runOwnershipCheck(argv) {
11965
12215
  const { stdout } = await execa("git", ["diff", "--cached", "--name-status"], { cwd: root });
11966
12216
  ({ changed: changedFiles, deleted: deletedFiles } = parseNameStatus(stdout));
11967
12217
  if (messageFile) {
11968
- const { readFileSync: readFileSync46, existsSync: existsSync57 } = await import("fs");
11969
- if (existsSync57(messageFile)) commitMessage = readFileSync46(messageFile, "utf8");
12218
+ const { readFileSync: readFileSync47, existsSync: existsSync59 } = await import("fs");
12219
+ if (existsSync59(messageFile)) commitMessage = readFileSync47(messageFile, "utf8");
11970
12220
  }
11971
12221
  } else {
11972
12222
  const base = process.env["GITHUB_BASE_REF"] ?? args[0];
@@ -12067,21 +12317,21 @@ ${BOLD}If the divergence is deliberate${OFF}
12067
12317
  }
12068
12318
 
12069
12319
  // src/scripts/check-distribution-inventory.ts
12070
- import { existsSync as existsSync44 } from "fs";
12071
- import { join as join48 } from "path";
12320
+ import { existsSync as existsSync45 } from "fs";
12321
+ import { join as join50 } from "path";
12072
12322
 
12073
12323
  // src/lib/distribution-inventory.ts
12074
- import { existsSync as existsSync43, readFileSync as readFileSync34 } from "fs";
12075
- import { join as join47 } from "path";
12324
+ import { existsSync as existsSync44, readFileSync as readFileSync35 } from "fs";
12325
+ import { join as join49 } from "path";
12076
12326
  var INVENTORY_FILENAME = "distribution-inventory.json";
12077
12327
  function loadDistributionInventory(root) {
12078
- const path = join47(root, INVENTORY_FILENAME);
12079
- if (!existsSync43(path)) {
12328
+ const path = join49(root, INVENTORY_FILENAME);
12329
+ if (!existsSync44(path)) {
12080
12330
  throw new Error(
12081
12331
  `${INVENTORY_FILENAME} not found at ${root} -- expected it beside core-manifest.json`
12082
12332
  );
12083
12333
  }
12084
- return JSON.parse(readFileSync34(path, "utf8"));
12334
+ return JSON.parse(readFileSync35(path, "utf8"));
12085
12335
  }
12086
12336
  function validateInventory(inventory) {
12087
12337
  const violations = [];
@@ -12241,7 +12491,7 @@ async function runDistributionInventoryCheck(root) {
12241
12491
  );
12242
12492
  return;
12243
12493
  }
12244
- if (!existsSync44(join48(repoRoot, INVENTORY_FILENAME))) {
12494
+ if (!existsSync45(join50(repoRoot, INVENTORY_FILENAME))) {
12245
12495
  console.log(
12246
12496
  `\u2713 distribution-inventory: skipped \u2014 no ${INVENTORY_FILENAME} at ${repoRoot}. This is a template-only registry; a checkout that legitimately has none has nothing for this guard to check.`
12247
12497
  );
@@ -12267,7 +12517,7 @@ async function runDistributionInventoryCheck(root) {
12267
12517
  }
12268
12518
 
12269
12519
  // src/scripts/check-distribution-remote-state.ts
12270
- import { join as join49 } from "path";
12520
+ import { join as join51 } from "path";
12271
12521
  async function ghExecCommand(file, args) {
12272
12522
  const result = await execa(file, args, { reject: false });
12273
12523
  return { stdout: String(result.stdout ?? ""), exitCode: result.exitCode ?? null };
@@ -12282,7 +12532,7 @@ async function runDistributionRemoteStateCheck(root) {
12282
12532
  }
12283
12533
  }
12284
12534
  console.log(
12285
- `distribution-remote-state: examined ${assertions.length} remote content assertion(s) declared across ${inventory.entries.length} inventory entries (${join49(repoRoot, "distribution-inventory.json")})`
12535
+ `distribution-remote-state: examined ${assertions.length} remote content assertion(s) declared across ${inventory.entries.length} inventory entries (${join51(repoRoot, "distribution-inventory.json")})`
12286
12536
  );
12287
12537
  if (assertions.length === 0) {
12288
12538
  console.log("\u2713 distribution-remote-state: nothing declared to check");
@@ -12327,9 +12577,9 @@ ${assertion.ref}`;
12327
12577
  }
12328
12578
 
12329
12579
  // src/lib/eventbridge-log-permission-guard.ts
12330
- import { readFileSync as readFileSync35, readdirSync as readdirSync20, statSync as statSync12 } from "fs";
12331
- import { join as join50 } from "path";
12332
- var SKIP_DIRS2 = /* @__PURE__ */ new Set(["node_modules", ".git", ".terraform", ".worktrees", "dist"]);
12580
+ import { readFileSync as readFileSync36, readdirSync as readdirSync21, statSync as statSync13 } from "fs";
12581
+ import { join as join52 } from "path";
12582
+ var SKIP_DIRS3 = /* @__PURE__ */ new Set(["node_modules", ".git", ".terraform", ".worktrees", "dist"]);
12333
12583
  var EVENT_TARGET_TYPE = "aws_cloudwatch_event_target";
12334
12584
  var LOG_RESOURCE_POLICY_TYPE = "aws_cloudwatch_log_resource_policy";
12335
12585
  var LOG_GROUP_ARN_REF = /aws_cloudwatch_log_group\.([\w-]+)(?:\[[^\]]*\])?\.arn/g;
@@ -12350,14 +12600,14 @@ function servicePrincipalsIn(body) {
12350
12600
  function grantsPrincipal(body, principal) {
12351
12601
  return servicePrincipalsIn(body).includes(principal);
12352
12602
  }
12353
- function lineNumber2(text, index) {
12603
+ function lineNumber3(text, index) {
12354
12604
  let count = 1;
12355
12605
  for (let i = 0; i < index && i < text.length; i++) {
12356
12606
  if (text[i] === "\n") count += 1;
12357
12607
  }
12358
12608
  return count;
12359
12609
  }
12360
- function balancedBraceSpan(text, openIndex) {
12610
+ function balancedBraceSpan2(text, openIndex) {
12361
12611
  let depth = 0;
12362
12612
  for (let i = openIndex; i < text.length; i++) {
12363
12613
  if (text[i] === "{") depth += 1;
@@ -12368,15 +12618,15 @@ function balancedBraceSpan(text, openIndex) {
12368
12618
  }
12369
12619
  return null;
12370
12620
  }
12371
- function findResourceBlocks(text, file, resourceType) {
12621
+ function findResourceBlocks2(text, file, resourceType) {
12372
12622
  const blocks = [];
12373
12623
  const pattern = new RegExp(`resource\\s+"${resourceType}"\\s+"([\\w-]+)"\\s*\\{`, "g");
12374
12624
  let m;
12375
12625
  while ((m = pattern.exec(text)) !== null) {
12376
12626
  const name = m[1];
12377
- const line = lineNumber2(text, m.index);
12627
+ const line = lineNumber3(text, m.index);
12378
12628
  const openIndex = m.index + m[0].length - 1;
12379
- const closeIndex = balancedBraceSpan(text, openIndex);
12629
+ const closeIndex = balancedBraceSpan2(text, openIndex);
12380
12630
  if (closeIndex === null) {
12381
12631
  blocks.push({ file, line, type: resourceType, name, body: null });
12382
12632
  continue;
@@ -12391,29 +12641,29 @@ function findResourceBlocks(text, file, resourceType) {
12391
12641
  }
12392
12642
  return blocks;
12393
12643
  }
12394
- function countRawResourceDeclarations(text, resourceType) {
12644
+ function countRawResourceDeclarations2(text, resourceType) {
12395
12645
  const needle = `resource "${resourceType}"`;
12396
12646
  return text.split(needle).length - 1;
12397
12647
  }
12398
- function walkTerraformFiles(root) {
12648
+ function walkTerraformFiles2(root) {
12399
12649
  const out = [];
12400
12650
  const walk2 = (dir) => {
12401
12651
  let entries;
12402
12652
  try {
12403
- entries = readdirSync20(dir);
12653
+ entries = readdirSync21(dir);
12404
12654
  } catch {
12405
12655
  return;
12406
12656
  }
12407
12657
  for (const entry of entries) {
12408
- const p = join50(dir, entry);
12658
+ const p = join52(dir, entry);
12409
12659
  let st;
12410
12660
  try {
12411
- st = statSync12(p);
12661
+ st = statSync13(p);
12412
12662
  } catch {
12413
12663
  continue;
12414
12664
  }
12415
12665
  if (st.isDirectory()) {
12416
- if (SKIP_DIRS2.has(entry)) continue;
12666
+ if (SKIP_DIRS3.has(entry)) continue;
12417
12667
  walk2(p);
12418
12668
  continue;
12419
12669
  }
@@ -12437,7 +12687,7 @@ function logGroupNamesReferencedIn(body) {
12437
12687
  return names;
12438
12688
  }
12439
12689
  function auditEventBridgeLogPermissions(root) {
12440
- const files = walkTerraformFiles(root);
12690
+ const files = walkTerraformFiles2(root);
12441
12691
  if (files.length === 0) {
12442
12692
  throw new Error(
12443
12693
  `auditEventBridgeLogPermissions: no .tf files found under ${root} \u2014 the guard cannot verify anything against input it cannot see. This is a hard failure, not "0 violations": fix the path, do not treat an empty scan as a clean pass.`
@@ -12448,11 +12698,11 @@ function auditEventBridgeLogPermissions(root) {
12448
12698
  let rawEventTargetCount = 0;
12449
12699
  let rawLogPolicyCount = 0;
12450
12700
  for (const file of files) {
12451
- const text = readFileSync35(file, "utf8");
12452
- rawEventTargetCount += countRawResourceDeclarations(text, EVENT_TARGET_TYPE);
12453
- rawLogPolicyCount += countRawResourceDeclarations(text, LOG_RESOURCE_POLICY_TYPE);
12454
- eventTargetBlocks.push(...findResourceBlocks(text, file, EVENT_TARGET_TYPE));
12455
- logResourcePolicyBlocks.push(...findResourceBlocks(text, file, LOG_RESOURCE_POLICY_TYPE));
12701
+ const text = readFileSync36(file, "utf8");
12702
+ rawEventTargetCount += countRawResourceDeclarations2(text, EVENT_TARGET_TYPE);
12703
+ rawLogPolicyCount += countRawResourceDeclarations2(text, LOG_RESOURCE_POLICY_TYPE);
12704
+ eventTargetBlocks.push(...findResourceBlocks2(text, file, EVENT_TARGET_TYPE));
12705
+ logResourcePolicyBlocks.push(...findResourceBlocks2(text, file, LOG_RESOURCE_POLICY_TYPE));
12456
12706
  }
12457
12707
  const unterminatedBlocks = [...eventTargetBlocks, ...logResourcePolicyBlocks].filter(
12458
12708
  (b) => b.body === null
@@ -12506,8 +12756,15 @@ async function runEventBridgeLogPermissionCheck() {
12506
12756
  try {
12507
12757
  report = auditEventBridgeLogPermissions(root);
12508
12758
  } catch (err) {
12759
+ const message = err instanceof Error ? err.message : String(err);
12760
+ if (message.includes("no .tf files found under")) {
12761
+ console.log(
12762
+ `\xB7 EventBridge log permission guard: not applicable \u2014 ${message} This repo carries no Terraform at all, which is expected for a non-instance satellite. Skipping.`
12763
+ );
12764
+ return;
12765
+ }
12509
12766
  console.error("\u2717 EventBridge log permission guard: could not run\n");
12510
- console.error(err instanceof Error ? err.message : String(err));
12767
+ console.error(message);
12511
12768
  process.exit(1);
12512
12769
  }
12513
12770
  console.log(`audited ${report.filesScanned} .tf file(s) under ${root}`);
@@ -12538,8 +12795,8 @@ async function runEventBridgeLogPermissionCheck() {
12538
12795
  }
12539
12796
 
12540
12797
  // src/scripts/check-instance-adoption.ts
12541
- import { existsSync as existsSync45 } from "fs";
12542
- import { join as join51 } from "path";
12798
+ import { existsSync as existsSync46 } from "fs";
12799
+ import { join as join53 } from "path";
12543
12800
  async function runInstanceAdoptionCheck(opts = {}) {
12544
12801
  if (!opts.instanceDir) {
12545
12802
  console.error(
@@ -12547,7 +12804,7 @@ async function runInstanceAdoptionCheck(opts = {}) {
12547
12804
  );
12548
12805
  process.exit(2);
12549
12806
  }
12550
- if (!existsSync45(opts.instanceDir)) {
12807
+ if (!existsSync46(opts.instanceDir)) {
12551
12808
  console.error(
12552
12809
  `\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.`
12553
12810
  );
@@ -12555,7 +12812,7 @@ async function runInstanceAdoptionCheck(opts = {}) {
12555
12812
  }
12556
12813
  const root = (await execa("git", ["rev-parse", "--show-toplevel"])).stdout.trim();
12557
12814
  const theirsDir = opts.theirsDir ?? root;
12558
- const instanceLabel = opts.instance ?? join51(opts.instanceDir).split("/").filter(Boolean).pop();
12815
+ const instanceLabel = opts.instance ?? join53(opts.instanceDir).split("/").filter(Boolean).pop();
12559
12816
  const report = checkInstanceAdoption(theirsDir, opts.instanceDir);
12560
12817
  console.log(
12561
12818
  `examined ${report.examinedInstances} instance (${instanceLabel}) against ${report.registeredPairs} registered pair(s), ${report.applicablePairs} applicable to this instance, against template tree ${theirsDir}`
@@ -12577,12 +12834,12 @@ async function runInstanceAdoptionCheck(opts = {}) {
12577
12834
  }
12578
12835
 
12579
12836
  // src/lib/lambda-output-guard.ts
12580
- import { readFileSync as readFileSync37 } from "fs";
12581
- import { join as join53 } from "path";
12837
+ import { readFileSync as readFileSync38 } from "fs";
12838
+ import { join as join55 } from "path";
12582
12839
 
12583
12840
  // src/lib/terraform-input-guard.ts
12584
- import { existsSync as existsSync46, readdirSync as readdirSync21, readFileSync as readFileSync36, statSync as statSync13 } from "fs";
12585
- import { join as join52 } from "path";
12841
+ import { existsSync as existsSync47, readdirSync as readdirSync22, readFileSync as readFileSync37, statSync as statSync14 } from "fs";
12842
+ import { join as join54 } from "path";
12586
12843
  var GUARDED_SUBCOMMANDS = [
12587
12844
  "init",
12588
12845
  "plan",
@@ -12596,18 +12853,18 @@ function stripComments2(source) {
12596
12853
  return source.split("\n").map((line) => line.replace(/(^|\s)#.*$/, "$1")).join("\n");
12597
12854
  }
12598
12855
  function vendoredPluginServiceDirs(repoRoot) {
12599
- const servicesDir = join52(repoRoot, "services");
12856
+ const servicesDir = join54(repoRoot, "services");
12600
12857
  const result = /* @__PURE__ */ new Set();
12601
12858
  let entries;
12602
12859
  try {
12603
- entries = readdirSync21(servicesDir);
12860
+ entries = readdirSync22(servicesDir);
12604
12861
  } catch {
12605
12862
  return result;
12606
12863
  }
12607
12864
  for (const entry of entries) {
12608
- const full = join52(servicesDir, entry);
12609
- if (!existsSync46(full) || !statSync13(full).isDirectory()) continue;
12610
- if (existsSync46(join52(full, "biffo.plugin.json"))) {
12865
+ const full = join54(servicesDir, entry);
12866
+ if (!existsSync47(full) || !statSync14(full).isDirectory()) continue;
12867
+ if (existsSync47(join54(full, "biffo.plugin.json"))) {
12611
12868
  result.add(entry);
12612
12869
  }
12613
12870
  }
@@ -12619,7 +12876,7 @@ function findWorkflowFiles(repoRoot) {
12619
12876
  const walk2 = (dir, relative11) => {
12620
12877
  let entries;
12621
12878
  try {
12622
- entries = readdirSync21(dir);
12879
+ entries = readdirSync22(dir);
12623
12880
  } catch {
12624
12881
  return;
12625
12882
  }
@@ -12629,11 +12886,11 @@ function findWorkflowFiles(repoRoot) {
12629
12886
  if (entry === ".github" && relative11.startsWith("services/") && vendoredPluginDirs.has(relative11.slice("services/".length))) {
12630
12887
  continue;
12631
12888
  }
12632
- const full = join52(dir, entry);
12889
+ const full = join54(dir, entry);
12633
12890
  const rel = relative11 ? `${relative11}/${entry}` : entry;
12634
12891
  let isDir;
12635
12892
  try {
12636
- isDir = statSync13(full).isDirectory();
12893
+ isDir = statSync14(full).isDirectory();
12637
12894
  } catch {
12638
12895
  continue;
12639
12896
  }
@@ -12679,7 +12936,7 @@ function checkWorkflowSource(file, rawSource) {
12679
12936
  }
12680
12937
  function checkTerraformInput(repoRoot) {
12681
12938
  return findWorkflowFiles(repoRoot).flatMap(
12682
- (file) => checkWorkflowSource(file, readFileSync36(join52(repoRoot, file), "utf8"))
12939
+ (file) => checkWorkflowSource(file, readFileSync37(join54(repoRoot, file), "utf8"))
12683
12940
  );
12684
12941
  }
12685
12942
 
@@ -12737,7 +12994,7 @@ function checkWorkflowSource2(file, rawSource) {
12737
12994
  }
12738
12995
  function checkLambdaOutput(repoRoot) {
12739
12996
  return findWorkflowFiles(repoRoot).flatMap(
12740
- (file) => checkWorkflowSource2(file, readFileSync37(join53(repoRoot, file), "utf8"))
12997
+ (file) => checkWorkflowSource2(file, readFileSync38(join55(repoRoot, file), "utf8"))
12741
12998
  );
12742
12999
  }
12743
13000
 
@@ -12765,8 +13022,8 @@ async function runLambdaOutputCheck() {
12765
13022
  }
12766
13023
 
12767
13024
  // src/scripts/check-migration-body-change.ts
12768
- import { existsSync as existsSync47 } from "fs";
12769
- import { join as join54 } from "path";
13025
+ import { existsSync as existsSync48 } from "fs";
13026
+ import { join as join56 } from "path";
12770
13027
 
12771
13028
  // src/lib/migration-body-change-guard.ts
12772
13029
  function checkMigrationBodyChangeMarkers(diffs) {
@@ -12832,7 +13089,7 @@ async function runMigrationBodyChangeCheck(argv) {
12832
13089
  );
12833
13090
  return;
12834
13091
  }
12835
- if (!existsSync47(join54(root, MIGRATIONS_VERSIONS_DIR))) {
13092
+ if (!existsSync48(join56(root, MIGRATIONS_VERSIONS_DIR))) {
12836
13093
  console.log(`\u2713 migration body-change guard: no ${MIGRATIONS_VERSIONS_DIR} in this repo.`);
12837
13094
  return;
12838
13095
  }
@@ -12934,8 +13191,8 @@ ${BOLD2}What to do${OFF2}
12934
13191
  }
12935
13192
 
12936
13193
  // src/scripts/check-orphan-ratchet.ts
12937
- import { existsSync as existsSync48 } from "fs";
12938
- import { basename as basename3 } from "path";
13194
+ import { existsSync as existsSync49 } from "fs";
13195
+ import { basename as basename3, join as join57 } from "path";
12939
13196
  function reportOrphan(entry, manifest) {
12940
13197
  console.error(` ${entry.path}`);
12941
13198
  const { templateOwnedMatch, nearestUserOwnedEntries } = explainOwnership(entry.path, manifest);
@@ -12957,7 +13214,7 @@ function reportOrphan(entry, manifest) {
12957
13214
  }
12958
13215
  }
12959
13216
  async function runOrphanRatchetCheck(opts = {}) {
12960
- if (opts.instanceDir !== void 0 && !existsSync48(opts.instanceDir)) {
13217
+ if (opts.instanceDir !== void 0 && !existsSync49(opts.instanceDir)) {
12961
13218
  console.error(
12962
13219
  `\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.`
12963
13220
  );
@@ -12974,6 +13231,12 @@ async function runOrphanRatchetCheck(opts = {}) {
12974
13231
  "self-check mode: --instance-dir was not given, so it defaulted to --theirs-dir (this repo's own root) alongside --base-dir \u2014 all three trees are the same, so this run can only ever find zero orphans by construction. See this script's doc comment. Pass a real instance tree with --instance-dir for a check that can actually find something. biffo-template#1714."
12975
13232
  );
12976
13233
  }
13234
+ if (!existsSync49(join57(theirsDir, CORE_MANIFEST_FILE))) {
13235
+ console.log(
13236
+ `orphan-ratchet guard (${label}): no ${CORE_MANIFEST_FILE} in ${theirsDir} \u2014 this is not a Biffo template/instance tree (a satellite repo never carries one), so there is no ownership manifest to classify paths against. Not applicable; skipping.`
13237
+ );
13238
+ return;
13239
+ }
12977
13240
  const manifest = readCoreManifest(theirsDir);
12978
13241
  const plan = await planCoreUpgrade({ baseDir, oursDir: instanceDir, theirsDir, manifest });
12979
13242
  const baseline = readOrphanBaseline(instanceDir);
@@ -12999,8 +13262,8 @@ baseline was ${String(ratchet.baseline)}, now ${String(ratchet.count)}. See biff
12999
13262
  }
13000
13263
 
13001
13264
  // src/scripts/check-pipe-trap.ts
13002
- import { readFileSync as readFileSync38, readdirSync as readdirSync22 } from "fs";
13003
- import { join as join55, relative as relative9 } from "path";
13265
+ import { readFileSync as readFileSync39, readdirSync as readdirSync23 } from "fs";
13266
+ import { join as join58, relative as relative9 } from "path";
13004
13267
 
13005
13268
  // src/lib/pipe-trap-guard.ts
13006
13269
  var STATUS_BEARING = [
@@ -13096,17 +13359,17 @@ function findPipeTraps(source) {
13096
13359
  function shellFiles(root) {
13097
13360
  const out = [];
13098
13361
  for (const dir of ["scripts", ".githooks"]) {
13099
- const full = join55(root, dir);
13362
+ const full = join58(root, dir);
13100
13363
  let entries;
13101
13364
  try {
13102
- entries = readdirSync22(full, { withFileTypes: true });
13365
+ entries = readdirSync23(full, { withFileTypes: true });
13103
13366
  } catch {
13104
13367
  continue;
13105
13368
  }
13106
13369
  for (const entry of entries) {
13107
13370
  if (!entry.isFile()) continue;
13108
13371
  if (dir === "scripts" && !entry.name.endsWith(".sh")) continue;
13109
- out.push(join55(full, entry.name));
13372
+ out.push(join58(full, entry.name));
13110
13373
  }
13111
13374
  }
13112
13375
  return out;
@@ -13122,7 +13385,7 @@ async function runPipeTrapCheck() {
13122
13385
  process.exit(1);
13123
13386
  }
13124
13387
  const findings = files.flatMap(
13125
- (file) => findPipeTraps(readFileSync38(file, "utf8")).map(
13388
+ (file) => findPipeTraps(readFileSync39(file, "utf8")).map(
13126
13389
  (t) => `${relative9(root, file)}:${t.line} ${t.text}
13127
13390
  ${t.reason}`
13128
13391
  )
@@ -13138,9 +13401,13 @@ async function runPipeTrapCheck() {
13138
13401
  console.log(`\u2713 Pipe-trap guard: no status-bearing command is piped away`);
13139
13402
  }
13140
13403
 
13404
+ // src/scripts/check-plugin-allowlist-convention.ts
13405
+ import { existsSync as existsSync50 } from "fs";
13406
+ import { join as join60 } from "path";
13407
+
13141
13408
  // src/lib/plugin-allowlist-convention.ts
13142
- import { readFileSync as readFileSync39 } from "fs";
13143
- import { join as join56 } from "path";
13409
+ import { readFileSync as readFileSync40 } from "fs";
13410
+ import { join as join59 } from "path";
13144
13411
  var COMPUTE_MAIN_TF = "modules/cloud/aws/compute/main.tf";
13145
13412
  var PLUGIN_TEMPLATE_MAIN_TF = "modules/plugins/_template/main.tf";
13146
13413
  var ALLOWLIST_MAIN_TF = "modules/cloud/aws/plugin-allowlist/main.tf";
@@ -13151,7 +13418,7 @@ var PLUGIN = "<plugin>";
13151
13418
  var ACCOUNT = "<account>";
13152
13419
  function read(repoRoot, relative11) {
13153
13420
  try {
13154
- return readFileSync39(join56(repoRoot, relative11), "utf8");
13421
+ return readFileSync40(join59(repoRoot, relative11), "utf8");
13155
13422
  } catch {
13156
13423
  throw new Error(`plugin-allowlist drift guard: cannot read ${relative11}`);
13157
13424
  }
@@ -13246,8 +13513,21 @@ Plugins would be rejected by require_service_principal (ADR-0009). Fix the glob,
13246
13513
  }
13247
13514
 
13248
13515
  // src/scripts/check-plugin-allowlist-convention.ts
13516
+ var REQUIRED_SOURCES = [
13517
+ COMPUTE_MAIN_TF,
13518
+ PLUGIN_TEMPLATE_MAIN_TF,
13519
+ ALLOWLIST_MAIN_TF,
13520
+ ALLOWLIST_VARIABLES_TF
13521
+ ];
13249
13522
  async function runPluginAllowlistConventionCheck() {
13250
13523
  const root = (await execa("git", ["rev-parse", "--show-toplevel"])).stdout.trim();
13524
+ const missing = REQUIRED_SOURCES.filter((relative11) => !existsSync50(join60(root, relative11)));
13525
+ if (missing.length === REQUIRED_SOURCES.length) {
13526
+ console.log(
13527
+ `\xB7 Plugin-allowlist convention guard: not applicable \u2014 ${missing.join(", ")} not found under ${root}. This is not a template/instance tree (a satellite repo never carries the plugin-allowlist Terraform modules). Skipping.`
13528
+ );
13529
+ return;
13530
+ }
13251
13531
  let violations;
13252
13532
  try {
13253
13533
  violations = checkAllowlistConvention(root);
@@ -13272,33 +13552,33 @@ async function runPluginAllowlistConventionCheck() {
13272
13552
  }
13273
13553
 
13274
13554
  // src/scripts/check-plugin-collisions.ts
13275
- import { existsSync as existsSync50 } from "fs";
13276
- import { join as join58 } from "path";
13555
+ import { existsSync as existsSync52 } from "fs";
13556
+ import { join as join62 } from "path";
13277
13557
 
13278
13558
  // src/lib/plugin-collision-guard.ts
13279
- import { existsSync as existsSync49, readdirSync as readdirSync23, statSync as statSync14 } from "fs";
13280
- import { join as join57 } from "path";
13559
+ import { existsSync as existsSync51, readdirSync as readdirSync24, statSync as statSync15 } from "fs";
13560
+ import { join as join61 } from "path";
13281
13561
  var PYTEST_SPECIAL = /* @__PURE__ */ new Set(["conftest.py"]);
13282
13562
  var IGNORED_DIRS = /* @__PURE__ */ new Set([".venv", "node_modules", "__pycache__", ".git", "dist", "build"]);
13283
13563
  function subdirectories(dir) {
13284
- if (!existsSync49(dir)) return [];
13285
- return readdirSync23(dir).filter((entry) => {
13564
+ if (!existsSync51(dir)) return [];
13565
+ return readdirSync24(dir).filter((entry) => {
13286
13566
  if (IGNORED_DIRS.has(entry) || entry.startsWith(".")) return false;
13287
13567
  try {
13288
- return statSync14(join57(dir, entry)).isDirectory();
13568
+ return statSync15(join61(dir, entry)).isDirectory();
13289
13569
  } catch {
13290
13570
  return false;
13291
13571
  }
13292
13572
  });
13293
13573
  }
13294
13574
  function regularPackagesOf(pluginDir2) {
13295
- return subdirectories(pluginDir2).filter((name) => existsSync49(join57(pluginDir2, name, "__init__.py"))).sort();
13575
+ return subdirectories(pluginDir2).filter((name) => existsSync51(join61(pluginDir2, name, "__init__.py"))).sort();
13296
13576
  }
13297
13577
  function bareTestModulesOf(pluginDir2) {
13298
- const testsDir = join57(pluginDir2, "tests");
13299
- if (!existsSync49(testsDir)) return [];
13300
- if (existsSync49(join57(testsDir, "__init__.py"))) return [];
13301
- return readdirSync23(testsDir).filter((f) => f.endsWith(".py") && !PYTEST_SPECIAL.has(f)).sort();
13578
+ const testsDir = join61(pluginDir2, "tests");
13579
+ if (!existsSync51(testsDir)) return [];
13580
+ if (existsSync51(join61(testsDir, "__init__.py"))) return [];
13581
+ return readdirSync24(testsDir).filter((f) => f.endsWith(".py") && !PYTEST_SPECIAL.has(f)).sort();
13302
13582
  }
13303
13583
  function findCollisions(servicesDir, pluginDirs) {
13304
13584
  const plugins = (pluginDirs ?? subdirectories(servicesDir)).filter((name) => !name.startsWith("_")).filter((name) => name !== "api").sort();
@@ -13306,7 +13586,7 @@ function findCollisions(servicesDir, pluginDirs) {
13306
13586
  const gather = (kind, namesOf) => {
13307
13587
  const claims = /* @__PURE__ */ new Map();
13308
13588
  for (const plugin of plugins) {
13309
- for (const name of namesOf(join57(servicesDir, plugin))) {
13589
+ for (const name of namesOf(join61(servicesDir, plugin))) {
13310
13590
  claims.set(name, [...claims.get(name) ?? [], plugin]);
13311
13591
  }
13312
13592
  }
@@ -13344,8 +13624,8 @@ function formatCollisions(collisions) {
13344
13624
  // src/scripts/check-plugin-collisions.ts
13345
13625
  async function runPluginCollisionCheck() {
13346
13626
  const root = (await execa("git", ["rev-parse", "--show-toplevel"])).stdout.trim();
13347
- const servicesDir = join58(root, "services");
13348
- if (!existsSync50(servicesDir)) {
13627
+ const servicesDir = join62(root, "services");
13628
+ if (!existsSync52(servicesDir)) {
13349
13629
  console.log("\u2713 plugin collision guard: no services/ directory \u2014 nothing to compare");
13350
13630
  return;
13351
13631
  }
@@ -13362,25 +13642,25 @@ async function runPluginCollisionCheck() {
13362
13642
  }
13363
13643
 
13364
13644
  // src/lib/plugin-terraform-guard.ts
13365
- import { existsSync as existsSync51, readFileSync as readFileSync40, readdirSync as readdirSync24 } from "fs";
13366
- import { dirname as dirname10, join as join59, relative as relative10, sep as sep4 } from "path";
13367
- var SKIP_DIRS3 = /* @__PURE__ */ new Set(["node_modules", ".git", ".worktrees", "dist", ".venv", "__pycache__"]);
13645
+ import { existsSync as existsSync53, readFileSync as readFileSync41, readdirSync as readdirSync25 } from "fs";
13646
+ import { dirname as dirname10, join as join63, relative as relative10, sep as sep4 } from "path";
13647
+ var SKIP_DIRS4 = /* @__PURE__ */ new Set(["node_modules", ".git", ".worktrees", "dist", ".venv", "__pycache__"]);
13368
13648
  var PLUGIN_MANIFEST_FILE2 = "biffo.plugin.json";
13369
13649
  function findPluginManifests(root) {
13370
13650
  const found = [];
13371
13651
  const walk2 = (dir) => {
13372
13652
  let entries;
13373
13653
  try {
13374
- entries = readdirSync24(dir, { withFileTypes: true });
13654
+ entries = readdirSync25(dir, { withFileTypes: true });
13375
13655
  } catch {
13376
13656
  return;
13377
13657
  }
13378
13658
  for (const entry of entries) {
13379
13659
  if (entry.isDirectory()) {
13380
- if (SKIP_DIRS3.has(entry.name)) continue;
13381
- walk2(join59(dir, entry.name));
13660
+ if (SKIP_DIRS4.has(entry.name)) continue;
13661
+ walk2(join63(dir, entry.name));
13382
13662
  } else if (entry.isFile() && entry.name === PLUGIN_MANIFEST_FILE2) {
13383
- found.push(relative10(root, join59(dir, entry.name)).split(sep4).join("/"));
13663
+ found.push(relative10(root, join63(dir, entry.name)).split(sep4).join("/"));
13384
13664
  }
13385
13665
  }
13386
13666
  };
@@ -13390,7 +13670,7 @@ function findPluginManifests(root) {
13390
13670
  function readSubscriptions(absManifestPath) {
13391
13671
  let parsed;
13392
13672
  try {
13393
- parsed = JSON.parse(readFileSync40(absManifestPath, "utf8"));
13673
+ parsed = JSON.parse(readFileSync41(absManifestPath, "utf8"));
13394
13674
  } catch {
13395
13675
  return null;
13396
13676
  }
@@ -13405,14 +13685,14 @@ function readSubscriptions(absManifestPath) {
13405
13685
  }
13406
13686
  function checkPluginTerraform(root) {
13407
13687
  const violations = [];
13408
- const coreManifest = existsSync51(join59(root, CORE_MANIFEST_FILE)) ? readCoreManifest(root) : null;
13688
+ const coreManifest = existsSync53(join63(root, CORE_MANIFEST_FILE)) ? readCoreManifest(root) : null;
13409
13689
  for (const manifest of findPluginManifests(root)) {
13410
13690
  if (coreManifest && !isTemplateOwned(manifest, coreManifest)) continue;
13411
- const absManifest = join59(root, manifest);
13691
+ const absManifest = join63(root, manifest);
13412
13692
  const subscriptions = readSubscriptions(absManifest);
13413
13693
  if (subscriptions === null) continue;
13414
13694
  const pluginDir2 = dirname10(absManifest);
13415
- if (existsSync51(join59(pluginDir2, "terraform"))) continue;
13695
+ if (existsSync53(join63(pluginDir2, "terraform"))) continue;
13416
13696
  const relPluginDir = relative10(root, pluginDir2).split(sep4).join("/");
13417
13697
  violations.push({
13418
13698
  manifest,
@@ -13443,12 +13723,12 @@ async function runPluginTerraformCheck() {
13443
13723
  }
13444
13724
 
13445
13725
  // src/scripts/check-plugin-tool-supply.ts
13446
- import { existsSync as existsSync53 } from "fs";
13447
- import { join as join61 } from "path";
13726
+ import { existsSync as existsSync55 } from "fs";
13727
+ import { join as join65 } from "path";
13448
13728
 
13449
13729
  // src/lib/plugin-tool-supply-audit.ts
13450
- import { existsSync as existsSync52, readFileSync as readFileSync41, readdirSync as readdirSync25, statSync as statSync15 } from "fs";
13451
- import { join as join60 } from "path";
13730
+ import { existsSync as existsSync54, readFileSync as readFileSync42, readdirSync as readdirSync26, statSync as statSync16 } from "fs";
13731
+ import { join as join64 } from "path";
13452
13732
 
13453
13733
  // src/lib/openrouter-model-snapshot.ts
13454
13734
  var OPENROUTER_MODEL_SNAPSHOT_FETCHED_AT = "2026-08-10T06:39:01Z";
@@ -13859,13 +14139,13 @@ var OPENROUTER_MODEL_IDS = [
13859
14139
  function listDirs(root) {
13860
14140
  let entries;
13861
14141
  try {
13862
- entries = readdirSync25(root);
14142
+ entries = readdirSync26(root);
13863
14143
  } catch {
13864
14144
  return [];
13865
14145
  }
13866
14146
  return entries.filter((e) => {
13867
14147
  try {
13868
- return statSync15(join60(root, e)).isDirectory();
14148
+ return statSync16(join64(root, e)).isDirectory();
13869
14149
  } catch {
13870
14150
  return false;
13871
14151
  }
@@ -13876,15 +14156,15 @@ function walkFiles2(root, accept, skipDir) {
13876
14156
  const walk2 = (dir) => {
13877
14157
  let entries;
13878
14158
  try {
13879
- entries = readdirSync25(dir);
14159
+ entries = readdirSync26(dir);
13880
14160
  } catch {
13881
14161
  return;
13882
14162
  }
13883
14163
  for (const entry of entries) {
13884
- const p = join60(dir, entry);
14164
+ const p = join64(dir, entry);
13885
14165
  let st;
13886
14166
  try {
13887
- st = statSync15(p);
14167
+ st = statSync16(p);
13888
14168
  } catch {
13889
14169
  continue;
13890
14170
  }
@@ -13907,14 +14187,14 @@ function pluginPythonFiles(pluginDir2) {
13907
14187
  );
13908
14188
  }
13909
14189
  function pluginTerraformFiles(pluginDir2) {
13910
- const tfDir = join60(pluginDir2, "terraform");
14190
+ const tfDir = join64(pluginDir2, "terraform");
13911
14191
  let entries;
13912
14192
  try {
13913
- entries = readdirSync25(tfDir);
14193
+ entries = readdirSync26(tfDir);
13914
14194
  } catch {
13915
14195
  return [];
13916
14196
  }
13917
- return entries.filter((e) => e.endsWith(".tf")).map((e) => join60(tfDir, e)).sort();
14197
+ return entries.filter((e) => e.endsWith(".tf")).map((e) => join64(tfDir, e)).sort();
13918
14198
  }
13919
14199
  function extractManifestTools(manifestText) {
13920
14200
  let parsed;
@@ -14166,8 +14446,8 @@ function isSnapshotStale(fetchedAt, now) {
14166
14446
  function normalizeModelId(id) {
14167
14447
  return id.endsWith(":online") ? id.slice(0, -":online".length) : id;
14168
14448
  }
14169
- var CONFIG_PY_PATH = join60("services", "api", "src", "api", "config.py");
14170
- var ORCHESTRATION_SCHEMA_PATH = join60(
14449
+ var CONFIG_PY_PATH = join64("services", "api", "src", "api", "config.py");
14450
+ var ORCHESTRATION_SCHEMA_PATH = join64(
14171
14451
  "services",
14172
14452
  "api",
14173
14453
  "src",
@@ -14179,10 +14459,10 @@ function auditDeclaredModelIds(repoRoot, options = {}) {
14179
14459
  const knownModelIds = options.knownModelIds ?? OPENROUTER_MODEL_IDS;
14180
14460
  const snapshotFetchedAt = options.snapshotFetchedAt ?? OPENROUTER_MODEL_SNAPSHOT_FETCHED_AT;
14181
14461
  const now = options.now ?? /* @__PURE__ */ new Date();
14182
- const configPath = join60(repoRoot, CONFIG_PY_PATH);
14183
- const orchestrationPath = join60(repoRoot, ORCHESTRATION_SCHEMA_PATH);
14184
- const configMissing = !existsSync52(configPath);
14185
- const orchestrationSchemaMissing = !existsSync52(orchestrationPath);
14462
+ const configPath = join64(repoRoot, CONFIG_PY_PATH);
14463
+ const orchestrationPath = join64(repoRoot, ORCHESTRATION_SCHEMA_PATH);
14464
+ const configMissing = !existsSync54(configPath);
14465
+ const orchestrationSchemaMissing = !existsSync54(orchestrationPath);
14186
14466
  const knownSet = new Set(knownModelIds);
14187
14467
  const snapshotEmpty = knownModelIds.length === 0;
14188
14468
  const snapshotStale = isSnapshotStale(snapshotFetchedAt, now);
@@ -14200,13 +14480,13 @@ function auditDeclaredModelIds(repoRoot, options = {}) {
14200
14480
  };
14201
14481
  let settingsBlind = false;
14202
14482
  if (!configMissing) {
14203
- const settingsFields = extractSettingsModelFields(readFileSync41(configPath, "utf8"));
14483
+ const settingsFields = extractSettingsModelFields(readFileSync42(configPath, "utf8"));
14204
14484
  if (settingsFields.length === 0) settingsBlind = true;
14205
14485
  for (const { field, value } of settingsFields) record(`${CONFIG_PY_PATH}#${field}`, value);
14206
14486
  }
14207
14487
  let curatedFieldsBlind = false;
14208
14488
  if (!orchestrationSchemaMissing) {
14209
- const curated = extractCuratedModelFields(readFileSync41(orchestrationPath, "utf8"));
14489
+ const curated = extractCuratedModelFields(readFileSync42(orchestrationPath, "utf8"));
14210
14490
  if (curated.rawFieldCount > 0 && curated.fields.every((f) => f.defaultValue === null && f.optionValues.length === 0)) {
14211
14491
  curatedFieldsBlind = true;
14212
14492
  }
@@ -14253,7 +14533,7 @@ function auditDeclaredModelIds(repoRoot, options = {}) {
14253
14533
  function discoverPluginDirs(pluginsRoot) {
14254
14534
  return listDirs(pluginsRoot).filter((name) => {
14255
14535
  try {
14256
- return statSync15(join60(pluginsRoot, name, "biffo.plugin.json")).isFile();
14536
+ return statSync16(join64(pluginsRoot, name, "biffo.plugin.json")).isFile();
14257
14537
  } catch {
14258
14538
  return false;
14259
14539
  }
@@ -14266,8 +14546,8 @@ function auditPluginToolSupply(pluginsRoot) {
14266
14546
  let terraformBlind = false;
14267
14547
  let totalDeclaredTools = 0;
14268
14548
  for (const name of pluginNames) {
14269
- const pluginDir2 = join60(pluginsRoot, name);
14270
- const manifestText = readFileSync41(join60(pluginDir2, "biffo.plugin.json"), "utf8");
14549
+ const pluginDir2 = join64(pluginsRoot, name);
14550
+ const manifestText = readFileSync42(join64(pluginDir2, "biffo.plugin.json"), "utf8");
14271
14551
  const manifest = extractManifestTools(manifestText);
14272
14552
  if (manifest.parseError) {
14273
14553
  findings.push({
@@ -14285,13 +14565,13 @@ function auditPluginToolSupply(pluginsRoot) {
14285
14565
  totalDeclaredTools += manifest.tools.length;
14286
14566
  const pySources = pluginPythonFiles(pluginDir2).map((f) => ({
14287
14567
  file: f,
14288
- text: readFileSync41(f, "utf8")
14568
+ text: readFileSync42(f, "utf8")
14289
14569
  }));
14290
14570
  const resolver = buildSymbolResolver(pySources);
14291
14571
  const registry = extractToolRegistryEntries(pySources, resolver);
14292
14572
  if (registry.rawToolDefinitionCount > 0 && registry.entries.length === 0) registryBlind = true;
14293
14573
  const tfFiles = pluginTerraformFiles(pluginDir2);
14294
- const tfText = tfFiles.map((f) => readFileSync41(f, "utf8")).join("\n");
14574
+ const tfText = tfFiles.map((f) => readFileSync42(f, "utf8")).join("\n");
14295
14575
  const terraform = extractTerraformEnvKeys(tfText);
14296
14576
  if (terraform.rawMarkerCount > 0 && terraform.resolvedBlockCount === 0) terraformBlind = true;
14297
14577
  for (const toolName of manifest.tools) {
@@ -14365,7 +14645,7 @@ function auditPluginToolSupply(pluginsRoot) {
14365
14645
  requiredEnvVars: envResult.envVars,
14366
14646
  missingEnvVars: anyWired ? [] : envResult.envVars,
14367
14647
  status: anyWired ? "ok" : "missing-env",
14368
- 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 ${join60(pluginDir2, "terraform")}, so this deployment can never supply it`
14648
+ 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 ${join64(pluginDir2, "terraform")}, so this deployment can never supply it`
14369
14649
  });
14370
14650
  }
14371
14651
  }
@@ -14398,8 +14678,8 @@ function auditPluginToolSupply(pluginsRoot) {
14398
14678
  async function runPluginToolSupplyCheck() {
14399
14679
  const root = (await execa("git", ["rev-parse", "--show-toplevel"])).stdout.trim();
14400
14680
  let allOk = true;
14401
- const pluginsRoot = join61(root, "services", "_plugins");
14402
- if (!existsSync53(pluginsRoot)) {
14681
+ const pluginsRoot = join65(root, "services", "_plugins");
14682
+ if (!existsSync55(pluginsRoot)) {
14403
14683
  console.log("\u2713 plugin tool-supply guard: no services/_plugins/ \u2014 nothing to audit");
14404
14684
  } else {
14405
14685
  const report = auditPluginToolSupply(pluginsRoot);
@@ -14429,8 +14709,8 @@ async function runPluginToolSupplyCheck() {
14429
14709
  console.log(`\u2713 plugin tool-supply guard: ${report.summary}`);
14430
14710
  }
14431
14711
  }
14432
- const servicesApiRoot = join61(root, "services", "api");
14433
- if (!existsSync53(servicesApiRoot)) {
14712
+ const servicesApiRoot = join65(root, "services", "api");
14713
+ if (!existsSync55(servicesApiRoot)) {
14434
14714
  console.log("\u2713 plugin model-id guard: no services/api/ \u2014 nothing to audit");
14435
14715
  } else {
14436
14716
  const modelReport = auditDeclaredModelIds(root);
@@ -14604,10 +14884,10 @@ async function runReleaseSubjectCheck(argv) {
14604
14884
  }
14605
14885
 
14606
14886
  // src/scripts/check-shared-file-reduction.ts
14607
- import { readFileSync as readFileSync43 } from "fs";
14887
+ import { readFileSync as readFileSync44 } from "fs";
14608
14888
 
14609
14889
  // src/lib/shared-file-reduction-guard.ts
14610
- import { readFileSync as readFileSync42 } from "fs";
14890
+ import { readFileSync as readFileSync43 } from "fs";
14611
14891
  var LEAF_TEST_CALLS = /* @__PURE__ */ new Set(["it", "test"]);
14612
14892
  var SUITE_CALLS = /* @__PURE__ */ new Set(["describe", "suite"]);
14613
14893
  var TEST_FILE_PATTERN = /\.(test|spec)\.(ts|tsx|mts|cts|js|jsx|mjs|cjs)$/;
@@ -14734,7 +15014,7 @@ function formatReductionReport(report) {
14734
15014
  // src/scripts/check-shared-file-reduction.ts
14735
15015
  function readStdin() {
14736
15016
  try {
14737
- return readFileSync43(0, "utf8");
15017
+ return readFileSync44(0, "utf8");
14738
15018
  } catch {
14739
15019
  return "";
14740
15020
  }
@@ -14751,15 +15031,15 @@ function pairsFromTsv(tsv) {
14751
15031
  const [target, existingPath, incomingPath] = fields;
14752
15032
  pairs.push({
14753
15033
  target,
14754
- existing: readFileSync43(existingPath, "utf8"),
14755
- incoming: readFileSync43(incomingPath, "utf8")
15034
+ existing: readFileSync44(existingPath, "utf8"),
15035
+ incoming: readFileSync44(incomingPath, "utf8")
14756
15036
  });
14757
15037
  }
14758
15038
  return pairs;
14759
15039
  }
14760
15040
  function loadAccepted(manifestPath) {
14761
15041
  if (!manifestPath) return {};
14762
- const parsed = JSON.parse(readFileSync43(manifestPath, "utf8"));
15042
+ const parsed = JSON.parse(readFileSync44(manifestPath, "utf8"));
14763
15043
  return parsed.acceptedReductions ?? {};
14764
15044
  }
14765
15045
  async function runSharedFileReductionCheck(args) {
@@ -14767,13 +15047,13 @@ async function runSharedFileReductionCheck(args) {
14767
15047
  let accepted;
14768
15048
  try {
14769
15049
  if (args.pairs) {
14770
- pairs = pairsFromTsv(args.pairs === "-" ? readStdin() : readFileSync43(args.pairs, "utf8"));
15050
+ pairs = pairsFromTsv(args.pairs === "-" ? readStdin() : readFileSync44(args.pairs, "utf8"));
14771
15051
  } else if (args.target && args.existing && args.incoming) {
14772
15052
  pairs = [
14773
15053
  {
14774
15054
  target: args.target,
14775
- existing: readFileSync43(args.existing, "utf8"),
14776
- incoming: readFileSync43(args.incoming, "utf8")
15055
+ existing: readFileSync44(args.existing, "utf8"),
15056
+ incoming: readFileSync44(args.incoming, "utf8")
14777
15057
  }
14778
15058
  ];
14779
15059
  } else {
@@ -14807,12 +15087,12 @@ async function runSharedFileReductionCheck(args) {
14807
15087
  }
14808
15088
 
14809
15089
  // src/scripts/check-skeleton-drift.ts
14810
- import { existsSync as existsSync54, readdirSync as readdirSync27 } from "fs";
14811
- import { join as join63 } from "path";
15090
+ import { existsSync as existsSync56, readdirSync as readdirSync28 } from "fs";
15091
+ import { join as join67 } from "path";
14812
15092
 
14813
15093
  // src/lib/skeleton-drift-guard.ts
14814
- import { readFileSync as readFileSync44, readdirSync as readdirSync26, statSync as statSync16 } from "fs";
14815
- import { join as join62 } from "path";
15094
+ import { readFileSync as readFileSync45, readdirSync as readdirSync27, statSync as statSync17 } from "fs";
15095
+ import { join as join66 } from "path";
14816
15096
  var isWorkflow = (rel) => rel.startsWith(".github/workflows/") && (rel.endsWith(".yml") || rel.endsWith(".yaml"));
14817
15097
  var isRootLayout = (rel) => rel.endsWith("src/app/layout.tsx");
14818
15098
  var uncommented = (contents) => contents.split("\n").filter((line) => !/^\s*(\/\/|\/\*|\*)/.test(line)).join("\n");
@@ -14870,16 +15150,16 @@ function walk(dir, base = dir) {
14870
15150
  const out = [];
14871
15151
  let entries;
14872
15152
  try {
14873
- entries = readdirSync26(dir);
15153
+ entries = readdirSync27(dir);
14874
15154
  } catch {
14875
15155
  return out;
14876
15156
  }
14877
15157
  for (const entry of entries) {
14878
15158
  if (entry === ".venv" || entry === "node_modules" || entry === ".git") continue;
14879
- const abs = join62(dir, entry);
15159
+ const abs = join66(dir, entry);
14880
15160
  let isDir;
14881
15161
  try {
14882
- isDir = statSync16(abs).isDirectory();
15162
+ isDir = statSync17(abs).isDirectory();
14883
15163
  } catch {
14884
15164
  continue;
14885
15165
  }
@@ -14898,7 +15178,7 @@ function auditSkeleton(skeletonRoot, name, rules = SKELETON_RULES) {
14898
15178
  if (!rule.appliesTo(rel)) continue;
14899
15179
  let contents;
14900
15180
  try {
14901
- contents = readFileSync44(join62(skeletonRoot, rel), "utf8");
15181
+ contents = readFileSync45(join66(skeletonRoot, rel), "utf8");
14902
15182
  } catch {
14903
15183
  continue;
14904
15184
  }
@@ -14927,23 +15207,23 @@ function formatViolations2(violations) {
14927
15207
 
14928
15208
  // src/scripts/check-skeleton-drift.ts
14929
15209
  function discoverSkeletons(root) {
14930
- const skeletonsDir = join63(root, "_skeletons");
15210
+ const skeletonsDir = join67(root, "_skeletons");
14931
15211
  let entries;
14932
15212
  try {
14933
- entries = readdirSync27(skeletonsDir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
15213
+ entries = readdirSync28(skeletonsDir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
14934
15214
  } catch {
14935
15215
  return [];
14936
15216
  }
14937
- return entries.filter((name) => existsSync54(join63(skeletonsDir, name, ".github", "workflows", "ci.yml"))).sort();
15217
+ return entries.filter((name) => existsSync56(join67(skeletonsDir, name, ".github", "workflows", "ci.yml"))).sort();
14938
15218
  }
14939
15219
  async function runSkeletonDriftCheck() {
14940
15220
  const root = (await execa("git", ["rev-parse", "--show-toplevel"])).stdout.trim();
14941
15221
  const skeletons = discoverSkeletons(root);
14942
15222
  let filesConsidered = 0;
14943
15223
  for (const name of skeletons) {
14944
- const skeletonRoot = join63(root, "_skeletons", name);
15224
+ const skeletonRoot = join67(root, "_skeletons", name);
14945
15225
  filesConsidered += findWorkflowFiles(skeletonRoot).length;
14946
- if (existsSync54(join63(skeletonRoot, "apps", "frontend", "src", "app", "layout.tsx"))) {
15226
+ if (existsSync56(join67(skeletonRoot, "apps", "frontend", "src", "app", "layout.tsx"))) {
14947
15227
  filesConsidered += 1;
14948
15228
  }
14949
15229
  }
@@ -14951,13 +15231,19 @@ async function runSkeletonDriftCheck() {
14951
15231
  `audited ${skeletons.length} skeleton(s) (${skeletons.join(", ") || "none"}), ${filesConsidered} file(s) considered, under ${root}/_skeletons`
14952
15232
  );
14953
15233
  if (skeletons.length === 0) {
15234
+ if (!existsSync56(join67(root, "_skeletons"))) {
15235
+ console.log(
15236
+ `\xB7 Skeleton-drift guard: no _skeletons/ directory under ${root} \u2014 this is not a repo that ships scaffolding (a satellite repo never carries one). Not applicable; skipping.`
15237
+ );
15238
+ return;
15239
+ }
14954
15240
  console.error(
14955
15241
  "\u2717 Skeleton-drift guard: found 0 repo skeletons under _skeletons/ \u2014 this looks like a broken scan, not a repo with no scaffolding. Refusing to report success over zero input."
14956
15242
  );
14957
15243
  process.exit(1);
14958
15244
  }
14959
15245
  const violations = skeletons.flatMap(
14960
- (name) => auditSkeleton(join63(root, "_skeletons", name), name)
15246
+ (name) => auditSkeleton(join67(root, "_skeletons", name), name)
14961
15247
  );
14962
15248
  if (violations.length > 0) {
14963
15249
  console.error("\u2717 Skeleton-drift guard: drift found between this repo and its scaffolding\n");
@@ -14993,7 +15279,7 @@ async function runTerraformInputCheck() {
14993
15279
 
14994
15280
  // src/commands/check.ts
14995
15281
  var checkCommand = new Command24("check").description(
14996
- "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)"
15282
+ "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, api-gateway-integration) run in CI and git hooks, plus out-of-band audits (branch protection, plugin-staleness)"
14997
15283
  );
14998
15284
  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 () => {
14999
15285
  await runOwnershipCheck(rawArgsAfter("ownership"));
@@ -15117,6 +15403,11 @@ checkCommand.command("terraform-input").description(
15117
15403
  ).action(async () => {
15118
15404
  await runTerraformInputCheck();
15119
15405
  });
15406
+ checkCommand.command("api-gateway-integration").description(
15407
+ "Refuse an env-owned aws_apigatewayv2_integration targeting a Lambda's raw, unqualified function_arn when modules/cloud/aws/api-gateway already fronts that same Lambda with an alias-qualified aws_lambda_permission (#1747) \u2014 terraform validate/plan pass on both sides in isolation, so this is the only signal before a real deploy 500s (biffo-template#1900; tabsii-platform took 11 public routes down this exact way)."
15408
+ ).action(async () => {
15409
+ await runApiGatewayIntegrationCheck();
15410
+ });
15120
15411
  checkCommand.command("shared-file-reduction").description(
15121
15412
  "Refuse a shared-files.json overwrite that would DELETE tests from the satellite it lands in (#1577) \u2014 filesIfPresent is a one-way cp and nothing ever compared the two copies. Invoked by scripts/shared-sync.sh at the moment of the write, not by per-PR CI: both sides of the comparison only exist together inside a sync run. Compares TEST TITLES in TS/JS test files and nothing else \u2014 see the guard module for what it does not catch."
15122
15413
  ).option("--pairs <tsv>", "TSV of target<TAB>existing<TAB>incoming lines, or - for stdin").option("--target <path>", "Single pair: path inside the satellite").option("--existing <path>", "Single pair: the satellite's current copy").option("--incoming <path>", "Single pair: the canonical copy that would replace it").option("--manifest <path>", "shared-files.json, read for acceptedReductions").action(
@@ -15148,8 +15439,8 @@ function rawArgsAfter(subcommand) {
15148
15439
  }
15149
15440
 
15150
15441
  // src/commands/doctor.ts
15151
- import { existsSync as existsSync55, readFileSync as readFileSync45 } from "fs";
15152
- import { join as join64, resolve as resolve20 } from "path";
15442
+ import { existsSync as existsSync57, readFileSync as readFileSync46 } from "fs";
15443
+ import { join as join68, resolve as resolve20 } from "path";
15153
15444
  import chalk21 from "chalk";
15154
15445
  import { Command as Command25 } from "commander";
15155
15446
 
@@ -15544,10 +15835,10 @@ async function runDoctorFixBranches(cwd, facts, deps = { git: new GitAdapter(),
15544
15835
  return reapAllBareBranches(cwd, facts.branches, facts.worktrees, facts.currentBranch, deps);
15545
15836
  }
15546
15837
  function readLocalCoreVersion(cwd) {
15547
- const path = join64(cwd, INSTANCE_CORE_FILE);
15548
- if (!existsSync55(path)) return null;
15838
+ const path = join68(cwd, INSTANCE_CORE_FILE);
15839
+ if (!existsSync57(path)) return null;
15549
15840
  try {
15550
- return extractVersionField(readFileSync45(path, "utf8"));
15841
+ return extractVersionField(readFileSync46(path, "utf8"));
15551
15842
  } catch {
15552
15843
  return null;
15553
15844
  }
@@ -15567,10 +15858,10 @@ function extractVersionField(contents) {
15567
15858
  return match?.[1] ?? null;
15568
15859
  }
15569
15860
  function readFossil(cwd) {
15570
- const path = join64(cwd, CORE_VERSION_FILE);
15571
- if (!existsSync55(path)) return null;
15861
+ const path = join68(cwd, CORE_VERSION_FILE);
15862
+ if (!existsSync57(path)) return null;
15572
15863
  try {
15573
- const value = readFileSync45(path, "utf8").trim();
15864
+ const value = readFileSync46(path, "utf8").trim();
15574
15865
  return value === "" ? null : value;
15575
15866
  } catch {
15576
15867
  return null;
@@ -16093,19 +16384,19 @@ function resolveGithubToken4() {
16093
16384
 
16094
16385
  // src/lib/packaged-script-command.ts
16095
16386
  import { spawnSync } from "child_process";
16096
- import { chmodSync as chmodSync2, statSync as statSync17 } from "fs";
16387
+ import { chmodSync as chmodSync2, statSync as statSync18 } from "fs";
16097
16388
  import { dirname as dirname12 } from "path";
16098
16389
  import { fileURLToPath as fileURLToPath6 } from "url";
16099
16390
  import { Command as Command27 } from "commander";
16100
16391
 
16101
16392
  // src/lib/packaged-scripts.ts
16102
- import { existsSync as existsSync56 } from "fs";
16103
- import { dirname as dirname11, join as join65 } from "path";
16393
+ import { existsSync as existsSync58 } from "fs";
16394
+ import { dirname as dirname11, join as join69 } from "path";
16104
16395
  function findPackagedScript(startDir, relativePath) {
16105
16396
  let dir = startDir;
16106
16397
  for (; ; ) {
16107
- const candidate = join65(dir, relativePath);
16108
- if (existsSync56(candidate)) return candidate;
16398
+ const candidate = join69(dir, relativePath);
16399
+ if (existsSync58(candidate)) return candidate;
16109
16400
  const parent = dirname11(dir);
16110
16401
  if (parent === dir) return null;
16111
16402
  dir = parent;
@@ -16119,7 +16410,7 @@ It ships with this package via cli/scripts/packaged-root-assets.mjs; if you are
16119
16410
  // src/lib/packaged-script-command.ts
16120
16411
  function ensureExecutable(script) {
16121
16412
  try {
16122
- const { mode } = statSync17(script);
16413
+ const { mode } = statSync18(script);
16123
16414
  if ((mode & 73) === 0) chmodSync2(script, mode | 493);
16124
16415
  } catch {
16125
16416
  }