@descryy/runtime-contracts 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/capability.d.ts +118 -0
- package/dist/capability.d.ts.map +1 -0
- package/dist/capability.js +30 -0
- package/dist/capability.js.map +1 -0
- package/dist/collector.d.ts +124 -0
- package/dist/collector.d.ts.map +1 -0
- package/dist/collector.js +42 -0
- package/dist/collector.js.map +1 -0
- package/dist/correlation.d.ts +61 -0
- package/dist/correlation.d.ts.map +1 -0
- package/dist/correlation.js +62 -0
- package/dist/correlation.js.map +1 -0
- package/dist/evidence.d.ts +296 -0
- package/dist/evidence.d.ts.map +1 -0
- package/dist/evidence.js +204 -0
- package/dist/evidence.js.map +1 -0
- package/dist/execution.d.ts +329 -0
- package/dist/execution.d.ts.map +1 -0
- package/dist/execution.js +55 -0
- package/dist/execution.js.map +1 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -0
- package/dist/runtime-event.d.ts +20 -0
- package/dist/runtime-event.d.ts.map +1 -0
- package/dist/runtime-event.js +252 -0
- package/dist/runtime-event.js.map +1 -0
- package/dist/source-root.d.ts +97 -0
- package/dist/source-root.d.ts.map +1 -0
- package/dist/source-root.js +107 -0
- package/dist/source-root.js.map +1 -0
- package/package.json +26 -0
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime Event vocabulary.
|
|
3
|
+
*
|
|
4
|
+
* The exact 19 values below are the canonical list from
|
|
5
|
+
* `documents/descry-runtime-plan.md` §14 — reproduced verbatim, not
|
|
6
|
+
* reconstructed. An earlier version of this file was written before that
|
|
7
|
+
* document was available in this repo and guessed at a vocabulary that did
|
|
8
|
+
* not match it; this replaces that guess.
|
|
9
|
+
*
|
|
10
|
+
* This vocabulary is DELIBERATELY INDEPENDENT of the Graph Engine's 15 node
|
|
11
|
+
* types / 15 edge types (descry-core CLAUDE.md, DEC-061) — plan §14 states
|
|
12
|
+
* this explicitly. A runtime event is an observation, not a graph fact, and
|
|
13
|
+
* this list may grow or change on its own schedule. If a runtime need ever
|
|
14
|
+
* looks like it wants a new *graph* node or edge type, that is out of scope
|
|
15
|
+
* here — stop and raise it (plan principle 12). The graph vocabulary is
|
|
16
|
+
* frozen; this one is not.
|
|
17
|
+
*/
|
|
18
|
+
export const RUNTIME_EVENT_TYPES = [
|
|
19
|
+
"PROCESS_STARTED",
|
|
20
|
+
"PROCESS_READY",
|
|
21
|
+
"PROCESS_EXITED",
|
|
22
|
+
"PAGE_CREATED",
|
|
23
|
+
"NAVIGATION",
|
|
24
|
+
"CLICK",
|
|
25
|
+
"INPUT",
|
|
26
|
+
/**
|
|
27
|
+
* Beyond plan §14's printed 19 — same standing as `COLLECTOR_ERROR` below,
|
|
28
|
+
* which that list also predates. §5 had no honest type for "an image was
|
|
29
|
+
* captured": not `INPUT`, not `CLICK`, not nothing. Landed together with
|
|
30
|
+
* its one emitter (`BrowserActionCollector.screenshot()`, RT-061) in the
|
|
31
|
+
* same change, per RT-053's own lesson — a declared-but-dead type with no
|
|
32
|
+
* annotation is the mistake, not a type existing at all, but the safest
|
|
33
|
+
* way not to repeat it is to not create one ahead of its wiring.
|
|
34
|
+
*/
|
|
35
|
+
"SCREENSHOT",
|
|
36
|
+
/**
|
|
37
|
+
* Video evidence (competitive research, `documents/decisions-inbox/`:
|
|
38
|
+
* `PROPOSAL-video-evidence-capture.md`, this repo, and
|
|
39
|
+
* `descry-desktop`'s companion `DEC-D-NEXT-video-evidence-kind.md` --
|
|
40
|
+
* Greptile's TREX: a captured video of a UI change playing out, attached
|
|
41
|
+
* to a finding alongside screenshots/logs). Same standing as `SCREENSHOT`
|
|
42
|
+
* immediately above and the same discipline: landed together with its one
|
|
43
|
+
* emitter (`BrowserActionCollector.captureVideo()`, `browser-session.ts`'s
|
|
44
|
+
* new `recordVideo` option), not ahead of it.
|
|
45
|
+
*
|
|
46
|
+
* `source: "browser-dom"`, the same source `SCREENSHOT` already uses --
|
|
47
|
+
* see `evidence.ts`'s `EVIDENCE_SOURCES` list for why this did NOT get a
|
|
48
|
+
* new source of its own. Every entry in that list names a domain being
|
|
49
|
+
* *watched*; a kind of captured artifact (an image, a video) is not a
|
|
50
|
+
* domain, which is exactly `SCREENSHOT`'s own precedent applied here
|
|
51
|
+
* rather than re-litigated.
|
|
52
|
+
*/
|
|
53
|
+
"VIDEO",
|
|
54
|
+
"CONSOLE_MESSAGE",
|
|
55
|
+
"NETWORK_REQUEST",
|
|
56
|
+
"NETWORK_RESPONSE",
|
|
57
|
+
"HTTP_ERROR",
|
|
58
|
+
"BACKEND_LOG",
|
|
59
|
+
"EXCEPTION",
|
|
60
|
+
"STACK_TRACE",
|
|
61
|
+
/**
|
|
62
|
+
* §17 (Test Runner Support). **All four are emitted**, by
|
|
63
|
+
* `packages/test-runner`, for `node:test` (RT-073), Jest and Vitest
|
|
64
|
+
* (RT-123) — each read from that runner's own structured reporter API,
|
|
65
|
+
* never from printed prose.
|
|
66
|
+
*
|
|
67
|
+
* The annotation this comment replaces said *"Nothing emits these three,
|
|
68
|
+
* on purpose, not yet"* and asked to be removed "when §17 lands and one
|
|
69
|
+
* of these three actually gets emitted somewhere." §17 landed in RT-073
|
|
70
|
+
* and the comment outlived it by four entries — noted because the
|
|
71
|
+
* mechanism this file exists to guard against is a claim that stops
|
|
72
|
+
* being true and nothing forcing it to be re-read.
|
|
73
|
+
*
|
|
74
|
+
* ## `TEST_SKIPPED` is the fourth, and it exists because the other three
|
|
75
|
+
* made a skipped test unsayable
|
|
76
|
+
*
|
|
77
|
+
* Measured across three runners (RT-124):
|
|
78
|
+
*
|
|
79
|
+
* | runner | `test.skip` | `test.todo` |
|
|
80
|
+
* | --- | --- | --- |
|
|
81
|
+
* | node:test | `test:pass` carrying `skip: true` | `test:pass` carrying `todo: true` |
|
|
82
|
+
* | Vitest 4 | `state: "skipped"` | `state: "skipped"` |
|
|
83
|
+
* | Jest 30 | omitted from `onTestCaseResult` entirely | `status: "todo"` |
|
|
84
|
+
*
|
|
85
|
+
* node:test's is why this is a contract change rather than a bug fix:
|
|
86
|
+
* **a skipped test arrives as a pass event**, and with no fourth member
|
|
87
|
+
* the translator mapped it to `TEST_PASSED`. A skipped test is not a
|
|
88
|
+
* pass (nothing was asserted), not a failure (nothing failed), and not
|
|
89
|
+
* absent (the developer wrote it and the runner knows about it).
|
|
90
|
+
*
|
|
91
|
+
* Counting them was tried first and is what shipped in RT-123. It is
|
|
92
|
+
* honest and insufficient: a counter carries no `sourceLocation`, no
|
|
93
|
+
* `qualifiedName` and no graph node, so **"the function you changed has
|
|
94
|
+
* a test and it is disabled" cannot be expressed at all** — a real
|
|
95
|
+
* finding, needing something resolvable to a `TEST_CASE` node to exist.
|
|
96
|
+
* Expressible-at-all, not accuracy, is what decided this.
|
|
97
|
+
*
|
|
98
|
+
* **Landed with its producers in the same change**, deliberately. §3's
|
|
99
|
+
* warning is about `DATABASE_QUERY`/`EXTERNAL_REQUEST` sitting declared
|
|
100
|
+
* and dead below; a type merged "ready for the producers next week" is
|
|
101
|
+
* that same shape regardless of intent.
|
|
102
|
+
*
|
|
103
|
+
* Consumers were checked before this was added rather than after: the
|
|
104
|
+
* only `eventType` branching outside `test-runner` is
|
|
105
|
+
* `evidence-package`'s two **allowlists** (`NETWORK_EVENTS`,
|
|
106
|
+
* `BACKEND_LOG_EVENTS`), and `composeFinding` does not read `eventType`
|
|
107
|
+
* at all. So nothing treats "not passed" as evidence of a problem, and a
|
|
108
|
+
* disabled test cannot be read as a failing one. A future
|
|
109
|
+
* `!== "TEST_PASSED"` would reintroduce exactly that.
|
|
110
|
+
*
|
|
111
|
+
* ## Why there is no `TEST_ABORTED` (RT-129)
|
|
112
|
+
*
|
|
113
|
+
* JUnit reports **four** outcomes, not three: `aborted` (a failed
|
|
114
|
+
* assumption — ran real code, bailed on a precondition) is distinct from
|
|
115
|
+
* `skipped` (`@Disabled` — never ran). Measured by Lane A (RT-088),
|
|
116
|
+
* along with the fact that JUnit's own XML collapses them into one bare
|
|
117
|
+
* `<skipped>` element while its `TestExecutionListener` keeps them apart.
|
|
118
|
+
*
|
|
119
|
+
* The distinction is real and did **not** get a type. The test applied:
|
|
120
|
+
* **does collapsing it change the answer to "was this code covered?"**
|
|
121
|
+
* Skipped-vs-passed does, oppositely, which is why the fourth member
|
|
122
|
+
* above exists. Skipped-vs-aborted does not — every one of them answers
|
|
123
|
+
* `notRun`, carries a location and a qualified name, and is not a
|
|
124
|
+
* failure, so no consumer routes differently. What differs is the
|
|
125
|
+
* *explanation*, and an explanation belongs in the payload.
|
|
126
|
+
*
|
|
127
|
+
* So `TEST_SKIPPED`'s payload carries `skipReason`
|
|
128
|
+
* (`test-runner`'s `SkipReason`): a closed, runner-agnostic vocabulary
|
|
129
|
+
* of `disabled` / `not-implemented` / `precondition-unmet` /
|
|
130
|
+
* `unspecified`. `unspecified` is a real answer, not a fallback — Vitest
|
|
131
|
+
* cannot tell `.skip` from `.todo`, and saying so beats guessing.
|
|
132
|
+
*
|
|
133
|
+
* This also repaired RT-124's own silent collapse: node:test **does**
|
|
134
|
+
* distinguish `skip` from `todo`, and the first version of this type
|
|
135
|
+
* threw that away.
|
|
136
|
+
*/
|
|
137
|
+
"TEST_STARTED",
|
|
138
|
+
"TEST_PASSED",
|
|
139
|
+
"TEST_FAILED",
|
|
140
|
+
"TEST_SKIPPED",
|
|
141
|
+
/**
|
|
142
|
+
* `DATABASE_QUERY` / `EXTERNAL_REQUEST`: §18 (Database / External Service
|
|
143
|
+
* Observation). Both are now real. `DATABASE_QUERY` is emitted by
|
|
144
|
+
* `@descryhq-wq/runtime-database-observation`'s `DatabaseQueryCollector`, via
|
|
145
|
+
* real `node:sqlite` driver instrumentation (RT-141). `EXTERNAL_REQUEST`
|
|
146
|
+
* is emitted by `@descryhq-wq/runtime-external-service-observation`'s
|
|
147
|
+
* `ExternalRequestCollector`, via real global-`fetch` instrumentation
|
|
148
|
+
* (RT-146) — same `--import`-preload mechanism, a different global.
|
|
149
|
+
* Neither is derived from logs. What §18 still does not have: a
|
|
150
|
+
* transaction-boundary concept for queries, graph correlation for either
|
|
151
|
+
* (no resolver from a table name or a hostname to a graph node exists),
|
|
152
|
+
* and provider classification for external requests (a captured
|
|
153
|
+
* hostname, never a guessed provider name) — real, disclosed gaps, not
|
|
154
|
+
* silent ones.
|
|
155
|
+
*/
|
|
156
|
+
"DATABASE_QUERY",
|
|
157
|
+
"EXTERNAL_REQUEST",
|
|
158
|
+
/**
|
|
159
|
+
* The observer failed, not the observed system. Added beyond plan §14's
|
|
160
|
+
* printed list, which §14 explicitly permits ("the exact event vocabulary
|
|
161
|
+
* can evolve") and which §17 in fact requires: `collector failure` is one
|
|
162
|
+
* of the failures the runtime model must represent, under the rule **"a
|
|
163
|
+
* collector failure must not be mistaken for an application success or
|
|
164
|
+
* absence of failure."**
|
|
165
|
+
*
|
|
166
|
+
* Without this type there was no way to state that at all. A collector
|
|
167
|
+
* whose consumption loop threw mid-stream logged to the console and
|
|
168
|
+
* resolved `stop()` cleanly, so a run that lost half its backend output
|
|
169
|
+
* was indistinguishable from a run where the backend printed half as
|
|
170
|
+
* much. That is precisely the confusion between *found nothing* and
|
|
171
|
+
* *stopped looking* that the five report categories exist to prevent.
|
|
172
|
+
*/
|
|
173
|
+
"COLLECTOR_ERROR",
|
|
174
|
+
/**
|
|
175
|
+
* `DEC-NEXT-fault-layer-empirical-confirmation.md`'s `dependency`
|
|
176
|
+
* mechanism, landed together with its one emitter
|
|
177
|
+
* (`packages/orchestrator/src/instrumented-execution.ts`, via
|
|
178
|
+
* `@descryy/runtime-controller`'s `checkDependencyVersions`) in the same
|
|
179
|
+
* change -- same "not declared ahead of its wiring" discipline
|
|
180
|
+
* `SCREENSHOT`'s own comment states above. Not `COLLECTOR_ERROR`: this is
|
|
181
|
+
* not the observer failing, it is a real structural fact the runtime
|
|
182
|
+
* established about the project it is executing (the lockfile-declared
|
|
183
|
+
* version of a dependency does not match what is actually installed),
|
|
184
|
+
* the same "a genuinely new kind of fact gets its own type" precedent
|
|
185
|
+
* `DATABASE_QUERY`/`EXTERNAL_REQUEST` already set for §18 above. Payload
|
|
186
|
+
* carries `name`/`declaredVersion`/`resolvedVersion`, mirroring
|
|
187
|
+
* `DependencyVersionMismatch`'s own shape.
|
|
188
|
+
*/
|
|
189
|
+
"DEPENDENCY_VERSION_MISMATCH",
|
|
190
|
+
/**
|
|
191
|
+
* The same ruling's `environment` mechanism, same emitter, same standing
|
|
192
|
+
* as `DEPENDENCY_VERSION_MISMATCH` above -- landed together with its one
|
|
193
|
+
* emitter, not a `COLLECTOR_ERROR` because nothing failed to observe.
|
|
194
|
+
* Payload carries `declaredVersion`/`declaredSource`/`observedVersion`,
|
|
195
|
+
* mirroring `EnvironmentVersionCheckResult`'s own shape.
|
|
196
|
+
*/
|
|
197
|
+
"ENVIRONMENT_VERSION_MISMATCH",
|
|
198
|
+
/**
|
|
199
|
+
* RT-225: `BrowserNetworkCollector`'s WebSocket lifecycle listener
|
|
200
|
+
* (`page.on("websocket")`, plus the per-socket `close`/`socketerror`
|
|
201
|
+
* events Playwright's `WebSocket` object exposes). Closes
|
|
202
|
+
* `documents/decisions-inbox/RT-PENDING-websocket-network-observation-gap.md` --
|
|
203
|
+
* a WebSocket connection, successful or refused, fired none of
|
|
204
|
+
* `NETWORK_REQUEST`/`NETWORK_RESPONSE`/`HTTP_ERROR` before this, because
|
|
205
|
+
* those three are wired to `request`/`requestfinished`/`requestfailed`
|
|
206
|
+
* only, and Playwright surfaces WebSocket traffic through a wholly
|
|
207
|
+
* separate event stream.
|
|
208
|
+
*
|
|
209
|
+
* A connection **attempt** reuses `NETWORK_REQUEST` (a real outbound call
|
|
210
|
+
* was made, to a URL -- the same fact that type already exists to
|
|
211
|
+
* record) and a connection **failure** (`socketerror`) reuses
|
|
212
|
+
* `HTTP_ERROR` with `status: null` -- the exact "no usable response ever
|
|
213
|
+
* came back" convention this vocabulary already applies to a refused
|
|
214
|
+
* `fetch()` (`requestfailed`) and to this collector's own timeout. Both
|
|
215
|
+
* reuses are deliberate: forcing WS's *fields* into the HTTP shape would
|
|
216
|
+
* fabricate a `method`/`status` nothing observed, but the *event*
|
|
217
|
+
* (request went out / no usable response arrived) is the identical fact
|
|
218
|
+
* under a different wire protocol, and reusing the type keeps this
|
|
219
|
+
* evidence inside the existing `network` signal channel
|
|
220
|
+
* (`packages/finding/src/signal-channel.ts`) and the existing
|
|
221
|
+
* `NETWORK_EVENTS` package view with no governance-table change.
|
|
222
|
+
*
|
|
223
|
+
* `WEBSOCKET_CLOSED` is the one genuinely new fact, because a **close**
|
|
224
|
+
* has no honest HTTP-shaped answer: `NETWORK_RESPONSE` means "a response
|
|
225
|
+
* with a status arrived," which forcing a close into it would fabricate.
|
|
226
|
+
* Measured, not assumed (`websocket-intercept-gap.test.ts`): Playwright's
|
|
227
|
+
* public `WebSocket.on("close")` carries no code or reason at all --
|
|
228
|
+
* identical for a clean server-initiated 1000 close and a raw abrupt TCP
|
|
229
|
+
* termination. So this type does not claim "normal" vs "abnormal"
|
|
230
|
+
* either; it states only that the socket closed, and says so in its own
|
|
231
|
+
* payload rather than guessing. Emitted only when no `socketerror` was
|
|
232
|
+
* already reported for the same socket, so a failed connection is never
|
|
233
|
+
* double-reported once as `HTTP_ERROR` and again as `WEBSOCKET_CLOSED`.
|
|
234
|
+
*
|
|
235
|
+
* Deliberately outside `signal-channel.ts`'s `CHANNEL_BY_EVENT_TYPE`
|
|
236
|
+
* table (§5.2 of the ratified `ai-governance-spec.md`, a separate,
|
|
237
|
+
* already-settled ruling this change does not reopen) -- falls through to
|
|
238
|
+
* `null`/no-signal there, which is that table's own documented "fail
|
|
239
|
+
* closed" default for a type it has not ruled on, and is the honest
|
|
240
|
+
* answer for a fact this thin: "the socket closed" carries far less on
|
|
241
|
+
* its own than a real request/response/error triple does.
|
|
242
|
+
*
|
|
243
|
+
* Per-message frame traffic (`framesent`/`framereceived`) is not
|
|
244
|
+
* captured as evidence at all -- out of scope here, disclosed rather than
|
|
245
|
+
* silently dropped, in `browser-network-collector.ts`'s own module
|
|
246
|
+
* comment. Neither is `EventSource`/SSE, which shares the same
|
|
247
|
+
* uncovered shape (the RT-PENDING note above flagged it as unmeasured;
|
|
248
|
+
* still unmeasured after this change).
|
|
249
|
+
*/
|
|
250
|
+
"WEBSOCKET_CLOSED",
|
|
251
|
+
];
|
|
252
|
+
//# sourceMappingURL=runtime-event.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runtime-event.js","sourceRoot":"","sources":["../src/runtime-event.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,MAAM,CAAC,MAAM,mBAAmB,GAAG;IACjC,iBAAiB;IACjB,eAAe;IACf,gBAAgB;IAChB,cAAc;IACd,YAAY;IACZ,OAAO;IACP,OAAO;IACP;;;;;;;;OAQG;IACH,YAAY;IACZ;;;;;;;;;;;;;;;;OAgBG;IACH,OAAO;IACP,iBAAiB;IACjB,iBAAiB;IACjB,kBAAkB;IAClB,YAAY;IACZ,aAAa;IACb,WAAW;IACX,aAAa;IACb;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA2EG;IACH,cAAc;IACd,aAAa;IACb,aAAa;IACb,cAAc;IACd;;;;;;;;;;;;;;OAcG;IACH,gBAAgB;IAChB,kBAAkB;IAClB;;;;;;;;;;;;;;OAcG;IACH,iBAAiB;IACjB;;;;;;;;;;;;;;OAcG;IACH,6BAA6B;IAC7B;;;;;;OAMG;IACH,8BAA8B;IAC9B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAmDG;IACH,kBAAkB;CACV,CAAC"}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Origin -> service root (RT-023, built on RT-028's `ProcessHandle.serviceName`/
|
|
3
|
+
* `.port` and `ExecutionConfiguration.services`). Answers "which service's
|
|
4
|
+
* declared `cwd` served this browser origin," the missing link
|
|
5
|
+
* `BrowserConsoleCollector` needs to turn `http://localhost:4000/app.js`
|
|
6
|
+
* into a candidate on-disk root before the existing `file://`-based source
|
|
7
|
+
* resolution logic can run.
|
|
8
|
+
*
|
|
9
|
+
* Deliberately stops here, at a configuration-layer fact, not a collector.
|
|
10
|
+
* `packages/browser`'s runtime code depends on `@descryy/runtime-contracts`
|
|
11
|
+
* only (RT-021's precedent) -- this lives in contracts so that boundary
|
|
12
|
+
* holds for this resolver too.
|
|
13
|
+
*
|
|
14
|
+
* Two distinct, RT-024-shaped honesty gaps live here, not one:
|
|
15
|
+
*
|
|
16
|
+
* 1. **The port join proves an address, not a correspondent.** Matching
|
|
17
|
+
* `origin.port` to the `ProcessHandle` we recorded on that port proves
|
|
18
|
+
* the browser's origin matches a port this execution assigned -- not
|
|
19
|
+
* that the browser actually talked to that specific process. Under
|
|
20
|
+
* ephemeral allocation (RT-024's own default) this is true almost all
|
|
21
|
+
* of the time, which is exactly the phrase RT-024 says not to write
|
|
22
|
+
* down as identity. Nothing here re-verifies the process is still the
|
|
23
|
+
* one that was live when the browser made the request.
|
|
24
|
+
* 2. **A resolved root is configured, not verified** -- see
|
|
25
|
+
* `ConfiguredServiceRoot`.
|
|
26
|
+
*
|
|
27
|
+
* Same class of gap, two layers: a port match confirms an address: a
|
|
28
|
+
* `cwd` confirms a declared root. Neither confirms who was actually there.
|
|
29
|
+
*/
|
|
30
|
+
import type { Execution } from "./execution.ts";
|
|
31
|
+
/**
|
|
32
|
+
* A service's declared working directory, recovered by joining a running
|
|
33
|
+
* process's assigned port back to `ExecutionConfiguration.services`.
|
|
34
|
+
*
|
|
35
|
+
* **Configured, not verified.** This proves the origin maps to a directory
|
|
36
|
+
* the orchestrator was told to run the service from -- it does not prove
|
|
37
|
+
* any specific file a browser frame names actually lives under it. A
|
|
38
|
+
* static file server rooted at a subdirectory (`public/`, `dist/`), a
|
|
39
|
+
* symlink, a proxy, or a `cwd` that changed after spawn would all break
|
|
40
|
+
* the stronger claim without breaking this weaker one. Same shape as
|
|
41
|
+
* RT-024's "readiness proves liveness, not identity": this proves *a*
|
|
42
|
+
* root, not that a given file came from the checkout in it.
|
|
43
|
+
*
|
|
44
|
+
* Not a refusal, because the weaker claim is still real and useful, and
|
|
45
|
+
* because this project already has a pattern for exactly this gap: the
|
|
46
|
+
* caller joins the frame's path onto `declaredCwd` and confirms the file
|
|
47
|
+
* exists there before trusting a source location built from it -- the same
|
|
48
|
+
* `existsSync` discipline `createNodeSourceLocationResolver` already
|
|
49
|
+
* applies to `file://` paths rather than trusting "the path looks right."
|
|
50
|
+
* A failed existence check downstream is the honest refusal; this function
|
|
51
|
+
* returning a root is not, by itself, a claim that the check will pass.
|
|
52
|
+
*/
|
|
53
|
+
export interface ConfiguredServiceRoot {
|
|
54
|
+
readonly serviceName: string;
|
|
55
|
+
readonly declaredCwd: string;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Closed, so a caller can branch on *why* rather than pattern-match prose --
|
|
59
|
+
* same reasoning as `descry-core`'s `attrs.refusalClass`: free-text refusal
|
|
60
|
+
* reasons can't be counted by a census or acted on differently by a caller.
|
|
61
|
+
* The four cases split into three different kinds of problem a caller
|
|
62
|
+
* would want to tell apart: `originNoPort`/`originUnparseable` mean the
|
|
63
|
+
* input itself wasn't a resolvable origin; `noProcessOnPort` means nothing
|
|
64
|
+
* in this execution claims that port at all; `processUnnamed` is a wiring
|
|
65
|
+
* gap (a real process, spawned without a `serviceName`) that's closable by
|
|
66
|
+
* whoever spawned it; `serviceNotConfigured` is a configuration error (a
|
|
67
|
+
* `serviceName` with no matching `services` entry) -- different from a
|
|
68
|
+
* wiring gap because the name exists but the config doesn't back it.
|
|
69
|
+
*/
|
|
70
|
+
export declare const SERVICE_ROOT_REFUSALS: readonly ["originNoPort", "originUnparseable", "noProcessOnPort", "processUnnamed", "serviceNotConfigured"];
|
|
71
|
+
export type ServiceRootRefusal = (typeof SERVICE_ROOT_REFUSALS)[number];
|
|
72
|
+
export type ServiceRootLookup = {
|
|
73
|
+
readonly found: true;
|
|
74
|
+
readonly root: ConfiguredServiceRoot;
|
|
75
|
+
} | {
|
|
76
|
+
readonly found: false;
|
|
77
|
+
readonly refusal: ServiceRootRefusal;
|
|
78
|
+
readonly message: string;
|
|
79
|
+
};
|
|
80
|
+
/**
|
|
81
|
+
* Resolves a browser origin (e.g. `"http://localhost:4000"`) to the
|
|
82
|
+
* service that served it, by port: `execution.processes[].port` (the real,
|
|
83
|
+
* OS-assigned port -- RT-028) to `.serviceName`, then `.serviceName` to
|
|
84
|
+
* `execution.configuration.services[name].cwd`.
|
|
85
|
+
*
|
|
86
|
+
* `origin` is a clean origin, not a full frame location -- stripping a
|
|
87
|
+
* `file:37:12`-shaped V8 frame down to its origin is the caller's job
|
|
88
|
+
* (mirrors `createNodeSourceLocationResolver`'s own division of labor:
|
|
89
|
+
* frame parsing is a separate concern from root resolution).
|
|
90
|
+
*
|
|
91
|
+
* Two services sharing the same `declaredCwd` in configuration is not
|
|
92
|
+
* ambiguous here: resolution runs process -> serviceName -> a direct keyed
|
|
93
|
+
* lookup in `services`, never a reverse search by directory, so a
|
|
94
|
+
* coincidentally-shared path on the output side has nothing to disambiguate.
|
|
95
|
+
*/
|
|
96
|
+
export declare function resolveServiceRootForOrigin(origin: string, execution: Pick<Execution, "processes" | "configuration">): ServiceRootLookup;
|
|
97
|
+
//# sourceMappingURL=source-root.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"source-root.d.ts","sourceRoot":"","sources":["../src/source-root.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAEhD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;CAC9B;AAED;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,qBAAqB,6GAMxB,CAAC;AACX,MAAM,MAAM,kBAAkB,GAAG,CAAC,OAAO,qBAAqB,CAAC,CAAC,MAAM,CAAC,CAAC;AAExE,MAAM,MAAM,iBAAiB,GACzB;IAAE,QAAQ,CAAC,KAAK,EAAE,IAAI,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,qBAAqB,CAAA;CAAE,GAC9D;IAAE,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,kBAAkB,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AAE9F;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,2BAA2B,CACzC,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,IAAI,CAAC,SAAS,EAAE,WAAW,GAAG,eAAe,CAAC,GACxD,iBAAiB,CA2CnB"}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Origin -> service root (RT-023, built on RT-028's `ProcessHandle.serviceName`/
|
|
3
|
+
* `.port` and `ExecutionConfiguration.services`). Answers "which service's
|
|
4
|
+
* declared `cwd` served this browser origin," the missing link
|
|
5
|
+
* `BrowserConsoleCollector` needs to turn `http://localhost:4000/app.js`
|
|
6
|
+
* into a candidate on-disk root before the existing `file://`-based source
|
|
7
|
+
* resolution logic can run.
|
|
8
|
+
*
|
|
9
|
+
* Deliberately stops here, at a configuration-layer fact, not a collector.
|
|
10
|
+
* `packages/browser`'s runtime code depends on `@descryy/runtime-contracts`
|
|
11
|
+
* only (RT-021's precedent) -- this lives in contracts so that boundary
|
|
12
|
+
* holds for this resolver too.
|
|
13
|
+
*
|
|
14
|
+
* Two distinct, RT-024-shaped honesty gaps live here, not one:
|
|
15
|
+
*
|
|
16
|
+
* 1. **The port join proves an address, not a correspondent.** Matching
|
|
17
|
+
* `origin.port` to the `ProcessHandle` we recorded on that port proves
|
|
18
|
+
* the browser's origin matches a port this execution assigned -- not
|
|
19
|
+
* that the browser actually talked to that specific process. Under
|
|
20
|
+
* ephemeral allocation (RT-024's own default) this is true almost all
|
|
21
|
+
* of the time, which is exactly the phrase RT-024 says not to write
|
|
22
|
+
* down as identity. Nothing here re-verifies the process is still the
|
|
23
|
+
* one that was live when the browser made the request.
|
|
24
|
+
* 2. **A resolved root is configured, not verified** -- see
|
|
25
|
+
* `ConfiguredServiceRoot`.
|
|
26
|
+
*
|
|
27
|
+
* Same class of gap, two layers: a port match confirms an address: a
|
|
28
|
+
* `cwd` confirms a declared root. Neither confirms who was actually there.
|
|
29
|
+
*/
|
|
30
|
+
/**
|
|
31
|
+
* Closed, so a caller can branch on *why* rather than pattern-match prose --
|
|
32
|
+
* same reasoning as `descry-core`'s `attrs.refusalClass`: free-text refusal
|
|
33
|
+
* reasons can't be counted by a census or acted on differently by a caller.
|
|
34
|
+
* The four cases split into three different kinds of problem a caller
|
|
35
|
+
* would want to tell apart: `originNoPort`/`originUnparseable` mean the
|
|
36
|
+
* input itself wasn't a resolvable origin; `noProcessOnPort` means nothing
|
|
37
|
+
* in this execution claims that port at all; `processUnnamed` is a wiring
|
|
38
|
+
* gap (a real process, spawned without a `serviceName`) that's closable by
|
|
39
|
+
* whoever spawned it; `serviceNotConfigured` is a configuration error (a
|
|
40
|
+
* `serviceName` with no matching `services` entry) -- different from a
|
|
41
|
+
* wiring gap because the name exists but the config doesn't back it.
|
|
42
|
+
*/
|
|
43
|
+
export const SERVICE_ROOT_REFUSALS = [
|
|
44
|
+
"originNoPort",
|
|
45
|
+
"originUnparseable",
|
|
46
|
+
"noProcessOnPort",
|
|
47
|
+
"processUnnamed",
|
|
48
|
+
"serviceNotConfigured",
|
|
49
|
+
];
|
|
50
|
+
/**
|
|
51
|
+
* Resolves a browser origin (e.g. `"http://localhost:4000"`) to the
|
|
52
|
+
* service that served it, by port: `execution.processes[].port` (the real,
|
|
53
|
+
* OS-assigned port -- RT-028) to `.serviceName`, then `.serviceName` to
|
|
54
|
+
* `execution.configuration.services[name].cwd`.
|
|
55
|
+
*
|
|
56
|
+
* `origin` is a clean origin, not a full frame location -- stripping a
|
|
57
|
+
* `file:37:12`-shaped V8 frame down to its origin is the caller's job
|
|
58
|
+
* (mirrors `createNodeSourceLocationResolver`'s own division of labor:
|
|
59
|
+
* frame parsing is a separate concern from root resolution).
|
|
60
|
+
*
|
|
61
|
+
* Two services sharing the same `declaredCwd` in configuration is not
|
|
62
|
+
* ambiguous here: resolution runs process -> serviceName -> a direct keyed
|
|
63
|
+
* lookup in `services`, never a reverse search by directory, so a
|
|
64
|
+
* coincidentally-shared path on the output side has nothing to disambiguate.
|
|
65
|
+
*/
|
|
66
|
+
export function resolveServiceRootForOrigin(origin, execution) {
|
|
67
|
+
let port;
|
|
68
|
+
try {
|
|
69
|
+
const url = new URL(origin);
|
|
70
|
+
if (url.port === "") {
|
|
71
|
+
return {
|
|
72
|
+
found: false,
|
|
73
|
+
refusal: "originNoPort",
|
|
74
|
+
message: `origin "${origin}" has no explicit port; cannot match against a process's assigned port`,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
port = Number(url.port);
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
return { found: false, refusal: "originUnparseable", message: `"${origin}" is not a parseable origin` };
|
|
81
|
+
}
|
|
82
|
+
const process = execution.processes.find((candidate) => candidate.port === port);
|
|
83
|
+
if (process === undefined) {
|
|
84
|
+
return {
|
|
85
|
+
found: false,
|
|
86
|
+
refusal: "noProcessOnPort",
|
|
87
|
+
message: `no process in this execution is bound to port ${port}`,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
if (process.serviceName === null) {
|
|
91
|
+
return {
|
|
92
|
+
found: false,
|
|
93
|
+
refusal: "processUnnamed",
|
|
94
|
+
message: `the process bound to port ${port} has no serviceName -- it was not spawned as a named service, so its declared root cannot be recovered from configuration.services`,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
const service = execution.configuration.services[process.serviceName];
|
|
98
|
+
if (service === undefined) {
|
|
99
|
+
return {
|
|
100
|
+
found: false,
|
|
101
|
+
refusal: "serviceNotConfigured",
|
|
102
|
+
message: `serviceName "${process.serviceName}" (from the process bound to port ${port}) has no matching entry in configuration.services`,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
return { found: true, root: { serviceName: process.serviceName, declaredCwd: service.cwd } };
|
|
106
|
+
}
|
|
107
|
+
//# sourceMappingURL=source-root.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"source-root.js","sourceRoot":"","sources":["../src/source-root.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AA+BH;;;;;;;;;;;;GAYG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG;IACnC,cAAc;IACd,mBAAmB;IACnB,iBAAiB;IACjB,gBAAgB;IAChB,sBAAsB;CACd,CAAC;AAOX;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,2BAA2B,CACzC,MAAc,EACd,SAAyD;IAEzD,IAAI,IAAY,CAAC;IACjB,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,CAAC;QAC5B,IAAI,GAAG,CAAC,IAAI,KAAK,EAAE,EAAE,CAAC;YACpB,OAAO;gBACL,KAAK,EAAE,KAAK;gBACZ,OAAO,EAAE,cAAc;gBACvB,OAAO,EAAE,WAAW,MAAM,wEAAwE;aACnG,CAAC;QACJ,CAAC;QACD,IAAI,GAAG,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAC1B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,mBAAmB,EAAE,OAAO,EAAE,IAAI,MAAM,6BAA6B,EAAE,CAAC;IAC1G,CAAC;IAED,MAAM,OAAO,GAAG,SAAS,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,SAAS,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC;IACjF,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;QAC1B,OAAO;YACL,KAAK,EAAE,KAAK;YACZ,OAAO,EAAE,iBAAiB;YAC1B,OAAO,EAAE,iDAAiD,IAAI,EAAE;SACjE,CAAC;IACJ,CAAC;IAED,IAAI,OAAO,CAAC,WAAW,KAAK,IAAI,EAAE,CAAC;QACjC,OAAO;YACL,KAAK,EAAE,KAAK;YACZ,OAAO,EAAE,gBAAgB;YACzB,OAAO,EAAE,6BAA6B,IAAI,oIAAoI;SAC/K,CAAC;IACJ,CAAC;IAED,MAAM,OAAO,GAAG,SAAS,CAAC,aAAa,CAAC,QAAQ,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;IACtE,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;QAC1B,OAAO;YACL,KAAK,EAAE,KAAK;YACZ,OAAO,EAAE,sBAAsB;YAC/B,OAAO,EAAE,gBAAgB,OAAO,CAAC,WAAW,qCAAqC,IAAI,mDAAmD;SACzI,CAAC;IACJ,CAAC;IAED,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,WAAW,EAAE,OAAO,CAAC,WAAW,EAAE,WAAW,EAAE,OAAO,CAAC,GAAG,EAAE,EAAE,CAAC;AAC/F,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@descryy/runtime-contracts",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "The six runtime contracts: Execution, Evidence, Runtime Event, Collector, Capability, Correlation. Agents 2-4 build against this; it does not depend on them.",
|
|
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://npm.pkg.github.com"
|
|
21
|
+
},
|
|
22
|
+
"scripts": {
|
|
23
|
+
"build": "tsc -b"
|
|
24
|
+
},
|
|
25
|
+
"dependencies": {}
|
|
26
|
+
}
|