@vantageos/backend-doctor 0.4.0 → 0.4.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.
@@ -1,4 +1,4 @@
1
1
  {
2
- "sourceDigest": "ed2e814dc6a69cc3ee7ddbb354981dfc9a8fb66e36390f50f4b498ba62ca0c26",
3
- "builtAt": "2026-10-11T00:31:27.173Z"
2
+ "sourceDigest": "bee564e05cd58fc5ee7437de55405225109b8011e71c05eadaa8d507207e505e",
3
+ "builtAt": "2026-10-11T16:49:36.400Z"
4
4
  }
@@ -3,7 +3,7 @@
3
3
  // Same kind as the six in security.ts: each reads the REAL backend SOURCE the
4
4
  // engine hands it in `run(tree)` — it does NOT consult the oracle CSV. Every
5
5
  // predicate below is derived from the ACTUAL convex source at
6
- // /root/coding/vantage-memory/convex, NEVER from the standard's §15 prose. Where
6
+ // vantage-peers convex/, NEVER from the standard's §15 prose. Where
7
7
  // the standard's illustration disagrees with the source, the SOURCE wins and the
8
8
  // detector records the stale line (see R-11's stale note).
9
9
  //
@@ -20,6 +20,7 @@ import { existsSync } from "node:fs";
20
20
  import { join } from "node:path";
21
21
  import { siteCount, withCount } from "../../violation-count.js";
22
22
  import { hasJustificationTokenInText } from "../oracle-axis2.js";
23
+ import { scanR11UnboundedReads } from "./isolation-read.js";
23
24
  /** The standard all four are pinned against (short hash a9d7877b). */
24
25
  export const SOURCE_CONTRACT_STANDARD_PIN = "analysis/vantagepeers/backend-standard/backend-standard.md @ a9d7877b4947ed8ae61d234433debb2182a2ba44 (elpiarthera/ElPi-Corp) §15";
25
26
  function emptyScan() {
@@ -179,6 +180,13 @@ export function scanR11(files) {
179
180
  }
180
181
  }
181
182
  }
183
+ // The unbounded-read arm (isolation-read.ts): a `.collect()` / `for await` read
184
+ // of a tenant table in a door a non-master caller is served, with no index range
185
+ // on the tenant key. R-55 hands this shape to R-11 by name.
186
+ const unboundedArm = scanR11UnboundedReads(files);
187
+ scan.conform.push(...unboundedArm.conform);
188
+ scan.violate.push(...unboundedArm.violate);
189
+ scan.cnj.push(...unboundedArm.cnj);
182
190
  return scan;
183
191
  }
184
192
  // ---------------------------------------------------------------------------
