@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.
- package/dist/confirm-observed-caller.d.ts +92 -0
- package/dist/confirm-observed-caller.d.ts.map +1 -0
- package/dist/confirm-observed-caller.js +117 -0
- package/dist/confirm-observed-caller.js.map +1 -0
- package/dist/database-table.d.ts +40 -0
- package/dist/database-table.d.ts.map +1 -0
- package/dist/database-table.js +52 -0
- package/dist/database-table.js.map +1 -0
- package/dist/endpoint.d.ts +73 -0
- package/dist/endpoint.d.ts.map +1 -0
- package/dist/endpoint.js +115 -0
- package/dist/endpoint.js.map +1 -0
- package/dist/frontend-caller.d.ts +67 -0
- package/dist/frontend-caller.d.ts.map +1 -0
- package/dist/frontend-caller.js +87 -0
- package/dist/frontend-caller.js.map +1 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +19 -0
- package/dist/index.js.map +1 -0
- package/dist/log-endpoint.d.ts +67 -0
- package/dist/log-endpoint.d.ts.map +1 -0
- package/dist/log-endpoint.js +97 -0
- package/dist/log-endpoint.js.map +1 -0
- package/dist/observed-frontend-caller.d.ts +70 -0
- package/dist/observed-frontend-caller.d.ts.map +1 -0
- package/dist/observed-frontend-caller.js +103 -0
- package/dist/observed-frontend-caller.js.map +1 -0
- package/dist/symbol.d.ts +244 -0
- package/dist/symbol.d.ts.map +1 -0
- package/dist/symbol.js +344 -0
- package/dist/symbol.js.map +1 -0
- package/package.json +31 -0
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The first production caller of `applyRuntimeObservations()` — architecture
|
|
3
|
+
* §11B.3's R4 runtime confirmation, wired to a real observation source.
|
|
4
|
+
*
|
|
5
|
+
* §11B.3 calls runtime confirmation *"the core technical moat rather than the
|
|
6
|
+
* graph itself"*, and states the consequence plainly: *"a purely static
|
|
7
|
+
* code-graph tool is capped at R3 permanently."* The mechanism has existed in
|
|
8
|
+
* `@descryy/core` since it was built, and the two EARNED tables it writes
|
|
9
|
+
* (`observed_edges`, `edge_corrections`) have been in the store's schema since
|
|
10
|
+
* the store was first written — each with a comment explaining why they cannot
|
|
11
|
+
* be regenerated from source at any price. **Nothing read or wrote either one.**
|
|
12
|
+
* This is what makes the moat run.
|
|
13
|
+
*
|
|
14
|
+
* ## Why this producer
|
|
15
|
+
*
|
|
16
|
+
* `resolveObservedFrontendCaller` already does the hard half. It takes a real
|
|
17
|
+
* captured `fetch()` call-site stack, resolves its site of interest to a
|
|
18
|
+
* `FUNCTION` node, and checks that node against the structural candidate set the
|
|
19
|
+
* graph holds for the endpoint. Its two observed outcomes *are* two of §11B.3's
|
|
20
|
+
* three arrows, and needed only to be spoken in the vocabulary
|
|
21
|
+
* `applyRuntimeObservations` reads:
|
|
22
|
+
*
|
|
23
|
+
* | resolution outcome | arrow |
|
|
24
|
+
* | --- | --- |
|
|
25
|
+
* | `observed` | the graph already holds this `USES_API` edge below R4 — **promote it** |
|
|
26
|
+
* | `structural-observed-conflict` | a real node the structural side never named — **mint the edge at R4** (§11B.3's *"recall gap closed"*) |
|
|
27
|
+
*
|
|
28
|
+
* Everything else — no stack, an unresolvable frame, an ambiguous or missing
|
|
29
|
+
* symbol — produces **no observation at all**. Not an empty confirmation that
|
|
30
|
+
* looks like one; none.
|
|
31
|
+
*
|
|
32
|
+
* ## Why there is no denial arrow here, and why that is a decision
|
|
33
|
+
*
|
|
34
|
+
* `applyRuntimeObservations` accepts `held: false` — runtime saw that an edge
|
|
35
|
+
* did not hold — and records a correction that demotes the edge's confidence.
|
|
36
|
+
* **This producer never emits one, and must not.**
|
|
37
|
+
*
|
|
38
|
+
* A frontend caller resolution can establish that a call *happened*. It cannot
|
|
39
|
+
* establish that one did not: a page visit exercises one code path, and the
|
|
40
|
+
* absence of a captured stack for some other function is absence of evidence,
|
|
41
|
+
* not evidence of absence. Minting a denial from that would demote a correct
|
|
42
|
+
* edge on the strength of a route the run happened not to take — precisely the
|
|
43
|
+
* wrong-direction failure rule 2 exists to prevent, arriving through the one
|
|
44
|
+
* mechanism designed to make the graph *more* trustworthy.
|
|
45
|
+
*
|
|
46
|
+
* The denial arrow is real and stays available; it belongs to a producer that
|
|
47
|
+
* can actually establish non-occurrence (an exhaustive call-graph trace over a
|
|
48
|
+
* fully exercised path), not to this one.
|
|
49
|
+
*
|
|
50
|
+
* ## What this does not do
|
|
51
|
+
*
|
|
52
|
+
* - **It does not judge a finding.** Resolution has no opinion on confidence
|
|
53
|
+
* (RT-027). Nothing here reads or writes a finding, hypothesis or category.
|
|
54
|
+
* - **It does not decide staleness.** An R4-vs-R4 disagreement is reported by
|
|
55
|
+
* the core function as `staleR4` and passed through untouched.
|
|
56
|
+
* - **It does not invent a node.** That refusal is core's, and its `refused`
|
|
57
|
+
* list is passed through rather than swallowed.
|
|
58
|
+
*/
|
|
59
|
+
import type { RuntimeEdgeObservation } from "@descryy/ir";
|
|
60
|
+
import { type RuntimeConfirmationResult, type SqlDriver } from "@descryy/core";
|
|
61
|
+
import type { StackTrace } from "@descryy/runtime-contracts";
|
|
62
|
+
import { type ObservedFrontendCallerResolution } from "./observed-frontend-caller.ts";
|
|
63
|
+
export interface ConfirmObservedCallerInput {
|
|
64
|
+
/** The endpoint whose callers this observation is about. */
|
|
65
|
+
readonly endpointNodeId: string;
|
|
66
|
+
/** The captured call-site stack, or `null` when the collector captured none. */
|
|
67
|
+
readonly stackTrace: StackTrace | null;
|
|
68
|
+
readonly repo: string;
|
|
69
|
+
/** The run that witnessed this. Becomes `edges.observed_by_run` and `observed_edges.run_id`. */
|
|
70
|
+
readonly runId: string;
|
|
71
|
+
/** The commit the observed application was built from, so a later reader can tell whether the code moved under the observation. */
|
|
72
|
+
readonly commitSha: string;
|
|
73
|
+
readonly repoRoot?: string;
|
|
74
|
+
}
|
|
75
|
+
export interface ConfirmObservedCallerOutcome {
|
|
76
|
+
/**
|
|
77
|
+
* The underlying resolution, unchanged and always present — including when it
|
|
78
|
+
* produced no observation. A caller that wants the structural answer still
|
|
79
|
+
* gets exactly what `resolveObservedFrontendCaller` would have returned.
|
|
80
|
+
*/
|
|
81
|
+
readonly resolution: ObservedFrontendCallerResolution;
|
|
82
|
+
/** What was sent to the graph. Empty when the resolution observed nothing. */
|
|
83
|
+
readonly observations: readonly RuntimeEdgeObservation[];
|
|
84
|
+
/** Core's own result, passed through whole — refusals and `staleR4` included. */
|
|
85
|
+
readonly confirmation: RuntimeConfirmationResult;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Resolve one observed frontend call and, when it genuinely observed something,
|
|
89
|
+
* record it as R4 evidence against the graph.
|
|
90
|
+
*/
|
|
91
|
+
export declare function confirmObservedFrontendCaller(driver: SqlDriver, input: ConfirmObservedCallerInput): ConfirmObservedCallerOutcome;
|
|
92
|
+
//# sourceMappingURL=confirm-observed-caller.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"confirm-observed-caller.d.ts","sourceRoot":"","sources":["../src/confirm-observed-caller.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyDG;AAEH,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAC;AAC1D,OAAO,EAEL,KAAK,yBAAyB,EAC9B,KAAK,SAAS,EACf,MAAM,eAAe,CAAC;AACvB,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAC;AAE7D,OAAO,EAEL,KAAK,gCAAgC,EACtC,MAAM,+BAA+B,CAAC;AAEvC,MAAM,WAAW,0BAA0B;IACzC,4DAA4D;IAC5D,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,gFAAgF;IAChF,QAAQ,CAAC,UAAU,EAAE,UAAU,GAAG,IAAI,CAAC;IACvC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,gGAAgG;IAChG,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,mIAAmI;IACnI,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,MAAM,WAAW,4BAA4B;IAC3C;;;;OAIG;IACH,QAAQ,CAAC,UAAU,EAAE,gCAAgC,CAAC;IACtD,8EAA8E;IAC9E,QAAQ,CAAC,YAAY,EAAE,SAAS,sBAAsB,EAAE,CAAC;IACzD,iFAAiF;IACjF,QAAQ,CAAC,YAAY,EAAE,yBAAyB,CAAC;CAClD;AAMD;;;GAGG;AACH,wBAAgB,6BAA6B,CAC3C,MAAM,EAAE,SAAS,EACjB,KAAK,EAAE,0BAA0B,GAChC,4BAA4B,CAyB9B"}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The first production caller of `applyRuntimeObservations()` — architecture
|
|
3
|
+
* §11B.3's R4 runtime confirmation, wired to a real observation source.
|
|
4
|
+
*
|
|
5
|
+
* §11B.3 calls runtime confirmation *"the core technical moat rather than the
|
|
6
|
+
* graph itself"*, and states the consequence plainly: *"a purely static
|
|
7
|
+
* code-graph tool is capped at R3 permanently."* The mechanism has existed in
|
|
8
|
+
* `@descryy/core` since it was built, and the two EARNED tables it writes
|
|
9
|
+
* (`observed_edges`, `edge_corrections`) have been in the store's schema since
|
|
10
|
+
* the store was first written — each with a comment explaining why they cannot
|
|
11
|
+
* be regenerated from source at any price. **Nothing read or wrote either one.**
|
|
12
|
+
* This is what makes the moat run.
|
|
13
|
+
*
|
|
14
|
+
* ## Why this producer
|
|
15
|
+
*
|
|
16
|
+
* `resolveObservedFrontendCaller` already does the hard half. It takes a real
|
|
17
|
+
* captured `fetch()` call-site stack, resolves its site of interest to a
|
|
18
|
+
* `FUNCTION` node, and checks that node against the structural candidate set the
|
|
19
|
+
* graph holds for the endpoint. Its two observed outcomes *are* two of §11B.3's
|
|
20
|
+
* three arrows, and needed only to be spoken in the vocabulary
|
|
21
|
+
* `applyRuntimeObservations` reads:
|
|
22
|
+
*
|
|
23
|
+
* | resolution outcome | arrow |
|
|
24
|
+
* | --- | --- |
|
|
25
|
+
* | `observed` | the graph already holds this `USES_API` edge below R4 — **promote it** |
|
|
26
|
+
* | `structural-observed-conflict` | a real node the structural side never named — **mint the edge at R4** (§11B.3's *"recall gap closed"*) |
|
|
27
|
+
*
|
|
28
|
+
* Everything else — no stack, an unresolvable frame, an ambiguous or missing
|
|
29
|
+
* symbol — produces **no observation at all**. Not an empty confirmation that
|
|
30
|
+
* looks like one; none.
|
|
31
|
+
*
|
|
32
|
+
* ## Why there is no denial arrow here, and why that is a decision
|
|
33
|
+
*
|
|
34
|
+
* `applyRuntimeObservations` accepts `held: false` — runtime saw that an edge
|
|
35
|
+
* did not hold — and records a correction that demotes the edge's confidence.
|
|
36
|
+
* **This producer never emits one, and must not.**
|
|
37
|
+
*
|
|
38
|
+
* A frontend caller resolution can establish that a call *happened*. It cannot
|
|
39
|
+
* establish that one did not: a page visit exercises one code path, and the
|
|
40
|
+
* absence of a captured stack for some other function is absence of evidence,
|
|
41
|
+
* not evidence of absence. Minting a denial from that would demote a correct
|
|
42
|
+
* edge on the strength of a route the run happened not to take — precisely the
|
|
43
|
+
* wrong-direction failure rule 2 exists to prevent, arriving through the one
|
|
44
|
+
* mechanism designed to make the graph *more* trustworthy.
|
|
45
|
+
*
|
|
46
|
+
* The denial arrow is real and stays available; it belongs to a producer that
|
|
47
|
+
* can actually establish non-occurrence (an exhaustive call-graph trace over a
|
|
48
|
+
* fully exercised path), not to this one.
|
|
49
|
+
*
|
|
50
|
+
* ## What this does not do
|
|
51
|
+
*
|
|
52
|
+
* - **It does not judge a finding.** Resolution has no opinion on confidence
|
|
53
|
+
* (RT-027). Nothing here reads or writes a finding, hypothesis or category.
|
|
54
|
+
* - **It does not decide staleness.** An R4-vs-R4 disagreement is reported by
|
|
55
|
+
* the core function as `staleR4` and passed through untouched.
|
|
56
|
+
* - **It does not invent a node.** That refusal is core's, and its `refused`
|
|
57
|
+
* list is passed through rather than swallowed.
|
|
58
|
+
*/
|
|
59
|
+
import { applyRuntimeObservations, } from "@descryy/core";
|
|
60
|
+
import { resolveObservedFrontendCaller, } from "./observed-frontend-caller.js";
|
|
61
|
+
const EMPTY = {
|
|
62
|
+
promoted: [], created: [], confirmed: [], contradictions: [], staleR4: [], refused: [],
|
|
63
|
+
};
|
|
64
|
+
/**
|
|
65
|
+
* Resolve one observed frontend call and, when it genuinely observed something,
|
|
66
|
+
* record it as R4 evidence against the graph.
|
|
67
|
+
*/
|
|
68
|
+
export function confirmObservedFrontendCaller(driver, input) {
|
|
69
|
+
const resolution = resolveObservedFrontendCaller(driver, input.endpointNodeId, input.stackTrace, input.repo, input.repoRoot === undefined ? {} : { repoRoot: input.repoRoot });
|
|
70
|
+
const observations = observationsFor(resolution, input.endpointNodeId);
|
|
71
|
+
if (observations.length === 0) {
|
|
72
|
+
// Nothing observed. Deliberately not a call with an empty list: an
|
|
73
|
+
// `adapter_runs` row would be minted for a run that witnessed nothing,
|
|
74
|
+
// which reads in the ledger exactly like a run that observed and found
|
|
75
|
+
// nothing to say. Those are different statements.
|
|
76
|
+
return { resolution, observations, confirmation: EMPTY };
|
|
77
|
+
}
|
|
78
|
+
const confirmation = applyRuntimeObservations(driver, {
|
|
79
|
+
runId: input.runId,
|
|
80
|
+
commitSha: input.commitSha,
|
|
81
|
+
observations,
|
|
82
|
+
});
|
|
83
|
+
return { resolution, observations, confirmation };
|
|
84
|
+
}
|
|
85
|
+
function observationsFor(resolution, endpointNodeId) {
|
|
86
|
+
if (resolution.outcome === "observed") {
|
|
87
|
+
return [
|
|
88
|
+
{
|
|
89
|
+
from: resolution.node.id,
|
|
90
|
+
to: endpointNodeId,
|
|
91
|
+
edgeType: "USES_API",
|
|
92
|
+
held: true,
|
|
93
|
+
detail: `A captured fetch() call-site stack resolved to ${resolution.node.name} ` +
|
|
94
|
+
`(${resolution.node.file ?? "unknown file"}), which the graph already names as a ` +
|
|
95
|
+
"structural caller of this endpoint.",
|
|
96
|
+
},
|
|
97
|
+
];
|
|
98
|
+
}
|
|
99
|
+
if (resolution.outcome === "structural-observed-conflict") {
|
|
100
|
+
return [
|
|
101
|
+
{
|
|
102
|
+
from: resolution.observedNode.id,
|
|
103
|
+
to: endpointNodeId,
|
|
104
|
+
edgeType: "USES_API",
|
|
105
|
+
held: true,
|
|
106
|
+
detail: `A captured fetch() call-site stack resolved to ${resolution.observedNode.name} ` +
|
|
107
|
+
`(${resolution.observedNode.file ?? "unknown file"}), which static analysis did not name ` +
|
|
108
|
+
`as a caller of this endpoint (${resolution.structuralCandidates.length} structural ` +
|
|
109
|
+
"candidate(s) found, none of them this one). Recorded as observed rather than adjudicated.",
|
|
110
|
+
},
|
|
111
|
+
];
|
|
112
|
+
}
|
|
113
|
+
// Every other outcome is a refusal or a purely structural answer. Neither is
|
|
114
|
+
// an observation, and neither becomes one by being written down.
|
|
115
|
+
return [];
|
|
116
|
+
}
|
|
117
|
+
//# sourceMappingURL=confirm-observed-caller.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"confirm-observed-caller.js","sourceRoot":"","sources":["../src/confirm-observed-caller.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyDG;AAGH,OAAO,EACL,wBAAwB,GAGzB,MAAM,eAAe,CAAC;AAGvB,OAAO,EACL,6BAA6B,GAE9B,MAAM,+BAA+B,CAAC;AA4BvC,MAAM,KAAK,GAA8B;IACvC,QAAQ,EAAE,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE,SAAS,EAAE,EAAE,EAAE,cAAc,EAAE,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE,OAAO,EAAE,EAAE;CACvF,CAAC;AAEF;;;GAGG;AACH,MAAM,UAAU,6BAA6B,CAC3C,MAAiB,EACjB,KAAiC;IAEjC,MAAM,UAAU,GAAG,6BAA6B,CAC9C,MAAM,EACN,KAAK,CAAC,cAAc,EACpB,KAAK,CAAC,UAAU,EAChB,KAAK,CAAC,IAAI,EACV,KAAK,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,KAAK,CAAC,QAAQ,EAAE,CACjE,CAAC;IAEF,MAAM,YAAY,GAAG,eAAe,CAAC,UAAU,EAAE,KAAK,CAAC,cAAc,CAAC,CAAC;IACvE,IAAI,YAAY,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC9B,mEAAmE;QACnE,uEAAuE;QACvE,uEAAuE;QACvE,kDAAkD;QAClD,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,YAAY,EAAE,KAAK,EAAE,CAAC;IAC3D,CAAC;IAED,MAAM,YAAY,GAAG,wBAAwB,CAAC,MAAM,EAAE;QACpD,KAAK,EAAE,KAAK,CAAC,KAAK;QAClB,SAAS,EAAE,KAAK,CAAC,SAAS;QAC1B,YAAY;KACb,CAAC,CAAC;IAEH,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,YAAY,EAAE,CAAC;AACpD,CAAC;AAED,SAAS,eAAe,CACtB,UAA4C,EAC5C,cAAsB;IAEtB,IAAI,UAAU,CAAC,OAAO,KAAK,UAAU,EAAE,CAAC;QACtC,OAAO;YACL;gBACE,IAAI,EAAE,UAAU,CAAC,IAAI,CAAC,EAAE;gBACxB,EAAE,EAAE,cAAc;gBAClB,QAAQ,EAAE,UAAU;gBACpB,IAAI,EAAE,IAAI;gBACV,MAAM,EACJ,kDAAkD,UAAU,CAAC,IAAI,CAAC,IAAI,GAAG;oBACzE,IAAI,UAAU,CAAC,IAAI,CAAC,IAAI,IAAI,cAAc,wCAAwC;oBAClF,qCAAqC;aACxC;SACF,CAAC;IACJ,CAAC;IAED,IAAI,UAAU,CAAC,OAAO,KAAK,8BAA8B,EAAE,CAAC;QAC1D,OAAO;YACL;gBACE,IAAI,EAAE,UAAU,CAAC,YAAY,CAAC,EAAE;gBAChC,EAAE,EAAE,cAAc;gBAClB,QAAQ,EAAE,UAAU;gBACpB,IAAI,EAAE,IAAI;gBACV,MAAM,EACJ,kDAAkD,UAAU,CAAC,YAAY,CAAC,IAAI,GAAG;oBACjF,IAAI,UAAU,CAAC,YAAY,CAAC,IAAI,IAAI,cAAc,wCAAwC;oBAC1F,iCAAiC,UAAU,CAAC,oBAAoB,CAAC,MAAM,cAAc;oBACrF,2FAA2F;aAC9F;SACF,CAAC;IACJ,CAAC;IAED,6EAA6E;IAC7E,iEAAiE;IACjE,OAAO,EAAE,CAAC;AACZ,CAAC"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolving a captured query's target table name (a real, observed string —
|
|
3
|
+
* `extractTargetTable`, `@descryhq-wq/runtime-database-observation`) to the
|
|
4
|
+
* `DATABASE_TABLE` node it names — the §10/§18 join `DatabaseQueryCollector`
|
|
5
|
+
* disclosed as missing since RT-141: real query capture, `graphNodeId`
|
|
6
|
+
* always null.
|
|
7
|
+
*
|
|
8
|
+
* **`DATABASE_TABLE` is workspace-scoped, not repo-scoped** (`adapter-sql`'s
|
|
9
|
+
* own `extract`/`adapter.ts`, DEC-054) — the same reason `resolveEndpointNode`
|
|
10
|
+
* (`endpoint.ts`, this package) takes no `repo` parameter: a migration can
|
|
11
|
+
* live in one repository and the service reading the table in another, and
|
|
12
|
+
* DEC-054 exists specifically so they still join. A runtime observation
|
|
13
|
+
* carries no repository at all for a bare table name — the observed
|
|
14
|
+
* process's own source may not even be one of the repos this graph indexed
|
|
15
|
+
* — so taking a `repo` filter here would be inventing a constraint the
|
|
16
|
+
* caller has no way to satisfy.
|
|
17
|
+
*
|
|
18
|
+
* **Unlike an endpoint, a table name is not a template.** No segment
|
|
19
|
+
* matching, no `{param}` — `DATABASE_TABLE.name` is the bare table name
|
|
20
|
+
* (`adapter-sql`'s `extract.ts`: `name: table.table`), so this is a plain
|
|
21
|
+
* equality match against every candidate, the simpler half of
|
|
22
|
+
* `resolveEndpointNode`'s own two-part job.
|
|
23
|
+
*/
|
|
24
|
+
import type { IRNode } from "@descryy/ir";
|
|
25
|
+
import { type SqlDriver } from "@descryy/core";
|
|
26
|
+
export type DatabaseTableResolution = {
|
|
27
|
+
readonly outcome: "resolved";
|
|
28
|
+
readonly node: IRNode;
|
|
29
|
+
} | {
|
|
30
|
+
readonly outcome: "not-found";
|
|
31
|
+
readonly reason: string;
|
|
32
|
+
} | {
|
|
33
|
+
readonly outcome: "ambiguous";
|
|
34
|
+
readonly candidates: readonly IRNode[];
|
|
35
|
+
readonly reason: string;
|
|
36
|
+
};
|
|
37
|
+
export declare function resolveDatabaseTableNode(driver: SqlDriver, tableName: string, options?: {
|
|
38
|
+
readonly limit?: number;
|
|
39
|
+
}): DatabaseTableResolution;
|
|
40
|
+
//# sourceMappingURL=database-table.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"database-table.d.ts","sourceRoot":"","sources":["../src/database-table.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAAe,KAAK,SAAS,EAAE,MAAM,eAAe,CAAC;AAE5D,MAAM,MAAM,uBAAuB,GAC/B;IAAE,QAAQ,CAAC,OAAO,EAAE,UAAU,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GACvD;IAAE,QAAQ,CAAC,OAAO,EAAE,WAAW,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAC1D;IAAE,QAAQ,CAAC,OAAO,EAAE,WAAW,CAAC;IAAC,QAAQ,CAAC,UAAU,EAAE,SAAS,MAAM,EAAE,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAEvG,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,SAAS,EACjB,SAAS,EAAE,MAAM,EACjB,OAAO,GAAE;IAAE,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAO,GACxC,uBAAuB,CA+BzB"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolving a captured query's target table name (a real, observed string —
|
|
3
|
+
* `extractTargetTable`, `@descryhq-wq/runtime-database-observation`) to the
|
|
4
|
+
* `DATABASE_TABLE` node it names — the §10/§18 join `DatabaseQueryCollector`
|
|
5
|
+
* disclosed as missing since RT-141: real query capture, `graphNodeId`
|
|
6
|
+
* always null.
|
|
7
|
+
*
|
|
8
|
+
* **`DATABASE_TABLE` is workspace-scoped, not repo-scoped** (`adapter-sql`'s
|
|
9
|
+
* own `extract`/`adapter.ts`, DEC-054) — the same reason `resolveEndpointNode`
|
|
10
|
+
* (`endpoint.ts`, this package) takes no `repo` parameter: a migration can
|
|
11
|
+
* live in one repository and the service reading the table in another, and
|
|
12
|
+
* DEC-054 exists specifically so they still join. A runtime observation
|
|
13
|
+
* carries no repository at all for a bare table name — the observed
|
|
14
|
+
* process's own source may not even be one of the repos this graph indexed
|
|
15
|
+
* — so taking a `repo` filter here would be inventing a constraint the
|
|
16
|
+
* caller has no way to satisfy.
|
|
17
|
+
*
|
|
18
|
+
* **Unlike an endpoint, a table name is not a template.** No segment
|
|
19
|
+
* matching, no `{param}` — `DATABASE_TABLE.name` is the bare table name
|
|
20
|
+
* (`adapter-sql`'s `extract.ts`: `name: table.table`), so this is a plain
|
|
21
|
+
* equality match against every candidate, the simpler half of
|
|
22
|
+
* `resolveEndpointNode`'s own two-part job.
|
|
23
|
+
*/
|
|
24
|
+
import { nodesOfType } from "@descryy/core";
|
|
25
|
+
export function resolveDatabaseTableNode(driver, tableName, options = {}) {
|
|
26
|
+
const { nodes, truncated } = nodesOfType(driver, "DATABASE_TABLE", options.limit ?? 5000);
|
|
27
|
+
const matches = nodes.filter((node) => node.name === tableName);
|
|
28
|
+
if (matches.length === 0) {
|
|
29
|
+
return {
|
|
30
|
+
outcome: "not-found",
|
|
31
|
+
reason: `No DATABASE_TABLE node named "${tableName}".` +
|
|
32
|
+
(truncated
|
|
33
|
+
? " The DATABASE_TABLE scan was truncated by the node budget, so this may be a coverage " +
|
|
34
|
+
"gap rather than a genuine absence."
|
|
35
|
+
: " Either no schema declares this table, or no SQL adapter has read the migration that " +
|
|
36
|
+
"creates it — a real gap to report rather than assume clean."),
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
if (matches.length > 1) {
|
|
40
|
+
return {
|
|
41
|
+
outcome: "ambiguous",
|
|
42
|
+
candidates: matches,
|
|
43
|
+
reason: `${matches.length} DATABASE_TABLE nodes are named "${tableName}" — ` +
|
|
44
|
+
`${matches.map((n) => `${n.attrs.schema ?? "?"}.${n.name}`).join(", ")}. ` +
|
|
45
|
+
"Two schemas naming the same table is real and not rare (a per-tenant schema pattern, " +
|
|
46
|
+
"or two workspaces this graph both indexed); picking one would be a guess this resolver " +
|
|
47
|
+
"has no basis for.",
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
return { outcome: "resolved", node: matches[0] };
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=database-table.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"database-table.js","sourceRoot":"","sources":["../src/database-table.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAGH,OAAO,EAAE,WAAW,EAAkB,MAAM,eAAe,CAAC;AAO5D,MAAM,UAAU,wBAAwB,CACtC,MAAiB,EACjB,SAAiB,EACjB,UAAuC,EAAE;IAEzC,MAAM,EAAE,KAAK,EAAE,SAAS,EAAE,GAAG,WAAW,CAAC,MAAM,EAAE,gBAAgB,EAAE,OAAO,CAAC,KAAK,IAAI,IAAI,CAAC,CAAC;IAC1F,MAAM,OAAO,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC;IAEhE,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACzB,OAAO;YACL,OAAO,EAAE,WAAW;YACpB,MAAM,EACJ,iCAAiC,SAAS,IAAI;gBAC9C,CAAC,SAAS;oBACR,CAAC,CAAC,uFAAuF;wBACvF,oCAAoC;oBACtC,CAAC,CAAC,uFAAuF;wBACvF,6DAA6D,CAAC;SACrE,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,oCAAoC,SAAS,MAAM;gBACpE,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAI,CAAC,CAAC,KAAK,CAAC,MAA6B,IAAI,GAAG,IAAI,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI;gBAClG,uFAAuF;gBACvF,yFAAyF;gBACzF,mBAAmB;SACtB,CAAC;IACJ,CAAC;IAED,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,OAAO,CAAC,CAAC,CAAE,EAAE,CAAC;AACpD,CAAC"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolving observed HTTP evidence (a real request's method + literal path)
|
|
3
|
+
* to the `API_ENDPOINT` node it was served by — plan §12/§25 ("Runtime
|
|
4
|
+
* should be able to build an observed path such as FUNCTION → API_ROUTE →
|
|
5
|
+
* API_ENDPOINT → FUNCTION → DATABASE_TABLE, where the static graph and
|
|
6
|
+
* runtime evidence actually support those links").
|
|
7
|
+
*
|
|
8
|
+
* `API_ENDPOINT` is workspace-scoped, not repo-scoped (DEC-054) — it is
|
|
9
|
+
* deliberately the one join node two repositories mint identically with no
|
|
10
|
+
* shared code (DEC-115). That means, unlike a `FUNCTION` lookup, there is no
|
|
11
|
+
* "wrong repository" version of this node to accidentally pick: any
|
|
12
|
+
* `API_ENDPOINT` in the store is the one shared join point by construction,
|
|
13
|
+
* so this file takes no `repo`/`scope` parameter — adding one would be
|
|
14
|
+
* inventing a filter with nothing behind it to filter on.
|
|
15
|
+
*
|
|
16
|
+
* What this file *does* have to solve, that a bare identity hash cannot: a
|
|
17
|
+
* real request's URL is a literal (`/orders/5`), while the route that served
|
|
18
|
+
* it may have been minted from a parameterised template
|
|
19
|
+
* (`/orders/{param}`, per `normaliseEndpointPath`). Those hash to different
|
|
20
|
+
* ids, so resolution here is a segment-wise match against every candidate's
|
|
21
|
+
* *name*, not a single `getNode` by a computed id. That is also what makes
|
|
22
|
+
* "similar endpoint" (plan §38's own negative-control name) a real,
|
|
23
|
+
* representable outcome here rather than a hypothetical: a literal route
|
|
24
|
+
* (`GET /orders/active`) and a parameterised one (`GET /orders/{param}`) can
|
|
25
|
+
* both legitimately match one observed request, and this resolver reports
|
|
26
|
+
* that as `ambiguous` rather than guessing which one actually served it —
|
|
27
|
+
* the same discipline `resolveOneNode` (descry-core's MCP lookup) applies to
|
|
28
|
+
* a name search, one layer down at the network-evidence layer instead.
|
|
29
|
+
*/
|
|
30
|
+
import { type IRNode } from "@descryy/ir";
|
|
31
|
+
import { type SqlDriver } from "@descryy/core";
|
|
32
|
+
export type EndpointResolution = {
|
|
33
|
+
readonly outcome: "resolved";
|
|
34
|
+
readonly node: IRNode;
|
|
35
|
+
} | {
|
|
36
|
+
readonly outcome: "not-found";
|
|
37
|
+
readonly reason: string;
|
|
38
|
+
} | {
|
|
39
|
+
readonly outcome: "ambiguous";
|
|
40
|
+
readonly candidates: readonly IRNode[];
|
|
41
|
+
readonly reason: string;
|
|
42
|
+
};
|
|
43
|
+
export declare function resolveEndpointNode(driver: SqlDriver, method: string, observedPath: string, options?: {
|
|
44
|
+
readonly limit?: number;
|
|
45
|
+
}): EndpointResolution;
|
|
46
|
+
/**
|
|
47
|
+
* Every `API_ENDPOINT` whose path shape matches `observedPath`, **regardless
|
|
48
|
+
* of method** — the query `resolveEndpointNode` deliberately does not answer
|
|
49
|
+
* on its own, since it filters by method first and a caller asking "does
|
|
50
|
+
* this path exist under a different method" needs the method filter removed
|
|
51
|
+
* entirely, not relaxed.
|
|
52
|
+
*
|
|
53
|
+
* Built for one caller (`@descryhq-wq/runtime-openapi-observation`'s "wrong
|
|
54
|
+
* method" vs "undocumented" distinction, checklist §15): a request whose
|
|
55
|
+
* exact method+path resolves to nothing is a different fact from one whose
|
|
56
|
+
* path resolves under some *other* method — the first says the operation
|
|
57
|
+
* was never declared, the second says it was declared and the request used
|
|
58
|
+
* the wrong verb. Conflating them would report "undocumented" for a request
|
|
59
|
+
* a contract-reading person would call a client bug, not a documentation
|
|
60
|
+
* gap.
|
|
61
|
+
*
|
|
62
|
+
* Ambiguity is not resolved here either — every shape-matching node is
|
|
63
|
+
* returned, and the caller decides what "more than one, across methods"
|
|
64
|
+
* means for its own question, the same "refuse rather than rank" split
|
|
65
|
+
* `resolveEndpointNode` already uses for its own single-method case.
|
|
66
|
+
*/
|
|
67
|
+
export declare function resolveEndpointNodesByPath(driver: SqlDriver, observedPath: string, options?: {
|
|
68
|
+
readonly limit?: number;
|
|
69
|
+
}): {
|
|
70
|
+
readonly nodes: readonly IRNode[];
|
|
71
|
+
readonly truncated: boolean;
|
|
72
|
+
};
|
|
73
|
+
//# sourceMappingURL=endpoint.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"endpoint.d.ts","sourceRoot":"","sources":["../src/endpoint.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,OAAO,EAAsC,KAAK,MAAM,EAAE,MAAM,aAAa,CAAC;AAC9E,OAAO,EAAe,KAAK,SAAS,EAAE,MAAM,eAAe,CAAC;AAE5D,MAAM,MAAM,kBAAkB,GAC1B;IAAE,QAAQ,CAAC,OAAO,EAAE,UAAU,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GACvD;IAAE,QAAQ,CAAC,OAAO,EAAE,WAAW,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAC1D;IAAE,QAAQ,CAAC,OAAO,EAAE,WAAW,CAAC;IAAC,QAAQ,CAAC,UAAU,EAAE,SAAS,MAAM,EAAE,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAavG,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,SAAS,EACjB,MAAM,EAAE,MAAM,EACd,YAAY,EAAE,MAAM,EACpB,OAAO,GAAE;IAAE,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAO,GACxC,kBAAkB,CA2CpB;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,0BAA0B,CACxC,MAAM,EAAE,SAAS,EACjB,YAAY,EAAE,MAAM,EACpB,OAAO,GAAE;IAAE,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAO,GACxC;IAAE,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;IAAC,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAA;CAAE,CAapE"}
|
package/dist/endpoint.js
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolving observed HTTP evidence (a real request's method + literal path)
|
|
3
|
+
* to the `API_ENDPOINT` node it was served by — plan §12/§25 ("Runtime
|
|
4
|
+
* should be able to build an observed path such as FUNCTION → API_ROUTE →
|
|
5
|
+
* API_ENDPOINT → FUNCTION → DATABASE_TABLE, where the static graph and
|
|
6
|
+
* runtime evidence actually support those links").
|
|
7
|
+
*
|
|
8
|
+
* `API_ENDPOINT` is workspace-scoped, not repo-scoped (DEC-054) — it is
|
|
9
|
+
* deliberately the one join node two repositories mint identically with no
|
|
10
|
+
* shared code (DEC-115). That means, unlike a `FUNCTION` lookup, there is no
|
|
11
|
+
* "wrong repository" version of this node to accidentally pick: any
|
|
12
|
+
* `API_ENDPOINT` in the store is the one shared join point by construction,
|
|
13
|
+
* so this file takes no `repo`/`scope` parameter — adding one would be
|
|
14
|
+
* inventing a filter with nothing behind it to filter on.
|
|
15
|
+
*
|
|
16
|
+
* What this file *does* have to solve, that a bare identity hash cannot: a
|
|
17
|
+
* real request's URL is a literal (`/orders/5`), while the route that served
|
|
18
|
+
* it may have been minted from a parameterised template
|
|
19
|
+
* (`/orders/{param}`, per `normaliseEndpointPath`). Those hash to different
|
|
20
|
+
* ids, so resolution here is a segment-wise match against every candidate's
|
|
21
|
+
* *name*, not a single `getNode` by a computed id. That is also what makes
|
|
22
|
+
* "similar endpoint" (plan §38's own negative-control name) a real,
|
|
23
|
+
* representable outcome here rather than a hypothetical: a literal route
|
|
24
|
+
* (`GET /orders/active`) and a parameterised one (`GET /orders/{param}`) can
|
|
25
|
+
* both legitimately match one observed request, and this resolver reports
|
|
26
|
+
* that as `ambiguous` rather than guessing which one actually served it —
|
|
27
|
+
* the same discipline `resolveOneNode` (descry-core's MCP lookup) applies to
|
|
28
|
+
* a name search, one layer down at the network-evidence layer instead.
|
|
29
|
+
*/
|
|
30
|
+
import { endpointQsp, normaliseEndpointPath } from "@descryy/ir";
|
|
31
|
+
import { nodesOfType } from "@descryy/core";
|
|
32
|
+
function pathSegments(path) {
|
|
33
|
+
// `endpointQsp`'s own qualified path is "METHOD /normalised/path" — split
|
|
34
|
+
// on "/" after the method prefix is stripped, same shape either side.
|
|
35
|
+
return normaliseEndpointPath(path).split("/");
|
|
36
|
+
}
|
|
37
|
+
function segmentsMatch(templateSegments, observedSegments) {
|
|
38
|
+
if (templateSegments.length !== observedSegments.length)
|
|
39
|
+
return false;
|
|
40
|
+
return templateSegments.every((segment, i) => segment === "{param}" || segment === observedSegments[i]);
|
|
41
|
+
}
|
|
42
|
+
export function resolveEndpointNode(driver, method, observedPath, options = {}) {
|
|
43
|
+
const methodUpper = method.toUpperCase();
|
|
44
|
+
const observedSegments = pathSegments(observedPath);
|
|
45
|
+
const prefix = `${methodUpper} `;
|
|
46
|
+
const { nodes, truncated } = nodesOfType(driver, "API_ENDPOINT", options.limit ?? 5000);
|
|
47
|
+
const matches = [];
|
|
48
|
+
for (const node of nodes) {
|
|
49
|
+
if (!node.name.startsWith(prefix))
|
|
50
|
+
continue;
|
|
51
|
+
const templateSegments = node.name.slice(prefix.length).split("/");
|
|
52
|
+
if (segmentsMatch(templateSegments, observedSegments))
|
|
53
|
+
matches.push(node);
|
|
54
|
+
}
|
|
55
|
+
const observed = endpointQsp(methodUpper, observedPath);
|
|
56
|
+
if (matches.length === 0) {
|
|
57
|
+
return {
|
|
58
|
+
outcome: "not-found",
|
|
59
|
+
reason: `No API_ENDPOINT node's shape matches ${observed}.` +
|
|
60
|
+
(truncated
|
|
61
|
+
? " The API_ENDPOINT scan was truncated by the node budget, so this may be a coverage " +
|
|
62
|
+
"gap rather than a genuine absence."
|
|
63
|
+
: " Either no route was minted for this request, or it was, and this is a real gap to " +
|
|
64
|
+
"report rather than assume clean."),
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
if (matches.length > 1) {
|
|
68
|
+
return {
|
|
69
|
+
outcome: "ambiguous",
|
|
70
|
+
candidates: matches,
|
|
71
|
+
reason: `${matches.length} API_ENDPOINT nodes' shapes all match ${observed} — ` +
|
|
72
|
+
`${matches.map((n) => n.name).join(", ")}. This is the "similar endpoint" case (a literal ` +
|
|
73
|
+
"route and a parameterised one can both textually match one real request): this resolver " +
|
|
74
|
+
"has no router-precedence information to break the tie, so it refuses rather than guessing " +
|
|
75
|
+
"which one actually served the request.",
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
return { outcome: "resolved", node: matches[0] };
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Every `API_ENDPOINT` whose path shape matches `observedPath`, **regardless
|
|
82
|
+
* of method** — the query `resolveEndpointNode` deliberately does not answer
|
|
83
|
+
* on its own, since it filters by method first and a caller asking "does
|
|
84
|
+
* this path exist under a different method" needs the method filter removed
|
|
85
|
+
* entirely, not relaxed.
|
|
86
|
+
*
|
|
87
|
+
* Built for one caller (`@descryhq-wq/runtime-openapi-observation`'s "wrong
|
|
88
|
+
* method" vs "undocumented" distinction, checklist §15): a request whose
|
|
89
|
+
* exact method+path resolves to nothing is a different fact from one whose
|
|
90
|
+
* path resolves under some *other* method — the first says the operation
|
|
91
|
+
* was never declared, the second says it was declared and the request used
|
|
92
|
+
* the wrong verb. Conflating them would report "undocumented" for a request
|
|
93
|
+
* a contract-reading person would call a client bug, not a documentation
|
|
94
|
+
* gap.
|
|
95
|
+
*
|
|
96
|
+
* Ambiguity is not resolved here either — every shape-matching node is
|
|
97
|
+
* returned, and the caller decides what "more than one, across methods"
|
|
98
|
+
* means for its own question, the same "refuse rather than rank" split
|
|
99
|
+
* `resolveEndpointNode` already uses for its own single-method case.
|
|
100
|
+
*/
|
|
101
|
+
export function resolveEndpointNodesByPath(driver, observedPath, options = {}) {
|
|
102
|
+
const observedSegments = pathSegments(observedPath);
|
|
103
|
+
const { nodes, truncated } = nodesOfType(driver, "API_ENDPOINT", options.limit ?? 5000);
|
|
104
|
+
const matches = [];
|
|
105
|
+
for (const node of nodes) {
|
|
106
|
+
const spaceIndex = node.name.indexOf(" ");
|
|
107
|
+
if (spaceIndex === -1)
|
|
108
|
+
continue; // not this adapter's "METHOD /path" naming -- refuse rather than guess at a split
|
|
109
|
+
const templateSegments = node.name.slice(spaceIndex + 1).split("/");
|
|
110
|
+
if (segmentsMatch(templateSegments, observedSegments))
|
|
111
|
+
matches.push(node);
|
|
112
|
+
}
|
|
113
|
+
return { nodes: matches, truncated };
|
|
114
|
+
}
|
|
115
|
+
//# sourceMappingURL=endpoint.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"endpoint.js","sourceRoot":"","sources":["../src/endpoint.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,67 @@
|
|
|
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 { type SqlDriver } from "@descryy/core";
|
|
36
|
+
import type { IRNode } from "@descryy/ir";
|
|
37
|
+
export type FrontendCallerResolution =
|
|
38
|
+
/** Exactly one frontend function declares a call to this endpoint. The only candidate — **not** proof that it is the one that ran. */
|
|
39
|
+
{
|
|
40
|
+
readonly outcome: "sole-structural-caller";
|
|
41
|
+
readonly node: IRNode;
|
|
42
|
+
readonly reason: string;
|
|
43
|
+
}
|
|
44
|
+
/** Several functions call this endpoint. The graph cannot say which one issued the observed request, and neither can this. */
|
|
45
|
+
| {
|
|
46
|
+
readonly outcome: "ambiguous";
|
|
47
|
+
readonly candidates: readonly IRNode[];
|
|
48
|
+
readonly reason: string;
|
|
49
|
+
}
|
|
50
|
+
/** No `USES_API` edge points at this endpoint — the caller is in code this adapter did not read, or is not a direct call at all. */
|
|
51
|
+
| {
|
|
52
|
+
readonly outcome: "not-found";
|
|
53
|
+
readonly reason: string;
|
|
54
|
+
};
|
|
55
|
+
/**
|
|
56
|
+
* Every frontend function the graph says calls this endpoint.
|
|
57
|
+
*
|
|
58
|
+
* Ambiguity is returned to the caller rather than ranked, the same way
|
|
59
|
+
* `resolveSymbolNode` and `resolveEndpointNode` refuse it. There is a
|
|
60
|
+
* tempting heuristic here — prefer the caller in the file the browser most
|
|
61
|
+
* recently loaded, or the one whose line number is nearest the event — and
|
|
62
|
+
* both are timing and locality standing in for causality, which §27's own
|
|
63
|
+
* rule forbids in one sentence: *similarity must never be treated as
|
|
64
|
+
* causality*.
|
|
65
|
+
*/
|
|
66
|
+
export declare function resolveFrontendCallers(driver: SqlDriver, endpointNodeId: string): FrontendCallerResolution;
|
|
67
|
+
//# sourceMappingURL=frontend-caller.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"frontend-caller.d.ts","sourceRoot":"","sources":["../src/frontend-caller.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAEH,OAAO,EAAY,KAAK,SAAS,EAAE,MAAM,eAAe,CAAC;AACzD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAG1C,MAAM,MAAM,wBAAwB;AAClC,sIAAsI;AACpI;IAAE,QAAQ,CAAC,OAAO,EAAE,wBAAwB,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CAAE;AAChG,8HAA8H;GAC5H;IAAE,QAAQ,CAAC,OAAO,EAAE,WAAW,CAAC;IAAC,QAAQ,CAAC,UAAU,EAAE,SAAS,MAAM,EAAE,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CAAE;AACpG,oIAAoI;GAClI;IAAE,QAAQ,CAAC,OAAO,EAAE,WAAW,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAE/D;;;;;;;;;;GAUG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,GAAG,wBAAwB,CA2C1G"}
|