@inflexa-ai/harness 0.19.0 → 0.19.2

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 (101) hide show
  1. package/dist/agents/report-session-agent.d.ts +32 -9
  2. package/dist/agents/report-session-agent.js +22 -6
  3. package/dist/app/report-session-runtime.js +26 -0
  4. package/dist/app/spawn-report-session.d.ts +32 -5
  5. package/dist/app/spawn-report-session.js +19 -0
  6. package/dist/auth/local-run-authorizer.d.ts +3 -2
  7. package/dist/auth/local-run-authorizer.js +4 -2
  8. package/dist/config/environment-stores.d.ts +12 -7
  9. package/dist/config/resource-limits.d.ts +6 -10
  10. package/dist/config/resource-limits.js +6 -44
  11. package/dist/contracts/report-reference.d.ts +4 -0
  12. package/dist/contracts/report-reference.js +3 -0
  13. package/dist/execution/run-authorizer.d.ts +18 -0
  14. package/dist/execution/run-canceler.d.ts +67 -0
  15. package/dist/execution/run-canceler.js +130 -0
  16. package/dist/index.d.ts +7 -3
  17. package/dist/index.js +6 -1
  18. package/dist/lib/chrome.d.ts +14 -0
  19. package/dist/lib/chrome.js +7 -0
  20. package/dist/lib/page-capture.d.ts +47 -0
  21. package/dist/lib/page-capture.js +83 -0
  22. package/dist/loop/run-agent.js +104 -15
  23. package/dist/memory/ai-sdk-message-storage.d.ts +9 -4
  24. package/dist/memory/ai-sdk-message-storage.js +9 -4
  25. package/dist/memory/count-tokens.js +34 -5
  26. package/dist/prompts/report-session.d.ts +1 -1
  27. package/dist/prompts/report-session.js +21 -0
  28. package/dist/providers/ai-sdk.d.ts +81 -3
  29. package/dist/providers/ai-sdk.js +91 -10
  30. package/dist/providers/types.d.ts +17 -0
  31. package/dist/report-model/assert-rules.d.ts +44 -0
  32. package/dist/report-model/assert-rules.js +97 -0
  33. package/dist/report-model/draft-hash.d.ts +14 -0
  34. package/dist/report-model/draft-hash.js +36 -0
  35. package/dist/report-model/fixture-resolver.js +2 -71
  36. package/dist/report-model/production-resolver.d.ts +124 -0
  37. package/dist/report-model/production-resolver.js +801 -0
  38. package/dist/report-model/reference-resolver.d.ts +6 -0
  39. package/dist/report-model/validate.js +5 -0
  40. package/dist/report-render/assets.d.ts +42 -0
  41. package/dist/report-render/assets.js +61 -0
  42. package/dist/report-render/chart.js +31 -10
  43. package/dist/report-render/design.d.ts +120 -0
  44. package/dist/report-render/design.js +808 -0
  45. package/dist/report-render/page.d.ts +51 -111
  46. package/dist/report-render/page.js +158 -268
  47. package/dist/report-render/render.js +34 -11
  48. package/dist/report-render/views/chart-view.d.ts +6 -3
  49. package/dist/report-render/views/chart-view.js +10 -5
  50. package/dist/report-render/views/page-view.d.ts +19 -8
  51. package/dist/report-render/views/page-view.js +31 -11
  52. package/dist/report-render/views/prose.d.ts +1 -0
  53. package/dist/report-render/views/prose.js +7 -13
  54. package/dist/report-render/views/references-view.js +5 -5
  55. package/dist/report-render/views/values.d.ts +15 -6
  56. package/dist/report-render/views/values.js +29 -10
  57. package/dist/runtime/assemble.d.ts +38 -4
  58. package/dist/runtime/assemble.js +43 -3
  59. package/dist/sandbox/k8s-client.js +18 -0
  60. package/dist/sandbox/mount-plan.d.ts +7 -0
  61. package/dist/sandbox/mount-plan.js +7 -2
  62. package/dist/sandbox/types.d.ts +6 -0
  63. package/dist/state/analyses.d.ts +3 -26
  64. package/dist/state/analyses.js +6 -64
  65. package/dist/state/index.d.ts +2 -2
  66. package/dist/state/index.js +2 -2
  67. package/dist/state/init.js +23 -10
  68. package/dist/state/report-session-state.d.ts +27 -0
  69. package/dist/state/report-session-state.js +29 -2
  70. package/dist/state/runs.d.ts +10 -0
  71. package/dist/state/runs.js +21 -0
  72. package/dist/tasks/data-profile.d.ts +4 -0
  73. package/dist/tasks/data-profile.js +14 -0
  74. package/dist/tasks/extract-values-script.d.ts +64 -0
  75. package/dist/tasks/extract-values-script.js +180 -0
  76. package/dist/tasks/extract-values.d.ts +124 -0
  77. package/dist/tasks/extract-values.js +193 -0
  78. package/dist/tools/approval/gateway.d.ts +8 -1
  79. package/dist/tools/approval/gateway.js +9 -2
  80. package/dist/tools/approval/queries.d.ts +9 -2
  81. package/dist/tools/approval/queries.js +12 -4
  82. package/dist/tools/define-tool.d.ts +29 -0
  83. package/dist/tools/define-tool.js +26 -0
  84. package/dist/tools/report/preview-snapshot.d.ts +1 -1
  85. package/dist/tools/report/preview-snapshot.js +11 -50
  86. package/dist/tools/report-authoring/authoring-tools.d.ts +54 -4
  87. package/dist/tools/report-authoring/authoring-tools.js +1 -1
  88. package/dist/tools/report-session/examine-page.d.ts +90 -0
  89. package/dist/tools/report-session/examine-page.js +136 -0
  90. package/dist/tools/report-session/index.d.ts +3 -1
  91. package/dist/tools/report-session/index.js +3 -1
  92. package/dist/tools/report-session/preview-report.d.ts +31 -6
  93. package/dist/tools/report-session/preview-report.js +63 -16
  94. package/dist/tools/report-session/record-version.d.ts +102 -0
  95. package/dist/tools/report-session/record-version.js +139 -0
  96. package/dist/tools/sandbox/list-available-packages.js +2 -1
  97. package/dist/tools/sandbox/list-available-refs.d.ts +5 -1
  98. package/dist/tools/sandbox/list-available-refs.js +13 -6
  99. package/dist/workflows/sandbox-step.d.ts +4 -0
  100. package/dist/workflows/sandbox-step.js +21 -2
  101. package/package.json +14 -8
