@descryy/runtime-evidence-correlation 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/collector-version.d.ts +10 -0
- package/dist/collector-version.d.ts.map +1 -0
- package/dist/collector-version.js +12 -0
- package/dist/collector-version.js.map +1 -0
- package/dist/correlate-execution.d.ts +262 -0
- package/dist/correlate-execution.d.ts.map +1 -0
- package/dist/correlate-execution.js +499 -0
- package/dist/correlate-execution.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/package.json +35 -0
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `Evidence.collectorVersion`'s source for the `source: "harness"` rows this
|
|
3
|
+
* package writes when the correlation pass itself breaks. Read from this
|
|
4
|
+
* package's own `package.json` rather than hand-duplicated into a second
|
|
5
|
+
* string that can drift from what actually shipped — the same rule
|
|
6
|
+
* `packages/orchestrator/src/collector-version.ts` already states for the
|
|
7
|
+
* orchestrator's own direct emissions.
|
|
8
|
+
*/
|
|
9
|
+
export declare const CORRELATOR_VERSION: string;
|
|
10
|
+
//# sourceMappingURL=collector-version.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"collector-version.d.ts","sourceRoot":"","sources":["../src/collector-version.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAMH,eAAO,MAAM,kBAAkB,EAAE,MAA6E,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `Evidence.collectorVersion`'s source for the `source: "harness"` rows this
|
|
3
|
+
* package writes when the correlation pass itself breaks. Read from this
|
|
4
|
+
* package's own `package.json` rather than hand-duplicated into a second
|
|
5
|
+
* string that can drift from what actually shipped — the same rule
|
|
6
|
+
* `packages/orchestrator/src/collector-version.ts` already states for the
|
|
7
|
+
* orchestrator's own direct emissions.
|
|
8
|
+
*/
|
|
9
|
+
import { createRequire } from "node:module";
|
|
10
|
+
const require = createRequire(import.meta.url);
|
|
11
|
+
export const CORRELATOR_VERSION = require("../package.json").version;
|
|
12
|
+
//# sourceMappingURL=collector-version.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"collector-version.js","sourceRoot":"","sources":["../src/collector-version.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAE5C,MAAM,OAAO,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAE/C,MAAM,CAAC,MAAM,kBAAkB,GAAY,OAAO,CAAC,iBAAiB,CAAkC,CAAC,OAAO,CAAC"}
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* **The production correlation pass — the caller RT-230 found missing.**
|
|
3
|
+
*
|
|
4
|
+
* `graph-correlator` answers "which graph node is this evidence about" as
|
|
5
|
+
* pure functions over a `SqlDriver`. `EvidenceStore.attribute()` makes an
|
|
6
|
+
* answer durable. Between those two there was nothing: every real
|
|
7
|
+
* `attribute()` call site in this repository lived inside a `test/` file,
|
|
8
|
+
* and the three per-package wrappers
|
|
9
|
+
* (`openapi-observation/endpoint-comparison.ts`,
|
|
10
|
+
* `database-observation/graph-mapping.ts`, `test-runner/graph-mapping.ts`)
|
|
11
|
+
* each say in their own doc comment that attribution needs an `evidenceId`
|
|
12
|
+
* that only exists after `store.write()` returns — "outside this
|
|
13
|
+
* collector's own reach. The caller that already holds both the store and
|
|
14
|
+
* the graph `driver` attributes; this only resolves."
|
|
15
|
+
*
|
|
16
|
+
* **This module is that caller.** It is the one shipped thing that owns an
|
|
17
|
+
* `EvidenceStore` and a graph `driver` together and performs
|
|
18
|
+
* resolve-then-attribute, so a real Descry run produces evidence that is
|
|
19
|
+
* *correlated* and not merely *correlatable*.
|
|
20
|
+
*
|
|
21
|
+
* ## Why it is a separate module and not part of `runInstrumentedExecution`
|
|
22
|
+
*
|
|
23
|
+
* `instrumented-execution.ts`'s own header states the boundary: it "does not
|
|
24
|
+
* decide whether a finding exists, does not correlate to graph nodes, and
|
|
25
|
+
* does not drive a browser. Those are separate stages with separate
|
|
26
|
+
* contracts, and folding them in here would make the one place that
|
|
27
|
+
* assembles the runtime also the place that judges it." That boundary is
|
|
28
|
+
* kept. This is a **post-run pass**, run after an execution's evidence is in
|
|
29
|
+
* the store, taking the graph as a parameter — the same way every other
|
|
30
|
+
* production function in this repo takes a `SqlDriver` it does not open.
|
|
31
|
+
* The stage order the architecture diagram (§3) already draws — collectors →
|
|
32
|
+
* evidence store → graph correlator → runtime findings — is the order these
|
|
33
|
+
* packages now depend in, with nothing inverted: this package depends on
|
|
34
|
+
* `graph-correlator`, and `finding` depends on neither.
|
|
35
|
+
*
|
|
36
|
+
* ## What it does NOT do, deliberately
|
|
37
|
+
*
|
|
38
|
+
* It does not compose findings (RT-027 places that in the finding layer, and
|
|
39
|
+
* `composeFinding` is where it lives). It does not open a graph, does not
|
|
40
|
+
* walk one (`traceApi` is the caller's call, RT-026), and does not decide
|
|
41
|
+
* confidence. It resolves and it attributes; that is the whole job.
|
|
42
|
+
*
|
|
43
|
+
* ## The OpenAPI family, wired (RT-232) — and how the earlier objection was met
|
|
44
|
+
*
|
|
45
|
+
* This header used to say the OpenAPI comparison family
|
|
46
|
+
* (`openapi-observation`'s `compareObservedEndpoint` /
|
|
47
|
+
* `compareObservedExchange`) was left out because "folding a conformance
|
|
48
|
+
* verdict into an attribution pass would put two different claims in one
|
|
49
|
+
* output." That objection was right about the risk and wrong about the
|
|
50
|
+
* remedy: the two claims must not share **an output**, which is not the same
|
|
51
|
+
* as not sharing **a pass**. So the family is wired, and the separation is
|
|
52
|
+
* structural rather than by omission:
|
|
53
|
+
*
|
|
54
|
+
* - **One graph query, not two.** `compareObservedEndpoint` *calls*
|
|
55
|
+
* `resolveEndpointNode` and returns the very same node under
|
|
56
|
+
* `documented`. Running both would ask the graph the identical question
|
|
57
|
+
* twice and let two families race for `Evidence.graphNodeId`. The HTTP
|
|
58
|
+
* family therefore routes **through** the wrapper, which is strictly more
|
|
59
|
+
* informative and resolves exactly once.
|
|
60
|
+
* - **Identity keeps its own vocabulary.** `attributed` and `refusals` are
|
|
61
|
+
* unchanged: a resolved endpoint is `family: "endpoint"`,
|
|
62
|
+
* `method: "request-url"`; a miss is `not-found`, the same discriminant
|
|
63
|
+
* `resolveEndpointNode` itself returns, carrying the wrapper's reason
|
|
64
|
+
* verbatim. `undocumented` / `wrong-method` are contract words, and they
|
|
65
|
+
* stay out of the identity channel.
|
|
66
|
+
* - **Conformance is its own field** (`conformance`, `CorrelationConformance`),
|
|
67
|
+
* never persisted, never written to the evidence stream, never an
|
|
68
|
+
* `EvidenceAttribution`. A reader that only cares about identity can
|
|
69
|
+
* ignore it entirely, which is what "not one output" has to mean to be
|
|
70
|
+
* worth anything.
|
|
71
|
+
*
|
|
72
|
+
* **What the exchange half needs, and what supplies it.** `compareObservedExchange`
|
|
73
|
+
* additionally consumes an observed **status** and, optionally, the request /
|
|
74
|
+
* response **bodies** — all three come off evidence that already exists:
|
|
75
|
+
* `InboundProxy` emits `{method, path, status}` on `NETWORK_RESPONSE` and
|
|
76
|
+
* `HTTP_ERROR`, and `BrowserNetworkCollector`'s `HTTP_ERROR` adds the captured
|
|
77
|
+
* response `body`. No declared OpenAPI *document* is read here or anywhere at
|
|
78
|
+
* runtime: the contract is whatever `adapter-openapi` already wrote into the
|
|
79
|
+
* graph (`API_ROUTE --SERVES_API--> API_ENDPOINT --`, `--USES_TYPE(roles)--> DTO`),
|
|
80
|
+
* so nothing new has to be supplied by a run. Two honest limits follow, and are
|
|
81
|
+
* reported rather than papered over:
|
|
82
|
+
*
|
|
83
|
+
* 1. A graph whose `API_ENDPOINT` nodes came from a **framework extractor**
|
|
84
|
+
* rather than an OpenAPI document has no DTOs at all, and every declared
|
|
85
|
+
* role reads empty — which `compareObservedExchange` correctly reports as
|
|
86
|
+
* `statusSurprising: true`, meaning "this operation declares no shape for
|
|
87
|
+
* an outcome like this one." Read alone that is indistinguishable from a
|
|
88
|
+
* contract violation, so `contractSchemasDeclared` is reported beside it:
|
|
89
|
+
* `false` means there was no contract to violate.
|
|
90
|
+
* 2. A `NETWORK_REQUEST` carries a request body (`postData`) but no status, so
|
|
91
|
+
* only the endpoint half runs for it — request-body conformance needs the
|
|
92
|
+
* exchange, and a status is not invented to obtain one.
|
|
93
|
+
*
|
|
94
|
+
* Bodies are read **after** `EvidenceStore` redaction, which re-serializes a
|
|
95
|
+
* JSON body with sensitive values replaced by a marker string. Field
|
|
96
|
+
* *presence* survives that intact; a `type-mismatch` on a non-string-typed
|
|
97
|
+
* field whose name matches a credential/PII pattern would be redaction's
|
|
98
|
+
* artifact, not the app's bug.
|
|
99
|
+
*
|
|
100
|
+
* ## Families still not wired here, named rather than left implicit
|
|
101
|
+
*
|
|
102
|
+
* **`resolveObservedFrontendCaller`** is deliberately excluded, not
|
|
103
|
+
* pending. It answers "which frontend function calls this endpoint", which
|
|
104
|
+
* is a question about a *node*, not about an evidence item, and it falls
|
|
105
|
+
* back to a structural answer when the observed stack does not resolve.
|
|
106
|
+
* Attributing evidence to a structurally-derived caller would break the
|
|
107
|
+
* rule the whole confirmed/strongly-supported gate rests on: a named node
|
|
108
|
+
* must be named by the observation itself.
|
|
109
|
+
*
|
|
110
|
+
* ## Refusals stay refusals (RT-229)
|
|
111
|
+
*
|
|
112
|
+
* `not-found` / `ambiguous` / `line-mismatch` / `producer-emitted-none` /
|
|
113
|
+
* `no-table` / `no-location` are correctly-typed **honest refusals**, and
|
|
114
|
+
* RT-229 ruled that writing `COLLECTOR_ERROR` for one would be a false
|
|
115
|
+
* failure signal. They are carried through into `refusals`, verbatim
|
|
116
|
+
* outcome and reason, and nothing about them touches the evidence stream.
|
|
117
|
+
* The only thing that becomes `COLLECTOR_ERROR` is a resolver or the store
|
|
118
|
+
* genuinely **throwing** — see `harnessErrors` below.
|
|
119
|
+
*/
|
|
120
|
+
import type { Evidence } from "@descryy/runtime-contracts";
|
|
121
|
+
import type { EvidenceStore } from "@descryy/runtime-evidence-store";
|
|
122
|
+
import type { SqlDriver } from "@descryy/core";
|
|
123
|
+
import { type EndpointComparison, type StatusBucket } from "@descryhq-wq/runtime-openapi-observation";
|
|
124
|
+
/**
|
|
125
|
+
* Which resolver answered (or refused). Not a confidence ranking — the
|
|
126
|
+
* families are disjoint by the evidence they read, except that
|
|
127
|
+
* `log-text-endpoint` is a *second* naming a backend line can carry
|
|
128
|
+
* alongside its own stack frame (RT-052's case: one observation, two real
|
|
129
|
+
* graph entities).
|
|
130
|
+
*/
|
|
131
|
+
export type CorrelationFamily = "endpoint" | "symbol" | "database-table" | "test-case" | "log-text-endpoint";
|
|
132
|
+
/**
|
|
133
|
+
* What the caller declares about one service's evidence: which repository
|
|
134
|
+
* its symbols belong to, and the on-disk root its runtime paths are
|
|
135
|
+
* absolute against.
|
|
136
|
+
*
|
|
137
|
+
* **Required, never defaulted.** `resolveSymbolNode` takes `repo` as a
|
|
138
|
+
* required parameter precisely because there is no honest default, and a
|
|
139
|
+
* pass that guessed one would resolve the wrong repository's identically
|
|
140
|
+
* named file — RT-031's identity-proxy error at the correlation layer. A
|
|
141
|
+
* service with no entry here has its symbol-family and test-family evidence
|
|
142
|
+
* left alone and its name reported in `unscopedServices`; it is never
|
|
143
|
+
* resolved against a repository nobody named.
|
|
144
|
+
*/
|
|
145
|
+
export interface CorrelationScope {
|
|
146
|
+
readonly repo: string;
|
|
147
|
+
/** Absolute on-disk root, so an observed absolute path can be translated exactly. See `resolveSymbolNode`'s own doc. */
|
|
148
|
+
readonly repoRoot?: string;
|
|
149
|
+
/** The directory the observed process was launched in, for runtimes that echo their invocation argument verbatim (RT-113). */
|
|
150
|
+
readonly cwd?: string;
|
|
151
|
+
}
|
|
152
|
+
export interface CorrelateExecutionInput {
|
|
153
|
+
/** The store the execution's evidence was written to. Provided, never constructed, so the caller owns its lifetime — same rule `InstrumentedExecutionInput.store` states. */
|
|
154
|
+
readonly store: EvidenceStore;
|
|
155
|
+
/** The `descry-core` graph this evidence is resolved against. Consumed, never modified. */
|
|
156
|
+
readonly driver: SqlDriver;
|
|
157
|
+
readonly executionId: string;
|
|
158
|
+
/** Service name (`Evidence.service`) → the repository scope its symbols resolve in. */
|
|
159
|
+
readonly scopeByService?: Readonly<Record<string, CorrelationScope>>;
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* One (evidence, node) association this pass made. Structurally an
|
|
163
|
+
* `EvidenceAttribution` (`@descryy/runtime-finding`) — `evidenceId`,
|
|
164
|
+
* `graphNodeId`, `method` — so `secondary` can be handed straight to
|
|
165
|
+
* `composeFinding`'s fourth argument without a translation step. That
|
|
166
|
+
* assignability is asserted by an executing test rather than by a
|
|
167
|
+
* dependency, because this package must not depend on the governance layer
|
|
168
|
+
* it feeds (RT-027: resolution has no opinion on confidence).
|
|
169
|
+
*/
|
|
170
|
+
export interface CorrelationAttribution {
|
|
171
|
+
readonly evidenceId: string;
|
|
172
|
+
readonly graphNodeId: string;
|
|
173
|
+
/** How the association was made — `EvidenceAttribution.method`'s own vocabulary: `"request-url"`, `"stack-frame"`, `"query-table"`, `"test-location"`, `"log-text-endpoint"`. */
|
|
174
|
+
readonly method: string;
|
|
175
|
+
readonly family: CorrelationFamily;
|
|
176
|
+
/** True when this is the naming persisted onto `Evidence.graphNodeId`; false when it is a second naming the store's single column cannot hold. */
|
|
177
|
+
readonly persisted: boolean;
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* A resolver looked and could not answer. **Never an error.** The `outcome`
|
|
181
|
+
* is the resolver's own discriminant, unchanged, and the `reason` is the
|
|
182
|
+
* resolver's own sentence — this pass adds no interpretation of its own to
|
|
183
|
+
* either.
|
|
184
|
+
*/
|
|
185
|
+
export interface CorrelationRefusal {
|
|
186
|
+
readonly evidenceId: string;
|
|
187
|
+
readonly family: CorrelationFamily;
|
|
188
|
+
/** A resolver's own discriminant — `"not-found"`, `"ambiguous"`, `"line-mismatch"`, `"producer-emitted-none"`, `"no-table"`, `"no-location"` — or one of this pass's two input refusals, `"no-method"` and `"no-path"`, which say the observation itself did not name enough to look anything up. */
|
|
189
|
+
readonly outcome: string;
|
|
190
|
+
readonly reason: string;
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* A resolver or the store **threw** — not "looked and could not answer",
|
|
194
|
+
* but the correlation machinery itself breaking. Each distinct failure is
|
|
195
|
+
* also written into the evidence stream as `COLLECTOR_ERROR` /
|
|
196
|
+
* `source: "harness"`, which is the whole point: RT-229 declined to add
|
|
197
|
+
* this wrap because there was no production call site to put it at, and
|
|
198
|
+
* this is that call site.
|
|
199
|
+
*/
|
|
200
|
+
export interface CorrelationHarnessError {
|
|
201
|
+
/** The `evidenceId` of the `COLLECTOR_ERROR` row written for this failure. */
|
|
202
|
+
readonly evidenceId: string;
|
|
203
|
+
readonly detail: string;
|
|
204
|
+
/** How many times this same failure occurred across the pass. One row is written per distinct failure, not per occurrence. */
|
|
205
|
+
readonly occurrences: number;
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* What `openapi-observation` concluded about one observed HTTP item's
|
|
209
|
+
* **contract**, as opposed to its identity. Every field is the comparison's
|
|
210
|
+
* own discriminant or boolean, carried verbatim — this pass derives exactly
|
|
211
|
+
* one value of its own, `contractSchemasDeclared`, and it is a read of
|
|
212
|
+
* `ExchangeComparison.declaredRoles`, not a judgement.
|
|
213
|
+
*
|
|
214
|
+
* **Nothing here is ever persisted or written into the evidence stream.** A
|
|
215
|
+
* contract verdict is a claim about the *contract*, not an observation about
|
|
216
|
+
* the run, and the evidence stream holds observations.
|
|
217
|
+
*/
|
|
218
|
+
export interface CorrelationConformance {
|
|
219
|
+
readonly evidenceId: string;
|
|
220
|
+
/** `EndpointComparison`'s own discriminant, verbatim. */
|
|
221
|
+
readonly endpointOutcome: EndpointComparison["outcome"];
|
|
222
|
+
/** The comparison's own sentence. `null` for `documented`, which states none. */
|
|
223
|
+
readonly reason: string | null;
|
|
224
|
+
/** Non-empty only for `wrong-method`: the verbs this path IS declared under. */
|
|
225
|
+
readonly declaredUnderMethods: readonly string[];
|
|
226
|
+
/** `null` when the observation carried no numeric status, so no exchange comparison ran. */
|
|
227
|
+
readonly statusBucket: StatusBucket | null;
|
|
228
|
+
/** `ExchangeComparison.statusSurprising`, verbatim. Read together with `contractSchemasDeclared` — see the header. */
|
|
229
|
+
readonly statusSurprising: boolean | null;
|
|
230
|
+
/** `SchemaComparison`'s own discriminant, verbatim; `null` when no exchange comparison ran or the endpoint did not resolve. */
|
|
231
|
+
readonly requestSchema: string | null;
|
|
232
|
+
readonly responseSchema: string | null;
|
|
233
|
+
/**
|
|
234
|
+
* Whether the resolved endpoint's serving routes declare **any** DTO, in any
|
|
235
|
+
* role. `false` with `statusSurprising: true` means "no contract is indexed
|
|
236
|
+
* for this endpoint", never "the contract was violated". `null` when no
|
|
237
|
+
* exchange comparison ran.
|
|
238
|
+
*/
|
|
239
|
+
readonly contractSchemasDeclared: boolean | null;
|
|
240
|
+
}
|
|
241
|
+
export interface CorrelationPassResult {
|
|
242
|
+
readonly executionId: string;
|
|
243
|
+
/** Evidence items a family claimed and actually attempted to resolve. */
|
|
244
|
+
readonly considered: number;
|
|
245
|
+
/** Evidence items no family claimed — a CLICK, a SCREENSHOT, a PROCESS_STARTED. Nothing to resolve is not a gap, so these are counted, not refused. */
|
|
246
|
+
readonly skipped: number;
|
|
247
|
+
/** Every association made, persisted ones first. */
|
|
248
|
+
readonly attributed: readonly CorrelationAttribution[];
|
|
249
|
+
/** The subset that could not be persisted because `Evidence.graphNodeId` holds one node — ready for `composeFinding`'s attributions argument. */
|
|
250
|
+
readonly secondary: readonly CorrelationAttribution[];
|
|
251
|
+
readonly refusals: readonly CorrelationRefusal[];
|
|
252
|
+
/** One entry per HTTP item the OpenAPI comparison actually reached. A separate channel from `attributed`/`refusals`, on purpose — see `CorrelationConformance`. */
|
|
253
|
+
readonly conformance: readonly CorrelationConformance[];
|
|
254
|
+
/** Service names carrying symbol/test evidence that `scopeByService` never declared a repository for. Named, never guessed at. */
|
|
255
|
+
readonly unscopedServices: readonly string[];
|
|
256
|
+
/** Non-empty means the pass itself broke. See `CorrelationHarnessError`. */
|
|
257
|
+
readonly harnessErrors: readonly CorrelationHarnessError[];
|
|
258
|
+
}
|
|
259
|
+
/** Which resolver family owns this evidence item, or `null` when none does. */
|
|
260
|
+
export declare function classifyEvidence(evidence: Pick<Evidence, "eventType" | "sourceLocation">): CorrelationFamily | null;
|
|
261
|
+
export declare function correlateExecution(input: CorrelateExecutionInput): CorrelationPassResult;
|
|
262
|
+
//# sourceMappingURL=correlate-execution.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"correlate-execution.d.ts","sourceRoot":"","sources":["../src/correlate-execution.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,499 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* **The production correlation pass — the caller RT-230 found missing.**
|
|
3
|
+
*
|
|
4
|
+
* `graph-correlator` answers "which graph node is this evidence about" as
|
|
5
|
+
* pure functions over a `SqlDriver`. `EvidenceStore.attribute()` makes an
|
|
6
|
+
* answer durable. Between those two there was nothing: every real
|
|
7
|
+
* `attribute()` call site in this repository lived inside a `test/` file,
|
|
8
|
+
* and the three per-package wrappers
|
|
9
|
+
* (`openapi-observation/endpoint-comparison.ts`,
|
|
10
|
+
* `database-observation/graph-mapping.ts`, `test-runner/graph-mapping.ts`)
|
|
11
|
+
* each say in their own doc comment that attribution needs an `evidenceId`
|
|
12
|
+
* that only exists after `store.write()` returns — "outside this
|
|
13
|
+
* collector's own reach. The caller that already holds both the store and
|
|
14
|
+
* the graph `driver` attributes; this only resolves."
|
|
15
|
+
*
|
|
16
|
+
* **This module is that caller.** It is the one shipped thing that owns an
|
|
17
|
+
* `EvidenceStore` and a graph `driver` together and performs
|
|
18
|
+
* resolve-then-attribute, so a real Descry run produces evidence that is
|
|
19
|
+
* *correlated* and not merely *correlatable*.
|
|
20
|
+
*
|
|
21
|
+
* ## Why it is a separate module and not part of `runInstrumentedExecution`
|
|
22
|
+
*
|
|
23
|
+
* `instrumented-execution.ts`'s own header states the boundary: it "does not
|
|
24
|
+
* decide whether a finding exists, does not correlate to graph nodes, and
|
|
25
|
+
* does not drive a browser. Those are separate stages with separate
|
|
26
|
+
* contracts, and folding them in here would make the one place that
|
|
27
|
+
* assembles the runtime also the place that judges it." That boundary is
|
|
28
|
+
* kept. This is a **post-run pass**, run after an execution's evidence is in
|
|
29
|
+
* the store, taking the graph as a parameter — the same way every other
|
|
30
|
+
* production function in this repo takes a `SqlDriver` it does not open.
|
|
31
|
+
* The stage order the architecture diagram (§3) already draws — collectors →
|
|
32
|
+
* evidence store → graph correlator → runtime findings — is the order these
|
|
33
|
+
* packages now depend in, with nothing inverted: this package depends on
|
|
34
|
+
* `graph-correlator`, and `finding` depends on neither.
|
|
35
|
+
*
|
|
36
|
+
* ## What it does NOT do, deliberately
|
|
37
|
+
*
|
|
38
|
+
* It does not compose findings (RT-027 places that in the finding layer, and
|
|
39
|
+
* `composeFinding` is where it lives). It does not open a graph, does not
|
|
40
|
+
* walk one (`traceApi` is the caller's call, RT-026), and does not decide
|
|
41
|
+
* confidence. It resolves and it attributes; that is the whole job.
|
|
42
|
+
*
|
|
43
|
+
* ## The OpenAPI family, wired (RT-232) — and how the earlier objection was met
|
|
44
|
+
*
|
|
45
|
+
* This header used to say the OpenAPI comparison family
|
|
46
|
+
* (`openapi-observation`'s `compareObservedEndpoint` /
|
|
47
|
+
* `compareObservedExchange`) was left out because "folding a conformance
|
|
48
|
+
* verdict into an attribution pass would put two different claims in one
|
|
49
|
+
* output." That objection was right about the risk and wrong about the
|
|
50
|
+
* remedy: the two claims must not share **an output**, which is not the same
|
|
51
|
+
* as not sharing **a pass**. So the family is wired, and the separation is
|
|
52
|
+
* structural rather than by omission:
|
|
53
|
+
*
|
|
54
|
+
* - **One graph query, not two.** `compareObservedEndpoint` *calls*
|
|
55
|
+
* `resolveEndpointNode` and returns the very same node under
|
|
56
|
+
* `documented`. Running both would ask the graph the identical question
|
|
57
|
+
* twice and let two families race for `Evidence.graphNodeId`. The HTTP
|
|
58
|
+
* family therefore routes **through** the wrapper, which is strictly more
|
|
59
|
+
* informative and resolves exactly once.
|
|
60
|
+
* - **Identity keeps its own vocabulary.** `attributed` and `refusals` are
|
|
61
|
+
* unchanged: a resolved endpoint is `family: "endpoint"`,
|
|
62
|
+
* `method: "request-url"`; a miss is `not-found`, the same discriminant
|
|
63
|
+
* `resolveEndpointNode` itself returns, carrying the wrapper's reason
|
|
64
|
+
* verbatim. `undocumented` / `wrong-method` are contract words, and they
|
|
65
|
+
* stay out of the identity channel.
|
|
66
|
+
* - **Conformance is its own field** (`conformance`, `CorrelationConformance`),
|
|
67
|
+
* never persisted, never written to the evidence stream, never an
|
|
68
|
+
* `EvidenceAttribution`. A reader that only cares about identity can
|
|
69
|
+
* ignore it entirely, which is what "not one output" has to mean to be
|
|
70
|
+
* worth anything.
|
|
71
|
+
*
|
|
72
|
+
* **What the exchange half needs, and what supplies it.** `compareObservedExchange`
|
|
73
|
+
* additionally consumes an observed **status** and, optionally, the request /
|
|
74
|
+
* response **bodies** — all three come off evidence that already exists:
|
|
75
|
+
* `InboundProxy` emits `{method, path, status}` on `NETWORK_RESPONSE` and
|
|
76
|
+
* `HTTP_ERROR`, and `BrowserNetworkCollector`'s `HTTP_ERROR` adds the captured
|
|
77
|
+
* response `body`. No declared OpenAPI *document* is read here or anywhere at
|
|
78
|
+
* runtime: the contract is whatever `adapter-openapi` already wrote into the
|
|
79
|
+
* graph (`API_ROUTE --SERVES_API--> API_ENDPOINT --`, `--USES_TYPE(roles)--> DTO`),
|
|
80
|
+
* so nothing new has to be supplied by a run. Two honest limits follow, and are
|
|
81
|
+
* reported rather than papered over:
|
|
82
|
+
*
|
|
83
|
+
* 1. A graph whose `API_ENDPOINT` nodes came from a **framework extractor**
|
|
84
|
+
* rather than an OpenAPI document has no DTOs at all, and every declared
|
|
85
|
+
* role reads empty — which `compareObservedExchange` correctly reports as
|
|
86
|
+
* `statusSurprising: true`, meaning "this operation declares no shape for
|
|
87
|
+
* an outcome like this one." Read alone that is indistinguishable from a
|
|
88
|
+
* contract violation, so `contractSchemasDeclared` is reported beside it:
|
|
89
|
+
* `false` means there was no contract to violate.
|
|
90
|
+
* 2. A `NETWORK_REQUEST` carries a request body (`postData`) but no status, so
|
|
91
|
+
* only the endpoint half runs for it — request-body conformance needs the
|
|
92
|
+
* exchange, and a status is not invented to obtain one.
|
|
93
|
+
*
|
|
94
|
+
* Bodies are read **after** `EvidenceStore` redaction, which re-serializes a
|
|
95
|
+
* JSON body with sensitive values replaced by a marker string. Field
|
|
96
|
+
* *presence* survives that intact; a `type-mismatch` on a non-string-typed
|
|
97
|
+
* field whose name matches a credential/PII pattern would be redaction's
|
|
98
|
+
* artifact, not the app's bug.
|
|
99
|
+
*
|
|
100
|
+
* ## Families still not wired here, named rather than left implicit
|
|
101
|
+
*
|
|
102
|
+
* **`resolveObservedFrontendCaller`** is deliberately excluded, not
|
|
103
|
+
* pending. It answers "which frontend function calls this endpoint", which
|
|
104
|
+
* is a question about a *node*, not about an evidence item, and it falls
|
|
105
|
+
* back to a structural answer when the observed stack does not resolve.
|
|
106
|
+
* Attributing evidence to a structurally-derived caller would break the
|
|
107
|
+
* rule the whole confirmed/strongly-supported gate rests on: a named node
|
|
108
|
+
* must be named by the observation itself.
|
|
109
|
+
*
|
|
110
|
+
* ## Refusals stay refusals (RT-229)
|
|
111
|
+
*
|
|
112
|
+
* `not-found` / `ambiguous` / `line-mismatch` / `producer-emitted-none` /
|
|
113
|
+
* `no-table` / `no-location` are correctly-typed **honest refusals**, and
|
|
114
|
+
* RT-229 ruled that writing `COLLECTOR_ERROR` for one would be a false
|
|
115
|
+
* failure signal. They are carried through into `refusals`, verbatim
|
|
116
|
+
* outcome and reason, and nothing about them touches the evidence stream.
|
|
117
|
+
* The only thing that becomes `COLLECTOR_ERROR` is a resolver or the store
|
|
118
|
+
* genuinely **throwing** — see `harnessErrors` below.
|
|
119
|
+
*/
|
|
120
|
+
import { resolveEndpointFromLogText, resolveSymbolNode } from "@descryy/runtime-graph-correlator";
|
|
121
|
+
import { compareObservedEndpoint, compareObservedExchange, } from "@descryhq-wq/runtime-openapi-observation";
|
|
122
|
+
import { resolveObservedDatabaseQuery } from "@descryhq-wq/runtime-database-observation";
|
|
123
|
+
import { resolveObservedTestCase } from "@descryhq-wq/runtime-test-runner";
|
|
124
|
+
import { CORRELATOR_VERSION } from "./collector-version.js";
|
|
125
|
+
const TEST_EVENT_TYPES = new Set([
|
|
126
|
+
"TEST_STARTED",
|
|
127
|
+
"TEST_PASSED",
|
|
128
|
+
"TEST_FAILED",
|
|
129
|
+
"TEST_SKIPPED",
|
|
130
|
+
]);
|
|
131
|
+
/**
|
|
132
|
+
* HTTP evidence, whichever collector produced it: the browser network
|
|
133
|
+
* collector (`payload.url`, absolute), `InboundProxy` (`payload.path`,
|
|
134
|
+
* already a path) and `ExternalRequestCollector` (`payload.url`).
|
|
135
|
+
*
|
|
136
|
+
* `NETWORK_RESPONSE` is in this set and will usually refuse with
|
|
137
|
+
* `"no-method"` — the browser collector's response payload carries `url`
|
|
138
|
+
* but no `method`, because a response observation does not on its own state
|
|
139
|
+
* which verb produced it. **That is left as a disclosed refusal rather than
|
|
140
|
+
* joined to the matching request by `requestId`.** The failing exchange is
|
|
141
|
+
* already attributed through the `HTTP_ERROR` the same collector emits
|
|
142
|
+
* beside it, which does carry `method`, so the join would buy little and
|
|
143
|
+
* would make an otherwise strictly per-item pass depend on other items.
|
|
144
|
+
*/
|
|
145
|
+
const HTTP_EVENT_TYPES = new Set([
|
|
146
|
+
"NETWORK_REQUEST",
|
|
147
|
+
"NETWORK_RESPONSE",
|
|
148
|
+
"HTTP_ERROR",
|
|
149
|
+
"EXTERNAL_REQUEST",
|
|
150
|
+
]);
|
|
151
|
+
/** Which resolver family owns this evidence item, or `null` when none does. */
|
|
152
|
+
export function classifyEvidence(evidence) {
|
|
153
|
+
if (evidence.eventType === "DATABASE_QUERY")
|
|
154
|
+
return "database-table";
|
|
155
|
+
if (TEST_EVENT_TYPES.has(evidence.eventType))
|
|
156
|
+
return "test-case";
|
|
157
|
+
// Checked before the source-location branch on purpose: the browser's
|
|
158
|
+
// NETWORK_REQUEST carries a `sourceLocation` for the *frontend* frame that
|
|
159
|
+
// issued the call, and treating that as the item's identity would resolve
|
|
160
|
+
// a request to its caller instead of to the endpoint it named.
|
|
161
|
+
if (HTTP_EVENT_TYPES.has(evidence.eventType))
|
|
162
|
+
return "endpoint";
|
|
163
|
+
const location = evidence.sourceLocation;
|
|
164
|
+
if (location !== null && location.file !== null && location.functionName !== null)
|
|
165
|
+
return "symbol";
|
|
166
|
+
return null;
|
|
167
|
+
}
|
|
168
|
+
function asRecord(payload) {
|
|
169
|
+
return typeof payload === "object" && payload !== null ? payload : null;
|
|
170
|
+
}
|
|
171
|
+
function stringOrNull(value) {
|
|
172
|
+
return typeof value === "string" ? value : null;
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* `{method, path}` from an HTTP evidence payload, or the reason it is not
|
|
176
|
+
* there. Accepts `url` (absolute, browser/fetch collectors) and `path`
|
|
177
|
+
* (`InboundProxy`, which records `req.url` exactly as the client sent it) —
|
|
178
|
+
* a query string is stripped from either, since `API_ENDPOINT` names carry
|
|
179
|
+
* a path and never a query.
|
|
180
|
+
*
|
|
181
|
+
* `status` and the two bodies are the **exchange** half's inputs, and they
|
|
182
|
+
* are read here rather than in a second walker so that one function owns the
|
|
183
|
+
* whole "what did this payload actually observe" question. Each is `null`
|
|
184
|
+
* when the payload does not carry it — never defaulted, since a guessed
|
|
185
|
+
* status would manufacture a status-bucket verdict out of nothing.
|
|
186
|
+
* `postData` is `BrowserNetworkCollector`'s request-body key and `body` is
|
|
187
|
+
* its response-body key; `requestBody`/`responseBody` are accepted as the
|
|
188
|
+
* names a future producer would most plausibly use.
|
|
189
|
+
*/
|
|
190
|
+
function httpTarget(payload) {
|
|
191
|
+
const record = asRecord(payload);
|
|
192
|
+
if (record === null) {
|
|
193
|
+
return { ok: false, outcome: "no-path", reason: "The payload is not an object, so it names neither a URL nor a method." };
|
|
194
|
+
}
|
|
195
|
+
const method = typeof record["method"] === "string" ? record["method"] : null;
|
|
196
|
+
const rawUrl = typeof record["url"] === "string" ? record["url"] : null;
|
|
197
|
+
const rawPath = typeof record["path"] === "string" ? record["path"] : null;
|
|
198
|
+
let path = null;
|
|
199
|
+
if (rawUrl !== null) {
|
|
200
|
+
try {
|
|
201
|
+
path = new URL(rawUrl).pathname;
|
|
202
|
+
}
|
|
203
|
+
catch {
|
|
204
|
+
return {
|
|
205
|
+
ok: false,
|
|
206
|
+
outcome: "no-path",
|
|
207
|
+
reason: `payload.url ${JSON.stringify(rawUrl)} is not a parseable absolute URL, so no path can be read from it without guessing at a base.`,
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
else if (rawPath !== null) {
|
|
212
|
+
const queryIndex = rawPath.indexOf("?");
|
|
213
|
+
path = queryIndex === -1 ? rawPath : rawPath.slice(0, queryIndex);
|
|
214
|
+
}
|
|
215
|
+
if (path === null) {
|
|
216
|
+
return { ok: false, outcome: "no-path", reason: "The payload carries neither `url` nor `path`, so no request path was observed." };
|
|
217
|
+
}
|
|
218
|
+
if (method === null) {
|
|
219
|
+
return {
|
|
220
|
+
ok: false,
|
|
221
|
+
outcome: "no-method",
|
|
222
|
+
reason: "The payload names a path but no HTTP method. An API_ENDPOINT is identified by method AND path, and this " +
|
|
223
|
+
"observation does not state the method — reported rather than assumed to be GET, and rather than joined to " +
|
|
224
|
+
"another evidence item that happens to share a request id.",
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
return {
|
|
228
|
+
ok: true,
|
|
229
|
+
method,
|
|
230
|
+
path,
|
|
231
|
+
status: typeof record["status"] === "number" ? record["status"] : null,
|
|
232
|
+
requestBody: stringOrNull(record["postData"]) ?? stringOrNull(record["requestBody"]),
|
|
233
|
+
responseBody: stringOrNull(record["body"]) ?? stringOrNull(record["responseBody"]),
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* `EndpointComparison`/`ExchangeComparison` → the two channels, kept apart.
|
|
238
|
+
*
|
|
239
|
+
* The identity half deliberately collapses `undocumented` and `wrong-method`
|
|
240
|
+
* back to `not-found`: both mean "no `API_ENDPOINT` matched this method and
|
|
241
|
+
* path", which is `resolveEndpointNode`'s own answer and the discriminant the
|
|
242
|
+
* identity channel has always used. The reason is the comparison's own
|
|
243
|
+
* sentence, verbatim — for `wrong-method` that sentence is strictly richer
|
|
244
|
+
* than the resolver's, which is the point of routing through the wrapper.
|
|
245
|
+
*/
|
|
246
|
+
function splitComparison(evidenceId, comparison, exchange) {
|
|
247
|
+
const identity = comparison.outcome === "documented"
|
|
248
|
+
? { family: "endpoint", method: "request-url", nodeId: comparison.endpoint.id, refusal: null }
|
|
249
|
+
: {
|
|
250
|
+
family: "endpoint",
|
|
251
|
+
method: "request-url",
|
|
252
|
+
nodeId: null,
|
|
253
|
+
refusal: { outcome: comparison.outcome === "ambiguous" ? "ambiguous" : "not-found", reason: comparison.reason },
|
|
254
|
+
};
|
|
255
|
+
const declaredRoles = exchange?.declaredRoles ?? null;
|
|
256
|
+
const conformance = {
|
|
257
|
+
evidenceId,
|
|
258
|
+
endpointOutcome: comparison.outcome,
|
|
259
|
+
reason: comparison.outcome === "documented" ? null : comparison.reason,
|
|
260
|
+
declaredUnderMethods: comparison.outcome === "wrong-method" ? comparison.declaredUnderMethods : [],
|
|
261
|
+
statusBucket: exchange?.statusBucket ?? null,
|
|
262
|
+
statusSurprising: exchange?.statusSurprising ?? null,
|
|
263
|
+
requestSchema: exchange?.requestSchema?.outcome ?? null,
|
|
264
|
+
responseSchema: exchange?.responseSchema?.outcome ?? null,
|
|
265
|
+
contractSchemasDeclared: declaredRoles === null
|
|
266
|
+
? null
|
|
267
|
+
: declaredRoles.request.length + declaredRoles.response.length + declaredRoles.errorResponse.length > 0,
|
|
268
|
+
};
|
|
269
|
+
return { identity, conformance };
|
|
270
|
+
}
|
|
271
|
+
export function correlateExecution(input) {
|
|
272
|
+
const { store, driver, executionId } = input;
|
|
273
|
+
const scopeByService = input.scopeByService ?? {};
|
|
274
|
+
const attributed = [];
|
|
275
|
+
const secondary = [];
|
|
276
|
+
const refusals = [];
|
|
277
|
+
const conformance = [];
|
|
278
|
+
const unscopedServices = new Set();
|
|
279
|
+
/** detail → occurrences. Written to the store once each, after the pass, so a dead driver produces one row rather than one per evidence item. */
|
|
280
|
+
const thrown = new Map();
|
|
281
|
+
let considered = 0;
|
|
282
|
+
let skipped = 0;
|
|
283
|
+
/**
|
|
284
|
+
* RT-229's declined wrap, now that a call site exists for it. Every
|
|
285
|
+
* resolver reads the graph through `@descryy/core`'s store reader,
|
|
286
|
+
* which has no try/catch of its own: a `DriverError` from it propagates
|
|
287
|
+
* out of a resolver as a throw, and unguarded here it would reject this
|
|
288
|
+
* whole pass with no evidence trail — the identical "harness machinery
|
|
289
|
+
* breaks, zero evidence trail" shape RT-219 fixed for `collector.start()`
|
|
290
|
+
* and RT-228 for `ExecutionController.run()`.
|
|
291
|
+
*
|
|
292
|
+
* A throw is NOT a refusal and is deliberately not folded into one: the
|
|
293
|
+
* item is left unattributed and the failure is reported as the harness's
|
|
294
|
+
* own, so a reader can tell "we looked and the graph does not have it"
|
|
295
|
+
* from "our own reader broke" without inspecting payload text.
|
|
296
|
+
*/
|
|
297
|
+
function attempt(fn, what) {
|
|
298
|
+
try {
|
|
299
|
+
return fn();
|
|
300
|
+
}
|
|
301
|
+
catch (error) {
|
|
302
|
+
const cause = error instanceof Error ? `${error.name}: ${error.message}` : String(error);
|
|
303
|
+
const detail = `${what} threw instead of returning a resolution: ${cause}`;
|
|
304
|
+
thrown.set(detail, (thrown.get(detail) ?? 0) + 1);
|
|
305
|
+
return null;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
for (const evidence of store.getByExecution(executionId)) {
|
|
309
|
+
const family = classifyEvidence(evidence);
|
|
310
|
+
// A backend line carries a possible endpoint naming in its own text
|
|
311
|
+
// whether or not any family claimed it: a `BACKEND_LOG` with no stack
|
|
312
|
+
// frame classifies as nothing, and is still the only record that a
|
|
313
|
+
// particular route was exercised.
|
|
314
|
+
const backendSourced = evidence.source === "backend-log" || evidence.source === "backend-process";
|
|
315
|
+
if (family === null && !backendSourced) {
|
|
316
|
+
skipped += 1;
|
|
317
|
+
continue;
|
|
318
|
+
}
|
|
319
|
+
const outcomes = [];
|
|
320
|
+
if (family === null)
|
|
321
|
+
considered += 1;
|
|
322
|
+
if (family === "endpoint") {
|
|
323
|
+
considered += 1;
|
|
324
|
+
const target = httpTarget(evidence.payload);
|
|
325
|
+
if (!target.ok) {
|
|
326
|
+
outcomes.push({ family, method: "request-url", nodeId: null, refusal: { outcome: target.outcome, reason: target.reason } });
|
|
327
|
+
}
|
|
328
|
+
else {
|
|
329
|
+
const observed = { method: target.method, path: target.path };
|
|
330
|
+
// One graph query, through `openapi-observation`'s wrapper — which is
|
|
331
|
+
// where `resolveEndpointNode` is actually called. Which wrapper
|
|
332
|
+
// depends on whether a status was observed, and nothing else: the
|
|
333
|
+
// exchange comparison needs one and it is never invented.
|
|
334
|
+
const status = target.status;
|
|
335
|
+
const exchange = status === null
|
|
336
|
+
? null
|
|
337
|
+
: attempt(() => compareObservedExchange(driver, {
|
|
338
|
+
...observed,
|
|
339
|
+
status,
|
|
340
|
+
requestBody: target.requestBody,
|
|
341
|
+
responseBody: target.responseBody,
|
|
342
|
+
}), "compareObservedExchange");
|
|
343
|
+
const comparison = status === null ? attempt(() => compareObservedEndpoint(driver, observed), "compareObservedEndpoint") : (exchange?.endpoint ?? null);
|
|
344
|
+
if (comparison !== null) {
|
|
345
|
+
const split = splitComparison(evidence.evidenceId, comparison, exchange);
|
|
346
|
+
outcomes.push(split.identity);
|
|
347
|
+
conformance.push(split.conformance);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
else if (family === "database-table") {
|
|
352
|
+
considered += 1;
|
|
353
|
+
const resolution = attempt(() => resolveObservedDatabaseQuery(driver, evidence), "resolveObservedDatabaseQuery");
|
|
354
|
+
if (resolution !== null) {
|
|
355
|
+
outcomes.push({
|
|
356
|
+
family,
|
|
357
|
+
method: "query-table",
|
|
358
|
+
nodeId: resolution.outcome === "resolved" ? resolution.node.id : null,
|
|
359
|
+
refusal: resolution.outcome === "resolved" ? null : { outcome: resolution.outcome, reason: resolution.reason },
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
else if (family === "test-case" || family === "symbol") {
|
|
364
|
+
const service = evidence.service;
|
|
365
|
+
const scope = service === null ? undefined : scopeByService[service];
|
|
366
|
+
if (scope === undefined) {
|
|
367
|
+
// Not a refusal: the caller never claimed this service's evidence
|
|
368
|
+
// was resolvable against a repository, so nothing was attempted.
|
|
369
|
+
// Named here so "we did not try" stays distinguishable from "we
|
|
370
|
+
// tried and found nothing" (plan §17). The log-text pass below still
|
|
371
|
+
// runs for backend evidence — `API_ENDPOINT` is workspace-scoped
|
|
372
|
+
// (DEC-054) and needs no repository to resolve against.
|
|
373
|
+
unscopedServices.add(service ?? "(no service)");
|
|
374
|
+
skipped += 1;
|
|
375
|
+
}
|
|
376
|
+
else {
|
|
377
|
+
considered += 1;
|
|
378
|
+
const options = {
|
|
379
|
+
...(scope.repoRoot === undefined ? {} : { repoRoot: scope.repoRoot }),
|
|
380
|
+
...(scope.cwd === undefined ? {} : { cwd: scope.cwd }),
|
|
381
|
+
};
|
|
382
|
+
const location = evidence.sourceLocation;
|
|
383
|
+
const resolution = family === "test-case"
|
|
384
|
+
? attempt(() => resolveObservedTestCase(driver, evidence, scope.repo, options), "resolveObservedTestCase")
|
|
385
|
+
: attempt(() => resolveSymbolNode(driver, {
|
|
386
|
+
file: location.file,
|
|
387
|
+
symbolName: location.functionName,
|
|
388
|
+
...(location.line === null ? {} : { line: location.line }),
|
|
389
|
+
}, scope.repo, options), "resolveSymbolNode");
|
|
390
|
+
if (resolution !== null) {
|
|
391
|
+
outcomes.push({
|
|
392
|
+
family,
|
|
393
|
+
method: family === "test-case" ? "test-location" : "stack-frame",
|
|
394
|
+
nodeId: resolution.outcome === "resolved" ? resolution.node.id : null,
|
|
395
|
+
refusal: resolution.outcome === "resolved" ? null : { outcome: resolution.outcome, reason: resolution.reason },
|
|
396
|
+
});
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
// The second naming a backend line can carry alongside its own frame
|
|
401
|
+
// (RT-052, and `continuous-chain.test.ts`'s `extraAttributions` block,
|
|
402
|
+
// which is the glue this replaces). Attempted for backend-sourced
|
|
403
|
+
// evidence regardless of which family claimed the item — including a
|
|
404
|
+
// BACKEND_LOG with no stack at all, where it becomes the item's only
|
|
405
|
+
// naming and is therefore the one persisted.
|
|
406
|
+
if (backendSourced) {
|
|
407
|
+
const text = typeof evidence.payload === "string" ? evidence.payload : JSON.stringify(evidence.payload);
|
|
408
|
+
const resolution = attempt(() => resolveEndpointFromLogText(driver, text), "resolveEndpointFromLogText");
|
|
409
|
+
if (resolution !== null && resolution.outcome === "resolved") {
|
|
410
|
+
outcomes.push({ family: "log-text-endpoint", method: "log-text-endpoint", nodeId: resolution.nodeId, refusal: null });
|
|
411
|
+
}
|
|
412
|
+
// A `no-mention` is not a refusal worth reporting: most log lines
|
|
413
|
+
// simply are not about an endpoint, and recording one per line would
|
|
414
|
+
// bury the refusals that mean something. `not-found`/`ambiguous` DO
|
|
415
|
+
// mean something -- the line named routes and the graph could not
|
|
416
|
+
// place them -- and are reported.
|
|
417
|
+
else if (resolution !== null && resolution.outcome !== "no-mention") {
|
|
418
|
+
refusals.push({ evidenceId: evidence.evidenceId, family: "log-text-endpoint", outcome: resolution.outcome, reason: resolution.reason });
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
let persistedNodeId = evidence.graphNodeId;
|
|
422
|
+
for (const outcome of outcomes) {
|
|
423
|
+
if (outcome.refusal !== null) {
|
|
424
|
+
refusals.push({ evidenceId: evidence.evidenceId, family: outcome.family, outcome: outcome.refusal.outcome, reason: outcome.refusal.reason });
|
|
425
|
+
continue;
|
|
426
|
+
}
|
|
427
|
+
if (outcome.nodeId === null)
|
|
428
|
+
continue;
|
|
429
|
+
if (persistedNodeId === null) {
|
|
430
|
+
// `attribute()` throws for an unknown id and refuses to overwrite a
|
|
431
|
+
// different node. Both are real bugs in a caller, and both are
|
|
432
|
+
// wrapped rather than allowed to abort the pass — the store is the
|
|
433
|
+
// other half of the machinery this wrap exists to observe.
|
|
434
|
+
const written = attempt(() => {
|
|
435
|
+
store.attribute(evidence.evidenceId, outcome.nodeId);
|
|
436
|
+
return true;
|
|
437
|
+
}, "EvidenceStore.attribute()");
|
|
438
|
+
if (written === null)
|
|
439
|
+
continue;
|
|
440
|
+
persistedNodeId = outcome.nodeId;
|
|
441
|
+
attributed.push({ evidenceId: evidence.evidenceId, graphNodeId: outcome.nodeId, method: outcome.method, family: outcome.family, persisted: true });
|
|
442
|
+
continue;
|
|
443
|
+
}
|
|
444
|
+
if (persistedNodeId === outcome.nodeId) {
|
|
445
|
+
// Already durable — a re-run of the pass, or the same node named
|
|
446
|
+
// twice. `attribute()` is idempotent for this case, so nothing is
|
|
447
|
+
// written and the association is still reported.
|
|
448
|
+
attributed.push({ evidenceId: evidence.evidenceId, graphNodeId: outcome.nodeId, method: outcome.method, family: outcome.family, persisted: true });
|
|
449
|
+
continue;
|
|
450
|
+
}
|
|
451
|
+
const association = {
|
|
452
|
+
evidenceId: evidence.evidenceId,
|
|
453
|
+
graphNodeId: outcome.nodeId,
|
|
454
|
+
method: outcome.method,
|
|
455
|
+
family: outcome.family,
|
|
456
|
+
persisted: false,
|
|
457
|
+
};
|
|
458
|
+
attributed.push(association);
|
|
459
|
+
secondary.push(association);
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
const harnessErrors = [];
|
|
463
|
+
for (const [detail, occurrences] of thrown) {
|
|
464
|
+
const row = store.write({
|
|
465
|
+
executionId,
|
|
466
|
+
timestamp: new Date().toISOString(),
|
|
467
|
+
source: "harness",
|
|
468
|
+
service: null,
|
|
469
|
+
process: null,
|
|
470
|
+
eventType: "COLLECTOR_ERROR",
|
|
471
|
+
payload: {
|
|
472
|
+
raw: `correlateExecution: ${detail}${occurrences > 1 ? ` (${occurrences} occurrences in this pass)` : ""}`,
|
|
473
|
+
error: detail,
|
|
474
|
+
occurrences,
|
|
475
|
+
},
|
|
476
|
+
traceId: null,
|
|
477
|
+
requestId: null,
|
|
478
|
+
correlationId: null,
|
|
479
|
+
graphNodeId: null,
|
|
480
|
+
sourceLocation: null,
|
|
481
|
+
stackTrace: null,
|
|
482
|
+
confidence: 1,
|
|
483
|
+
collectorVersion: CORRELATOR_VERSION,
|
|
484
|
+
});
|
|
485
|
+
harnessErrors.push({ evidenceId: row.evidenceId, detail, occurrences });
|
|
486
|
+
}
|
|
487
|
+
return {
|
|
488
|
+
executionId,
|
|
489
|
+
considered,
|
|
490
|
+
skipped,
|
|
491
|
+
attributed,
|
|
492
|
+
secondary,
|
|
493
|
+
refusals,
|
|
494
|
+
conformance,
|
|
495
|
+
unscopedServices: [...unscopedServices],
|
|
496
|
+
harnessErrors,
|
|
497
|
+
};
|
|
498
|
+
}
|
|
499
|
+
//# sourceMappingURL=correlate-execution.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"correlate-execution.js","sourceRoot":"","sources":["../src/correlate-execution.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export { correlateExecution, classifyEvidence, type CorrelateExecutionInput, type CorrelationScope, type CorrelationFamily, type CorrelationAttribution, type CorrelationRefusal, type CorrelationConformance, type CorrelationHarnessError, type CorrelationPassResult, } from "./correlate-execution.ts";
|
|
2
|
+
export { CORRELATOR_VERSION } from "./collector-version.ts";
|
|
3
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,kBAAkB,EAClB,gBAAgB,EAChB,KAAK,uBAAuB,EAC5B,KAAK,gBAAgB,EACrB,KAAK,iBAAiB,EACtB,KAAK,sBAAsB,EAC3B,KAAK,kBAAkB,EACvB,KAAK,sBAAsB,EAC3B,KAAK,uBAAuB,EAC5B,KAAK,qBAAqB,GAC3B,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,kBAAkB,EAClB,gBAAgB,GASjB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@descryy/runtime-evidence-correlation",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "The production correlation pass (RT-230): the one shipped component that owns an EvidenceStore and a descry-core graph driver together and performs resolve-then-attribute over a finished execution's stored evidence. graph-correlator's typed refusals are carried through as refusals, never converted into errors.",
|
|
6
|
+
"license": "UNLICENSED",
|
|
7
|
+
"engines": {
|
|
8
|
+
"node": ">=22.5"
|
|
9
|
+
},
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"default": "./dist/index.js"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist"
|
|
18
|
+
],
|
|
19
|
+
"publishConfig": {
|
|
20
|
+
"registry": "https://registry.npmjs.org",
|
|
21
|
+
"access": "public"
|
|
22
|
+
},
|
|
23
|
+
"scripts": {
|
|
24
|
+
"build": "tsc -b"
|
|
25
|
+
},
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"@descryy/runtime-contracts": "*",
|
|
28
|
+
"@descryy/runtime-evidence-store": "*",
|
|
29
|
+
"@descryy/runtime-graph-correlator": "*",
|
|
30
|
+
"@descryhq-wq/runtime-openapi-observation": "*",
|
|
31
|
+
"@descryhq-wq/runtime-database-observation": "*",
|
|
32
|
+
"@descryhq-wq/runtime-test-runner": "*",
|
|
33
|
+
"@descryy/core": "^0.1.0"
|
|
34
|
+
}
|
|
35
|
+
}
|