@clear-capabilities/agentic-security-scanner 0.195.24 → 0.195.26

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.
@@ -13,8 +13,8 @@ __webpack_require__.d(__webpack_exports__, {
13
13
 
14
14
  // EXTERNAL MODULE: ./src/ir/index.js + 11 modules
15
15
  var ir = __webpack_require__(98297);
16
- // EXTERNAL MODULE: ./src/context-intelligence/graph-schema.js + 1 modules
17
- var graph_schema = __webpack_require__(44287);
16
+ // EXTERNAL MODULE: ./src/context-intelligence/graph-schema.js
17
+ var graph_schema = __webpack_require__(50152);
18
18
  ;// CONCATENATED MODULE: ./src/context-intelligence/adapters/existing-ir.js
19
19
  //
20
20
  // Adapter: shared IR (`buildProjectIR`) -> symbol-graph/1.
@@ -1667,9 +1667,10 @@ function looksLikeTelemetry(name) { return TELEMETRY_NAME.test(String(name || ''
1667
1667
  /* harmony export */ });
1668
1668
  /* unused harmony export DISPOSITIONS */
1669
1669
  /* harmony import */ var _canonical_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(44652);
1670
- /* harmony import */ var _spans_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(77311);
1670
+ /* harmony import */ var _spans_js__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(77311);
1671
1671
  /* harmony import */ var _controls_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(17202);
1672
1672
  /* harmony import */ var _posture_entrypoint_inventory_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(11366);
1673
+ /* harmony import */ var _imports_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(86588);
1673
1674
  //
1674
1675
  // Entry points -> exact handler symbols.
1675
1676
  //
@@ -1689,6 +1690,7 @@ function looksLikeTelemetry(name) { return TELEMETRY_NAME.test(String(name || ''
1689
1690
 
1690
1691
 
1691
1692
 
1693
+
1692
1694
  const DISPOSITIONS = Object.freeze(['resolved', 'partially_resolved', 'unresolved', 'excluded_by_policy']);
1693
1695
  const ENTRYPOINT_KINDS = Object.freeze(['http', 'webhook', 'queue', 'cron', 'upload', 'cli']);
1694
1696
 
@@ -1753,15 +1755,18 @@ function collectUses(fileContents) {
1753
1755
  for (let i = 0; i < lines.length; i++) {
1754
1756
  const m = /^\s*([A-Za-z_$][\w$]*)\.use\s*\(/.exec(lines[i]);
1755
1757
  if (!m) continue;
1756
- const st = (0,_spans_js__WEBPACK_IMPORTED_MODULE_3__/* .callStatement */ .yP)(lines, i + 1);
1758
+ const st = (0,_spans_js__WEBPACK_IMPORTED_MODULE_4__/* .callStatement */ .yP)(lines, i + 1);
1757
1759
  if (!st) continue;
1758
- const args = (0,_spans_js__WEBPACK_IMPORTED_MODULE_3__/* .splitArgs */ .sG)(st.text);
1760
+ const args = (0,_spans_js__WEBPACK_IMPORTED_MODULE_4__/* .splitArgs */ .sG)(st.text);
1759
1761
  let prefix = '/';
1760
1762
  let rest = args;
1761
1763
  const q = args[0] && /^(['"`])(.*)\1$/.exec(args[0]);
1762
1764
  if (q) { prefix = q[2]; rest = args.slice(1); }
1765
+ // GA-03.2: a first argument that is a call, a template with interpolation or a concatenation is a prefix
1766
+ // the scan cannot read; the use still records its middleware, but nothing is mounted under a guessed '/'.
1767
+ const prefixDynamic = !q && !!args[0] && (/^[A-Za-z_$][\w$.]*\s*\(/.test(args[0]) && !/^require\s*\(/.test(args[0]) || /\$\{|\+/.test(args[0]));
1763
1768
  const names = rest.map(a => { const id = IDENT_CHAIN.exec(a.replace(/^\[|\]$/g, '')); return id ? { name: id[0], text: a } : { name: null, text: a }; });
1764
- uses.push({ file, line: i + 1, receiver: m[1], prefix, args: names });
1769
+ uses.push({ file, line: i + 1, receiver: m[1], prefix, prefixDynamic, args: names });
1765
1770
  }
1766
1771
  }
1767
1772
  return uses;
@@ -1805,7 +1810,7 @@ function resolveHandler(route, ctx) {
1805
1810
  else reasons.push(`handler-${r.resolution}`);
1806
1811
  } else if (h.kind === 'inline') {
1807
1812
  out.handlerKind = 'inline';
1808
- out.rootSpan = { file: route.file, ...(0,_spans_js__WEBPACK_IMPORTED_MODULE_3__/* .inlineBlockSpan */ .$X)(lines, route.line) };
1813
+ out.rootSpan = { file: route.file, ...(0,_spans_js__WEBPACK_IMPORTED_MODULE_4__/* .inlineBlockSpan */ .$X)(lines, route.line) };
1809
1814
  const enclosing = fileSyms.filter(s => s.kind === 'function' && s.startLine <= route.line && s.endLine >= route.line).sort((a, b) => (b.startLine - a.startLine))[0];
1810
1815
  out.handlerSymbol = enclosing ? enclosing.id : null;
1811
1816
  out.confidence = enclosing ? 'inline-block' : 'none';
@@ -1813,7 +1818,7 @@ function resolveHandler(route, ctx) {
1813
1818
  } else {
1814
1819
  reasons.push('handler-not-statically-known');
1815
1820
  // the manifest still shows where the route is registered, so the gap is inspectable rather than absent
1816
- out.rootSpan = { file: route.file, ...(0,_spans_js__WEBPACK_IMPORTED_MODULE_3__/* .inlineBlockSpan */ .$X)(lines, route.line) };
1821
+ out.rootSpan = { file: route.file, ...(0,_spans_js__WEBPACK_IMPORTED_MODULE_4__/* .inlineBlockSpan */ .$X)(lines, route.line) };
1817
1822
  }
1818
1823
  return out;
1819
1824
  }
@@ -1836,7 +1841,7 @@ function resolveHandler(route, ctx) {
1836
1841
  return out;
1837
1842
  }
1838
1843
 
1839
- const st = JS_FILE.test(route.file) ? (0,_spans_js__WEBPACK_IMPORTED_MODULE_3__/* .callStatement */ .yP)(lines, route.line) : null;
1844
+ const st = JS_FILE.test(route.file) ? (0,_spans_js__WEBPACK_IMPORTED_MODULE_4__/* .callStatement */ .yP)(lines, route.line) : null;
1840
1845
  if (!st) {
1841
1846
  // not a call-style registration we can parse: fall back to the enclosing declaration, flagged
1842
1847
  const enclosing = fileSyms.filter(s => s.kind === 'function' && s.startLine <= route.line && s.endLine >= route.line).sort((a, b) => b.startLine - a.startLine)[0];
@@ -1845,10 +1850,10 @@ function resolveHandler(route, ctx) {
1845
1850
  return out;
1846
1851
  }
1847
1852
  out.rootSpan = { file: route.file, startLine: route.line, endLine: st.endLine, precision: 'brace' };
1848
- const args = (0,_spans_js__WEBPACK_IMPORTED_MODULE_3__/* .splitArgs */ .sG)(st.text).slice(1); // drop the path
1853
+ const args = (0,_spans_js__WEBPACK_IMPORTED_MODULE_4__/* .splitArgs */ .sG)(st.text).slice(1); // drop the path
1849
1854
  const flat = [];
1850
1855
  for (const a of args) {
1851
- if (a.startsWith('[') && a.endsWith(']')) flat.push(...(0,_spans_js__WEBPACK_IMPORTED_MODULE_3__/* .splitArgs */ .sG)(`(${a.slice(1, -1)})`));
1856
+ if (a.startsWith('[') && a.endsWith(']')) flat.push(...(0,_spans_js__WEBPACK_IMPORTED_MODULE_4__/* .splitArgs */ .sG)(`(${a.slice(1, -1)})`));
1852
1857
  else flat.push(a);
1853
1858
  }
1854
1859
  if (!flat.length) { reasons.push('registration-has-no-handler-argument'); return out; }
@@ -1884,6 +1889,107 @@ function resolveHandler(route, ctx) {
1884
1889
  return out;
1885
1890
  }
1886
1891
 
1892
+ // ── Router mounts (GA-03.2, 0.195.26) ──────────────────────────────────────
1893
+ //
1894
+ // `router.get('/:id', h)` is reachable at `/users/:id` only through `app.use('/users', router)`, and that mount
1895
+ // can sit in another file (`app.use('/users', require('./users'))`, `import users from './users.js'`). Before
1896
+ // this, every route on a router-looking receiver was `partially_resolved` with `router-mount-not-resolved` and
1897
+ // its path was the local one. Now a mount is found when the mounted argument is (a) a receiver with routes in
1898
+ // the same file, (b) a name imported from a file whose `module.exports` / `export default` is that file's
1899
+ // router receiver, or (c) an inline `require('./x')` of such a file. Mounts compose: `app.use('/api', v1)` and
1900
+ // `v1.use('/users', users)` make `/api/users/:id`. A router mounted at several prefixes is reachable at each,
1901
+ // so it yields one entry point per prefix (sorted; at most MAX_MOUNT_PATHS). Depth is bounded; a cycle stops.
1902
+ // A mount's other identifier arguments (`app.use('/api', auth, router)`) are middleware every mounted route
1903
+ // inherits, recorded with the mount's prefix as their scope. What stays unresolved: a router passed through a
1904
+ // variable the import map cannot follow, a mount whose prefix is not a string literal, a dynamic
1905
+ // `app.use(prefixVar, router)`, and anything registered outside the scanned files.
1906
+ const MAX_MOUNT_DEPTH = 4;
1907
+ const MAX_MOUNT_PATHS = 8;
1908
+ const ROUTE_RECEIVER_RE = /([A-Za-z_$][\w$]*)\.(?:get|post|put|patch|delete|all|options|head|route)\s*\(/;
1909
+ const ROUTER_NAME_RE = /^(?:router|routes?|r|api|\w*Router)$/i;
1910
+
1911
+ function routeReceiver(fileContents, route) {
1912
+ if (!JS_FILE.test(route.file)) return null;
1913
+ const line = (fileContents[route.file] || '').split('\n')[route.line - 1] || '';
1914
+ const m = ROUTE_RECEIVER_RE.exec(line);
1915
+ return m ? m[1] : null;
1916
+ }
1917
+
1918
+ /** The receiver a file exports as its module (`module.exports = router`, `export default router`), or null. */
1919
+ function exportedReceiver(text) {
1920
+ const t = String(text || '');
1921
+ const m = /\bmodule\.exports\s*=\s*([A-Za-z_$][\w$]*)\s*;?/.exec(t) || /\bexport\s+default\s+([A-Za-z_$][\w$]*)\s*;?/.exec(t);
1922
+ return m ? m[1] : null;
1923
+ }
1924
+
1925
+ function joinMountPath(prefix, routePath) {
1926
+ const p = String(prefix || '/').replace(/\/+$/, '');
1927
+ const r = String(routePath || '');
1928
+ if (!p) return r.startsWith('/') ? r : '/' + r;
1929
+ if (!r || r === '/') return p;
1930
+ return p + (r.startsWith('/') ? r : '/' + r);
1931
+ }
1932
+
1933
+ /**
1934
+ * Every mount of a router: key `${file}|${receiver}` -> [{ prefix, file, line, receiver, middleware }], where
1935
+ * `receiver` is the receiver the router was mounted ON (so a chain can be followed upward).
1936
+ */
1937
+ function buildMountIndex(fileContents, uses, routes) {
1938
+ const files = new Set(Object.keys(fileContents));
1939
+ const imports = (0,_imports_js__WEBPACK_IMPORTED_MODULE_3__/* .buildImportMap */ .kB)(fileContents);
1940
+ const receiversByFile = new Map();
1941
+ for (const r of routes) {
1942
+ const recv = routeReceiver(fileContents, r);
1943
+ if (!recv) continue;
1944
+ if (!receiversByFile.has(r.file)) receiversByFile.set(r.file, new Set());
1945
+ receiversByFile.get(r.file).add(recv);
1946
+ }
1947
+ // a receiver that mounts something is a router too (an intermediate `v1.use('/users', users)`)
1948
+ for (const u of uses) { if (!receiversByFile.has(u.file)) receiversByFile.set(u.file, new Set()); receiversByFile.get(u.file).add(u.receiver); }
1949
+ const index = new Map();
1950
+ const add = (file, receiver, mount) => { const k = `${file}|${receiver}`; if (!index.has(k)) index.set(k, []); index.get(k).push(mount); };
1951
+ // a module is a router module only when its exported receiver registers routes or mounts in that file
1952
+ const isRouter = (file, receiver) => !!receiver && (receiversByFile.get(file) || new Set()).has(receiver);
1953
+ for (const u of uses) {
1954
+ if (u.prefixDynamic) continue;
1955
+ const middleware = [];
1956
+ for (const a of u.args) {
1957
+ let target = null;
1958
+ const req = /^require\(\s*(['"])([^'"]+)\1\s*\)$/.exec(String(a.text || '').trim());
1959
+ if (req) {
1960
+ const res = (0,_imports_js__WEBPACK_IMPORTED_MODULE_3__/* .resolveJsModule */ .iC)(u.file, req[2], files);
1961
+ const recv = res && res.target && files.has(res.target) ? exportedReceiver(fileContents[res.target]) : null;
1962
+ if (recv && isRouter(res.target, recv)) target = { file: res.target, receiver: recv };
1963
+ } else if (a.name && a.name !== u.receiver && isRouter(u.file, a.name)) {
1964
+ target = { file: u.file, receiver: a.name };
1965
+ } else if (a.name) {
1966
+ const via = (0,_imports_js__WEBPACK_IMPORTED_MODULE_3__/* .resolveViaImports */ .xU)(imports, u.file, a.name);
1967
+ const recv = via && via.kind === 'exact-import' && via.targetFile && files.has(via.targetFile) ? exportedReceiver(fileContents[via.targetFile]) : null;
1968
+ if (recv && isRouter(via.targetFile, recv)) target = { file: via.targetFile, receiver: recv };
1969
+ }
1970
+ if (target) add(target.file, target.receiver, { prefix: u.prefix, file: u.file, line: u.line, receiver: u.receiver, middleware: [...middleware] });
1971
+ else if (a.name && !req) middleware.push(a.name);
1972
+ }
1973
+ }
1974
+ return index;
1975
+ }
1976
+
1977
+ /** The mount chains (root first) through which (file, receiver) is reachable; [] when it is mounted nowhere. */
1978
+ function mountChains(index, file, receiver, depth = 0, seen = new Set()) {
1979
+ const key = `${file}|${receiver}`;
1980
+ const mounts = index.get(key) || [];
1981
+ if (!mounts.length || depth >= MAX_MOUNT_DEPTH || seen.has(key)) return [];
1982
+ const out = [];
1983
+ const next = new Set(seen); next.add(key);
1984
+ for (const m of mounts) {
1985
+ const parents = mountChains(index, m.file, m.receiver, depth + 1, next);
1986
+ if (!parents.length) out.push([m]);
1987
+ else for (const pc of parents) out.push([...pc, m]);
1988
+ if (out.length >= MAX_MOUNT_PATHS) break;
1989
+ }
1990
+ return out.slice(0, MAX_MOUNT_PATHS);
1991
+ }
1992
+
1887
1993
  function pathMatchesPrefix(routePath, prefix) {
1888
1994
  if (prefix === '/' || prefix === '') return true;
1889
1995
  const p = prefix.replace(/\/+$/, '');
@@ -1898,23 +2004,24 @@ async function collectEntrypoints({ fileContents, graph, config = {} }) {
1898
2004
  const kinds = new Set(config.entrypointKinds && config.entrypointKinds.length ? config.entrypointKinds : ['http']);
1899
2005
  const idx = symbolIndexes(graph);
1900
2006
  const uses = collectUses(fileContents);
2007
+ const routes = (kinds.has('http') || kinds.has('webhook')) ? await collectRoutes(fileContents) : [];
2008
+ const mountIndex = buildMountIndex(fileContents, uses, routes);
1901
2009
  const exclude = (config.exclude || []).map(globToRe);
1902
2010
  const cap = Number.isInteger(config.maxEntrypoints) && config.maxEntrypoints > 0 ? config.maxEntrypoints : 5000;
1903
2011
  const ctx = { fileContents, idx, graph };
1904
2012
  const records = [];
1905
2013
 
1906
2014
  if (kinds.has('http') || kinds.has('webhook')) {
1907
- for (const route of await collectRoutes(fileContents)) {
2015
+ for (const route of routes) {
1908
2016
  const isWebhook = /webhook/i.test(route.path || '');
1909
2017
  if (isWebhook && !kinds.has('webhook') && !kinds.has('http')) continue;
1910
2018
  const h = resolveHandler(route, ctx);
1911
2019
  const reasons = [...h.reasons];
1912
2020
  const middleware = [...h.middleware];
2021
+ const receiver = routeReceiver(fileContents, route);
2022
+ const chains = receiver ? mountChains(mountIndex, route.file, receiver) : [];
1913
2023
  // inherited middleware registered with `X.use(...)` on the same receiver, earlier in the same file
1914
2024
  if (JS_FILE.test(route.file)) {
1915
- const lines = fileContents[route.file].split('\n');
1916
- const recvM = /([A-Za-z_$][\w$]*)\.(?:get|post|put|patch|delete|all|options|head|route)\s*\(/.exec(lines[route.line - 1] || '');
1917
- const receiver = recvM ? recvM[1] : null;
1918
2025
  for (const u of uses) {
1919
2026
  if (u.file !== route.file || u.line >= route.line || u.receiver !== receiver) continue;
1920
2027
  if (!pathMatchesPrefix(String(route.path || ''), u.prefix)) continue;
@@ -1925,17 +2032,35 @@ async function collectEntrypoints({ fileContents, graph, config = {} }) {
1925
2032
  if (!r.symbol) reasons.push(`inherited-middleware-${r.resolution}:${a.name}`);
1926
2033
  }
1927
2034
  }
1928
- if (receiver && /^(?:router|routes?|r|api|\w*Router)$/i.test(receiver) && !uses.some(u => u.receiver !== receiver && u.args.some(a => a.name === receiver))) {
1929
- reasons.push('router-mount-not-resolved');
1930
- }
2035
+ if (receiver && !chains.length && ROUTER_NAME_RE.test(receiver)) reasons.push('router-mount-not-resolved');
1931
2036
  }
2037
+ // middleware the mount statements apply to everything mounted under them (`app.use('/api', auth, router)`):
2038
+ // per chain, so a router mounted twice inherits each mount's own middleware and not the other's
2039
+ const chainMiddleware = (chain) => {
2040
+ const mw = []; const why = [];
2041
+ for (const hop of chain) {
2042
+ for (const name of hop.middleware) {
2043
+ const r = resolveName(name, hop.file, idx);
2044
+ mw.push({ name, symbolId: r.symbol ? r.symbol.id : null, resolution: r.resolution, line: hop.line, scope: hop.prefix, inherited: true, viaMount: true });
2045
+ if (!r.symbol) why.push(`inherited-middleware-${r.resolution}:${name}`);
2046
+ }
2047
+ }
2048
+ return { mw, why };
2049
+ };
1932
2050
  const authScheme = route.webModel
1933
2051
  ? { status: route.webModel.auth.status, kind: route.webModel.auth.kind || null, basis: 'haskell-web-model', role: route.webModel.role.status, ownership: route.webModel.ownership || null, idLookups: route.webModel.idLookups || [], csrf: !!route.webModel.csrf }
1934
2052
  : { status: route.hasAuth ? 'authenticated' : 'none', basis: 'route-scan-heuristic', note: 'authentication seen near the registration; says nothing about ownership' };
2053
+ const mountsOf = chains.map(chain => ({
2054
+ prefix: chain.reduce((acc, hop) => joinMountPath(acc, hop.prefix), '/'),
2055
+ chain: chain.map(hop => ({ file: hop.file, line: hop.line, receiver: hop.receiver, prefix: hop.prefix })),
2056
+ ...chainMiddleware(chain),
2057
+ })).sort((a, b) => (a.prefix < b.prefix ? -1 : a.prefix > b.prefix ? 1 : 0));
1935
2058
  const base = {
1936
2059
  kind: isWebhook ? 'webhook' : 'http',
1937
2060
  method: String(route.method || 'GET'),
1938
2061
  pathTemplate: String(route.path || ''),
2062
+ registeredPath: String(route.path || ''),
2063
+ mount: null,
1939
2064
  framework: route.framework || null,
1940
2065
  language: route.language === 'haskell' ? 'haskell' : (JS_FILE.test(route.file) ? 'javascript' : (route.file.split('.').pop())),
1941
2066
  registrationSite: { file: route.file, line: route.line },
@@ -1948,7 +2073,15 @@ async function collectEntrypoints({ fileContents, graph, config = {} }) {
1948
2073
  externalDependencies: [],
1949
2074
  resolutionReasons: [...new Set(reasons)].sort(),
1950
2075
  };
1951
- records.push(base);
2076
+ if (!mountsOf.length) { records.push(base); continue; }
2077
+ for (const m of mountsOf) {
2078
+ const { mw, why, ...mount } = m;
2079
+ records.push({
2080
+ ...base, pathTemplate: joinMountPath(mount.prefix, base.registeredPath), mount: { ...mount, alternatives: mountsOf.length },
2081
+ middleware: [...base.middleware.map(x => ({ ...x })), ...mw],
2082
+ resolutionReasons: [...new Set([...base.resolutionReasons, ...why])].sort(),
2083
+ });
2084
+ }
1952
2085
  }
1953
2086
  }
1954
2087
 
@@ -2108,151 +2241,18 @@ function readGitSnapshot(root, ref, { maxFiles = 3000 } = {}) {
2108
2241
 
2109
2242
  /***/ }),
2110
2243
 
2111
- /***/ 44287:
2244
+ /***/ 50152:
2112
2245
  /***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
2113
2246
 
2114
-
2115
- // EXPORTS
2116
- __webpack_require__.d(__webpack_exports__, {
2117
- U2: () => (/* binding */ MATERIAL_REASONS),
2118
- Nk: () => (/* binding */ buildSymbolGraph),
2119
- dC: () => (/* binding */ validateGraph)
2120
- });
2121
-
2122
- // UNUSED EXPORTS: CONTINUATIONS, EDGE_KINDS, GRAPH_SCHEMA_VERSION, RESOLVERS, conditionalityOf
2123
-
2124
- // EXTERNAL MODULE: ./src/context-intelligence/canonical.js
2125
- var canonical = __webpack_require__(44652);
2126
- // EXTERNAL MODULE: ./src/context-intelligence/spans.js
2127
- var spans = __webpack_require__(77311);
2128
- // EXTERNAL MODULE: external "node:path"
2129
- var external_node_path_ = __webpack_require__(76760);
2130
- ;// CONCATENATED MODULE: ./src/context-intelligence/imports.js
2131
- //
2132
- // Import maps (JS/TS and Python): which local name in a file refers to which module, and which declaration in it.
2133
- //
2134
- // The shared IR resolves a call to a declaration only for the shapes it models; a destructured `require` or a named
2135
- // ES import is not one of them, so a call such as `getReport(id)` arrives as an edge to nowhere. Left alone, nearly
2136
- // every Express/Node route would be reported as having an unresolved call. This module recovers those edges from the
2137
- // import statements themselves and records HOW each was resolved, so the result is still evidence and not a guess:
2138
- //
2139
- // exact-import the local name is bound by an import of a module in the repository, and that module declares a
2140
- // function of that name (or, for a namespace, of the member name)
2141
- // third-party the module is a package, not a repository file: a boundary, not first-party code
2142
- // unresolved a relative import that no repository file satisfies
2143
- //
2144
- // Scope, stated plainly: this is statement-level pattern matching. Re-export chains, `import()` expressions,
2145
- // computed `require` arguments and package-relative aliases (tsconfig paths) are not followed.
2146
-
2147
-
2148
- const JS_EXTS = ['', '.js', '.mjs', '.cjs', '.ts', '.tsx', '.jsx', '/index.js', '/index.mjs', '/index.ts', '/index.tsx'];
2149
-
2150
- function resolveJsModule(fromFile, source, files) {
2151
- if (!source.startsWith('.')) return { kind: 'third-party', target: null };
2152
- const base = external_node_path_.posix.normalize(external_node_path_.posix.join(external_node_path_.posix.dirname(fromFile), source));
2153
- for (const ext of JS_EXTS) if (files.has(base + ext)) return { kind: 'repo', target: base + ext };
2154
- return { kind: 'unresolved', target: null };
2155
- }
2156
-
2157
- function resolvePyModule(fromFile, dotted, files) {
2158
- let dir = external_node_path_.posix.dirname(fromFile);
2159
- let mod = dotted;
2160
- const lead = /^\.+/.exec(mod);
2161
- const candidatesBase = [];
2162
- if (lead) {
2163
- for (let i = 1; i < lead[0].length; i++) dir = external_node_path_.posix.dirname(dir);
2164
- mod = mod.slice(lead[0].length);
2165
- candidatesBase.push(dir === '.' ? '' : dir);
2166
- } else {
2167
- candidatesBase.push('', external_node_path_.posix.dirname(fromFile) === '.' ? '' : external_node_path_.posix.dirname(fromFile));
2168
- }
2169
- for (const b of candidatesBase) {
2170
- const rel = mod.replace(/\./g, '/');
2171
- for (const cand of [rel ? `${rel}.py` : '__init__.py', rel ? `${rel}/__init__.py` : '__init__.py']) {
2172
- const full = b ? external_node_path_.posix.join(b, cand) : cand;
2173
- if (files.has(full)) return { kind: 'repo', target: full };
2174
- }
2175
- }
2176
- return { kind: lead ? 'unresolved' : 'third-party', target: null };
2177
- }
2178
-
2179
- function stripComments(text) {
2180
- return text.replace(/\/\*[\s\S]*?\*\//g, m => m.replace(/[^\n]/g, ' ')).replace(/(^|[^:'"`\\])\/\/[^\n]*/g, (m, p1) => p1 + ' '.repeat(m.length - p1.length));
2181
- }
2182
-
2183
- function names(list) {
2184
- return list.split(',').map(s => s.trim()).filter(Boolean).map(s => {
2185
- const m = /^(?:\w+\s+as\s+)?([\w$]+)$/.exec(s.replace(/^type\s+/, '').replace(/^([\w$]+)\s*:\s*([\w$]+)$/, '$1 as $2'));
2186
- const parts = s.replace(/^type\s+/, '').split(/\s+as\s+|\s*:\s*/).map(x => x.trim()).filter(Boolean);
2187
- void m;
2188
- if (parts.length === 2) return { imported: parts[0], local: parts[1] };
2189
- if (parts.length === 1) return { imported: parts[0], local: parts[0] };
2190
- return null;
2191
- }).filter(Boolean).filter(n => /^[\w$]+$/.test(n.imported) && /^[\w$]+$/.test(n.local));
2192
- }
2193
-
2194
- /** Import entries for one JS/TS file. */
2195
- function jsImports(file, text, files) {
2196
- const t = stripComments(text);
2197
- const out = [];
2198
- const add = (local, source, member) => out.push({ file, local, source, member, ...resolveJsModule(file, source, files) });
2199
- for (const m of t.matchAll(/\b(?:const|let|var)\s*\{([^}]*)\}\s*=\s*(?:await\s+)?require\(\s*['"]([^'"]+)['"]\s*\)/g)) for (const n of names(m[1])) add(n.local, m[2], n.imported);
2200
- for (const m of t.matchAll(/\b(?:const|let|var)\s+([\w$]+)\s*=\s*(?:await\s+)?require\(\s*['"]([^'"]+)['"]\s*\)/g)) add(m[1], m[2], '*');
2201
- for (const m of t.matchAll(/\bimport\s+(?:type\s+)?([\w$]+)?\s*,?\s*(?:\{([^}]*)\}|\*\s+as\s+([\w$]+))?\s*from\s*['"]([^'"]+)['"]/g)) {
2202
- if (m[1]) add(m[1], m[4], 'default');
2203
- if (m[2]) for (const n of names(m[2])) add(n.local, m[4], n.imported);
2204
- if (m[3]) add(m[3], m[4], '*');
2205
- }
2206
- return out;
2207
- }
2208
-
2209
- function pyImports(file, text, files) {
2210
- const out = [];
2211
- const add = (local, source, member) => out.push({ file, local, source, member, ...resolvePyModule(file, source, files) });
2212
- // the unparenthesised form ends at the line end; only the parenthesised form may span lines
2213
- for (const m of text.matchAll(/^[ \t]*from[ \t]+([.\w]+)[ \t]+import[ \t]+(?:\(([\w\s,]+)\)|([\w \t,]+))/gm)) {
2214
- for (const n of (m[2] ?? m[3]).split(',').map(s => s.trim()).filter(Boolean)) {
2215
- const p = n.split(/\s+as\s+/);
2216
- add(p[1] || p[0], m[1], p[0]);
2217
- }
2218
- }
2219
- for (const m of text.matchAll(/^[ \t]*import\s+([.\w]+)(?:\s+as\s+(\w+))?\s*$/gm)) add(m[2] || m[1].split('.')[0], m[1], '*');
2220
- return out;
2221
- }
2222
-
2223
- /** All import entries, deterministically ordered. */
2224
- function buildImportMap(fileContents) {
2225
- const files = new Set(Object.keys(fileContents));
2226
- const out = [];
2227
- for (const file of [...files].sort()) {
2228
- if (/\.(?:js|jsx|mjs|cjs|ts|tsx)$/i.test(file)) out.push(...jsImports(file, fileContents[file], files));
2229
- else if (/\.py$/i.test(file)) out.push(...pyImports(file, fileContents[file], files));
2230
- }
2231
- return out.sort((a, b) => (a.file < b.file ? -1 : a.file > b.file ? 1 : 0) || (a.local < b.local ? -1 : a.local > b.local ? 1 : 0) || (a.source < b.source ? -1 : 1));
2232
- }
2233
-
2234
- /**
2235
- * Resolve a callee name used in `file` against the import map.
2236
- * Returns { kind:'exact-import', targetFile, member } | { kind:'third-party' } | { kind:'unresolved-import' } | null.
2237
- */
2238
- function resolveViaImports(imports, file, calleeName) {
2239
- const parts = String(calleeName || '').split('.');
2240
- const head = parts[0];
2241
- const imp = imports.filter(i => i.file === file && i.local === head);
2242
- if (!imp.length) return null;
2243
- const i = imp[0];
2244
- if (i.kind === 'third-party') return { kind: 'third-party' };
2245
- if (i.kind === 'unresolved') return { kind: 'unresolved-import' };
2246
- if (parts.length === 1) {
2247
- const member = i.member === '*' || i.member === 'default' ? head : i.member;
2248
- return { kind: 'exact-import', targetFile: i.target, member, namespaceCall: i.member === '*' || i.member === 'default' };
2249
- }
2250
- if (i.member === '*' || i.member === 'default') return { kind: 'exact-import', targetFile: i.target, member: parts[parts.length - 1], namespaceCall: false };
2251
- // `db.query()` where `db` is a NAMED import: a method of an imported object, not a function of that module
2252
- return { kind: 'imported-object-method' };
2253
- }
2254
-
2255
- ;// CONCATENATED MODULE: ./src/context-intelligence/graph-schema.js
2247
+ /* harmony export */ __webpack_require__.d(__webpack_exports__, {
2248
+ /* harmony export */ Nk: () => (/* binding */ buildSymbolGraph),
2249
+ /* harmony export */ U2: () => (/* binding */ MATERIAL_REASONS),
2250
+ /* harmony export */ dC: () => (/* binding */ validateGraph)
2251
+ /* harmony export */ });
2252
+ /* unused harmony exports GRAPH_SCHEMA_VERSION, CONTINUATIONS, EDGE_KINDS, RESOLVERS, conditionalityOf */
2253
+ /* harmony import */ var _canonical_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(44652);
2254
+ /* harmony import */ var _spans_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(77311);
2255
+ /* harmony import */ var _imports_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(86588);
2256
2256
  //
2257
2257
  // Versioned, typed declaration/reference graph (symbol-graph/1).
2258
2258
  //
@@ -2355,8 +2355,8 @@ function buildSymbolGraph({ fileContents, functions, edges }) {
2355
2355
  ordinal.set(base, k + 1);
2356
2356
  const id = !anon && k === 0 ? base : (anon ? base : `${base}#${k + 1}`);
2357
2357
  idByQid.set(fn.qid, id);
2358
- const span = (0,spans/* spanFor */.Wc)(fn.file, lines(fn.file), fn.line, cfgMaxLine(fn));
2359
- const text = (0,canonical/* sliceLines */.iq)(fileContents[fn.file], span.startLine, span.endLine);
2358
+ const span = (0,_spans_js__WEBPACK_IMPORTED_MODULE_2__/* .spanFor */ .Wc)(fn.file, lines(fn.file), fn.line, cfgMaxLine(fn));
2359
+ const text = (0,_canonical_js__WEBPACK_IMPORTED_MODULE_0__/* .sliceLines */ .iq)(fileContents[fn.file], span.startLine, span.endLine);
2360
2360
  symbols.push({
2361
2361
  id,
2362
2362
  name: fn.name || '<anon>',
@@ -2366,7 +2366,7 @@ function buildSymbolGraph({ fileContents, functions, edges }) {
2366
2366
  endLine: span.endLine,
2367
2367
  declLine: fn.line,
2368
2368
  spanPrecision: span.precision,
2369
- spanHash: (0,canonical/* sha256 */.sc)(text),
2369
+ spanHash: (0,_canonical_js__WEBPACK_IMPORTED_MODULE_0__/* .sha256 */ .sc)(text),
2370
2370
  params: Array.isArray(fn.params) ? fn.params.map(String) : [],
2371
2371
  resolver: 'source-static',
2372
2372
  });
@@ -2381,7 +2381,7 @@ function buildSymbolGraph({ fileContents, functions, edges }) {
2381
2381
  byLastName.get(key).push(s.id);
2382
2382
  }
2383
2383
 
2384
- const imports = buildImportMap(fileContents);
2384
+ const imports = (0,_imports_js__WEBPACK_IMPORTED_MODULE_1__/* .buildImportMap */ .kB)(fileContents);
2385
2385
  const symbolsByFileName = new Map();
2386
2386
  for (const s of symbols) {
2387
2387
  if (s.kind !== 'function' || s.id.includes('::<anon>@')) continue;
@@ -2429,7 +2429,7 @@ function buildSymbolGraph({ fileContents, functions, edges }) {
2429
2429
  if (q.length === 1) { graphEdges.push({ ...base, to: q[0], kind: 'call', confidence: 'high', unresolvedReason: null, candidates: [], resolution: 'qualified-name' }); continue; }
2430
2430
  }
2431
2431
 
2432
- const imp = resolveViaImports(imports, callerFile, name);
2432
+ const imp = (0,_imports_js__WEBPACK_IMPORTED_MODULE_1__/* .resolveViaImports */ .xU)(imports, callerFile, name);
2433
2433
  if (imp && imp.kind === 'exact-import') {
2434
2434
  const ids = (symbolsByFileName.get(`${imp.targetFile}\0${imp.member}`) || []).filter(id => id !== from);
2435
2435
  if (ids.length === 1) { graphEdges.push({ ...base, to: ids[0], kind: 'call', confidence: 'high', unresolvedReason: null, candidates: [], resolution: 'import' }); continue; }
@@ -2450,9 +2450,9 @@ function buildSymbolGraph({ fileContents, functions, edges }) {
2450
2450
  graphEdges.sort((a, b) =>
2451
2451
  (a.from < b.from ? -1 : a.from > b.from ? 1 : 0) || (a.line - b.line) || (a.calleeName < b.calleeName ? -1 : a.calleeName > b.calleeName ? 1 : 0) || ((a.to || '') < (b.to || '') ? -1 : 1));
2452
2452
 
2453
- const snapshotHash = (0,canonical/* hashOf */.qQ)(Object.keys(fileContents).sort().map(f => [f, (0,canonical/* sha256 */.sc)(fileContents[f])]));
2453
+ const snapshotHash = (0,_canonical_js__WEBPACK_IMPORTED_MODULE_0__/* .hashOf */ .qQ)(Object.keys(fileContents).sort().map(f => [f, (0,_canonical_js__WEBPACK_IMPORTED_MODULE_0__/* .sha256 */ .sc)(fileContents[f])]));
2454
2454
  const body = { schemaVersion: GRAPH_SCHEMA_VERSION, snapshotHash, symbols, edges: graphEdges, imports: imports.map(i => ({ file: i.file, local: i.local, source: i.source, member: i.member, kind: i.kind, target: i.target })) };
2455
- return { ...body, hash: (0,canonical/* hashOf */.qQ)(body) };
2455
+ return { ...body, hash: (0,_canonical_js__WEBPACK_IMPORTED_MODULE_0__/* .hashOf */ .qQ)(body) };
2456
2456
  }
2457
2457
 
2458
2458
  /**
@@ -2469,7 +2469,7 @@ function validateGraph(graph, fileContents) {
2469
2469
  if (typeof text !== 'string') { problems.push(`missing-file:${s.file}`); continue; }
2470
2470
  const n = text.split('\n').length;
2471
2471
  if (!(s.startLine >= 1 && s.endLine >= s.startLine && s.endLine <= n)) { problems.push(`span-out-of-range:${s.id}`); continue; }
2472
- if ((0,canonical/* sha256 */.sc)((0,canonical/* sliceLines */.iq)(text, s.startLine, s.endLine)) !== s.spanHash) problems.push(`stale-span-hash:${s.id}`);
2472
+ if ((0,_canonical_js__WEBPACK_IMPORTED_MODULE_0__/* .sha256 */ .sc)((0,_canonical_js__WEBPACK_IMPORTED_MODULE_0__/* .sliceLines */ .iq)(text, s.startLine, s.endLine)) !== s.spanHash) problems.push(`stale-span-hash:${s.id}`);
2473
2473
  }
2474
2474
  for (const e of graph.edges) {
2475
2475
  if (!ids.has(e.from)) problems.push(`dangling-from:${e.from}`);
@@ -2477,11 +2477,147 @@ function validateGraph(graph, fileContents) {
2477
2477
  for (const c of e.candidates || []) if (!ids.has(c)) problems.push(`dangling-candidate:${c}`);
2478
2478
  }
2479
2479
  const { hash, ...rest } = graph;
2480
- if ((0,canonical/* hashOf */.qQ)(rest) !== hash) problems.push('graph-hash-mismatch');
2480
+ if ((0,_canonical_js__WEBPACK_IMPORTED_MODULE_0__/* .hashOf */ .qQ)(rest) !== hash) problems.push('graph-hash-mismatch');
2481
2481
  return problems;
2482
2482
  }
2483
2483
 
2484
2484
 
2485
+ /***/ }),
2486
+
2487
+ /***/ 86588:
2488
+ /***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
2489
+
2490
+ /* harmony export */ __webpack_require__.d(__webpack_exports__, {
2491
+ /* harmony export */ iC: () => (/* binding */ resolveJsModule),
2492
+ /* harmony export */ kB: () => (/* binding */ buildImportMap),
2493
+ /* harmony export */ xU: () => (/* binding */ resolveViaImports)
2494
+ /* harmony export */ });
2495
+ /* harmony import */ var node_path__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(76760);
2496
+ //
2497
+ // Import maps (JS/TS and Python): which local name in a file refers to which module, and which declaration in it.
2498
+ //
2499
+ // The shared IR resolves a call to a declaration only for the shapes it models; a destructured `require` or a named
2500
+ // ES import is not one of them, so a call such as `getReport(id)` arrives as an edge to nowhere. Left alone, nearly
2501
+ // every Express/Node route would be reported as having an unresolved call. This module recovers those edges from the
2502
+ // import statements themselves and records HOW each was resolved, so the result is still evidence and not a guess:
2503
+ //
2504
+ // exact-import the local name is bound by an import of a module in the repository, and that module declares a
2505
+ // function of that name (or, for a namespace, of the member name)
2506
+ // third-party the module is a package, not a repository file: a boundary, not first-party code
2507
+ // unresolved a relative import that no repository file satisfies
2508
+ //
2509
+ // Scope, stated plainly: this is statement-level pattern matching. Re-export chains, `import()` expressions,
2510
+ // computed `require` arguments and package-relative aliases (tsconfig paths) are not followed.
2511
+
2512
+
2513
+ const JS_EXTS = ['', '.js', '.mjs', '.cjs', '.ts', '.tsx', '.jsx', '/index.js', '/index.mjs', '/index.ts', '/index.tsx'];
2514
+
2515
+ function resolveJsModule(fromFile, source, files) {
2516
+ if (!source.startsWith('.')) return { kind: 'third-party', target: null };
2517
+ const base = node_path__WEBPACK_IMPORTED_MODULE_0__.posix.normalize(node_path__WEBPACK_IMPORTED_MODULE_0__.posix.join(node_path__WEBPACK_IMPORTED_MODULE_0__.posix.dirname(fromFile), source));
2518
+ for (const ext of JS_EXTS) if (files.has(base + ext)) return { kind: 'repo', target: base + ext };
2519
+ return { kind: 'unresolved', target: null };
2520
+ }
2521
+
2522
+ function resolvePyModule(fromFile, dotted, files) {
2523
+ let dir = node_path__WEBPACK_IMPORTED_MODULE_0__.posix.dirname(fromFile);
2524
+ let mod = dotted;
2525
+ const lead = /^\.+/.exec(mod);
2526
+ const candidatesBase = [];
2527
+ if (lead) {
2528
+ for (let i = 1; i < lead[0].length; i++) dir = node_path__WEBPACK_IMPORTED_MODULE_0__.posix.dirname(dir);
2529
+ mod = mod.slice(lead[0].length);
2530
+ candidatesBase.push(dir === '.' ? '' : dir);
2531
+ } else {
2532
+ candidatesBase.push('', node_path__WEBPACK_IMPORTED_MODULE_0__.posix.dirname(fromFile) === '.' ? '' : node_path__WEBPACK_IMPORTED_MODULE_0__.posix.dirname(fromFile));
2533
+ }
2534
+ for (const b of candidatesBase) {
2535
+ const rel = mod.replace(/\./g, '/');
2536
+ for (const cand of [rel ? `${rel}.py` : '__init__.py', rel ? `${rel}/__init__.py` : '__init__.py']) {
2537
+ const full = b ? node_path__WEBPACK_IMPORTED_MODULE_0__.posix.join(b, cand) : cand;
2538
+ if (files.has(full)) return { kind: 'repo', target: full };
2539
+ }
2540
+ }
2541
+ return { kind: lead ? 'unresolved' : 'third-party', target: null };
2542
+ }
2543
+
2544
+ function stripComments(text) {
2545
+ return text.replace(/\/\*[\s\S]*?\*\//g, m => m.replace(/[^\n]/g, ' ')).replace(/(^|[^:'"`\\])\/\/[^\n]*/g, (m, p1) => p1 + ' '.repeat(m.length - p1.length));
2546
+ }
2547
+
2548
+ function names(list) {
2549
+ return list.split(',').map(s => s.trim()).filter(Boolean).map(s => {
2550
+ const m = /^(?:\w+\s+as\s+)?([\w$]+)$/.exec(s.replace(/^type\s+/, '').replace(/^([\w$]+)\s*:\s*([\w$]+)$/, '$1 as $2'));
2551
+ const parts = s.replace(/^type\s+/, '').split(/\s+as\s+|\s*:\s*/).map(x => x.trim()).filter(Boolean);
2552
+ void m;
2553
+ if (parts.length === 2) return { imported: parts[0], local: parts[1] };
2554
+ if (parts.length === 1) return { imported: parts[0], local: parts[0] };
2555
+ return null;
2556
+ }).filter(Boolean).filter(n => /^[\w$]+$/.test(n.imported) && /^[\w$]+$/.test(n.local));
2557
+ }
2558
+
2559
+ /** Import entries for one JS/TS file. */
2560
+ function jsImports(file, text, files) {
2561
+ const t = stripComments(text);
2562
+ const out = [];
2563
+ const add = (local, source, member) => out.push({ file, local, source, member, ...resolveJsModule(file, source, files) });
2564
+ for (const m of t.matchAll(/\b(?:const|let|var)\s*\{([^}]*)\}\s*=\s*(?:await\s+)?require\(\s*['"]([^'"]+)['"]\s*\)/g)) for (const n of names(m[1])) add(n.local, m[2], n.imported);
2565
+ for (const m of t.matchAll(/\b(?:const|let|var)\s+([\w$]+)\s*=\s*(?:await\s+)?require\(\s*['"]([^'"]+)['"]\s*\)/g)) add(m[1], m[2], '*');
2566
+ for (const m of t.matchAll(/\bimport\s+(?:type\s+)?([\w$]+)?\s*,?\s*(?:\{([^}]*)\}|\*\s+as\s+([\w$]+))?\s*from\s*['"]([^'"]+)['"]/g)) {
2567
+ if (m[1]) add(m[1], m[4], 'default');
2568
+ if (m[2]) for (const n of names(m[2])) add(n.local, m[4], n.imported);
2569
+ if (m[3]) add(m[3], m[4], '*');
2570
+ }
2571
+ return out;
2572
+ }
2573
+
2574
+ function pyImports(file, text, files) {
2575
+ const out = [];
2576
+ const add = (local, source, member) => out.push({ file, local, source, member, ...resolvePyModule(file, source, files) });
2577
+ // the unparenthesised form ends at the line end; only the parenthesised form may span lines
2578
+ for (const m of text.matchAll(/^[ \t]*from[ \t]+([.\w]+)[ \t]+import[ \t]+(?:\(([\w\s,]+)\)|([\w \t,]+))/gm)) {
2579
+ for (const n of (m[2] ?? m[3]).split(',').map(s => s.trim()).filter(Boolean)) {
2580
+ const p = n.split(/\s+as\s+/);
2581
+ add(p[1] || p[0], m[1], p[0]);
2582
+ }
2583
+ }
2584
+ for (const m of text.matchAll(/^[ \t]*import\s+([.\w]+)(?:\s+as\s+(\w+))?\s*$/gm)) add(m[2] || m[1].split('.')[0], m[1], '*');
2585
+ return out;
2586
+ }
2587
+
2588
+ /** All import entries, deterministically ordered. */
2589
+ function buildImportMap(fileContents) {
2590
+ const files = new Set(Object.keys(fileContents));
2591
+ const out = [];
2592
+ for (const file of [...files].sort()) {
2593
+ if (/\.(?:js|jsx|mjs|cjs|ts|tsx)$/i.test(file)) out.push(...jsImports(file, fileContents[file], files));
2594
+ else if (/\.py$/i.test(file)) out.push(...pyImports(file, fileContents[file], files));
2595
+ }
2596
+ return out.sort((a, b) => (a.file < b.file ? -1 : a.file > b.file ? 1 : 0) || (a.local < b.local ? -1 : a.local > b.local ? 1 : 0) || (a.source < b.source ? -1 : 1));
2597
+ }
2598
+
2599
+ /**
2600
+ * Resolve a callee name used in `file` against the import map.
2601
+ * Returns { kind:'exact-import', targetFile, member } | { kind:'third-party' } | { kind:'unresolved-import' } | null.
2602
+ */
2603
+ function resolveViaImports(imports, file, calleeName) {
2604
+ const parts = String(calleeName || '').split('.');
2605
+ const head = parts[0];
2606
+ const imp = imports.filter(i => i.file === file && i.local === head);
2607
+ if (!imp.length) return null;
2608
+ const i = imp[0];
2609
+ if (i.kind === 'third-party') return { kind: 'third-party' };
2610
+ if (i.kind === 'unresolved') return { kind: 'unresolved-import' };
2611
+ if (parts.length === 1) {
2612
+ const member = i.member === '*' || i.member === 'default' ? head : i.member;
2613
+ return { kind: 'exact-import', targetFile: i.target, member, namespaceCall: i.member === '*' || i.member === 'default' };
2614
+ }
2615
+ if (i.member === '*' || i.member === 'default') return { kind: 'exact-import', targetFile: i.target, member: parts[parts.length - 1], namespaceCall: false };
2616
+ // `db.query()` where `db` is a NAMED import: a method of an imported object, not a function of that module
2617
+ return { kind: 'imported-object-method' };
2618
+ }
2619
+
2620
+
2485
2621
  /***/ }),
2486
2622
 
2487
2623
  /***/ 1325:
@@ -3320,7 +3456,7 @@ function importBatch(root, batchId, resultsText, ctx) {
3320
3456
  /* unused harmony exports DEFAULT_LIMITS, PROTECTION_DIMENSIONS */
3321
3457
  /* harmony import */ var _controls_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(17202);
3322
3458
  /* harmony import */ var _canonical_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(44652);
3323
- /* harmony import */ var _graph_schema_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(44287);
3459
+ /* harmony import */ var _graph_schema_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(50152);
3324
3460
  //
3325
3461
  // Deterministic, bounded reachability from an entry point's handler.
3326
3462
  //
@@ -3791,8 +3927,13 @@ function resolveCitation(c, segments, fileContents) {
3791
3927
  if (typeof text !== 'string') return 'file-not-in-snapshot';
3792
3928
  const fileLines = text.split('\n');
3793
3929
  if (c.endLine > fileLines.length) return 'beyond-end-of-file';
3794
- const inside = segments.some(s => s.file === c.file && c.startLine >= s.startLine && c.endLine <= s.endLine);
3795
- if (!inside) return 'outside-provided-context';
3930
+ const inside = segments.filter(s => s.file === c.file && c.startLine >= s.startLine && c.endLine <= s.endLine);
3931
+ if (!inside.length) return 'outside-provided-context';
3932
+ // GA-03.2 (0.195.26): the citation is bound to the exact lines the bundle was built from. A segment carries the
3933
+ // hash of its span at build time; if the snapshot's lines no longer hash to it, the model reviewed text that is
3934
+ // not what is on disk now, and the citation is stale rather than evidence. A segment with no recorded hash
3935
+ // (a hand-built one) cannot be judged and is not refused for that reason.
3936
+ if (inside.every(s => typeof s.spanHash === 'string' && (0,_canonical_js__WEBPACK_IMPORTED_MODULE_5__/* .sha256 */ .sc)((0,_canonical_js__WEBPACK_IMPORTED_MODULE_5__/* .sliceLines */ .iq)(text, s.startLine, s.endLine)) !== s.spanHash)) return 'stale-context';
3796
3937
  return claimGrounded(c.claim, fileLines.slice(c.startLine - 1, c.endLine).join('\n')) ? null : 'claim-not-grounded-in-cited-lines';
3797
3938
  }
3798
3939