@descryy/runtime-orchestrator 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.
Files changed (49) hide show
  1. package/dist/attach-to-running-jvm-process.d.ts +139 -0
  2. package/dist/attach-to-running-jvm-process.d.ts.map +1 -0
  3. package/dist/attach-to-running-jvm-process.js +338 -0
  4. package/dist/attach-to-running-jvm-process.js.map +1 -0
  5. package/dist/attach-to-running-node-process.d.ts +138 -0
  6. package/dist/attach-to-running-node-process.d.ts.map +1 -0
  7. package/dist/attach-to-running-node-process.js +315 -0
  8. package/dist/attach-to-running-node-process.js.map +1 -0
  9. package/dist/attach-to-running-process.d.ts +70 -0
  10. package/dist/attach-to-running-process.d.ts.map +1 -0
  11. package/dist/attach-to-running-process.js +90 -0
  12. package/dist/attach-to-running-process.js.map +1 -0
  13. package/dist/cdp-client.d.ts +37 -0
  14. package/dist/cdp-client.d.ts.map +1 -0
  15. package/dist/cdp-client.js +111 -0
  16. package/dist/cdp-client.js.map +1 -0
  17. package/dist/cgroup-partial-restriction.d.ts +127 -0
  18. package/dist/cgroup-partial-restriction.d.ts.map +1 -0
  19. package/dist/cgroup-partial-restriction.js +236 -0
  20. package/dist/cgroup-partial-restriction.js.map +1 -0
  21. package/dist/collector-version.d.ts +12 -0
  22. package/dist/collector-version.d.ts.map +1 -0
  23. package/dist/collector-version.js +14 -0
  24. package/dist/collector-version.js.map +1 -0
  25. package/dist/index.d.ts +19 -0
  26. package/dist/index.d.ts.map +1 -0
  27. package/dist/index.js +10 -0
  28. package/dist/index.js.map +1 -0
  29. package/dist/instrumented-execution.d.ts +111 -0
  30. package/dist/instrumented-execution.d.ts.map +1 -0
  31. package/dist/instrumented-execution.js +363 -0
  32. package/dist/instrumented-execution.js.map +1 -0
  33. package/dist/jvm-agent/build.d.ts +72 -0
  34. package/dist/jvm-agent/build.d.ts.map +1 -0
  35. package/dist/jvm-agent/build.js +156 -0
  36. package/dist/jvm-agent/build.js.map +1 -0
  37. package/dist/polling-output-source.d.ts +28 -0
  38. package/dist/polling-output-source.d.ts.map +1 -0
  39. package/dist/polling-output-source.js +73 -0
  40. package/dist/polling-output-source.js.map +1 -0
  41. package/dist/profile-backend-observation.d.ts +94 -0
  42. package/dist/profile-backend-observation.d.ts.map +1 -0
  43. package/dist/profile-backend-observation.js +136 -0
  44. package/dist/profile-backend-observation.js.map +1 -0
  45. package/dist/respawn-and-supervise.d.ts +119 -0
  46. package/dist/respawn-and-supervise.d.ts.map +1 -0
  47. package/dist/respawn-and-supervise.js +212 -0
  48. package/dist/respawn-and-supervise.js.map +1 -0
  49. package/package.json +45 -0