@@ -3,9 +3,9 @@
3
3
  *
4
4
  * `createReportSessionAgent` is the composition root of the report path. It gets
5
5
  * the shared deps one time, and it hands each tool exactly what it needs. The
6
- * authoring tools and the preview tool bind to the session-state gateway, thus one
7
- * assembled definition serves every thread, and the per-session state stays behind
8
- * the tool boundary.
6
+ * authoring tools, the preview tool, the eyes tool, and the record tool bind to the
7
+ * session-state gateway, thus one assembled definition serves every thread, and the
8
+ * per-session state stays behind the tool boundary.
9
9
  *
10
10
  * The roster is read-only toward the analysis. It holds the workspace read tools,
11
11
  * the workspace search, the run inspection, and the data-profile inspection. It
@@ -19,20 +19,25 @@
19
19
  * across threads, and the provider cache reuses it.
20
20
  */
21
21
  import type { Pool } from "pg";
22
+ import type { AuthContext } from "../auth/types.js";
22
23
  import type { AgentDefinition } from "../loop/types.js";
23
24
  import type { EmbeddingProvider } from "../providers/types.js";
24
25
  import type { WorkspaceFilesystem } from "../workspace/filesystem.js";
25
26
  import type { ResolveWorkspaceRoot } from "../workspace/paths.js";
27
+ import type { ChromeConfig } from "../lib/chrome.js";
26
28
  import type { Logger } from "../lib/logger.js";
29
+ import type { ThreadStore } from "../memory/thread-store.js";
27
30
  import type { ReferenceResolver } from "../report-model/reference-resolver.js";
31
+ import type { ReportVersionStore } from "../state/report-versions.js";
28
32
  import type { ReportSessionStateGateway } from "../tools/report-authoring/authoring-tools.js";
33
+ import { type ResolvePageAsset } from "../tools/report-session/index.js";
29
34
  /** Canonical agent id -- distinct from the id of the old templating agent. */
30
35
  export declare const REPORT_SESSION_AGENT_ID: "report-session";
31
36
  /**
32
37
  * The shared dependencies of the report agent. The gateway binds the per-session
33
38
  * state to the thread, thus every tool that touches the state gets the same
34
- * gateway. The resolver is optional, because a resolver realization can be absent,
35
- * and the preview tool degrades as data when it is.
39
+ * gateway. The resolver factory is optional, because a resolver realization can be
40
+ * absent, and the preview tool and the record tool degrade as data when it is.
36
41
  */
