@descryy/runtime-contracts 0.2.0 → 0.3.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.
@@ -1,19 +1,9 @@
1
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.
2
+ * Runtime Event vocabulary, per §14. Deliberately independent of the
3
+ * Graph Engine's 15 node/15 edge types (DEC-061) — a runtime event is an
4
+ * observation, not a graph fact, and can grow on its own schedule. A need
5
+ * that looks like a new graph node/edge type is out of scope here — the
6
+ * graph vocabulary is frozen, this one is not.
17
7
  */
18
8
  export const RUNTIME_EVENT_TYPES = [
19
9
  "PROCESS_STARTED",
@@ -23,32 +13,13 @@ export const RUNTIME_EVENT_TYPES = [
23
13
  "NAVIGATION",
24
14
  "CLICK",
25
15
  "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
- */
16
+ /** Beyond plan §14's printed 19: no honest type existed for "an image was captured." Landed with its one emitter (`BrowserActionCollector.screenshot()`, RT-061), not ahead of it. */
35
17
  "SCREENSHOT",
36
18
  /**
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.
19
+ * Video evidence, landed with its one emitter
20
+ * (`BrowserActionCollector.captureVideo()`), same discipline as
21
+ * `SCREENSHOT`. Uses `source: "browser-dom"`, not a new source — a
22
+ * captured-artifact kind isn't a watched domain (see `EVIDENCE_SOURCES`).
52
23
  */
53
24
  "VIDEO",
54
25
  "CONSOLE_MESSAGE",
@@ -59,213 +30,105 @@ export const RUNTIME_EVENT_TYPES = [
59
30
  "EXCEPTION",
60
31
  "STACK_TRACE",
61
32
  /**
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
33
+ * §17. All four emitted by `packages/test-runner` for node:test,
34
+ * Jest and Vitest, each read from the runner's own structured reporter
35
+ * API, never printed prose.
76
36
  *
77
- * Measured across three runners (RT-124):
37
+ * `TEST_SKIPPED` exists because the other three make a skipped test
38
+ * unsayable. Measured across runners (RT-124):
78
39
  *
79
40
  * | runner | `test.skip` | `test.todo` |
80
41
  * | --- | --- | --- |
81
- * | node:test | `test:pass` carrying `skip: true` | `test:pass` carrying `todo: true` |
42
+ * | node:test | `test:pass` w/ `skip: true` | `test:pass` w/ `todo: true` |
82
43
  * | 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.
44
+ * | Jest 30 | omitted from `onTestCaseResult` | `status: "todo"` |
45
+ *
46
+ * node:test reports a skip as a pass event — without a fourth member
47
+ * the translator mapped it to `TEST_PASSED`. A counter was tried first
48
+ * (RT-123) but can't carry `sourceLocation`/`qualifiedName`, so "this
49
+ * function's test is disabled" isn't expressible as a finding.
50
+ *
51
+ * No `TEST_ABORTED`: JUnit's `aborted` (failed assumption) is distinct
52
+ * from `skipped` (`@Disabled`, RT-088/RT-129), but both answer `notRun`
53
+ * and neither is a failure, so no consumer routes differently — the
54
+ * difference is explanation, not type. `TEST_SKIPPED`'s payload carries
55
+ * `skipReason` (`disabled`/`not-implemented`/`precondition-unmet`/
56
+ * `unspecified`) instead; `unspecified` is a real answer since Vitest
57
+ * can't tell `.skip` from `.todo`.
136
58
  */
137
59
  "TEST_STARTED",
138
60
  "TEST_PASSED",
139
61
  "TEST_FAILED",
140
62
  "TEST_SKIPPED",
141
63
  /**
142
- * `DATABASE_QUERY` / `EXTERNAL_REQUEST`: §18 (Database / External Service
143
- * Observation). Both are now real. `DATABASE_QUERY` is emitted by
144
- * `@descryy/runtime-database-observation`'s `DatabaseQueryCollector`, via
145
- * real `node:sqlite` driver instrumentation (RT-141). `EXTERNAL_REQUEST`
146
- * is emitted by `@descryy/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.
64
+ * §18. `DATABASE_QUERY` via `DatabaseQueryCollector`'s real `node:sqlite`
65
+ * instrumentation (RT-141); `EXTERNAL_REQUEST` via `ExternalRequestCollector`'s
66
+ * real global-`fetch` instrumentation (RT-146). Neither derived from logs.
67
+ * Disclosed gaps: no transaction-boundary concept, no graph correlation
68
+ * (table name / hostname to graph node), no provider classification for
69
+ * external requests (hostname captured, never a guessed provider).
155
70
  */
156
71
  "DATABASE_QUERY",
157
72
  "EXTERNAL_REQUEST",
158
73
  /**
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.
74
+ * The observer failed, not the observed system (§17: "a collector
75
+ * failure must not be mistaken for an application success or absence of
76
+ * failure"). Without it, a collector whose consumption loop threw
77
+ * mid-stream logged to console and resolved `stop()` cleanly — a run
78
+ * that lost half its backend output was indistinguishable from one
79
+ * where the backend just printed less.
172
80
  */
173
81
  "COLLECTOR_ERROR",
174
82
  /**
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.
83
+ * `checkDependencyVersions`: lockfile-declared dependency version vs.
84
+ * what's actually installed. Not `COLLECTOR_ERROR` — nothing failed to
85
+ * observe, this is a structural fact about the project. Payload carries
86
+ * `name`/`declaredVersion`/`resolvedVersion`.
188
87
  */
189
88
  "DEPENDENCY_VERSION_MISMATCH",
190
89
  /**
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.
90
+ * `checkEnvironmentVersion`, same standing as `DEPENDENCY_VERSION_MISMATCH`.
91
+ * Payload carries `declaredVersion`/`declaredSource`/`observedVersion`.
196
92
  */
197
93
  "ENVIRONMENT_VERSION_MISMATCH",
198
94
  /**
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).
95
+ * RT-225: `BrowserNetworkCollector`'s WebSocket lifecycle listener.
96
+ * Before this, a WS connection fired none of
97
+ * `NETWORK_REQUEST`/`NETWORK_RESPONSE`/`HTTP_ERROR` — those are wired to
98
+ * `request`/`requestfinished`/`requestfailed` only, and Playwright
99
+ * surfaces WebSocket traffic through a separate event stream.
100
+ *
101
+ * A connection attempt reuses `NETWORK_REQUEST`; a connection failure
102
+ * (`socketerror`) reuses `HTTP_ERROR` with `status: null` — same "no
103
+ * usable response" convention as a refused `fetch()`. Forcing WS fields
104
+ * into the HTTP shape would fabricate a method/status, but the event
105
+ * itself is the same fact under a different protocol, so reuse keeps
106
+ * this in the existing network signal channel.
107
+ *
108
+ * `WEBSOCKET_CLOSED` is the one new fact: a close has no honest
109
+ * HTTP-shaped answer. Measured (`websocket-intercept-gap.test.ts`):
110
+ * Playwright's `WebSocket.on("close")` carries no code or reason —
111
+ * identical for a clean 1000 close and an abrupt TCP termination — so
112
+ * this type doesn't claim normal vs. abnormal either. Emitted only when
113
+ * no `socketerror` was already reported for the same socket, so a
114
+ * failed connection isn't double-reported.
115
+ *
116
+ * Deliberately outside `signal-channel.ts`'s `CHANNEL_BY_EVENT_TYPE` —
117
+ * falls through to no-signal, the documented fail-closed default.
118
+ * Per-message frame traffic and EventSource/SSE are not captured
119
+ * (disclosed gap, `browser-network-collector.ts`).
249
120
  */
250
121
  "WEBSOCKET_CLOSED",
251
122
  ];
252
123
  /**
253
- * **The prefix Descry reserves on an observed process's stdout.**
254
- *
255
- * A preload that instruments a process from the inside reports each
256
- * observation as one line of structured JSON on that process's own stdout --
257
- * `DESCRY_DB_QUERY {...}`, `DESCRY_EXTERNAL_REQUEST {...}`. Inside an observed
258
- * process stdout *is* the evidence channel, so that is right.
259
- *
260
- * It also means the backend log collector, reading the same stdout, sees them.
261
- * Without knowing the prefix it attributed them to the application, and a
262
- * user's report showed Descry's own instrumentation chatter as lines their
263
- * service printed. This constant is the one place that says which vocabulary
264
- * is Descry's, so the collector can leave it alone and every marker module can
265
- * derive from it instead of spelling the prefix again.
266
- *
267
- * Anchored at the start of a line by every consumer: a log line that merely
268
- * *mentions* a marker is the application's own output.
124
+ * The prefix Descry reserves on an observed process's stdout. A preload
125
+ * instrumenting a process from the inside reports each observation as one
126
+ * line of structured JSON there (`DESCRY_DB_QUERY {...}`, etc). Found the
127
+ * hard way: without this, the backend log collector reading the same
128
+ * stdout attributed Descry's own instrumentation chatter to the
129
+ * application. One place defines the prefix; every marker module derives
130
+ * from it. Anchored at line start by every consumer — a line that merely
131
+ * mentions a marker is the application's own output.
269
132
  */
270
133
  export const RESERVED_MARKER_PREFIX = "DESCRY_";
271
134
  //# sourceMappingURL=runtime-event.js.map
@@ -1 +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;AAIX;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAG,SAAS,CAAC"}
1
+ {"version":3,"file":"runtime-event.js","sourceRoot":"","sources":["../src/runtime-event.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,MAAM,CAAC,MAAM,mBAAmB,GAAG;IACjC,iBAAiB;IACjB,eAAe;IACf,gBAAgB;IAChB,cAAc;IACd,YAAY;IACZ,OAAO;IACP,OAAO;IACP,sLAAsL;IACtL,YAAY;IACZ;;;;;OAKG;IACH,OAAO;IACP,iBAAiB;IACjB,iBAAiB;IACjB,kBAAkB;IAClB,YAAY;IACZ,aAAa;IACb,WAAW;IACX,aAAa;IACb;;;;;;;;;;;;;;;;;;;;;;;;;;OA0BG;IACH,cAAc;IACd,aAAa;IACb,aAAa;IACb,cAAc;IACd;;;;;;;OAOG;IACH,gBAAgB;IAChB,kBAAkB;IAClB;;;;;;;OAOG;IACH,iBAAiB;IACjB;;;;;OAKG;IACH,6BAA6B;IAC7B;;;OAGG;IACH,8BAA8B;IAC9B;;;;;;;;;;;;;;;;;;;;;;;;;;OA0BG;IACH,kBAAkB;CACV,CAAC;AAIX;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAG,SAAS,CAAC"}
@@ -1,71 +1,47 @@
1
1
  /**
2
2
  * Origin -> service root (RT-023, built on RT-028's `ProcessHandle.serviceName`/
3
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.
4
+ * declared `cwd` served this browser origin" — the link `BrowserConsoleCollector`
5
+ * needs to turn `http://localhost:4000/app.js` into a candidate on-disk root
6
+ * before `file://`-based source resolution can run.
8
7
  *
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.
8
+ * Stops at a configuration-layer fact, not a collector — `packages/browser`
9
+ * depends on `@descryy/runtime-contracts` only (RT-021), so this lives here.
13
10
  *
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.
11
+ * Two disclosed honesty gaps (RT-024-shaped):
12
+ * 1. The port join proves an address, not a correspondent — matching
13
+ * `origin.port` to a recorded `ProcessHandle` proves the origin matches
14
+ * a port this execution assigned, not that the browser talked to that
15
+ * specific process. Nothing re-verifies the process was still live.
16
+ * 2. A resolved root is configured, not verified — see `ConfiguredServiceRoot`.
29
17
  */
30
18
  import type { Execution } from "./execution.ts";
31
19
  /**
32
20
  * A service's declared working directory, recovered by joining a running
33
21
  * process's assigned port back to `ExecutionConfiguration.services`.
34
22
  *
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.
23
+ * Configured, not verified: proves the origin maps to a directory the
24
+ * orchestrator was told to run the service from, not that any specific
25
+ * file actually lives under it (a subdirectory-rooted static server,
26
+ * symlink, proxy, or post-spawn `cwd` change would all break the stronger
27
+ * claim without breaking this one).
43
28
  *
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.
29
+ * Not a refusal — the weaker claim is still useful. The caller should
30
+ * join the frame's path onto `declaredCwd` and confirm the file exists
31
+ * (same `existsSync` discipline `createNodeSourceLocationResolver` uses
32
+ * for `file://` paths) — that check, not this function, is where a
33
+ * failure should surface.
52
34
  */
53
35
  export interface ConfiguredServiceRoot {
54
36
  readonly serviceName: string;
55
37
  readonly declaredCwd: string;
56
38
  }
57
39
  /**
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.
40
+ * Closed, so a caller can branch on *why* rather than pattern-match prose.
41
+ * `originNoPort`/`originUnparseable`: input wasn't a resolvable origin.
42
+ * `noProcessOnPort`: nothing in this execution claims that port.
43
+ * `processUnnamed`: a real process spawned without a `serviceName` (wiring gap).
44
+ * `serviceNotConfigured`: a `serviceName` with no matching `services` entry.
69
45
  */
70
46
  export declare const SERVICE_ROOT_REFUSALS: readonly ["originNoPort", "originUnparseable", "noProcessOnPort", "processUnnamed", "serviceNotConfigured"];
71
47
  export type ServiceRootRefusal = (typeof SERVICE_ROOT_REFUSALS)[number];
@@ -79,19 +55,16 @@ export type ServiceRootLookup = {
79
55
  };
80
56
  /**
81
57
  * 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
58
+ * service that served it: `execution.processes[].port` (real OS-assigned
59
+ * port, RT-028) to `.serviceName`, then to
84
60
  * `execution.configuration.services[name].cwd`.
85
61
  *
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).
62
+ * `origin` is a clean origin, not a full frame location — stripping a
63
+ * `file:37:12`-shaped V8 frame down to its origin is the caller's job.
90
64
  *
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.
65
+ * Two services sharing the same `declaredCwd` isn't ambiguous here:
66
+ * resolution is process -> serviceName -> keyed lookup, never a reverse
67
+ * search by directory.
95
68
  */
96
69
  export declare function resolveServiceRootForOrigin(origin: string, execution: Pick<Execution, "processes" | "configuration">): ServiceRootLookup;
97
70
  //# sourceMappingURL=source-root.d.ts.map
@@ -1 +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"}
1
+ {"version":3,"file":"source-root.d.ts","sourceRoot":"","sources":["../src/source-root.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAEhD;;;;;;;;;;;;;;;GAeG;AACH,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;CAC9B;AAED;;;;;;GAMG;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;;;;;;;;;;;;GAYG;AACH,wBAAgB,2BAA2B,CACzC,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,IAAI,CAAC,SAAS,EAAE,WAAW,GAAG,eAAe,CAAC,GACxD,iBAAiB,CA2CnB"}
@@ -1,44 +1,26 @@
1
1
  /**
2
2
  * Origin -> service root (RT-023, built on RT-028's `ProcessHandle.serviceName`/
3
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.
4
+ * declared `cwd` served this browser origin" — the link `BrowserConsoleCollector`
5
+ * needs to turn `http://localhost:4000/app.js` into a candidate on-disk root
6
+ * before `file://`-based source resolution can run.
8
7
  *
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.
8
+ * Stops at a configuration-layer fact, not a collector — `packages/browser`
9
+ * depends on `@descryy/runtime-contracts` only (RT-021), so this lives here.
13
10
  *
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.
11
+ * Two disclosed honesty gaps (RT-024-shaped):
12
+ * 1. The port join proves an address, not a correspondent — matching
13
+ * `origin.port` to a recorded `ProcessHandle` proves the origin matches
14
+ * a port this execution assigned, not that the browser talked to that
15
+ * specific process. Nothing re-verifies the process was still live.
16
+ * 2. A resolved root is configured, not verified — see `ConfiguredServiceRoot`.
29
17
  */
30
18
  /**
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.
19
+ * Closed, so a caller can branch on *why* rather than pattern-match prose.
20
+ * `originNoPort`/`originUnparseable`: input wasn't a resolvable origin.
21
+ * `noProcessOnPort`: nothing in this execution claims that port.
22
+ * `processUnnamed`: a real process spawned without a `serviceName` (wiring gap).
23
+ * `serviceNotConfigured`: a `serviceName` with no matching `services` entry.
42
24
  */
43
25
  export const SERVICE_ROOT_REFUSALS = [
44
26
  "originNoPort",
@@ -49,19 +31,16 @@ export const SERVICE_ROOT_REFUSALS = [
49
31
  ];
50
32
  /**
51
33
  * 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
34
+ * service that served it: `execution.processes[].port` (real OS-assigned
35
+ * port, RT-028) to `.serviceName`, then to
54
36
  * `execution.configuration.services[name].cwd`.
55
37
  *
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).
38
+ * `origin` is a clean origin, not a full frame location — stripping a
39
+ * `file:37:12`-shaped V8 frame down to its origin is the caller's job.
60
40
  *
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.
41
+ * Two services sharing the same `declaredCwd` isn't ambiguous here:
42
+ * resolution is process -> serviceName -> keyed lookup, never a reverse
43
+ * search by directory.
65
44
  */
66
45
  export function resolveServiceRootForOrigin(origin, execution) {
67
46
  let port;
@@ -1 +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"}
1
+ {"version":3,"file":"source-root.js","sourceRoot":"","sources":["../src/source-root.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAyBH;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG;IACnC,cAAc;IACd,mBAAmB;IACnB,iBAAiB;IACjB,gBAAgB;IAChB,sBAAsB;CACd,CAAC;AAOX;;;;;;;;;;;;GAYG;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 CHANGED
@@ -1,9 +1,14 @@
1
1
  {
2
2
  "name": "@descryy/runtime-contracts",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "type": "module",
5
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
6
  "license": "UNLICENSED",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/descryhq-wq/descry-runtime.git",
10
+ "directory": "packages/contracts"
11
+ },
7
12
  "engines": {
8
13
  "node": ">=22.5"
9
14
  },