@descryy/runtime-graph-correlator 0.0.0

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.
@@ -0,0 +1,87 @@
1
+ /**
2
+ * The **first leg** of the path the product requires:
3
+ * `Frontend FUNCTION → API_ENDPOINT → API_ROUTE → Backend FUNCTION`.
4
+ *
5
+ * `RUNTIME-CHECKLIST.md` §11 states that path and its own boxes covered
6
+ * only the last three legs — the audit added a box for the first and marked
7
+ * it absent. The graph has had the edge all along: `adapter-typescript`
8
+ * emits `USES_API` from a frontend function to the endpoint it calls, read
9
+ * from source. Nothing correlated it.
10
+ *
11
+ * ---
12
+ *
13
+ * **This is a structural claim, and calling it anything else would be the
14
+ * central lie this project exists not to tell.**
15
+ *
16
+ * `USES_API` says *this function contains a call to that endpoint*. It does
17
+ * **not** say this function issued the request that was just observed. When
18
+ * three components call one endpoint, the graph names all three and the
19
+ * browser saw one — and nothing in a `NETWORK_REQUEST` says which. Playwright
20
+ * exposes an initiator chain that could narrow it; the network collector
21
+ * does not capture it today (§6 records that as a gap), and until it does,
22
+ * *which* caller ran is not something this repo knows.
23
+ *
24
+ * So the outcome vocabulary says so at the type level: a single caller is
25
+ * `sole-structural-caller`, never `resolved`. A reader who sees `resolved`
26
+ * elsewhere in this package is looking at an observation; here they are
27
+ * looking at the only candidate the code admits, which is a different and
28
+ * weaker fact even when it happens to be right.
29
+ *
30
+ * **Consequence for reliability, per the architecture's resolution cap:** a
31
+ * finding whose path rests on this leg is capped by the `USES_API` edge's
32
+ * own resolution, exactly as any other structural hop is. Extending the
33
+ * path does not upgrade the evidence.
34
+ */
35
+ import { incoming } from "@descryy/core";
36
+ import { getNode } from "@descryy/core";
37
+ /**
38
+ * Every frontend function the graph says calls this endpoint.
39
+ *
40
+ * Ambiguity is returned to the caller rather than ranked, the same way
41
+ * `resolveSymbolNode` and `resolveEndpointNode` refuse it. There is a
42
+ * tempting heuristic here — prefer the caller in the file the browser most
43
+ * recently loaded, or the one whose line number is nearest the event — and
44
+ * both are timing and locality standing in for causality, which §27's own
45
+ * rule forbids in one sentence: *similarity must never be treated as
46
+ * causality*.
47
+ */
48
+ export function resolveFrontendCallers(driver, endpointNodeId) {
49
+ const edges = incoming(driver, endpointNodeId, { types: ["USES_API"] });
50
+ const callers = [];
51
+ const seen = new Set();
52
+ for (const edge of edges) {
53
+ if (seen.has(edge.from))
54
+ continue;
55
+ seen.add(edge.from);
56
+ const node = getNode(driver, edge.from);
57
+ if (node === null || node === undefined)
58
+ continue; // an edge whose source is not in this store: a disclosed gap, not a caller to invent
59
+ callers.push(node);
60
+ }
61
+ if (callers.length === 0) {
62
+ return {
63
+ outcome: "not-found",
64
+ reason: `No USES_API edge points at endpoint "${endpointNodeId}". The calling code was not read by an adapter, ` +
65
+ "or the call is not a direct one this extractor recognises. Reported as a gap in the graph rather than " +
66
+ "as an absence of callers.",
67
+ };
68
+ }
69
+ if (callers.length > 1) {
70
+ return {
71
+ outcome: "ambiguous",
72
+ candidates: callers,
73
+ reason: `${callers.length} frontend functions call this endpoint: ${callers.map((c) => c.name).join(", ")}. ` +
74
+ "The graph names all of them and the browser observed one request; nothing in that request says which " +
75
+ "caller issued it. Refused rather than ranked — preferring the nearest line or the most recently loaded " +
76
+ "file would be locality standing in for causality.",
77
+ };
78
+ }
79
+ return {
80
+ outcome: "sole-structural-caller",
81
+ node: callers[0],
82
+ reason: `"${callers[0].name}" is the only function the graph says calls this endpoint. That makes it the sole ` +
83
+ "candidate, not a confirmed caller: USES_API is read from source and states that a call exists, not that " +
84
+ "this call is the one that ran.",
85
+ };
86
+ }
87
+ //# sourceMappingURL=frontend-caller.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"frontend-caller.js","sourceRoot":"","sources":["../src/frontend-caller.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAEH,OAAO,EAAE,QAAQ,EAAkB,MAAM,eAAe,CAAC;AAEzD,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAUxC;;;;;;;;;;GAUG;AACH,MAAM,UAAU,sBAAsB,CAAC,MAAiB,EAAE,cAAsB;IAC9E,MAAM,KAAK,GAAG,QAAQ,CAAC,MAAM,EAAE,cAAc,EAAE,EAAE,KAAK,EAAE,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC;IAExE,MAAM,OAAO,GAAa,EAAE,CAAC;IAC7B,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAC/B,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC;YAAE,SAAS;QAClC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACpB,MAAM,IAAI,GAAG,OAAO,CAAC,MAAM,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;QACxC,IAAI,IAAI,KAAK,IAAI,IAAI,IAAI,KAAK,SAAS;YAAE,SAAS,CAAC,qFAAqF;QACxI,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACrB,CAAC;IAED,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACzB,OAAO;YACL,OAAO,EAAE,WAAW;YACpB,MAAM,EACJ,wCAAwC,cAAc,kDAAkD;gBACxG,wGAAwG;gBACxG,2BAA2B;SAC9B,CAAC;IACJ,CAAC;IAED,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACvB,OAAO;YACL,OAAO,EAAE,WAAW;YACpB,UAAU,EAAE,OAAO;YACnB,MAAM,EACJ,GAAG,OAAO,CAAC,MAAM,2CAA2C,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI;gBACrG,uGAAuG;gBACvG,yGAAyG;gBACzG,mDAAmD;SACtD,CAAC;IACJ,CAAC;IAED,OAAO;QACL,OAAO,EAAE,wBAAwB;QACjC,IAAI,EAAE,OAAO,CAAC,CAAC,CAAE;QACjB,MAAM,EACJ,IAAI,OAAO,CAAC,CAAC,CAAE,CAAC,IAAI,oFAAoF;YACxG,0GAA0G;YAC1G,gCAAgC;KACnC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * The graph correlator: resolves runtime evidence to descry-core graph
3
+ * nodes. Deliberately does not re-wrap graph-path building — `traceApi`
4
+ * (`@descryy/core`) already answers "how does this reach that" end to end,
5
+ * `direction: "both"` already crosses the `API_ENDPOINT` join with no extra
6
+ * option needed (DEC-228 traced this directly; `crossApiEndpoint` is only
7
+ * for `impactOf`'s single-direction walk). A caller resolves both ends here,
8
+ * then calls `traceApi` from `@descryy/core` directly — adding a wrapper
9
+ * around a function that already does the whole job would be an
10
+ * abstraction with nothing behind it.
11
+ */
12
+ export { resolveEndpointNode, resolveEndpointNodesByPath, type EndpointResolution } from "./endpoint.ts";
13
+ export { resolveSymbolNode, resolveSymbolAcrossRepos, resolveContainingModule, resolveTestCaseNode, type SymbolResolution, type CrossRepoSymbolResolution, type ModuleResolution, type TestCaseResolution, } from "./symbol.ts";
14
+ export { resolveEndpointFromLogText, findEndpointMentions, type LogEndpointResolution, type LogEndpointMention, } from "./log-endpoint.ts";
15
+ export { resolveFrontendCallers, type FrontendCallerResolution } from "./frontend-caller.ts";
16
+ export { resolveObservedFrontendCaller, type ObservedFrontendCallerResolution } from "./observed-frontend-caller.ts";
17
+ export { resolveDatabaseTableNode, type DatabaseTableResolution } from "./database-table.ts";
18
+ export { confirmObservedFrontendCaller, type ConfirmObservedCallerInput, type ConfirmObservedCallerOutcome, } from "./confirm-observed-caller.ts";
19
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,mBAAmB,EAAE,0BAA0B,EAAE,KAAK,kBAAkB,EAAE,MAAM,eAAe,CAAC;AACzG,OAAO,EACL,iBAAiB,EACjB,wBAAwB,EACxB,uBAAuB,EACvB,mBAAmB,EACnB,KAAK,gBAAgB,EACrB,KAAK,yBAAyB,EAC9B,KAAK,gBAAgB,EACrB,KAAK,kBAAkB,GACxB,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,0BAA0B,EAC1B,oBAAoB,EACpB,KAAK,qBAAqB,EAC1B,KAAK,kBAAkB,GACxB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,sBAAsB,EAAE,KAAK,wBAAwB,EAAE,MAAM,sBAAsB,CAAC;AAC7F,OAAO,EAAE,6BAA6B,EAAE,KAAK,gCAAgC,EAAE,MAAM,+BAA+B,CAAC;AACrH,OAAO,EAAE,wBAAwB,EAAE,KAAK,uBAAuB,EAAE,MAAM,qBAAqB,CAAC;AAC7F,OAAO,EACL,6BAA6B,EAC7B,KAAK,0BAA0B,EAC/B,KAAK,4BAA4B,GAClC,MAAM,8BAA8B,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,19 @@
1
+ /**
2
+ * The graph correlator: resolves runtime evidence to descry-core graph
3
+ * nodes. Deliberately does not re-wrap graph-path building — `traceApi`
4
+ * (`@descryy/core`) already answers "how does this reach that" end to end,
5
+ * `direction: "both"` already crosses the `API_ENDPOINT` join with no extra
6
+ * option needed (DEC-228 traced this directly; `crossApiEndpoint` is only
7
+ * for `impactOf`'s single-direction walk). A caller resolves both ends here,
8
+ * then calls `traceApi` from `@descryy/core` directly — adding a wrapper
9
+ * around a function that already does the whole job would be an
10
+ * abstraction with nothing behind it.
11
+ */
12
+ export { resolveEndpointNode, resolveEndpointNodesByPath } from "./endpoint.js";
13
+ export { resolveSymbolNode, resolveSymbolAcrossRepos, resolveContainingModule, resolveTestCaseNode, } from "./symbol.js";
14
+ export { resolveEndpointFromLogText, findEndpointMentions, } from "./log-endpoint.js";
15
+ export { resolveFrontendCallers } from "./frontend-caller.js";
16
+ export { resolveObservedFrontendCaller } from "./observed-frontend-caller.js";
17
+ export { resolveDatabaseTableNode } from "./database-table.js";
18
+ export { confirmObservedFrontendCaller, } from "./confirm-observed-caller.js";
19
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,mBAAmB,EAAE,0BAA0B,EAA2B,MAAM,eAAe,CAAC;AACzG,OAAO,EACL,iBAAiB,EACjB,wBAAwB,EACxB,uBAAuB,EACvB,mBAAmB,GAKpB,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,0BAA0B,EAC1B,oBAAoB,GAGrB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,sBAAsB,EAAiC,MAAM,sBAAsB,CAAC;AAC7F,OAAO,EAAE,6BAA6B,EAAyC,MAAM,+BAA+B,CAAC;AACrH,OAAO,EAAE,wBAAwB,EAAgC,MAAM,qBAAqB,CAAC;AAC7F,OAAO,EACL,6BAA6B,GAG9B,MAAM,8BAA8B,CAAC"}
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Resolving the API endpoint a **backend log line names in its own text** to
3
+ * an `API_ENDPOINT` node.
4
+ *
5
+ * **Why this exists.** RT-044's measurement, re-run after RT-045, left
6
+ * `confirmed` blocked by one precisely-named thing: four evidence items
7
+ * share the real request id, but three of them are the same source
8
+ * (`browser-network`) and the fourth — the backend exception — resolves to
9
+ * the *function* that threw, via its stack. So no node has two independent
10
+ * sources in one request, and the top report category is unreachable in
11
+ * principle rather than merely unmet.
12
+ *
13
+ * The backend's own header line, measured from the real fixture, is:
14
+ *
15
+ * ```
16
+ * [broken-invoices] DELETE /api/invoices/3 failed [request-id=...]: TypeError: ...
17
+ * ```
18
+ *
19
+ * It names the endpoint. A browser saying "I sent DELETE /api/invoices/3"
20
+ * and a backend saying "I failed DELETE /api/invoices/3" are **two
21
+ * independent observations of one request from opposite sides of the wire**,
22
+ * joined by an id the backend itself echoed. That is corroboration in the
23
+ * exact sense the gate means, and missing it was a gap in resolution, not a
24
+ * property of the evidence.
25
+ *
26
+ * **What this deliberately does not do.** It does not scan for a bare path.
27
+ * A log line mentioning `/api/invoices/3` with no method is not a statement
28
+ * that *this* endpoint was exercised — `GET` and `DELETE` on one path are
29
+ * different endpoints, and `endpoint.ts` already refuses a method mismatch
30
+ * for exactly that reason. Requiring an explicit HTTP method in the text is
31
+ * what keeps this from becoming a substring search that resolves whatever
32
+ * happens to look familiar (RT-031's identity-proxy class, applied to prose).
33
+ */
34
+ import type { SqlDriver } from "@descryy/core";
35
+ export interface LogEndpointMention {
36
+ readonly method: string;
37
+ readonly path: string;
38
+ }
39
+ /** Every `METHOD /path` pair the text states, in order, deduplicated. Empty when the text names none — the common and correct case for an ordinary log line. */
40
+ export declare function findEndpointMentions(text: string): readonly LogEndpointMention[];
41
+ export type LogEndpointResolution = {
42
+ readonly outcome: "resolved";
43
+ readonly nodeId: string;
44
+ readonly mention: LogEndpointMention;
45
+ } | {
46
+ readonly outcome: "no-mention";
47
+ readonly reason: string;
48
+ } | {
49
+ readonly outcome: "ambiguous";
50
+ readonly reason: string;
51
+ readonly mentions: readonly LogEndpointMention[];
52
+ } | {
53
+ readonly outcome: "not-found";
54
+ readonly reason: string;
55
+ readonly mentions: readonly LogEndpointMention[];
56
+ };
57
+ /**
58
+ * The endpoint a log line names, when it names exactly one that resolves.
59
+ *
60
+ * **Two mentions resolving to two different endpoints is refused, not
61
+ * ranked.** A line naming several routes (a proxy summary, a batch report)
62
+ * genuinely does not identify one, and picking the first would be a
63
+ * heuristic standing in for evidence. `endpoint.ts` refuses ambiguity to the
64
+ * caller and this inherits that rather than deciding differently.
65
+ */
66
+ export declare function resolveEndpointFromLogText(driver: SqlDriver, text: string): LogEndpointResolution;
67
+ //# sourceMappingURL=log-endpoint.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"log-endpoint.d.ts","sourceRoot":"","sources":["../src/log-endpoint.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAY/C,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED,gKAAgK;AAChK,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,kBAAkB,EAAE,CAchF;AAED,MAAM,MAAM,qBAAqB,GAC7B;IAAE,QAAQ,CAAC,OAAO,EAAE,UAAU,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,kBAAkB,CAAA;CAAE,GAC/F;IAAE,QAAQ,CAAC,OAAO,EAAE,YAAY,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAC3D;IAAE,QAAQ,CAAC,OAAO,EAAE,WAAW,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,QAAQ,EAAE,SAAS,kBAAkB,EAAE,CAAA;CAAE,GAC5G;IAAE,QAAQ,CAAC,OAAO,EAAE,WAAW,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,QAAQ,EAAE,SAAS,kBAAkB,EAAE,CAAA;CAAE,CAAC;AAEjH;;;;;;;;GAQG;AACH,wBAAgB,0BAA0B,CAAC,MAAM,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,GAAG,qBAAqB,CA8BjG"}
@@ -0,0 +1,97 @@
1
+ /**
2
+ * Resolving the API endpoint a **backend log line names in its own text** to
3
+ * an `API_ENDPOINT` node.
4
+ *
5
+ * **Why this exists.** RT-044's measurement, re-run after RT-045, left
6
+ * `confirmed` blocked by one precisely-named thing: four evidence items
7
+ * share the real request id, but three of them are the same source
8
+ * (`browser-network`) and the fourth — the backend exception — resolves to
9
+ * the *function* that threw, via its stack. So no node has two independent
10
+ * sources in one request, and the top report category is unreachable in
11
+ * principle rather than merely unmet.
12
+ *
13
+ * The backend's own header line, measured from the real fixture, is:
14
+ *
15
+ * ```
16
+ * [broken-invoices] DELETE /api/invoices/3 failed [request-id=...]: TypeError: ...
17
+ * ```
18
+ *
19
+ * It names the endpoint. A browser saying "I sent DELETE /api/invoices/3"
20
+ * and a backend saying "I failed DELETE /api/invoices/3" are **two
21
+ * independent observations of one request from opposite sides of the wire**,
22
+ * joined by an id the backend itself echoed. That is corroboration in the
23
+ * exact sense the gate means, and missing it was a gap in resolution, not a
24
+ * property of the evidence.
25
+ *
26
+ * **What this deliberately does not do.** It does not scan for a bare path.
27
+ * A log line mentioning `/api/invoices/3` with no method is not a statement
28
+ * that *this* endpoint was exercised — `GET` and `DELETE` on one path are
29
+ * different endpoints, and `endpoint.ts` already refuses a method mismatch
30
+ * for exactly that reason. Requiring an explicit HTTP method in the text is
31
+ * what keeps this from becoming a substring search that resolves whatever
32
+ * happens to look familiar (RT-031's identity-proxy class, applied to prose).
33
+ */
34
+ import { resolveEndpointNode } from "./endpoint.js";
35
+ /**
36
+ * `DELETE /api/invoices/3` anywhere in a line, method uppercase and
37
+ * standalone. The trailing character class stops at whitespace and at the
38
+ * punctuation that ordinarily follows a path in prose (`failed:`, `(404)`,
39
+ * `"..."`), so a real path is captured without swallowing the sentence.
40
+ */
41
+ const METHOD_AND_PATH = /\b(GET|POST|PUT|PATCH|DELETE|HEAD|OPTIONS)\s+(\/[^\s"'(),;]*)/g;
42
+ /** Every `METHOD /path` pair the text states, in order, deduplicated. Empty when the text names none — the common and correct case for an ordinary log line. */
43
+ export function findEndpointMentions(text) {
44
+ const seen = new Set();
45
+ const mentions = [];
46
+ for (const match of text.matchAll(METHOD_AND_PATH)) {
47
+ const method = match[1] ?? "";
48
+ // A trailing '.' or ':' is sentence punctuation, not part of the path.
49
+ const path = (match[2] ?? "").replace(/[.:,;]+$/, "");
50
+ if (path === "")
51
+ continue;
52
+ const key = `${method} ${path}`;
53
+ if (seen.has(key))
54
+ continue;
55
+ seen.add(key);
56
+ mentions.push({ method, path });
57
+ }
58
+ return mentions;
59
+ }
60
+ /**
61
+ * The endpoint a log line names, when it names exactly one that resolves.
62
+ *
63
+ * **Two mentions resolving to two different endpoints is refused, not
64
+ * ranked.** A line naming several routes (a proxy summary, a batch report)
65
+ * genuinely does not identify one, and picking the first would be a
66
+ * heuristic standing in for evidence. `endpoint.ts` refuses ambiguity to the
67
+ * caller and this inherits that rather than deciding differently.
68
+ */
69
+ export function resolveEndpointFromLogText(driver, text) {
70
+ const mentions = findEndpointMentions(text);
71
+ if (mentions.length === 0) {
72
+ return { outcome: "no-mention", reason: "The text states no `METHOD /path` pair. A bare path is not a statement that a particular endpoint was exercised." };
73
+ }
74
+ const resolved = [];
75
+ for (const mention of mentions) {
76
+ const resolution = resolveEndpointNode(driver, mention.method, mention.path);
77
+ if (resolution.outcome === "resolved")
78
+ resolved.push({ nodeId: resolution.node.id, mention });
79
+ }
80
+ if (resolved.length === 0) {
81
+ return {
82
+ outcome: "not-found",
83
+ reason: `The text names ${mentions.map((m) => `${m.method} ${m.path}`).join(", ")}, none of which resolves to an API_ENDPOINT in this graph.`,
84
+ mentions,
85
+ };
86
+ }
87
+ const distinctNodes = new Set(resolved.map((r) => r.nodeId));
88
+ if (distinctNodes.size > 1) {
89
+ return {
90
+ outcome: "ambiguous",
91
+ reason: `The text names ${distinctNodes.size} different endpoints that all resolve; a line naming several routes does not identify one, and picking the first would be a heuristic standing in for evidence.`,
92
+ mentions,
93
+ };
94
+ }
95
+ return { outcome: "resolved", nodeId: resolved[0].nodeId, mention: resolved[0].mention };
96
+ }
97
+ //# sourceMappingURL=log-endpoint.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"log-endpoint.js","sourceRoot":"","sources":["../src/log-endpoint.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAIH,OAAO,EAAE,mBAAmB,EAA2B,MAAM,eAAe,CAAC;AAE7E;;;;;GAKG;AACH,MAAM,eAAe,GAAG,gEAAgE,CAAC;AAOzF,gKAAgK;AAChK,MAAM,UAAU,oBAAoB,CAAC,IAAY;IAC/C,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAC/B,MAAM,QAAQ,GAAyB,EAAE,CAAC;IAC1C,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,QAAQ,CAAC,eAAe,CAAC,EAAE,CAAC;QACnD,MAAM,MAAM,GAAG,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QAC9B,uEAAuE;QACvE,MAAM,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC;QACtD,IAAI,IAAI,KAAK,EAAE;YAAE,SAAS;QAC1B,MAAM,GAAG,GAAG,GAAG,MAAM,IAAI,IAAI,EAAE,CAAC;QAChC,IAAI,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;YAAE,SAAS;QAC5B,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACd,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;IAClC,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC;AAQD;;;;;;;;GAQG;AACH,MAAM,UAAU,0BAA0B,CAAC,MAAiB,EAAE,IAAY;IACxE,MAAM,QAAQ,GAAG,oBAAoB,CAAC,IAAI,CAAC,CAAC;IAC5C,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC1B,OAAO,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,EAAE,kHAAkH,EAAE,CAAC;IAC/J,CAAC;IAED,MAAM,QAAQ,GAAsD,EAAE,CAAC;IACvE,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;QAC/B,MAAM,UAAU,GAAuB,mBAAmB,CAAC,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;QACjG,IAAI,UAAU,CAAC,OAAO,KAAK,UAAU;YAAE,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,UAAU,CAAC,IAAI,CAAC,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC;IAChG,CAAC;IAED,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC1B,OAAO;YACL,OAAO,EAAE,WAAW;YACpB,MAAM,EAAE,kBAAkB,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,4DAA4D;YAC7I,QAAQ;SACT,CAAC;IACJ,CAAC;IAED,MAAM,aAAa,GAAG,IAAI,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC;IAC7D,IAAI,aAAa,CAAC,IAAI,GAAG,CAAC,EAAE,CAAC;QAC3B,OAAO;YACL,OAAO,EAAE,WAAW;YACpB,MAAM,EAAE,kBAAkB,aAAa,CAAC,IAAI,iKAAiK;YAC7M,QAAQ;SACT,CAAC;IACJ,CAAC;IAED,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAE,CAAC,MAAM,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAC,CAAE,CAAC,OAAO,EAAE,CAAC;AAC7F,CAAC"}
@@ -0,0 +1,70 @@
1
+ /**
2
+ * The **observed** half of `frontend-caller.ts`'s deliberately structural
3
+ * answer — closing the gap that file's own doc comment names: "Playwright
4
+ * exposes an initiator chain that could narrow it; the network collector
5
+ * does not capture it today."
6
+ *
7
+ * `@descryhq-wq/runtime-browser`'s `frontend-initiator-capture.ts` now captures
8
+ * a real call-site stack for a `fetch()` request and attaches it to that
9
+ * request's `NETWORK_REQUEST` evidence. This file is what joins that stack
10
+ * to the graph: it resolves the stack's own site of interest
11
+ * (`primaryFrameLocation`, the identical rule every other stack-to-evidence
12
+ * join in this codebase already uses) to a `FUNCTION` node via
13
+ * `resolveSymbolNode`, and checks it against `resolveFrontendCallers`'s own
14
+ * structural candidate set for the endpoint in question.
15
+ *
16
+ * **Additive, not a replacement.** `resolveFrontendCallers` is untouched —
17
+ * every existing caller of it keeps exactly the outcomes it already had.
18
+ * This function calls it internally and only ever *adds* two outcomes on
19
+ * top: `observed`, when the stack resolves to a node the structural side
20
+ * already named as a candidate, and `structural-observed-conflict`, when it
21
+ * resolves to a real node the structural side did not name at all. Outside
22
+ * both — no stack, an unresolvable frame, an ambiguous or missing symbol —
23
+ * this returns exactly what `resolveFrontendCallers` alone would have, so
24
+ * every existing refusal discipline (`sole-structural-caller`, `ambiguous`,
25
+ * `not-found`) stands unchanged.
26
+ *
27
+ * **A disagreement is reported, never adjudicated.** When the resolved node
28
+ * is real but not among the structural candidates, that is the graph and
29
+ * the observation disagreeing — possibly because `USES_API` extraction
30
+ * missed a real call site (a dynamic call, a wrapper the adapter does not
31
+ * recognise), possibly because the resolved frame is itself wrong in some
32
+ * way this function cannot see. Both are worth a reader's attention;
33
+ * neither is silently preferred over the other. This is evidence detail
34
+ * within whichever of this project's five report categories the caller's
35
+ * own claim already lands in — not a sixth category, and not decided here.
36
+ */
37
+ import type { IRNode } from "@descryy/ir";
38
+ import type { SqlDriver } from "@descryy/core";
39
+ import { type StackTrace } from "@descryy/runtime-contracts";
40
+ import { type FrontendCallerResolution } from "./frontend-caller.ts";
41
+ export type ObservedFrontendCallerResolution = FrontendCallerResolution | {
42
+ /** A real captured call-site stack resolved to a node the structural side already named for this endpoint. The genuinely observed answer, not the sole candidate the source admits. */
43
+ readonly outcome: "observed";
44
+ readonly node: IRNode;
45
+ /** Every structural candidate this observation was checked against, for audit — including when it is a single-element `sole-structural-caller` list. */
46
+ readonly structuralCandidates: readonly IRNode[];
47
+ readonly reason: string;
48
+ } | {
49
+ /** A real captured call-site stack resolved to a real node, and that node is NOT among the structural candidates (possibly none at all) for this endpoint. */
50
+ readonly outcome: "structural-observed-conflict";
51
+ readonly structuralCandidates: readonly IRNode[];
52
+ readonly observedNode: IRNode;
53
+ readonly reason: string;
54
+ };
55
+ /**
56
+ * `stackTrace` is exactly `NETWORK_REQUEST.stackTrace` off the observed
57
+ * request that hit `endpointNodeId` — `null` whenever the collector could
58
+ * not attach one (no parser wired, header stripped in transit, nothing
59
+ * buffered for the id), which this function treats identically to "no
60
+ * observation was ever attempted": the structural answer, unchanged.
61
+ *
62
+ * `repo`/`repoRoot` are `resolveSymbolNode`'s own — the frontend repository
63
+ * the observed stack's frames are expected to belong to, since `FUNCTION`
64
+ * is repo-scoped and there is no honest default for it (see `symbol.ts`'s
65
+ * own doc comment).
66
+ */
67
+ export declare function resolveObservedFrontendCaller(driver: SqlDriver, endpointNodeId: string, stackTrace: StackTrace | null, repo: string, options?: {
68
+ readonly repoRoot?: string;
69
+ }): ObservedFrontendCallerResolution;
70
+ //# sourceMappingURL=observed-frontend-caller.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"observed-frontend-caller.d.ts","sourceRoot":"","sources":["../src/observed-frontend-caller.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAC/C,OAAO,EAAwB,KAAK,UAAU,EAAE,MAAM,4BAA4B,CAAC;AAEnF,OAAO,EAA0B,KAAK,wBAAwB,EAAE,MAAM,sBAAsB,CAAC;AAG7F,MAAM,MAAM,gCAAgC,GACxC,wBAAwB,GACxB;IACE,uLAAuL;IACvL,QAAQ,CAAC,OAAO,EAAE,UAAU,CAAC;IAC7B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,wJAAwJ;IACxJ,QAAQ,CAAC,oBAAoB,EAAE,SAAS,MAAM,EAAE,CAAC;IACjD,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB,GACD;IACE,8JAA8J;IAC9J,QAAQ,CAAC,OAAO,EAAE,8BAA8B,CAAC;IACjD,QAAQ,CAAC,oBAAoB,EAAE,SAAS,MAAM,EAAE,CAAC;IACjD,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB,CAAC;AAQN;;;;;;;;;;;GAWG;AACH,wBAAgB,6BAA6B,CAC3C,MAAM,EAAE,SAAS,EACjB,cAAc,EAAE,MAAM,EACtB,UAAU,EAAE,UAAU,GAAG,IAAI,EAC7B,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE;IAAE,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAO,GAC3C,gCAAgC,CAwDlC"}
@@ -0,0 +1,103 @@
1
+ /**
2
+ * The **observed** half of `frontend-caller.ts`'s deliberately structural
3
+ * answer — closing the gap that file's own doc comment names: "Playwright
4
+ * exposes an initiator chain that could narrow it; the network collector
5
+ * does not capture it today."
6
+ *
7
+ * `@descryhq-wq/runtime-browser`'s `frontend-initiator-capture.ts` now captures
8
+ * a real call-site stack for a `fetch()` request and attaches it to that
9
+ * request's `NETWORK_REQUEST` evidence. This file is what joins that stack
10
+ * to the graph: it resolves the stack's own site of interest
11
+ * (`primaryFrameLocation`, the identical rule every other stack-to-evidence
12
+ * join in this codebase already uses) to a `FUNCTION` node via
13
+ * `resolveSymbolNode`, and checks it against `resolveFrontendCallers`'s own
14
+ * structural candidate set for the endpoint in question.
15
+ *
16
+ * **Additive, not a replacement.** `resolveFrontendCallers` is untouched —
17
+ * every existing caller of it keeps exactly the outcomes it already had.
18
+ * This function calls it internally and only ever *adds* two outcomes on
19
+ * top: `observed`, when the stack resolves to a node the structural side
20
+ * already named as a candidate, and `structural-observed-conflict`, when it
21
+ * resolves to a real node the structural side did not name at all. Outside
22
+ * both — no stack, an unresolvable frame, an ambiguous or missing symbol —
23
+ * this returns exactly what `resolveFrontendCallers` alone would have, so
24
+ * every existing refusal discipline (`sole-structural-caller`, `ambiguous`,
25
+ * `not-found`) stands unchanged.
26
+ *
27
+ * **A disagreement is reported, never adjudicated.** When the resolved node
28
+ * is real but not among the structural candidates, that is the graph and
29
+ * the observation disagreeing — possibly because `USES_API` extraction
30
+ * missed a real call site (a dynamic call, a wrapper the adapter does not
31
+ * recognise), possibly because the resolved frame is itself wrong in some
32
+ * way this function cannot see. Both are worth a reader's attention;
33
+ * neither is silently preferred over the other. This is evidence detail
34
+ * within whichever of this project's five report categories the caller's
35
+ * own claim already lands in — not a sixth category, and not decided here.
36
+ */
37
+ import { primaryFrameLocation } from "@descryy/runtime-contracts";
38
+ import { resolveFrontendCallers } from "./frontend-caller.js";
39
+ import { resolveSymbolNode } from "./symbol.js";
40
+ function structuralCandidatesOf(structural) {
41
+ if (structural.outcome === "sole-structural-caller")
42
+ return [structural.node];
43
+ if (structural.outcome === "ambiguous")
44
+ return structural.candidates;
45
+ return [];
46
+ }
47
+ /**
48
+ * `stackTrace` is exactly `NETWORK_REQUEST.stackTrace` off the observed
49
+ * request that hit `endpointNodeId` — `null` whenever the collector could
50
+ * not attach one (no parser wired, header stripped in transit, nothing
51
+ * buffered for the id), which this function treats identically to "no
52
+ * observation was ever attempted": the structural answer, unchanged.
53
+ *
54
+ * `repo`/`repoRoot` are `resolveSymbolNode`'s own — the frontend repository
55
+ * the observed stack's frames are expected to belong to, since `FUNCTION`
56
+ * is repo-scoped and there is no honest default for it (see `symbol.ts`'s
57
+ * own doc comment).
58
+ */
59
+ export function resolveObservedFrontendCaller(driver, endpointNodeId, stackTrace, repo, options = {}) {
60
+ const structural = resolveFrontendCallers(driver, endpointNodeId);
61
+ const location = primaryFrameLocation(stackTrace);
62
+ // No stack, an empty stack, or one whose site of interest names nothing
63
+ // (a native/anonymous frame, or one this parser could not symbolicate at
64
+ // all) -- resolveSymbolNode has no name to search for either way, so the
65
+ // observation adds nothing usable. Same "no id, no functionName" fallback
66
+ // `resolveInitiatorStack` on the collector side already degrades to.
67
+ if (location === null || location.file === null || location.functionName === null) {
68
+ return structural;
69
+ }
70
+ const resolved = resolveSymbolNode(driver, location.line === null
71
+ ? { file: location.file, symbolName: location.functionName }
72
+ : { file: location.file, symbolName: location.functionName, line: location.line }, repo, options);
73
+ // Unresolved, ambiguous, or a line that does not fall inside the matched
74
+ // node's range: none of those is "the observed function", so none of
75
+ // them can upgrade or conflict with the structural claim. The existing
76
+ // structural-only outcome stands -- this function never invents a
77
+ // caller resolveSymbolNode itself refused to name.
78
+ if (resolved.outcome !== "resolved") {
79
+ return structural;
80
+ }
81
+ const structuralCandidates = structuralCandidatesOf(structural);
82
+ const isStructuralCandidate = structuralCandidates.some((candidate) => candidate.id === resolved.node.id);
83
+ if (isStructuralCandidate) {
84
+ return {
85
+ outcome: "observed",
86
+ node: resolved.node,
87
+ structuralCandidates,
88
+ reason: `"${resolved.node.name}" was resolved from a real call-site stack captured on the observed request, and it is ` +
89
+ `among the function(s) the graph's USES_API edge${structuralCandidates.length > 1 ? "s name" : " names"} for ` +
90
+ "this endpoint -- a genuinely observed answer, not the sole candidate the source admits.",
91
+ };
92
+ }
93
+ return {
94
+ outcome: "structural-observed-conflict",
95
+ structuralCandidates,
96
+ observedNode: resolved.node,
97
+ reason: `The observed request's call-site stack resolved to "${resolved.node.name}", which is NOT among the ` +
98
+ `function(s) the graph's USES_API edges name for this endpoint (${structuralCandidates.map((n) => n.name).join(", ") || "none"}). ` +
99
+ "This is a real disagreement between the static graph and what was actually observed -- reported as a " +
100
+ "conflict rather than silently trusted or discarded in either direction.",
101
+ };
102
+ }
103
+ //# sourceMappingURL=observed-frontend-caller.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"observed-frontend-caller.js","sourceRoot":"","sources":["../src/observed-frontend-caller.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AAIH,OAAO,EAAE,oBAAoB,EAAmB,MAAM,4BAA4B,CAAC;AAEnF,OAAO,EAAE,sBAAsB,EAAiC,MAAM,sBAAsB,CAAC;AAC7F,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAoBhD,SAAS,sBAAsB,CAAC,UAAoC;IAClE,IAAI,UAAU,CAAC,OAAO,KAAK,wBAAwB;QAAE,OAAO,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;IAC9E,IAAI,UAAU,CAAC,OAAO,KAAK,WAAW;QAAE,OAAO,UAAU,CAAC,UAAU,CAAC;IACrE,OAAO,EAAE,CAAC;AACZ,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,6BAA6B,CAC3C,MAAiB,EACjB,cAAsB,EACtB,UAA6B,EAC7B,IAAY,EACZ,UAA0C,EAAE;IAE5C,MAAM,UAAU,GAAG,sBAAsB,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;IAElE,MAAM,QAAQ,GAAG,oBAAoB,CAAC,UAAU,CAAC,CAAC;IAClD,wEAAwE;IACxE,yEAAyE;IACzE,yEAAyE;IACzE,0EAA0E;IAC1E,qEAAqE;IACrE,IAAI,QAAQ,KAAK,IAAI,IAAI,QAAQ,CAAC,IAAI,KAAK,IAAI,IAAI,QAAQ,CAAC,YAAY,KAAK,IAAI,EAAE,CAAC;QAClF,OAAO,UAAU,CAAC;IACpB,CAAC;IAED,MAAM,QAAQ,GAAG,iBAAiB,CAChC,MAAM,EACN,QAAQ,CAAC,IAAI,KAAK,IAAI;QACpB,CAAC,CAAC,EAAE,IAAI,EAAE,QAAQ,CAAC,IAAI,EAAE,UAAU,EAAE,QAAQ,CAAC,YAAY,EAAE;QAC5D,CAAC,CAAC,EAAE,IAAI,EAAE,QAAQ,CAAC,IAAI,EAAE,UAAU,EAAE,QAAQ,CAAC,YAAY,EAAE,IAAI,EAAE,QAAQ,CAAC,IAAI,EAAE,EACnF,IAAI,EACJ,OAAO,CACR,CAAC;IAEF,yEAAyE;IACzE,qEAAqE;IACrE,uEAAuE;IACvE,kEAAkE;IAClE,mDAAmD;IACnD,IAAI,QAAQ,CAAC,OAAO,KAAK,UAAU,EAAE,CAAC;QACpC,OAAO,UAAU,CAAC;IACpB,CAAC;IAED,MAAM,oBAAoB,GAAG,sBAAsB,CAAC,UAAU,CAAC,CAAC;IAChE,MAAM,qBAAqB,GAAG,oBAAoB,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,SAAS,CAAC,EAAE,KAAK,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAE1G,IAAI,qBAAqB,EAAE,CAAC;QAC1B,OAAO;YACL,OAAO,EAAE,UAAU;YACnB,IAAI,EAAE,QAAQ,CAAC,IAAI;YACnB,oBAAoB;YACpB,MAAM,EACJ,IAAI,QAAQ,CAAC,IAAI,CAAC,IAAI,yFAAyF;gBAC/G,kDAAkD,oBAAoB,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,QAAQ,OAAO;gBAC9G,yFAAyF;SAC5F,CAAC;IACJ,CAAC;IAED,OAAO;QACL,OAAO,EAAE,8BAA8B;QACvC,oBAAoB;QACpB,YAAY,EAAE,QAAQ,CAAC,IAAI;QAC3B,MAAM,EACJ,uDAAuD,QAAQ,CAAC,IAAI,CAAC,IAAI,4BAA4B;YACrG,kEAAkE,oBAAoB,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,MAAM,KAAK;YACnI,uGAAuG;YACvG,yEAAyE;KAC5E,CAAC;AACJ,CAAC"}