37
42
  export interface ReportSessionAgentDeps {
38
43
  /** Model id -- the provenance and metric label. The provider owns the wire model. */
@@ -43,12 +48,30 @@ export interface ReportSessionAgentDeps {
43
48
  readonly embedding: EmbeddingProvider;
44
49
  /** Workspace filesystem read seam -- the four read tools. */
45
50
  readonly workspaceFs: WorkspaceFilesystem;
46
- /** Session-state gateway -- the authoring tools and the preview tool bind to it. */
51
+ /** Session-state gateway -- the authoring tools, the preview tool, the eyes tool, and the record tool bind to it. */
47
52
  readonly gateway: ReportSessionStateGateway;
48
- /** Workspace-root resolution seam -- the preview tool resolves the page root per call. */
53
+ /** Workspace-root resolution seam -- the preview tool and the eyes tool resolve the page root per call. */
49
54
  readonly resolveWorkspaceRoot: ResolveWorkspaceRoot;
50
- /** Reference-resolution seam -- absent until a realization lands; the preview tool degrades as data. */
51
- readonly resolver?: ReferenceResolver;
55
+ /** Append-only version store -- the record tool gates the whole document first, then records one version. */
56
+ readonly store: ReportVersionStore;
57
+ /** Thread reader -- the record tool reads the anchor of the report thread. */
58
+ readonly threads: Pick<ThreadStore, "getThread">;
59
+ /** Headless-Chrome config -- the eyes tool opens the rendered page. */
60
+ readonly chrome: ChromeConfig;
61
+ /**
62
+ * Reference-resolution factory -- absent until a realization lands; the preview tool and the record tool
63
+ * degrade as data. The factory binds one analysis, thus a tool makes the resolver over the scope of the call.
64
+ */
65
+ readonly makeResolver?: (scope: {
66
+ analysisId: string;
67
+ auth: AuthContext;
68
+ }) => ReferenceResolver;
69
+ /**
70
+ * Page-asset lookup -- the preview tool maps the module specifier of a manifest
71
+ * entry onto a file on disk. Omitted falls back to the module resolution of the
72
+ * installation of the harness.
73
+ */
74
+ readonly resolvePageAsset?: ResolvePageAsset;
52
75
  /** Operational logging seam; omitted falls back to no-op. */
53
76
  readonly logger?: Logger;
54
77
  }
@@ -3,9 +3,9 @@
3
3
  *
4
4
  * `createReportSessionAgent` is the composition root of the report path. It gets
5
5
  * the shared deps one time, and it hands each tool exactly what it needs. The
6
- * authoring tools and the preview tool bind to the session-state gateway, thus one
7
- * assembled definition serves every thread, and the per-session state stays behind
8
- * the tool boundary.
6
+ * authoring tools, the preview tool, the eyes tool, and the record tool bind to the
7
+ * session-state gateway, thus one assembled definition serves every thread, and the
8
+ * per-session state stays behind the tool boundary.
9
9
  *
10
10
  * The roster is read-only toward the analysis. It holds the workspace read tools,
11
11
  * the workspace search, the run inspection, and the data-profile inspection. It
@@ -19,7 +19,7 @@
19
19
  * across threads, and the provider cache reuses it.
20
20
  */
21
21
  import { createReportAuthoringTools } from "../tools/report-authoring/authoring-tools.js";
22
- import { createPreviewReportTool } from "../tools/report-session/preview-report.js";
22
+ import { createExaminePageTool, createPreviewReportTool, createRecordVersionTool } from "../tools/report-session/index.js";
23
23
  import { createFileStatTool, createGrepTool, createListFilesTool, createReadFileTool, createWorkspaceSearchTool } from "../tools/workspace/index.js";
24
24
  import { createInspectDataProfileTool, createInspectRunTool } from "../tools/research/index.js";
25
25
  import { reportSessionPrompt } from "../prompts/report-session.js";
@@ -33,7 +33,7 @@ export const REPORT_SESSION_AGENT_ID = "report-session";
33
33
  const REPORT_SESSION_MAX_ITERATIONS = 50;
34
34
  /** Build the report `AgentDefinition` with every tool bound to its deps. */
35
35
  export function createReportSessionAgent(deps) {
36
- const { model, pool, embedding, workspaceFs, gateway, resolveWorkspaceRoot, resolver, logger } = deps;
36
+ const { model, pool, embedding, workspaceFs, gateway, resolveWorkspaceRoot, store, threads, chrome, makeResolver, resolvePageAsset, logger } = deps;
37
37
  const authoring = createReportAuthoringTools(gateway);
38
38
  const tools = [
39
39
  // The read surface toward the analysis. It roams the tree read-only.
@@ -58,7 +58,23 @@ export function createReportSessionAgent(deps) {
58
58
  createPreviewReportTool({
59
59
  gateway,
60
60
  resolveWorkspaceRoot,
61
- ...(resolver ? { resolver } : {}),
61
+ ...(makeResolver ? { makeResolver } : {}),
62
+ ...(resolvePageAsset ? { resolvePageAsset } : {}),
63
+ ...(logger ? { logger } : {}),
64
+ }),
65
+ // The eyes tool. It opens the rendered page in headless Chrome.
66
+ createExaminePageTool({
67
+ gateway,
68
+ resolveWorkspaceRoot,
69
+ chrome,
70
+ ...(logger ? { logger } : {}),
71
+ }),
72
+ // The record tool. It gates the whole document, then records one version.
73
+ createRecordVersionTool({
74
+ gateway,
75
+ store,
76
+ threads,
77
+ ...(makeResolver ? { makeResolver } : {}),
62
78
  ...(logger ? { logger } : {}),
63
79
  }),
64
80
  ];
@@ -115,6 +115,7 @@ export function createReportSessionRuntime(deps) {
115
115
  state: { document: state.document ?? EMPTY_DRAFT, snapshot: state.snapshot },
116
116
  analysisId: state.analysisId,
117
117
  token: state.document,
118
+ seenDocumentHash: state.seenDocumentHash,
118
119
  };
119
120
  }
120
121
  const gateway = {
@@ -152,6 +153,31 @@ export function createReportSessionRuntime(deps) {
152
153
  return { outcome: "failed", detail: describeDbError(error) };
153
154
  });
154
155
  },
156
+ async stampRendered(threadId, hash) {
157
+ const result = await store.stampRendered(threadId, hash);
158
+ return result.match((outcome) => (outcome === "stamped" ? { outcome: "stamped" } : { outcome: "absent" }), (error) => {
159
+ log.error("the rendered-hash stamp failed", { threadId, ...log.errorFields(error.cause) });
160
+ return { outcome: "failed", detail: describeDbError(error) };
161
+ });
162
+ },
163
+ async stampSeen(threadId) {
164
+ const result = await store.stampSeen(threadId);
165
+ return result.match((outcome) => {
166
+ switch (outcome) {
167
+ case "stamped":
168
+ return { outcome: "stamped" };
169
+ case "no-rendered":
170
+ // The row holds no rendered hash, thus no preview stamped one and the copy found
171
+ // none. The eyes direct a new preview from this arm.
172
+ return { outcome: "no-rendered" };
173
+ case "absent":
174
+ return { outcome: "absent" };
175
+ }
176
+ }, (error) => {
177
+ log.error("the seen-hash stamp failed", { threadId, ...log.errorFields(error.cause) });
178
+ return { outcome: "failed", detail: describeDbError(error) };
179
+ });
180
+ },
155
181
  };