@@ -375,7 +383,7 @@ export function scanR26(files) {
375
383
  function srcDetector(ruleId, id, check, scanFn, emptyReason) {
376
384
  return { id, run: (tree) => decide(ruleId, id, check, scanFn(tree.files), emptyReason) };
377
385
  }
378
- export const R11 = srcDetector("R-11", "R-11 tenant isolation is an in-query index predicate, not a post-query filter", "a .filter on a scope key is paired with an adjacent .withIndex/.withSearchIndex .eq on that key", scanR11, "R-11: zero scope-key .filter sites matched in the scanned convex tree — cannot certify the isolation locus. NOT a clean bill.");
386
+ export const R11 = srcDetector("R-11", "R-11 tenant isolation is an in-query index predicate, not a post-query filter", "a .filter on a scope key is paired with an adjacent .withIndex/.withSearchIndex .eq on that key; an unbounded read of a tenant table in a door a non-master caller is served carries the tenant key in its index range", scanR11, "R-11: zero scope-key .filter sites and zero unbounded tenant-table reads matched in the scanned convex tree — cannot certify the isolation locus. NOT a clean bill.");
379
387
  export const R24 = srcDetector("R-24", "R-24 filter-selected bulk-write defaults to dry run", "a bulk-write mutation defaults dryRun true and gates apply behind an explicit flag + caller guard", scanR24, "R-24: zero bulk-write mutations matched in the scanned convex tree — cannot certify dry-run-by-default. NOT a clean bill.");
380
388
  export const R25 = srcDetector("R-25", "R-25 minted address / cross-boundary bundle org-bound at both ends", "a storage mint or bundle redemption is org-bound by an auth guard; a minted bundle URL expires", scanR25, "R-25: zero storage mint/redemption sites matched (kb*/okfBundle*) — cannot certify org-binding. NOT a clean bill.");
381
389
  export const R26 = srcDetector("R-26", "R-26 library-owned component declares its seam boundary", "each app.use(component) seam declares an authority tier + boundary key", scanR26, "R-26: no component mount seam (*.config.ts app.use) found — cannot certify the component seam contract. NOT a clean bill.");
@@ -91,10 +91,14 @@ export declare function summarizeValidator(fn: ts.FunctionLikeDeclaration, o: {
91
91
  key: string;
92
92
  depth: number;
93
93
  }): ValidatorSummary;
94
+ /** Exported for its tests: the definition-side judgement above. */
95
+ export declare function isFleetStandingPredicate(fn: ts.FunctionLikeDeclaration): boolean;
94
96
  /** Judge a function as a REFUSING PREDICATE (shape 2 above): parameter
95
97
  * `identityIdx` receives the caller's verified scope, `boundIdx` the value the
96
98
  * caller then writes. */
97
- export declare function judgePredicateFunction(fn: ts.FunctionLikeDeclaration, identityIdx: number, boundIdx: number): HelperVerdict;
99
+ export declare function judgePredicateFunction(fn: ts.FunctionLikeDeclaration, identityIdx: number, boundIdx: number,
100
+ /** names seen from `fn`'s file, to credit a fleet-standing predicate */
101
+ resolve?: CallResolver): HelperVerdict;
98
102
  /** What a mutation handler's own body declares and refuses before `beforeLine`. */
99
103
  export interface HandlerGuards {
100
104
  /** Top-level `const` locals: whether bound straight to an identity read, or
@@ -30,6 +30,7 @@
30
30
  // carrying return/throw/break/continue, a `finally`, an expression-bodied
31
31
  // arrow) makes the verdict COULD-NOT-JUDGE — never honoured.
32
32
  import * as ts from "typescript";
33
+ import { IDENTITY_PACKAGE } from "./identity-by-id.js";
33
34
  class Undecidable extends Error {
34
35
  }
35
36
  const isFunctionLike = (n) => ts.isFunctionDeclaration(n) ||
@@ -251,7 +252,7 @@ class Walker {
251
252
  const v = this.v;
252
253
  if (!v)
253
254
  return s;
254
- if (positiveMasterTest(cond, v.pure))
255
+ if (positiveMasterTest(cond, v.pure, v.resolve))
255
256
  return { ...s, guarded: true, master: true };
256
257
  if (this.posBinds(cond, s.tainted))
257
258
  return { ...s, guarded: true, bound: true };
@@ -297,7 +298,7 @@ class Walker {
297
298
  const c = unwrap(cond);
298
299
  if (ts.isPrefixUnaryExpression(c) &&
299
300
  c.operator === ts.SyntaxKind.ExclamationToken)
300
- return positiveMasterTest(c.operand, pure);
301
+ return positiveMasterTest(c.operand, pure, this.v?.resolve);
301
302
  if (ts.isBinaryExpression(c) &&
302
303
  c.operatorToken.kind === ts.SyntaxKind.BarBarToken)
303
304
  return this.negMaster(c.left, pure) || this.negMaster(c.right, pure);
@@ -321,7 +322,7 @@ class Walker {
321
322
  // An un-awaited async predicate is a Promise: always truthy.
322
323
  if (!awaitedCall(cond) && returnsPromise(d.fn))
323
324
  return false;
324
- return judgePredicateFunction(d.fn, i, j).verdict === "honoured";
325
+ return judgePredicateFunction(d.fn, i, j, d.resolve).verdict === "honoured";
325
326
  }
326
327
  /** A statement `[await] Q(…)` whose callee the tree defines: Q, walked as a
327
328
  * validator with the identity and target this call hands it, either binds
@@ -451,7 +452,7 @@ class Walker {
451
452
  : this.negBinds(cond, cs.tainted)),
452
453
  master: p.master ||
453
454
  (holds
454
- ? positiveMasterTest(cond, v.pure)
455
+ ? positiveMasterTest(cond, v.pure, v.resolve)
455
456
  : this.negMaster(cond, v.pure)),
456
457
  };
457
458
  if (alwaysThrows(a))
@@ -918,21 +919,282 @@ export function summarizeValidator(fn, o) {
918
919
  }
919
920
  /** `cond` is a POSITIVE test of `isMaster` on the identity (alone or as a
920
921
  * conjunct): the arm it guards runs for the fleet master only. */
921
- function positiveMasterTest(cond, tainted) {
922
+ function positiveMasterTest(cond, tainted, resolve) {
922
923
  const c = unwrap(cond);
923
924
  if (ts.isBinaryExpression(c) &&
924
925
  c.operatorToken.kind === ts.SyntaxKind.AmpersandAmpersandToken)
925
- return (positiveMasterTest(c.left, tainted) ||
926
- positiveMasterTest(c.right, tainted));
926
+ return (positiveMasterTest(c.left, tainted, resolve) ||
927
+ positiveMasterTest(c.right, tainted, resolve));
927
928
  if (ts.isBinaryExpression(c) &&
928
929
  (c.operatorToken.kind === ts.SyntaxKind.EqualsEqualsEqualsToken ||
929
930
  c.operatorToken.kind === ts.SyntaxKind.EqualsEqualsToken) &&
930
931
  c.right.kind === ts.SyntaxKind.TrueKeyword)
931
- return positiveMasterTest(c.left, tainted);
932
+ return positiveMasterTest(c.left, tainted, resolve);
933
+ if (resolve && fleetStandingCall(c, tainted, resolve))
934
+ return true;
932
935
  return (ts.isPropertyAccessExpression(c) &&
933
936
  c.name.text === "isMaster" &&
934
937
  dependsOnIdentity(c.expression, tainted));
935
938
  }
939
+ // ---------------------------------------------------------------------------
940
+ // THE FLEET STANDING. vantage-peers 6b44c8f (convex/lib/auth.ts) decides
941
+ // "fleet" on `hasFleetStanding(scope)`: a standing `withOrgScope` attaches from
942
+ // what @vantageos/cloud-identity resolved (a `fleet` principal, the
943
+ // `operator-reader` person the package judged an org admin, or the explicit
944
+ // `internal` marker). Read on its definition, that call is the same fleet
945
+ // carve-out as a positive `scope.isMaster` test — never on its name.
946
+ //
947
+ // `F(<identity>)` is credited when F resolves in the tree and its definition:
948
+ // - takes ONE plain parameter P, handed the identity itself (not a value built
949
+ // from it), and makes no call;
950
+ // - reads nothing off P but `P.standing` (directly or through a const);
951
+ // - can be truthy only through `<standing>.principal… === <literal>` (the kind
952
+ // of the principal the package produced, or its absence for `internal`);
953
+ // every other return is a falsy literal, and at least one return reads
954
+ // `<standing>.principal.kind`;
955
+ // - returns the principal's ABSENCE (`<standing>.principal === undefined` /
956
+ // `null`) only inside a branch selected by the grant `internal`: the
957
+ // `case "internal":` clause of a `switch (<standing>.grant)` that no other
958
+ // case falls into, or the then-arm of `if (<standing>.grant === "internal")`
959
+ // (the else-arm of `!==`). An absence returned under `default:`, another
960
+ // case, an unrelated branch, or a concise arrow body makes every grant that
961
+ // carries no principal read as fleet, so the predicate is credited nothing.
962
+ // The guard-clause form (`if (<standing>.grant !== "internal") return false;`
963
+ // then the absence test) is NOT credited either: only then/else arms and
964
+ // `case` clauses are read. Fail-closed by design; write the `case` or `if`
965
+ // form to be credited (Eta on #68, probe V6);
966
+ // - lives in a file that imports @vantageos/cloud-identity AND produces that
967
+ // principal: a `principal: { …, kind: x.kind }` built from a parameter whose
968
+ // declared type is a name imported from the package.
969
+ // A local boolean named the same (reads `isMaster`, returns `true`, reads only
970
+ // `standing.grant`), a body in a file with no package producer, or a name with
971
+ // no definition in the tree is credited nothing.
972
+ // ---------------------------------------------------------------------------
973
+ const standingMemo = new WeakMap();
974
+ function fleetStandingCall(c, tainted, resolve) {
975
+ if (!ts.isCallExpression(c) || !ts.isIdentifier(c.expression))
976
+ return false;
977
+ if (c.arguments.length !== 1)
978
+ return false;
979
+ const a = unwrap(c.arguments[0]);
980
+ if (!ts.isIdentifier(a) || !tainted.has(a.text))
981
+ return false;
982
+ const d = resolve(c.expression.text);
983
+ if (!d)
984
+ return false;
985
+ let v = standingMemo.get(d.fn);
986
+ if (v === undefined) {
987
+ v = isFleetStandingPredicate(d.fn);
988
+ standingMemo.set(d.fn, v);
989
+ }
990
+ return v;
991
+ }
992
+ const EQ_OPS = new Set([
993
+ ts.SyntaxKind.EqualsEqualsEqualsToken,
994
+ ts.SyntaxKind.EqualsEqualsToken,
995
+ ]);
996
+ const NE_OPS = new Set([
997
+ ts.SyntaxKind.ExclamationEqualsEqualsToken,
998
+ ts.SyntaxKind.ExclamationEqualsToken,
999
+ ]);
1000
+ const isLiteralValue = (e) => {
1001
+ const u = unwrap(e);
1002
+ return (ts.isStringLiteralLike(u) ||
1003
+ u.kind === ts.SyntaxKind.NullKeyword ||
1004
+ (ts.isIdentifier(u) && u.text === "undefined"));
1005
+ };
1006
+ /** Exported for its tests: the definition-side judgement above. */
1007
+ export function isFleetStandingPredicate(fn) {
1008
+ if (fn.parameters.length !== 1)
1009
+ return false;
1010
+ const pn = fn.parameters[0].name;
1011
+ if (!ts.isIdentifier(pn) || !fn.body)
1012
+ return false;
1013
+ const P = pn.text;
1014
+ const sf = fn.getSourceFile();
1015
+ if (!packageProducesPrincipal(sf))
1016
+ return false;
1017
+ // locals bound to `P.standing`
1018
+ const standingLocals = new Set();
1019
+ const isStanding = (e) => {
1020
+ const u = unwrap(e);
1021
+ if (ts.isIdentifier(u))
1022
+ return standingLocals.has(u.text);
1023
+ return (ts.isPropertyAccessExpression(u) &&
1024
+ u.name.text === "standing" &&
1025
+ ts.isIdentifier(unwrap(u.expression)) &&
1026
+ unwrap(u.expression).text === P);
1027
+ };
1028
+ let ok = true;
1029
+ walkOwn(fn.body, (x) => {
1030
+ if (ts.isVariableDeclaration(x)) {
1031
+ if (ts.isIdentifier(x.name) && x.initializer && isStanding(x.initializer))
1032
+ standingLocals.add(x.name.text);
1033
+ else
1034
+ ok = false;
1035
+ }
1036
+ if (ts.isCallExpression(x) || ts.isNewExpression(x))
1037
+ ok = false;
1038
+ if (ts.isBinaryExpression(x) &&
1039
+ x.operatorToken.kind >= ts.SyntaxKind.FirstAssignment &&
1040
+ x.operatorToken.kind <= ts.SyntaxKind.LastAssignment)
1041
+ ok = false;
1042
+ // nothing read off P but `standing`
1043
+ if (ts.isIdentifier(x) &&
1044
+ x.text === P &&
1045
+ x.parent !== fn.parameters[0] &&
1046
+ !(x.parent &&
1047
+ ts.isPropertyAccessExpression(x.parent) &&
1048
+ x.parent.expression === x &&
1049
+ x.parent.name.text === "standing"))
1050
+ ok = false;
1051
+ });
1052
+ if (!ok)
1053
+ return false;
1054
+ // `<standing>.principal` / `<standing>.principal.kind` against a literal
1055
+ const principalSide = (e) => {
1056
+ const u = unwrap(e);
1057
+ if (!ts.isPropertyAccessExpression(u))
1058
+ return undefined;
1059
+ if (u.name.text === "principal" && isStanding(u.expression))
1060
+ return "principal";
1061
+ const inner = unwrap(u.expression);
1062
+ if (u.name.text === "kind" &&
1063
+ ts.isPropertyAccessExpression(inner) &&
1064
+ inner.name.text === "principal" &&
1065
+ isStanding(inner.expression))
1066
+ return "kind";
1067
+ return undefined;
1068
+ };
1069
+ // `<standing>.grant` compared with the literal "internal"
1070
+ const isGrant = (g) => {
1071
+ const x = unwrap(g);
1072
+ return (ts.isPropertyAccessExpression(x) &&
1073
+ x.name.text === "grant" &&
1074
+ isStanding(x.expression));
1075
+ };
1076
+ const isInternal = (l) => {
1077
+ const x = unwrap(l);
1078
+ return ts.isStringLiteralLike(x) && x.text === "internal";
1079
+ };
1080
+ const isInternalTest = (e, ops) => {
1081
+ const u = unwrap(e);
1082
+ if (!ts.isBinaryExpression(u) || !ops.has(u.operatorToken.kind))
1083
+ return false;
1084
+ return ((isGrant(u.left) && isInternal(u.right)) ||
1085
+ (isGrant(u.right) && isInternal(u.left)));
1086
+ };
1087
+ const endsBranch = (sts) => {
1088
+ const last = sts[sts.length - 1];
1089
+ return (last !== undefined &&
1090
+ (ts.isReturnStatement(last) ||
1091
+ ts.isThrowStatement(last) ||
1092
+ ts.isBreakStatement(last)));
1093
+ };
1094
+ /** Is `at` inside a branch that only the grant `internal` selects? */
1095
+ const underInternal = (at) => {
1096
+ for (let c = at, p = at.parent; p && c !== fn.body;) {
1097
+ if (ts.isCaseClause(p)) {
1098
+ const sw = p.parent.parent;
1099
+ const ix = p.parent.clauses.indexOf(p);
1100
+ const prev = ix > 0 ? p.parent.clauses[ix - 1] : undefined;
1101
+ if (ts.isSwitchStatement(sw) &&
1102
+ isGrant(sw.expression) &&
1103
+ isInternal(p.expression) &&
1104
+ // no other case falls into this one
1105
+ (prev === undefined || endsBranch(prev.statements)))
1106
+ return true;
1107
+ }
1108
+ if (ts.isIfStatement(p)) {
1109
+ if (p.thenStatement === c && isInternalTest(p.expression, EQ_OPS))
1110
+ return true;
1111
+ if (p.elseStatement === c && isInternalTest(p.expression, NE_OPS))
1112
+ return true;
1113
+ }
1114
+ c = p;
1115
+ p = p.parent;
1116
+ }
1117
+ return false;
1118
+ };
1119
+ let readsKind = false;
1120
+ const truthyOk = (e, at) => {
1121
+ if (isFalsyLiteral(e))
1122
+ return true;
1123
+ const u = unwrap(e);
1124
+ if (!ts.isBinaryExpression(u) || !EQ_OPS.has(u.operatorToken.kind))
1125
+ return false;
1126
+ const side = (isLiteralValue(u.right) && principalSide(u.left)) ||
1127
+ (isLiteralValue(u.left) && principalSide(u.right));
1128
+ if (!side)
1129
+ return false;
1130
+ if (side === "kind")
1131
+ readsKind = true;
1132
+ // the principal's absence is fleet standing for `internal` alone
1133
+ if (side === "principal" && (at === undefined || !underInternal(at)))
1134
+ return false;
1135
+ return true;
1136
+ };
1137
+ if (!ts.isBlock(fn.body))
1138
+ return truthyOk(fn.body, undefined) && readsKind;
1139
+ let returnsAll = true;
1140
+ walkOwn(fn.body, (x) => {
1141
+ if (ts.isReturnStatement(x) && !truthyOk(x.expression, x))
1142
+ returnsAll = false;
1143
+ });
1144
+ return returnsAll && readsKind;
1145
+ }
1146
+ /** The file imports @vantageos/cloud-identity and builds a standing principal
1147
+ * from a value typed with one of the package's names:
1148
+ * `principal: { …, kind: <param>.kind }` where `<param>: <PackageName>`. */
1149
+ function packageProducesPrincipal(sf) {
1150
+ const fromPackage = new Set();
1151
+ for (const st of sf.statements) {
1152
+ if (!ts.isImportDeclaration(st) ||
1153
+ !ts.isStringLiteral(st.moduleSpecifier) ||
1154
+ st.moduleSpecifier.text !== IDENTITY_PACKAGE)
1155
+ continue;
1156
+ const b = st.importClause?.namedBindings;
1157
+ if (b && ts.isNamedImports(b))
1158
+ for (const e of b.elements)
1159
+ fromPackage.add(e.name.text);
1160
+ }
1161
+ if (fromPackage.size === 0)
1162
+ return false;
1163
+ let found = false;
1164
+ const visit = (x) => {
1165
+ if (found)
1166
+ return;
1167
+ if (ts.isPropertyAssignment(x) &&
1168
+ ts.isIdentifier(x.name) &&
1169
+ x.name.text === "kind" &&
1170
+ x.parent?.parent &&
1171
+ ts.isPropertyAssignment(x.parent.parent) &&
1172
+ ts.isIdentifier(x.parent.parent.name) &&
1173
+ x.parent.parent.name.text === "principal") {
1174
+ const v = unwrap(x.initializer);
1175
+ const root = ts.isPropertyAccessExpression(v) && v.name.text === "kind"
1176
+ ? unwrap(v.expression)
1177
+ : undefined;
1178
+ if (root && ts.isIdentifier(root)) {
1179
+ // the parameter it reads, typed with a package name
1180
+ for (let f = x.parent; f; f = f.parent) {
1181
+ if (!ts.isFunctionLike(f))
1182
+ continue;
1183
+ const prm = f.parameters.find((q) => ts.isIdentifier(q.name) && q.name.text === root.text);
1184
+ if (prm?.type &&
1185
+ ts.isTypeReferenceNode(prm.type) &&
1186
+ ts.isIdentifier(prm.type.typeName) &&
1187
+ fromPackage.has(prm.type.typeName.text))
1188
+ found = true;
1189
+ break;
1190
+ }
1191
+ }
1192
+ }
1193
+ x.forEachChild(visit);
1194
+ };
1195
+ visit(sf);
1196
+ return found;
1197
+ }
936
1198
  /** `e` can only be truthy when the bound value EQUALS an identity-derived one,
937
1199
  * or lies UNDER an identity-derived prefix (`ns.startsWith(`${ownPrefix}/`)`).
938
1200
  * A conjunction binds when either conjunct does; a disjunction only when BOTH
@@ -986,7 +1248,9 @@ const isFalsyLiteral = (e) => {
986
1248
  /** Judge a function as a REFUSING PREDICATE (shape 2 above): parameter
987
1249
  * `identityIdx` receives the caller's verified scope, `boundIdx` the value the
988
1250
  * caller then writes. */
989
- export function judgePredicateFunction(fn, identityIdx, boundIdx) {
1251
+ export function judgePredicateFunction(fn, identityIdx, boundIdx,
1252
+ /** names seen from `fn`'s file, to credit a fleet-standing predicate */
1253
+ resolve) {
990
1254
  if (!fn.body || !ts.isBlock(fn.body))
991
1255
  return {
992
1256
  verdict: "could-not-judge",
@@ -1020,7 +1284,7 @@ export function judgePredicateFunction(fn, identityIdx, boundIdx) {
1020
1284
  return null;
1021
1285
  const u = unwrap(e);
1022
1286
  if (u.kind === ts.SyntaxKind.TrueKeyword) {
1023
- if (cond && positiveMasterTest(cond, tainted))
1287
+ if (cond && positiveMasterTest(cond, tainted, resolve))
1024
1288
  return null;
1025
1289
  return {
1026
1290
  verdict: "refused",
@@ -704,7 +704,7 @@ function predicateBoundValues(h, f, writeLine, fns, src) {
704
704
  const d = fns.lookup(r.predicate, f.rel);
705
705
  if (!d)
706
706
  continue;
707
- const v = fns.judge(`P:${d.rel}:${d.f.line}:${i}:${j}`, () => judgePredicateFunction(d.f.fn, i, j));
707
+ const v = fns.judge(`P:${d.rel}:${d.f.line}:${i}:${j}`, () => judgePredicateFunction(d.f.fn, i, j, fns.callResolver(d.rel)));
708
708
  if (v.verdict === "honoured")
709
709
  out.set(value, `${r.predicate}() (definition ${d.rel}:${d.f.line})`);
710
710
  }
@@ -0,0 +1,2 @@
1
+ import { type Scan, type SrcFile } from "./_scan.js";
2
+ export declare function scanR11UnboundedReads(files: SrcFile[]): Scan;