@@ -0,0 +1,28 @@
1
+ /**
2
+ * A live `ProcessOutputSource` over a polled buffer — the "future poll
3
+ * adapter" `process-output.ts`'s own header anticipated and left unbuilt.
4
+ *
5
+ * `ManagedProcess.readOutput()` returns the whole accumulated
6
+ * stdout+stderr, not a delta. **Reading it repeatedly and re-emitting
7
+ * everything would duplicate every line on every tick**, which is the
8
+ * mistake that made a correlation test read the wrong request id when a
9
+ * lane hit the same API from the other side. This source keeps the length
10
+ * it has already consumed and slices forward from it, so each line is
11
+ * yielded exactly once.
12
+ *
13
+ * It ends when `isFinished()` returns true **and** the buffer has stopped
14
+ * growing — checked in that order, because a process can exit with output
15
+ * still unread, and stopping at the exit event alone silently drops
16
+ * whatever it printed on the way out.
17
+ */
18
+ import { type ProcessOutputSource } from "@descryy/runtime-backend-observation";
19
+ export interface PollingProcessOutputSourceOptions {
20
+ readonly processId: string;
21
+ /** The whole accumulated buffer so far — `ManagedProcess.readOutput` has exactly this shape. */
22
+ readonly readOutput: () => string;
23
+ /** True once the process has exited. Output already buffered is still drained afterwards. */
24
+ readonly isFinished: () => boolean;
25
+ readonly pollIntervalMs?: number;
26
+ }
27
+ export declare function createPollingProcessOutputSource(options: PollingProcessOutputSourceOptions): ProcessOutputSource;
28
+ //# sourceMappingURL=polling-output-source.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"polling-output-source.d.ts","sourceRoot":"","sources":["../src/polling-output-source.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAAwC,KAAK,mBAAmB,EAAE,MAAM,sCAAsC,CAAC;AAEtH,MAAM,WAAW,iCAAiC;IAChD,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,gGAAgG;IAChG,QAAQ,CAAC,UAAU,EAAE,MAAM,MAAM,CAAC;IAClC,6FAA6F;IAC7F,QAAQ,CAAC,UAAU,EAAE,MAAM,OAAO,CAAC;IACnC,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;CAClC;AAID,wBAAgB,gCAAgC,CAAC,OAAO,EAAE,iCAAiC,GAAG,mBAAmB,CA4DhH"}
@@ -0,0 +1,73 @@
1
+ /**
2
+ * A live `ProcessOutputSource` over a polled buffer — the "future poll
3
+ * adapter" `process-output.ts`'s own header anticipated and left unbuilt.
4
+ *
5
+ * `ManagedProcess.readOutput()` returns the whole accumulated
6
+ * stdout+stderr, not a delta. **Reading it repeatedly and re-emitting
7
+ * everything would duplicate every line on every tick**, which is the
8
+ * mistake that made a correlation test read the wrong request id when a
9
+ * lane hit the same API from the other side. This source keeps the length
10
+ * it has already consumed and slices forward from it, so each line is
11
+ * yielded exactly once.
12
+ *
13
+ * It ends when `isFinished()` returns true **and** the buffer has stopped
14
+ * growing — checked in that order, because a process can exit with output
15
+ * still unread, and stopping at the exit event alone silently drops
16
+ * whatever it printed on the way out.
17
+ */
18
+ import { LineSplitter } from "@descryy/runtime-backend-observation";
19
+ const DEFAULT_POLL_INTERVAL_MS = 50;
20
+ export function createPollingProcessOutputSource(options) {
21
+ const pollIntervalMs = options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
22
+ return {
23
+ processId: options.processId,
24
+ lines: (async function* () {
25
+ const splitter = new LineSplitter();
26
+ let consumed = 0;
27
+ for (;;) {
28
+ const whole = options.readOutput();
29
+ // The forward-slice above rests on one assumption about a
30
+ // collaborator: that the buffer only ever grows. `ManagedProcess`
31
+ // satisfies it today (`outputBuffer += chunk`, uncapped), but that
32
+ // is an implementation detail of another package and an uncapped
33
+ // buffer is exactly the thing someone eventually caps. On the day a
34
+ // cap or a rotation lands, `consumed` would sit past the end of the
35
+ // new buffer and this source would silently emit nothing, forever,
36
+ // while reporting healthy -- a collector that has stopped working
37
+ // and does not say so.
38
+ //
39
+ // Throwing is the honest response and it already has a destination:
40
+ // `LogCollector`'s consume loop catches it, emits COLLECTOR_ERROR
41
+ // and flips `backendLogAccess` to unavailable (RT-036). Silence is
42
+ // the one option not available.
43
+ if (whole.length < consumed) {
44
+ throw new Error(`process output buffer shrank from ${consumed} to ${whole.length} characters — this source ` +
45
+ "slices forward from a consumed offset and cannot reconstruct a truncated or rotated buffer " +
46
+ "without either duplicating or dropping lines. Reported rather than guessed.");
47
+ }
48
+ if (whole.length > consumed) {
49
+ const chunk = whole.slice(consumed);
50
+ consumed = whole.length;
51
+ const observedAt = new Date().toISOString();
52
+ for (const text of splitter.push(chunk)) {
53
+ yield { text, stream: "combined", observedAt };
54
+ }
55
+ // Deliberately loops again without sleeping: a burst of output
56
+ // should drain at memory speed, not at the poll interval.
57
+ continue;
58
+ }
59
+ if (options.isFinished()) {
60
+ const observedAt = new Date().toISOString();
61
+ // A final unterminated line is real output and is the one most
62
+ // likely to matter -- a process that dies mid-write.
63
+ for (const text of splitter.flush()) {
64
+ yield { text, stream: "combined", observedAt };
65
+ }
66
+ return;
67
+ }
68
+ await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
69
+ }
70
+ })(),
71
+ };
72
+ }
73
+ //# sourceMappingURL=polling-output-source.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"polling-output-source.js","sourceRoot":"","sources":["../src/polling-output-source.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAAE,YAAY,EAAoD,MAAM,sCAAsC,CAAC;AAWtH,MAAM,wBAAwB,GAAG,EAAE,CAAC;AAEpC,MAAM,UAAU,gCAAgC,CAAC,OAA0C;IACzF,MAAM,cAAc,GAAG,OAAO,CAAC,cAAc,IAAI,wBAAwB,CAAC;IAE1E,OAAO;QACL,SAAS,EAAE,OAAO,CAAC,SAAS;QAC5B,KAAK,EAAE,CAAC,KAAK,SAAS,CAAC;YACrB,MAAM,QAAQ,GAAG,IAAI,YAAY,EAAE,CAAC;YACpC,IAAI,QAAQ,GAAG,CAAC,CAAC;YAEjB,SAAS,CAAC;gBACR,MAAM,KAAK,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC;gBAEnC,0DAA0D;gBAC1D,kEAAkE;gBAClE,mEAAmE;gBACnE,iEAAiE;gBACjE,oEAAoE;gBACpE,oEAAoE;gBACpE,mEAAmE;gBACnE,kEAAkE;gBAClE,uBAAuB;gBACvB,EAAE;gBACF,oEAAoE;gBACpE,kEAAkE;gBAClE,mEAAmE;gBACnE,gCAAgC;gBAChC,IAAI,KAAK,CAAC,MAAM,GAAG,QAAQ,EAAE,CAAC;oBAC5B,MAAM,IAAI,KAAK,CACb,qCAAqC,QAAQ,OAAO,KAAK,CAAC,MAAM,4BAA4B;wBAC1F,6FAA6F;wBAC7F,6EAA6E,CAChF,CAAC;gBACJ,CAAC;gBAED,IAAI,KAAK,CAAC,MAAM,GAAG,QAAQ,EAAE,CAAC;oBAC5B,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;oBACpC,QAAQ,GAAG,KAAK,CAAC,MAAM,CAAC;oBACxB,MAAM,UAAU,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;oBAC5C,KAAK,MAAM,IAAI,IAAI,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;wBACxC,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,UAAU,EAAE,CAAC;oBACjD,CAAC;oBACD,+DAA+D;oBAC/D,0DAA0D;oBAC1D,SAAS;gBACX,CAAC;gBAED,IAAI,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC;oBACzB,MAAM,UAAU,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;oBAC5C,+DAA+D;oBAC/D,qDAAqD;oBACrD,KAAK,MAAM,IAAI,IAAI,QAAQ,CAAC,KAAK,EAAE,EAAE,CAAC;wBACpC,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,UAAU,EAAE,CAAC;oBACjD,CAAC;oBACD,OAAO;gBACT,CAAC;gBAED,MAAM,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,cAAc,CAAC,CAAC,CAAC;YACtE,CAAC;QACH,CAAC,CAAC,EAAE;KACL,CAAC;AACJ,CAAC"}
@@ -0,0 +1,94 @@
1
+ /**
2
+ * Profile-driven backend traffic observation (RT-197, DEC-271).
3
+ *
4
+ * `InboundProxy` (RT-192) is wired into `runInstrumentedExecution` via
5
+ * `frontWithInboundProxy` — the **local, Descry-booted** half of DEC-271.
6
+ * The other half, `@descryhq-wq/runtime-remote-log-ingestion`, existed with real
7
+ * tests (`correlator.test.ts`, `file-log-adapter.test.ts`) and **zero
8
+ * callers outside its own package** — the same "built, never wired" shape
9
+ * DEC-270 found for `safety-gate.ts`'s `evaluateAction`. This module is
10
+ * that missing wiring, and the decision point DEC-271's own table
11
+ * describes: given an `EnvironmentProfile`, decide which of the two
12
+ * mechanisms applies, and never both.
13
+ *
14
+ * **The mutual-exclusivity guarantee is structural, not asserted.** This
15
+ * file does not import `createInboundProxy` — nothing in it, in any branch,
16
+ * is capable of constructing a proxy. A profile whose `mode` is `"remote"`
17
+ * or `"production"` can only ever reach `ingestFileLog`; a profile whose
18
+ * mode is `"localBooted"` is refused here with a `validationError` before
19
+ * any ingestion is attempted, on the grounds that DEC-271 names no
20
+ * ingestion mechanism for a profile Descry itself booted (that profile's
21
+ * proxy path is `runInstrumentedExecution`'s `frontWithInboundProxy`,
22
+ * driven by `ExecutionConfiguration`, not by this function).
23
+ *
24
+ * `"localAttached"` is refused the same way, for a different reason,
25
+ * disclosed rather than guessed at: DEC-271's own table names exactly two
26
+ * rows — "local, booted" and "remote (staging/preview) and production" —
27
+ * and says nothing about the third and fourth of DEC-266's four kinds.
28
+ * Local-attached is not remote infra (a proxy is not structurally
29
+ * forbidden the way it is for a target Descry does not own), but it is
30
+ * also not a process Descry started (so `runInstrumentedExecution`'s
31
+ * readiness-then-front sequencing, which RT-024 depends on, does not
32
+ * apply). Inventing an answer for a case the decision text does not cover
33
+ * would be exactly the guess DEC-266/DEC-271 both reject; this function
34
+ * says so and refuses instead.
35
+ */
36
+ import type { Evidence } from "@descryy/runtime-contracts";
37
+ import type { EnvironmentProfile } from "@descryy/runtime-environment-profile";
38
+ import { EvidenceStore } from "@descryy/runtime-evidence-store";
39
+ import { type RemoteLogSourceConfig } from "@descryhq-wq/runtime-remote-log-ingestion";
40
+ /**
41
+ * The mechanism decision itself, exposed as data rather than only as a
42
+ * side effect — so a caller (or a test) can inspect *which* mechanism a
43
+ * profile resolves to without having to run it, and so the "never both"
44
+ * property is checkable structurally: exactly one of `"inboundProxy"` /
45
+ * `"remoteLogIngestion"` / `"none"` is ever returned, never two.
46
+ */
47
+ export type BackendObservationMechanism = {
48
+ readonly kind: "inboundProxy";
49
+ } | {
50
+ readonly kind: "remoteLogIngestion";
51
+ readonly config: RemoteLogSourceConfig;
52
+ } | {
53
+ readonly kind: "none";
54
+ readonly reason: string;
55
+ };
56
+ /**
57
+ * Pure decision function — DEC-271's table read directly off
58
+ * `EnvironmentProfile.mode`. Never reaches the network or the filesystem;
59
+ * see `runRemoteLogObservation` below for the mechanism that actually
60
+ * performs ingestion.
61
+ */
62
+ export declare function backendObservationMechanismFor(profile: EnvironmentProfile): BackendObservationMechanism;
63
+ export interface RunRemoteLogObservationInput {
64
+ readonly profile: EnvironmentProfile;
65
+ readonly executionId: string;
66
+ /** Where ingested lines are written. Provided rather than constructed, same discipline as `InstrumentedExecutionInput.store`. */
67
+ readonly store: EvidenceStore;
68
+ }
69
+ export interface RunRemoteLogObservationResult {
70
+ readonly evidence: readonly Evidence[];
71
+ /** Set when this function refused to ingest anything -- a profile mode this function does not handle, or a declared source this pass has no adapter for. Evidence is empty and that is a refusal, not an absence of logs. */
72
+ readonly validationError: string | null;
73
+ }
74
+ /**
75
+ * The remote/production half of DEC-271, run against one profile's
76
+ * declared `remoteLogSource`. Reads the source **once** (today's only
77
+ * concrete adapter, `ingestFileLog`, is one-shot per its own doc — see
78
+ * `file-log-adapter.ts`) and writes every line through `EvidenceStore` as
79
+ * `BACKEND_LOG` evidence, so redaction runs (plan §28) exactly the same
80
+ * way it does for `LogCollector`'s local-process output.
81
+ *
82
+ * **Refuses rather than guesses** in three cases, each reported as a
83
+ * `validationError` rather than a silently empty result:
84
+ * 1. `profile.mode` is not `"remote"`/`"production"` (see this file's own
85
+ * header for the disclosed local-attached gap).
86
+ * 2. no `remoteLogSource` is declared (DEC-271's "not observable here").
87
+ * 3. `remoteLogSource.location.kind` is not `"file"` — the only concrete
88
+ * adapter this pass has; `"command"`/`"url"` are named-and-typed but
89
+ * unbuilt, per `log-source-config.ts`'s own doc.
90
+ *
91
+ * **Never constructs `InboundProxy`.** This file does not import it.
92
+ */
93
+ export declare function runRemoteLogObservation(input: RunRemoteLogObservationInput): Promise<RunRemoteLogObservationResult>;
94
+ //# sourceMappingURL=profile-backend-observation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"profile-backend-observation.d.ts","sourceRoot":"","sources":["../src/profile-backend-observation.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,4BAA4B,CAAC;AAC3D,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,sCAAsC,CAAC;AAC/E,OAAO,EAAE,aAAa,EAAE,MAAM,iCAAiC,CAAC;AAChE,OAAO,EAA2C,KAAK,qBAAqB,EAAE,MAAM,2CAA2C,CAAC;AAIhI;;;;;;GAMG;AACH,MAAM,MAAM,2BAA2B,GACnC;IAAE,QAAQ,CAAC,IAAI,EAAE,cAAc,CAAA;CAAE,GACjC;IAAE,QAAQ,CAAC,IAAI,EAAE,oBAAoB,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,qBAAqB,CAAA;CAAE,GAC/E;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAEvD;;;;;GAKG;AACH,wBAAgB,8BAA8B,CAAC,OAAO,EAAE,kBAAkB,GAAG,2BAA2B,CAmBvG;AAED,MAAM,WAAW,4BAA4B;IAC3C,QAAQ,CAAC,OAAO,EAAE,kBAAkB,CAAC;IACrC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,iIAAiI;IACjI,QAAQ,CAAC,KAAK,EAAE,aAAa,CAAC;CAC/B;AAED,MAAM,WAAW,6BAA6B;IAC5C,QAAQ,CAAC,QAAQ,EAAE,SAAS,QAAQ,EAAE,CAAC;IACvC,6NAA6N;IAC7N,QAAQ,CAAC,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;CACzC;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAsB,uBAAuB,CAAC,KAAK,EAAE,4BAA4B,GAAG,OAAO,CAAC,6BAA6B,CAAC,CA2DzH"}
@@ -0,0 +1,136 @@
1
+ /**
2
+ * Profile-driven backend traffic observation (RT-197, DEC-271).
3
+ *
4
+ * `InboundProxy` (RT-192) is wired into `runInstrumentedExecution` via
5
+ * `frontWithInboundProxy` — the **local, Descry-booted** half of DEC-271.
6
+ * The other half, `@descryhq-wq/runtime-remote-log-ingestion`, existed with real
7
+ * tests (`correlator.test.ts`, `file-log-adapter.test.ts`) and **zero
8
+ * callers outside its own package** — the same "built, never wired" shape
9
+ * DEC-270 found for `safety-gate.ts`'s `evaluateAction`. This module is
10
+ * that missing wiring, and the decision point DEC-271's own table
11
+ * describes: given an `EnvironmentProfile`, decide which of the two
12
+ * mechanisms applies, and never both.
13
+ *
14
+ * **The mutual-exclusivity guarantee is structural, not asserted.** This
15
+ * file does not import `createInboundProxy` — nothing in it, in any branch,
16
+ * is capable of constructing a proxy. A profile whose `mode` is `"remote"`
17
+ * or `"production"` can only ever reach `ingestFileLog`; a profile whose
18
+ * mode is `"localBooted"` is refused here with a `validationError` before
19
+ * any ingestion is attempted, on the grounds that DEC-271 names no
20
+ * ingestion mechanism for a profile Descry itself booted (that profile's
21
+ * proxy path is `runInstrumentedExecution`'s `frontWithInboundProxy`,
22
+ * driven by `ExecutionConfiguration`, not by this function).
23
+ *
24
+ * `"localAttached"` is refused the same way, for a different reason,
25
+ * disclosed rather than guessed at: DEC-271's own table names exactly two
26
+ * rows — "local, booted" and "remote (staging/preview) and production" —
27
+ * and says nothing about the third and fourth of DEC-266's four kinds.
28
+ * Local-attached is not remote infra (a proxy is not structurally
29
+ * forbidden the way it is for a target Descry does not own), but it is
30
+ * also not a process Descry started (so `runInstrumentedExecution`'s
31
+ * readiness-then-front sequencing, which RT-024 depends on, does not
32
+ * apply). Inventing an answer for a case the decision text does not cover
33
+ * would be exactly the guess DEC-266/DEC-271 both reject; this function
34
+ * says so and refuses instead.
35
+ */
36
+ import { EvidenceStore } from "@descryy/runtime-evidence-store";
37
+ import { ingestFileLog } from "@descryhq-wq/runtime-remote-log-ingestion";
38
+ import { COLLECTOR_VERSION } from "./collector-version.js";
39
+ /**
40
+ * Pure decision function — DEC-271's table read directly off
41
+ * `EnvironmentProfile.mode`. Never reaches the network or the filesystem;
42
+ * see `runRemoteLogObservation` below for the mechanism that actually
43
+ * performs ingestion.
44
+ */
45
+ export function backendObservationMechanismFor(profile) {
46
+ if (profile.mode === "localBooted") {
47
+ return { kind: "inboundProxy" };
48
+ }
49
+ if (profile.mode === "remote" || profile.mode === "production") {
50
+ if (profile.remoteLogSource == null) {
51
+ return {
52
+ kind: "none",
53
+ reason: `profile "${profile.name}" is mode "${profile.mode}" but declares no remoteLogSource -- DEC-271's honest "not observable here", not an error.`,
54
+ };
55
+ }
56
+ return { kind: "remoteLogIngestion", config: profile.remoteLogSource };
57
+ }
58
+ // mode === "localAttached" -- see this file's header for why this is a
59
+ // disclosed gap rather than a guessed answer.
60
+ return {
61
+ kind: "none",
62
+ reason: `profile "${profile.name}" is mode "localAttached" -- DEC-271 names a mechanism for neither local-attached nor any third case; not guessed at here.`,
63
+ };
64
+ }
65
+ /**
66
+ * The remote/production half of DEC-271, run against one profile's
67
+ * declared `remoteLogSource`. Reads the source **once** (today's only
68
+ * concrete adapter, `ingestFileLog`, is one-shot per its own doc — see
69
+ * `file-log-adapter.ts`) and writes every line through `EvidenceStore` as
70
+ * `BACKEND_LOG` evidence, so redaction runs (plan §28) exactly the same
71
+ * way it does for `LogCollector`'s local-process output.
72
+ *
73
+ * **Refuses rather than guesses** in three cases, each reported as a
74
+ * `validationError` rather than a silently empty result:
75
+ * 1. `profile.mode` is not `"remote"`/`"production"` (see this file's own
76
+ * header for the disclosed local-attached gap).
77
+ * 2. no `remoteLogSource` is declared (DEC-271's "not observable here").
78
+ * 3. `remoteLogSource.location.kind` is not `"file"` — the only concrete
79
+ * adapter this pass has; `"command"`/`"url"` are named-and-typed but
80
+ * unbuilt, per `log-source-config.ts`'s own doc.
81
+ *
82
+ * **Never constructs `InboundProxy`.** This file does not import it.
83
+ */
84
+ export async function runRemoteLogObservation(input) {
85
+ const { profile, executionId, store } = input;
86
+ const mechanism = backendObservationMechanismFor(profile);
87
+ if (mechanism.kind !== "remoteLogIngestion") {
88
+ const reason = mechanism.kind === "none"
89
+ ? mechanism.reason
90
+ : `profile "${profile.name}" resolves to mechanism "${mechanism.kind}", not remote log ingestion -- runRemoteLogObservation never constructs an InboundProxy, so it refuses rather than silently doing nothing.`;
91
+ return { evidence: [], validationError: reason };
92
+ }
93
+ const { config } = mechanism;
94
+ if (config.location.kind !== "file") {
95
+ return {
96
+ evidence: [],
97
+ validationError: `remoteLogSource.location.kind "${config.location.kind}" has no ingestion adapter yet -- only "file" is built (see file-log-adapter.ts's own doc); not observable here.`,
98
+ };
99
+ }
100
+ const fileConfig = { ...config, location: config.location };
101
+ let lines;
102
+ try {
103
+ lines = await ingestFileLog({ config: fileConfig });
104
+ }
105
+ catch (error) {
106
+ const detail = error instanceof Error ? error.message : String(error);
107
+ return { evidence: [], validationError: `remote log ingestion failed for profile "${profile.name}": ${detail}` };
108
+ }
109
+ const evidence = [];
110
+ for (const line of lines) {
111
+ evidence.push(store.write({
112
+ executionId,
113
+ timestamp: line.timestamp ?? new Date().toISOString(),
114
+ source: "backend-process",
115
+ service: null,
116
+ process: null,
117
+ eventType: "BACKEND_LOG",
118
+ payload: { raw: line.raw, ingestedVia: "remote-log-ingestion", profile: profile.name },
119
+ traceId: line.traceId,
120
+ requestId: line.requestId,
121
+ correlationId: null,
122
+ graphNodeId: null,
123
+ sourceLocation: null,
124
+ stackTrace: null,
125
+ // The observation itself (a line the target already logged) is not
126
+ // probabilistic -- same reasoning LogCollector's own emit gives
127
+ // for confidence: 1. Whether a LATER correlation to a captured
128
+ // request holds is a separate, lower-confidence question
129
+ // (`correlateLogLine`'s own confidence tiers), not this field's.
130
+ confidence: 1,
131
+ collectorVersion: COLLECTOR_VERSION,
132
+ }));
133
+ }
134
+ return { evidence, validationError: null };
135
+ }
136
+ //# sourceMappingURL=profile-backend-observation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"profile-backend-observation.js","sourceRoot":"","sources":["../src/profile-backend-observation.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,119 @@
1
+ /**
2
+ * Respawn-and-supervise: a real, separate execution mode next to attach
3
+ * mode, not a change to attach mode itself.
4
+ *
5
+ * **The tradeoff this makes, stated up front, because it must never be
6
+ * applied silently.** Attach mode (`attach-to-running-process.ts` and its
7
+ * siblings) reaches a process that was already running, unconfined, before
8
+ * Descry touched it -- `sandbox.ts`'s own module doc (`@descryhq-wq/runtime-
9
+ * controller`) says plainly there is no honest way to retroactively contain
10
+ * that process. This module does not try. It kills the attached target and
11
+ * spawns a fresh, equivalent process through the EXISTING `spawnProcess()`
12
+ * (`@descryy/runtime-controller`), which already gets full sandboxing
13
+ * (`applySandbox`, `applyResourceLimits`) for anything it spawns -- the same
14
+ * mechanism `ExecutionController.run()` uses, not a parallel one. The price
15
+ * is real: in-memory state, open connections, anything the old process held
16
+ * that was never persisted is gone the moment it is killed. A caller must
17
+ * choose this explicitly; nothing in this repo switches a running attach
18
+ * into a respawn on its own.
19
+ *
20
+ * **How this module learns what to respawn -- the design question this
21
+ * lane was told not to guess past.** This repo has a standing, named
22
+ * precedent for exactly this shape of question: RT-032 ("declared, never
23
+ * inferred" -- `ServiceConfiguration.dependsOn`, `substitutePortPlaceholders`,
24
+ * `discoverRunner`'s refusal to sniff a test framework from `package.json`).
25
+ * The caller-declared path (`RespawnCommand` with `source: "caller-declared"`)
26
+ * is the one this module recommends and the only one its own convenience
27
+ * default reaches for. A `/proc/<pid>/cmdline` readback (Linux-only, and
28
+ * genuinely insufficient on its own -- it cannot recover env vars set
29
+ * programmatically before `exec`, file descriptors already open and handed
30
+ * off, or a cwd that changed after launch) is real, but explicitly a
31
+ * best-effort convenience, never the default: `inferRespawnCommandFromProc`
32
+ * exists, is opt-in, and labels its own output `source: "proc-cmdline-
33
+ * inferred"` so nothing downstream can mistake an inference for a
34
+ * declaration. The two are never blurred into one shape.
35
+ *
36
+ * **Killing the old process.** `respawnAndSupervise` does not assume it owns
37
+ * the target's process group -- an attached-to process was not spawned by
38
+ * this runtime and may not even be its own group leader, so this signals
39
+ * the individual pid only (SIGTERM, then SIGKILL after `gracePeriodMs` if
40
+ * still alive), mirroring `process-manager.ts`'s own two-stage `kill()`
41
+ * without reusing its group-kill (`signalGroup`), which is `spawnProcess`'s
42
+ * own escape hatch for a group *it* created.
43
+ */
44
+ import { type ManagedProcess, type SpawnProcessOptions } from "@descryy/runtime-controller";
45
+ export type RespawnCommandSource = "caller-declared" | "proc-cmdline-inferred";
46
+ /**
47
+ * Everything `spawnProcess` needs to relaunch an equivalent of the killed
48
+ * target, plus `source` -- the one field that is never optional, so a
49
+ * caller (and anything logging or auditing this decision later) can always
50
+ * tell a real declaration from a best-effort guess at a glance, never by
51
+ * inspecting provenance out of band.
52
+ */
53
+ export interface RespawnCommand {
54
+ readonly command: string;
55
+ readonly args?: readonly string[];
56
+ readonly cwd: string;
57
+ readonly env?: Readonly<Record<string, string | undefined>>;
58
+ readonly source: RespawnCommandSource;
59
+ }
60
+ export interface RespawnAndSuperviseOptions {
61
+ readonly processId: string;
62
+ /** The pid of the already-running, attached-to target. Verified alive before anything is killed. */
63
+ readonly targetPid: number;
64
+ /** How the replacement process is launched. See this module's own doc for why `source: "caller-declared"` is the recommended, default-reachable path. */
65
+ readonly respawn: RespawnCommand;
66
+ /** How long to wait for the old target to exit after SIGTERM before escalating to SIGKILL. Same default as `ManagedProcess.kill()`. */
67
+ readonly gracePeriodMs?: number;
68
+ readonly serviceName?: string;
69
+ readonly port?: number;
70
+ readonly resourceLimits?: SpawnProcessOptions["resourceLimits"];
71
+ readonly filesystemPolicy?: SpawnProcessOptions["filesystemPolicy"];
72
+ readonly networkPolicy?: SpawnProcessOptions["networkPolicy"];
73
+ }
74
+ export interface RespawnAndSuperviseResult {
75
+ /** The freshly spawned, fully-supervised (and, if declared, sandboxed) replacement -- everything `spawnProcess()` itself returns, unwrapped. */
76
+ readonly managedProcess: ManagedProcess;
77
+ readonly killedPid: number;
78
+ /** Whether the old target was observed to exit within `gracePeriodMs` of SIGTERM, or needed SIGKILL. Either way this does not return until the old pid is confirmed dead. */
79
+ readonly killedViaSigkill: boolean;
80
+ readonly respawnSource: RespawnCommandSource;
81
+ }
82
+ /**
83
+ * Kills the process at `options.targetPid` and spawns a fresh, supervised,
84
+ * (optionally) sandboxed replacement via `spawnProcess()`.
85
+ *
86
+ * **Not a silent behavior change to attach mode.** A caller reaches this
87
+ * function by name, explicitly, instead of one of the `attachToRunning*`
88
+ * functions -- there is no code path in this repo that upgrades an attach
89
+ * into a respawn on its own. `ATTACH_MODE_SCOPE_DISCLOSURE`
90
+ * (`@descryy/runtime-controller`) still governs attach mode itself,
91
+ * unconditionally, whether or not a caller ever reaches for this
92
+ * alternative.
93
+ *
94
+ * Throws (does not silently proceed) if `targetPid` is not alive at call
95
+ * time, matching `attachToRunningProcess`'s own "refuse rather than operate
96
+ * on a target that does not exist" contract.
97
+ */
98
+ export declare function respawnAndSupervise(options: RespawnAndSuperviseOptions): Promise<RespawnAndSuperviseResult>;
99
+ /**
100
+ * The opt-in, disclosed, BEST-EFFORT convenience this module's own doc
101
+ * names -- never the default `respawnAndSupervise` reaches for on its own.
102
+ * Reads `/proc/<pid>/cmdline`, `/proc/<pid>/cwd` and `/proc/<pid>/environ`
103
+ * to reconstruct a `RespawnCommand`, labelling it `source:
104
+ * "proc-cmdline-inferred"` so it can never be mistaken for a caller
105
+ * declaration downstream.
106
+ *
107
+ * **Real, disclosed limitations, not hypothetical ones:** Linux-only (no
108
+ * `/proc` on macOS/Windows); env vars a process set on itself
109
+ * programmatically after `exec` (as opposed to what it was launched with)
110
+ * are invisible here, because `/proc/<pid>/environ` only ever reflects the
111
+ * environment at `execve()` time; already-open file descriptors handed to
112
+ * the original process (an inherited socket, a pipe from its own parent)
113
+ * have no representation in a command line at all and cannot be recovered
114
+ * by any means this function has. Returns `null` -- never throws, never
115
+ * guesses a partial answer -- when `/proc/<pid>/cmdline` cannot be read
116
+ * (platform mismatch, pid gone, permission denied) or is empty.
117
+ */
118
+ export declare function inferRespawnCommandFromProc(pid: number, platform?: NodeJS.Platform): RespawnCommand | null;
119
+ //# sourceMappingURL=respawn-and-supervise.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"respawn-and-supervise.d.ts","sourceRoot":"","sources":["../src/respawn-and-supervise.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AAEH,OAAO,EAAgB,KAAK,cAAc,EAAE,KAAK,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AAI1G,MAAM,MAAM,oBAAoB,GAAG,iBAAiB,GAAG,uBAAuB,CAAC;AAE/E;;;;;;GAMG;AACH,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,IAAI,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,GAAG,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC,CAAC;IAC5D,QAAQ,CAAC,MAAM,EAAE,oBAAoB,CAAC;CACvC;AAED,MAAM,WAAW,0BAA0B;IACzC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,oGAAoG;IACpG,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,yJAAyJ;IACzJ,QAAQ,CAAC,OAAO,EAAE,cAAc,CAAC;IACjC,uIAAuI;IACvI,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,cAAc,CAAC,EAAE,mBAAmB,CAAC,gBAAgB,CAAC,CAAC;IAChE,QAAQ,CAAC,gBAAgB,CAAC,EAAE,mBAAmB,CAAC,kBAAkB,CAAC,CAAC;IACpE,QAAQ,CAAC,aAAa,CAAC,EAAE,mBAAmB,CAAC,eAAe,CAAC,CAAC;CAC/D;AAED,MAAM,WAAW,yBAAyB;IACxC,gJAAgJ;IAChJ,QAAQ,CAAC,cAAc,EAAE,cAAc,CAAC;IACxC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,6KAA6K;IAC7K,QAAQ,CAAC,gBAAgB,EAAE,OAAO,CAAC;IACnC,QAAQ,CAAC,aAAa,EAAE,oBAAoB,CAAC;CAC9C;AAyDD;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,mBAAmB,CAAC,OAAO,EAAE,0BAA0B,GAAG,OAAO,CAAC,yBAAyB,CAAC,CA6BjH;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,2BAA2B,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,GAAE,MAAM,CAAC,QAA2B,GAAG,cAAc,GAAG,IAAI,CAgD5H"}