156
182
  return { gateway, ensureSessionState };
157
183
  }
@@ -18,10 +18,19 @@
18
18
  * A conversation thread id comes from the host UI. But the spawn is a harness
19
19
  * operation with no UI in front of it. Thus it mints a `randomUUID` and returns
20
20
  * the full row. A managed deployment gets the same behavior with no host code.
21
+ *
22
+ * The spawn also gates on the eyes of the composition. A report session records
23
+ * a version only after a look at the rendered page, and only a browser can look.
24
+ * Thus a composition with no browser and no injected capture seam can author a
25
+ * draft that no gate ever accepts. The spawn refuses that session before it
26
+ * writes a row, because a dead-end thread is worse than a refusal that names the
27
+ * absent capability.
21
28
  */
22
29
  import { type ResultAsync } from "neverthrow";
23
30
  import type { Pool } from "pg";
24
31
  import type { DbError } from "../lib/db-result.js";
32
+ import { type ChromeConfig } from "../lib/chrome.js";
33
+ import type { CapturePage } from "../lib/page-capture.js";
25
34
  import { type Thread, type ThreadInputError, type ThreadPage, type ThreadType } from "../memory/thread-store.js";
26
35
  /**
27
36
  * A spawn the operation refuses before it writes a row. Distinct from
@@ -34,9 +43,15 @@ import { type Thread, type ThreadInputError, type ThreadPage, type ThreadType }
34
43
  * state is not permitted. `parent_not_a_conversation` keeps the tree flat: a
35
44
  * report session cannot spawn another. `empty_parent_transcript` refuses a
36
45
  * report on a parent that holds no messages, because such a report reports
37
- * nothing.
46
+ * nothing. `no_browser` refuses every spawn under a composition that gives no
47
+ * eyes, and it carries the line that explains the absent capability.
38
48
  */
39
49
  export type SpawnRefusal = {
50
+ readonly type: "no_browser";
51
+ readonly op: string;
52
+ readonly parentThreadId: string;
53
+ readonly detail: string;
54
+ } | {
40
55
  readonly type: "parent_not_found";
41
56
  readonly op: string;
42
57
  readonly parentThreadId: string;
@@ -57,6 +72,18 @@ export interface ReportSessionPaging {
57
72
  }
58
73
  export interface ReportSessionSpawnDeps {
59
74
  readonly pool: Pool;
75
+ /**
76
+ * The chrome config that the same composition gives to the eyes tool. The
77
+ * spawn reads only whether it names a browser, thus the spawn and the tool
78
+ * decide the availability of the eyes from one value.
79
+ */
80
+ readonly chrome: ChromeConfig;
81
+ /**
82
+ * The capture seam that the same composition gives to the eyes tool. A
83
+ * present seam is a route to a look with no browser endpoint, thus it
84
+ * satisfies the gate on its own.
85
+ */
86
+ readonly capture?: CapturePage;
60
87
  }
61
88
  export interface ReportSessionSpawn {
62
89
  /**
@@ -64,10 +91,10 @@ export interface ReportSessionSpawn {
64
91
  * The child takes the analysis of the parent, the parent thread id, and the
65
92
  * anchor — the parent's latest `messages.seq` at this moment.
66
93
  *
67
- * Refused with a `SpawnRefusal`, no row written: an absent or archived
68
- * parent, a parent that is not a conversation, and a parent with no
69
- * messages. A store refusal (`DbError`, `ThreadInputError`) passes through
70
- * unchanged.
94
+ * Refused with a `SpawnRefusal`, no row written: a composition with no
95
+ * eyes, an absent or archived parent, a parent that is not a conversation,
96
+ * and a parent with no messages. A store refusal (`DbError`,
97
+ * `ThreadInputError`) passes through unchanged.
71
98
  */
72
99
  spawnReportSession(parentThreadId: string): ResultAsync<Thread, SpawnRefusal | DbError | ThreadInputError>;
73
100
  /**
@@ -18,13 +18,23 @@
18
18
  * A conversation thread id comes from the host UI. But the spawn is a harness
19
19
  * operation with no UI in front of it. Thus it mints a `randomUUID` and returns
20
20
  * the full row. A managed deployment gets the same behavior with no host code.
21
+ *
22
+ * The spawn also gates on the eyes of the composition. A report session records
23
+ * a version only after a look at the rendered page, and only a browser can look.
24
+ * Thus a composition with no browser and no injected capture seam can author a
25
+ * draft that no gate ever accepts. The spawn refuses that session before it
26
+ * writes a row, because a dead-end thread is worse than a refusal that names the
27
+ * absent capability.
21
28
  */
22
29
  import { randomUUID } from "node:crypto";
23
30
  import { errAsync } from "neverthrow";
31
+ import { hasBrowserUrl } from "../lib/chrome.js";
24
32
  import { createThreadHistory } from "../memory/thread-history.js";
25
33
  import { createThreadStore } from "../memory/thread-store.js";
26
34
  const _assertDomainError = true;
27
35
  const OP = "spawn-report-session";
36
+ /** The line that the caller reads when the composition gives no eyes. */
37
+ const NO_BROWSER_DETAIL = "the composition gives no browser, thus a report session can never record a version";
28
38
  /**
29
39
  * Compose the child title. N counts the existing report children of the parent
30
40
  * plus one. A parent with no title yields `Report N` alone, because the seed of
@@ -43,7 +53,16 @@ export function createReportSessionSpawn(deps) {
43
53
  const { pool } = deps;
44
54
  const store = createThreadStore(pool);
45
55
  const history = createThreadHistory(pool);
56
+ // The eyes of the composition are fixed at construction, thus the gate reads
57
+ // one boolean and never a live probe of the sidecar.
58
+ const eyesAvailable = deps.capture !== undefined || hasBrowserUrl(deps.chrome);
46
59
  function spawnReportSession(parentThreadId) {
60
+ // The gate runs before the parent read. A session with no route to a look
61
+ // reaches the record gate and refuses there forever, thus the spawn is the
62
+ // one honest place to say so.
63
+ if (!eyesAvailable) {
64
+ return errAsync({ type: "no_browser", op: OP, parentThreadId, detail: NO_BROWSER_DETAIL });
65
+ }
47
66
  return store.getThread(parentThreadId).andThen((parent) => {
48
67
  // An absent parent and an archived one arrive the same way — `getThread`
49
68
  // filters the tombstone — and both refuse as `parent_not_found`.
@@ -2,8 +2,9 @@
2
2
  * LocalRunAuthorizer — the OSS realization of the `RunAuthorizer` seam.
3
3
  *
4
4
  * Issues a durable `RunSession` straight from the async-edge input: no remote
5
- * authorization, no credential read. It owns nothing revocable, so `revoke` is
6
- * a no-op. The opaque caller `auth` is forwarded untouched — local code never
5
+ * authorization, no credential read. It owns nothing revocable (it mints no
6
+ * jti), so `revoke` and `revokeByJti` are no-ops. The opaque caller `auth` is
7
+ * forwarded untouched — local code never
7
8
  * inspects it (no `getAuth`).
8
9
  */
9
10
  import type { RunAuthorizer } from "../execution/run-authorizer.js";
@@ -2,8 +2,9 @@
2
2
  * LocalRunAuthorizer — the OSS realization of the `RunAuthorizer` seam.
3
3
  *
4
4
  * Issues a durable `RunSession` straight from the async-edge input: no remote
5
- * authorization, no credential read. It owns nothing revocable, so `revoke` is
6
- * a no-op. The opaque caller `auth` is forwarded untouched — local code never
5
+ * authorization, no credential read. It owns nothing revocable (it mints no
6
+ * jti), so `revoke` and `revokeByJti` are no-ops. The opaque caller `auth` is
7
+ * forwarded untouched — local code never
7
8
  * inspects it (no `getAuth`).
8
9
  */
9
10
  export function createLocalRunAuthorizer() {
@@ -19,5 +20,6 @@ export function createLocalRunAuthorizer() {
19
20
  return { runSession, ownsMandate: false }; // oss-core-managed-ok
20
21
  },
21
22
  async revoke() { },
23
+ async revokeByJti() { },
22
24
  };
23
25
  }
@@ -18,17 +18,22 @@
18
18
  * one. Neither store is ever written through these paths.
19
19
  *
20
20
  * Both are optional, and absence is a NORMAL state rather than an error: an
21
- * omitted path makes its tool report the store as unavailable or unknown, which
22
- * is a truthful answer an agent can act on. Absence must never be reported as a
23
- * failure, and never papered over with a guessed path.
21
+ * omitted path falls back to the store's container mountpoint — correct for a
22
+ * host whose own process sees the store the way a sandbox does, and harmless for
23
+ * one that does not, because every consumer stats the root before it reports
24
+ * anything. A host with nothing mounted there gets the same "unavailable" or
25
+ * "unknown" answer an omitted path has always produced, so the fallback can no
26
+ * more invent a store than an explicit path can. Set a path only to name a
27
+ * location the container mountpoint does not describe; absence must never be
28
+ * reported as a failure.
24
29
  */
25
30
  export interface EnvironmentStorePaths {
26
31
  /**
27
32
  * Host path of the reference store — the same bytes sandboxes mount at
28
- * `/mnt/refs`. Omit when no store is provisioned; reference discovery then
29
- * reports the store as unavailable. Note that an installed store reads as
30
- * absent if this is omitted, which is indistinguishable to an agent from
31
- * having no data — so omit it only when there is genuinely nothing staged.
33
+ * `/mnt/refs`. Omit when the host's own process sees the store at that same
34
+ * path, as a K8s pod holding the ref-store PVC does; set it when the host
35
+ * reads the store somewhere else, as a native process bind-mounting a host
36
+ * directory into Docker does.
32
37
  */
33
38
  readonly refStorePath?: string;
34
39
  /**
@@ -1,8 +1,11 @@
1
1
  /**
2
- * Resource limits — ported from orchestrator/src/config/resource-limits.ts.
2
+ * Resource limits — validates sandbox resource requests against cluster
3
+ * maximums.
3
4
  *
4
- * Validates sandbox resource requests against cluster maximums.
5
- * Used by K8sSandbox.
5
+ * The ceilings themselves are the embedder's: they arrive as
6
+ * `SandboxClientConfig.resourceLimits` and as the `resourcePolicy` this module
7
+ * validates. Nothing here reads the environment, so a host keeps one validated
8
+ * view of its own configuration rather than two that can disagree.
6
9
  */
7
10
  import { z } from "zod";
8
11
  export declare const GpuSpecSchema: z.ZodObject<{
@@ -47,13 +50,6 @@ export declare const DEFAULT_SANDBOX_MAX_STEPS = 75;
47
50
  export declare class ResourceLimitsConfigError extends Error {
48
51
  constructor(message: string);
49
52
  }
50
- /**
51
- * Load resource limits from environment variables.
52
- *
53
- * Required: SANDBOX_MAX_CPU, SANDBOX_MAX_MEMORY_GB, SANDBOX_MAX_GPU_COUNT
54
- * @throws ResourceLimitsConfigError if configuration is invalid
55
- */
56
- export declare function loadResourceLimits(): ResourceLimits;
57
53
  /**
58
54
  * Validate an embedder-supplied resource policy.
59
55
  *
@@ -1,8 +1,11 @@
1
1
  /**
2
- * Resource limits — ported from orchestrator/src/config/resource-limits.ts.
2
+ * Resource limits — validates sandbox resource requests against cluster
3
+ * maximums.
3
4
  *
4
- * Validates sandbox resource requests against cluster maximums.
5
- * Used by K8sSandbox.
5
+ * The ceilings themselves are the embedder's: they arrive as
6
+ * `SandboxClientConfig.resourceLimits` and as the `resourcePolicy` this module
7
+ * validates. Nothing here reads the environment, so a host keeps one validated
8
+ * view of its own configuration rather than two that can disagree.
6
9
  */
7
10
  import { z } from "zod";
8
11
  // ── Schemas ─────────────────────────────────────────────────────────
@@ -38,47 +41,6 @@ export class ResourceLimitsConfigError extends Error {
38
41
  this.name = "ResourceLimitsConfigError";
39
42
  }
40
43
  }
41
- // ── Parsing ─────────────────────────────────────────────────────────
42
- function parsePositiveNumber(name, value) {
43
- if (!value) {
44
- throw new ResourceLimitsConfigError(`${name} environment variable is required`);
45
- }
46
- const num = Number(value);
47
- if (isNaN(num) || num <= 0) {
48
- throw new ResourceLimitsConfigError(`${name} must be a positive number, got: ${value}`);
49
- }
50
- return num;
51
- }
52
- function parseNonNegativeInt(name, value) {
53
- if (!value) {
54
- throw new ResourceLimitsConfigError(`${name} environment variable is required`);
55
- }
56
- const num = Number(value);
57
- if (isNaN(num) || num < 0) {
58
- throw new ResourceLimitsConfigError(`${name} must be a non-negative number, got: ${value}`);
59
- }
60
- if (!Number.isInteger(num)) {
61
- throw new ResourceLimitsConfigError(`${name} must be an integer, got: ${value}`);
62
- }
63
- return num;
64
- }
65
- /**
66
- * Load resource limits from environment variables.
67
- *
68
- * Required: SANDBOX_MAX_CPU, SANDBOX_MAX_MEMORY_GB, SANDBOX_MAX_GPU_COUNT
69
- * @throws ResourceLimitsConfigError if configuration is invalid
70
- */
71
- export function loadResourceLimits() {
72
- const maxCpu = parsePositiveNumber("SANDBOX_MAX_CPU", process.env.SANDBOX_MAX_CPU);
73
- const maxMemoryGb = parsePositiveNumber("SANDBOX_MAX_MEMORY_GB", process.env.SANDBOX_MAX_MEMORY_GB);
74
- const maxGpuCount = parseNonNegativeInt("SANDBOX_MAX_GPU_COUNT", process.env.SANDBOX_MAX_GPU_COUNT);
75
- const limits = { maxCpu, maxMemoryGb, maxGpuCount };
76
- const result = ResourceLimitsSchema.safeParse(limits);
77
- if (!result.success) {
78
- throw new ResourceLimitsConfigError(`Invalid resource limits configuration: ${result.error.message}`);
79
- }
80
- return result.data;
81
- }
82
44
  /**
83
45
  * Validate an embedder-supplied resource policy.
84
46
  *
@@ -313,6 +313,8 @@ export declare const ScalarReferenceSchema: z.ZodDiscriminatedUnion<[z.ZodObject
313
313
  * the path. `unreadable-artifact` means that the entry exists, and that the resolver cannot read the
314
314
  * artifact as a table. Two conditions give the second reason. The file type refuses a read of a cell, or
315
315
  * the bytes are unrecoverable.
316
+ *
317
+ * `extraction-unavailable` means that the value tier must use an extraction arm that is not wired.
316
318
  */
317
319
  export declare const UnresolvedReasonSchema: z.ZodEnum<{
318
320
  "artifact-missing": "artifact-missing";
@@ -321,6 +323,7 @@ export declare const UnresolvedReasonSchema: z.ZodEnum<{
321
323
  "ambiguous-match": "ambiguous-match";
322
324
  "assertion-failed": "assertion-failed";
323
325
  "unreadable-artifact": "unreadable-artifact";
326
+ "extraction-unavailable": "extraction-unavailable";
324
327
  }>;
325
328
  /** A reference that resolution could not bind, with the reason and an optional detail. */
326
329
  export declare const UnresolvedReferenceSchema: z.ZodObject<{
@@ -419,6 +422,7 @@ export declare const UnresolvedReferenceSchema: z.ZodObject<{
419
422
  "ambiguous-match": "ambiguous-match";
420
423
  "assertion-failed": "assertion-failed";
421
424
  "unreadable-artifact": "unreadable-artifact";
425
+ "extraction-unavailable": "extraction-unavailable";
422
426
  }>;
423
427
  detail: z.ZodOptional<z.ZodString>;
424
428
  }, z.core.$strict>;
@@ -175,6 +175,8 @@ export const ScalarReferenceSchema = z.discriminatedUnion("kind", [ArtifactValue
175
175
  * the path. `unreadable-artifact` means that the entry exists, and that the resolver cannot read the
176
176
  * artifact as a table. Two conditions give the second reason. The file type refuses a read of a cell, or
177
177
  * the bytes are unrecoverable.
178
+ *
179
+ * `extraction-unavailable` means that the value tier must use an extraction arm that is not wired.
178
180
  */
179
181
  export const UnresolvedReasonSchema = z.enum([
180
182
  "artifact-missing",
@@ -183,6 +185,7 @@ export const UnresolvedReasonSchema = z.enum([
183
185
  "ambiguous-match",
184
186
  "assertion-failed",
185
187
  "unreadable-artifact",
188
+ "extraction-unavailable",
186
189
  ]);
187
190
  /** A reference that resolution could not bind, with the reason and an optional detail. */
188
191
  export const UnresolvedReferenceSchema = z.strictObject({
@@ -15,6 +15,16 @@
15
15
  * realization injected at the composition root.
16
16
  */
17
17
  import type { AuthContext, Provenance, RunFrame, RunSession, Scope } from "../auth/types.js";
18
+ /**
19
+ * Locates a run credential when no `RunAuthorization` exists: the persisted
20
+ * jti from `cortex_runs` plus the revoking caller's opaque `auth` — the sole
21
+ * carrier of credential/org behind a session, downcast only by managed
22
+ * realizations.
23
+ */
24
+ export interface RevokeByJtiRef {
25
+ readonly jti: string;
26
+ readonly auth: AuthContext;
27
+ }
18
28
  /** What an async edge knows when it authorizes a run: the opaque caller auth,
19
29
  * the scope + provenance to stamp on the run, and the run frame to mint for. */
20
30
  export interface AuthorizeRunInput {
@@ -40,4 +50,12 @@ export interface RunAuthorization {
40
50
  export interface RunAuthorizer {
41
51
  authorize(input: AuthorizeRunInput): Promise<RunAuthorization>;
42
52
  revoke(authorization: RunAuthorization, reason: string): Promise<void>;
53
+ /**
54
+ * Out-of-band revocation for paths holding no `RunAuthorization`. Normal
55
+ * terminal paths use `revoke(authorization, reason)` under the run
56
+ * credential; `revokeByJti` covers external cancellation, where the run
57
+ * credential's JWT was never persisted and only its jti survives on
58
+ * `cortex_runs`. Local/OSS authorizers no-op — they mint no jti.
59
+ */
60
+ revokeByJti(ref: RevokeByJtiRef, reason: string): Promise<void>;
43
61
  }
@@ -0,0 +1,67 @@
1
+ /**
2
+ * RunCanceler — the encapsulated external-cancel path for an `executeAnalysis` run.
3
+ *
4
+ * A cancelled DBOS workflow can never execute another step, so the body's own
5
+ * terminal block (`collectAndComplete`) is unreachable on external cancel and
6
+ * nothing converges: the run row stays `running` (blocking same-plan relaunch
7
+ * through `queryActiveRun`'s active-status dedup), pending step rows never
8
+ * sweep, the running charge never closes, and the run mandate is never
9
+ * revoked. This module is the host-side counterpart: cancel the engine's
10
+ * parent + children, then converge each ledger the workflow would have.
11
+ *
12
+ * The engine capability stays internal — the production default is the
13
+ * purger's `DBOSClient`-backed child-cascading cancel, which needs no launched
14
+ * engine (see `dbos-workflow-purger.ts` for why the static `DBOS` facade is
15
+ * the wrong tool here). `cancelWorkflows` is an optional test seam over it.
16
+ *
17
+ * No stream writes: `DBOS.writeStream` is body-only, so hosts synthesize the
18
+ * terminal frame read-side.
19
+ */
20
+ import type { Pool } from "pg";
21
+ import type { AgentSession } from "../auth/types.js";
22
+ import type { RunCharge } from "../billing/run-charge.js";
23
+ import type { Logger } from "../lib/logger.js";
24
+ import type { RunStatus } from "../state/schema.js";
25
+ import type { RunAuthorizer } from "./run-authorizer.js";
26
+ export declare class UnknownRunError extends Error {
27
+ readonly runId: string;
28
+ constructor(runId: string);
29
+ }
30
+ export interface CancelRunResult {
31
+ readonly runId: string;
32
+ /** The DBOS parent workflow id — equal to `runId` by the launch contract
33
+ * (`execute_analysis` launches with `workflowId: runId`). */
34
+ readonly workflowId: string;
35
+ readonly outcome: "canceled" | "already_terminal";
36
+ /**
37
+ * The run row's status after the cancel. `canceled` when this cancel won
38
+ * the row; the run's own terminal status when it completed concurrently
39
+ * (the conditional write refuses to clobber it).
40
+ */
41
+ readonly finalStatus: RunStatus;
42
+ /** What actually converged. `mandate` is vacuously true for a row with no
43
+ * persisted jti — nothing to revoke, so hosts alert on any false flag. */
44
+ readonly converged: {
45
+ readonly steps: boolean;
46
+ readonly charge: boolean;
47
+ readonly mandate: boolean;
48
+ };
49
+ }
50
+ export interface RunCancelerDeps {
51
+ readonly pool: Pool;
52
+ readonly runCharge: RunCharge;
53
+ readonly runAuthorizer: RunAuthorizer;
54
+ readonly logger?: Logger;
55
+ /** Test seam over the engine cancel. Production default: the workflow
56
+ * purger's `DBOSClient`-backed cancel, child-cascading and launch-free. */
57
+ readonly cancelWorkflows?: (workflowIds: readonly string[]) => Promise<void>;
58
+ }
59
+ export interface RunCanceler {
60
+ /**
61
+ * Cancel the run's workflows and converge its ledgers. Rejects when the
62
+ * engine cancel itself fails (retry-safe: nothing converged) or the run is
63
+ * unknown; convergence-phase failures resolve with their flag false.
64
+ */
65
+ cancel(runId: string, session: AgentSession): Promise<CancelRunResult>;
66
+ }
67
+ export declare function createRunCanceler(deps: RunCancelerDeps): RunCanceler;