@wrenai/genbi 0.0.4 → 0.0.6

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 (234) hide show
  1. package/README.md +65 -3
  2. package/dist/assets/{echarts-LTFC-ysd.js → echarts-DYM4hjvk.js} +2 -2
  3. package/dist/assets/index-A_Wrq7Ey.css +1 -0
  4. package/dist/assets/{index-Bo--QMUN.js → index-DsH129tV.js} +18 -17
  5. package/dist/index.html +2 -2
  6. package/dist-server/harness/bundle/schema.d.ts +38 -0
  7. package/dist-server/harness/bundle/schema.js +18 -10
  8. package/dist-server/harness/bundle/version.js +1 -1
  9. package/dist-server/harness/cli-args.d.ts +10 -2
  10. package/dist-server/harness/cli-args.js +24 -8
  11. package/dist-server/harness/cli.js +75 -9
  12. package/dist-server/harness/compile/cache.js +4 -4
  13. package/dist-server/harness/compile/compose-profile.d.ts +51 -14
  14. package/dist-server/harness/compile/compose-profile.js +85 -19
  15. package/dist-server/harness/compile/context-loader.d.ts +56 -0
  16. package/dist-server/harness/compile/context-loader.js +237 -0
  17. package/dist-server/harness/compile/errors.d.ts +19 -0
  18. package/dist-server/harness/compile/errors.js +35 -0
  19. package/dist-server/harness/compile/index.d.ts +4 -3
  20. package/dist-server/harness/compile/index.js +4 -3
  21. package/dist-server/harness/compile/pipeline.d.ts +15 -7
  22. package/dist-server/harness/compile/pipeline.js +62 -14
  23. package/dist-server/harness/compile/resolve-binary.d.ts +2 -2
  24. package/dist-server/harness/compile/resolve-binary.js +3 -3
  25. package/dist-server/harness/compile/types.d.ts +29 -0
  26. package/dist-server/harness/compile/warble-identity.d.ts +11 -0
  27. package/dist-server/harness/compile/warble-identity.js +13 -0
  28. package/dist-server/harness/components/ai-step.d.ts +4 -0
  29. package/dist-server/harness/components/ai-step.js +40 -0
  30. package/dist-server/harness/components/broker.d.ts +60 -0
  31. package/dist-server/harness/components/broker.js +117 -0
  32. package/dist-server/harness/components/capability-card.d.ts +98 -0
  33. package/dist-server/harness/components/capability-card.js +169 -0
  34. package/dist-server/harness/components/context.d.ts +2 -0
  35. package/dist-server/harness/components/context.js +30 -0
  36. package/dist-server/harness/components/display.d.ts +6 -0
  37. package/dist-server/harness/components/display.js +29 -0
  38. package/dist-server/harness/components/egress-judge.d.ts +12 -0
  39. package/dist-server/harness/components/egress-judge.js +27 -0
  40. package/dist-server/harness/components/egress.d.ts +142 -0
  41. package/dist-server/harness/components/egress.js +469 -0
  42. package/dist-server/harness/components/in-process.d.ts +7 -0
  43. package/dist-server/harness/components/in-process.js +245 -0
  44. package/dist-server/harness/components/normalize.d.ts +35 -0
  45. package/dist-server/harness/components/normalize.js +245 -0
  46. package/dist-server/harness/components/plan.d.ts +31 -0
  47. package/dist-server/harness/components/plan.js +184 -0
  48. package/dist-server/harness/components/report.d.ts +207 -0
  49. package/dist-server/harness/components/report.js +309 -0
  50. package/dist-server/harness/components/runner.d.ts +193 -0
  51. package/dist-server/harness/components/runner.js +492 -0
  52. package/dist-server/harness/components/semantic-guards.d.ts +20 -0
  53. package/dist-server/harness/components/semantic-guards.js +42 -0
  54. package/dist-server/harness/components/terminal-json.d.ts +18 -0
  55. package/dist-server/harness/components/terminal-json.js +82 -0
  56. package/dist-server/harness/components/usage.d.ts +14 -0
  57. package/dist-server/harness/components/usage.js +19 -0
  58. package/dist-server/harness/components/wren-access.d.ts +29 -0
  59. package/dist-server/harness/components/wren-access.js +233 -0
  60. package/dist-server/harness/components/zone-dry-run.d.ts +43 -0
  61. package/dist-server/harness/components/zone-dry-run.js +107 -0
  62. package/dist-server/harness/components/zone-gate.d.ts +76 -0
  63. package/dist-server/harness/components/zone-gate.js +144 -0
  64. package/dist-server/harness/events/index.d.ts +1 -1
  65. package/dist-server/harness/events/types.d.ts +52 -0
  66. package/dist-server/harness/index.d.ts +9 -2
  67. package/dist-server/harness/index.js +5 -1
  68. package/dist-server/harness/loop/executor.d.ts +1 -0
  69. package/dist-server/harness/loop/executor.js +37 -15
  70. package/dist-server/harness/npm-package-resolve.d.ts +2 -0
  71. package/dist-server/harness/npm-package-resolve.js +16 -4
  72. package/dist-server/harness/providers/adapters/openai-compatible.d.ts +13 -0
  73. package/dist-server/harness/providers/adapters/openai-compatible.js +17 -0
  74. package/dist-server/harness/providers/adapters/openai.d.ts +7 -0
  75. package/dist-server/harness/providers/adapters/openai.js +6 -0
  76. package/dist-server/harness/providers/adapters/pi-ai.d.ts +48 -0
  77. package/dist-server/harness/providers/adapters/pi-ai.js +518 -0
  78. package/dist-server/harness/providers/binding.d.ts +45 -4
  79. package/dist-server/harness/providers/binding.js +44 -8
  80. package/dist-server/harness/providers/index.d.ts +9 -3
  81. package/dist-server/harness/providers/index.js +10 -3
  82. package/dist-server/harness/providers/zone.d.ts +96 -0
  83. package/dist-server/harness/providers/zone.js +112 -0
  84. package/dist-server/harness/render/answering-query.d.ts +29 -0
  85. package/dist-server/harness/render/answering-query.js +62 -0
  86. package/dist-server/harness/render/envelope.d.ts +6 -0
  87. package/dist-server/harness/render/envelope.js +10 -3
  88. package/dist-server/harness/route/adapter-spec.d.ts +8 -9
  89. package/dist-server/harness/route/adapter-spec.js +13 -14
  90. package/dist-server/harness/route/codex-ask-events.js +2 -1
  91. package/dist-server/harness/route/codex-ask.d.ts +4 -0
  92. package/dist-server/harness/route/codex-ask.js +43 -45
  93. package/dist-server/harness/route/codex-local-manifest.d.ts +9 -1
  94. package/dist-server/harness/route/codex-local-manifest.js +96 -15
  95. package/dist-server/harness/route/describe.js +45 -6
  96. package/dist-server/harness/route/dispatched.d.ts +20 -0
  97. package/dist-server/harness/route/dispatched.js +122 -24
  98. package/dist-server/harness/route/host-query.d.ts +93 -0
  99. package/dist-server/harness/route/host-query.js +257 -0
  100. package/dist-server/harness/route/in-process.d.ts +22 -1
  101. package/dist-server/harness/route/in-process.js +54 -4
  102. package/dist-server/harness/route/route.js +8 -2
  103. package/dist-server/harness/route/types.d.ts +47 -1
  104. package/dist-server/harness/route/wren-host-proxy.d.ts +33 -0
  105. package/dist-server/harness/route/wren-host-proxy.js +228 -0
  106. package/dist-server/harness/session/run.js +30 -10
  107. package/dist-server/harness/setup/runner.d.ts +17 -3
  108. package/dist-server/harness/setup/runner.js +272 -89
  109. package/dist-server/harness/setup/yaml-scalar.d.ts +23 -0
  110. package/dist-server/harness/setup/yaml-scalar.js +62 -0
  111. package/dist-server/harness/tools/setup-native.d.ts +7 -0
  112. package/dist-server/harness/tools/setup-native.js +7 -4
  113. package/dist-server/server/adopt.js +1 -17
  114. package/dist-server/server/app.d.ts +1 -0
  115. package/dist-server/server/app.js +175 -17
  116. package/dist-server/server/auth-choice.d.ts +13 -1
  117. package/dist-server/server/auth-choice.js +66 -1
  118. package/dist-server/server/bin.js +185 -25
  119. package/dist-server/server/codex-native-session.d.ts +25 -0
  120. package/dist-server/server/codex-native-session.js +1 -0
  121. package/dist-server/server/codex-runtime-composition.d.ts +34 -0
  122. package/dist-server/server/codex-runtime-composition.js +250 -0
  123. package/dist-server/server/codex-structured-ask.d.ts +23 -0
  124. package/dist-server/server/codex-structured-ask.js +102 -0
  125. package/dist-server/server/conn-config.js +4 -16
  126. package/dist-server/server/conversation-socket.d.ts +16 -0
  127. package/dist-server/server/conversation-socket.js +65 -0
  128. package/dist-server/server/db.d.ts +46 -0
  129. package/dist-server/server/db.js +88 -9
  130. package/dist-server/server/env-detect.d.ts +6 -0
  131. package/dist-server/server/env-detect.js +7 -0
  132. package/dist-server/server/fold.d.ts +23 -4
  133. package/dist-server/server/fold.js +42 -5
  134. package/dist-server/server/harness.d.ts +2 -2
  135. package/dist-server/server/harness.js +33 -7
  136. package/dist-server/server/host-evidence-grounding.d.ts +21 -0
  137. package/dist-server/server/host-evidence-grounding.js +93 -0
  138. package/dist-server/server/interactive-terminal.d.ts +10 -6
  139. package/dist-server/server/interactive-terminal.js +33 -8
  140. package/dist-server/server/managed-wren-runtime.d.ts +116 -0
  141. package/dist-server/server/managed-wren-runtime.js +670 -0
  142. package/dist-server/server/native-artifacts.d.ts +112 -7
  143. package/dist-server/server/native-artifacts.js +194 -14
  144. package/dist-server/server/native-component-context.d.ts +19 -0
  145. package/dist-server/server/native-component-context.js +76 -0
  146. package/dist-server/server/native-component-preparation.d.ts +20 -0
  147. package/dist-server/server/native-component-preparation.js +129 -0
  148. package/dist-server/server/native-component-runtime.d.ts +36 -0
  149. package/dist-server/server/native-component-runtime.js +92 -0
  150. package/dist-server/server/native-components.d.ts +84 -0
  151. package/dist-server/server/native-components.js +160 -0
  152. package/dist-server/server/native-dispatch-process.d.ts +8 -0
  153. package/dist-server/server/native-dispatch-process.js +65 -0
  154. package/dist-server/server/native-runtime-spec.d.ts +89 -0
  155. package/dist-server/server/native-runtime-spec.js +169 -0
  156. package/dist-server/server/native-sessions.d.ts +172 -19
  157. package/dist-server/server/native-sessions.js +839 -244
  158. package/dist-server/server/native-shutdown.d.ts +6 -0
  159. package/dist-server/server/native-shutdown.js +28 -0
  160. package/dist-server/server/native-wren-home.d.ts +18 -0
  161. package/dist-server/server/native-wren-home.js +262 -0
  162. package/dist-server/server/node-pty-host.d.ts +2 -1
  163. package/dist-server/server/node-pty-host.js +3 -2
  164. package/dist-server/server/profile-registry.d.ts +139 -0
  165. package/dist-server/server/profile-registry.js +620 -0
  166. package/dist-server/server/runtime-binding.d.ts +4 -1
  167. package/dist-server/server/runtime-binding.js +50 -2
  168. package/dist-server/server/runtime-host/claude-component-step.d.ts +25 -0
  169. package/dist-server/server/runtime-host/claude-component-step.js +186 -0
  170. package/dist-server/server/runtime-host/codex-app-server.d.ts +75 -0
  171. package/dist-server/server/runtime-host/codex-app-server.js +239 -0
  172. package/dist-server/server/runtime-host/codex-compatibility.d.ts +110 -0
  173. package/dist-server/server/runtime-host/codex-compatibility.js +162 -0
  174. package/dist-server/server/runtime-host/codex-component-step.d.ts +19 -0
  175. package/dist-server/server/runtime-host/codex-component-step.js +308 -0
  176. package/dist-server/server/runtime-host/codex-conversation.d.ts +109 -0
  177. package/dist-server/server/runtime-host/codex-conversation.js +295 -0
  178. package/dist-server/server/runtime-host/codex-direct-tools.d.ts +23 -0
  179. package/dist-server/server/runtime-host/codex-direct-tools.js +59 -0
  180. package/dist-server/server/runtime-host/codex-events.d.ts +647 -0
  181. package/dist-server/server/runtime-host/codex-events.js +66 -0
  182. package/dist-server/server/runtime-host/codex-policy.d.ts +16 -0
  183. package/dist-server/server/runtime-host/codex-policy.js +151 -0
  184. package/dist-server/server/runtime-host/codex-process.d.ts +8 -0
  185. package/dist-server/server/runtime-host/codex-process.js +96 -0
  186. package/dist-server/server/runtime-host/codex-rpc.d.ts +47 -0
  187. package/dist-server/server/runtime-host/codex-rpc.js +182 -0
  188. package/dist-server/server/runtime-host/codex-session.d.ts +74 -0
  189. package/dist-server/server/runtime-host/codex-session.js +492 -0
  190. package/dist-server/server/runtime-host/codex-terminal-protocol.d.ts +35 -0
  191. package/dist-server/server/runtime-host/codex-terminal-protocol.js +243 -0
  192. package/dist-server/server/runtime-host/codex-terminal.d.ts +16 -0
  193. package/dist-server/server/runtime-host/codex-terminal.js +120 -0
  194. package/dist-server/server/runtime-host/index.d.ts +5 -0
  195. package/dist-server/server/runtime-host/index.js +3 -0
  196. package/dist-server/server/runtime-host/local.d.ts +29 -0
  197. package/dist-server/server/runtime-host/local.js +90 -0
  198. package/dist-server/server/runtime-host/policy.d.ts +8 -0
  199. package/dist-server/server/runtime-host/policy.js +78 -0
  200. package/dist-server/server/runtime-host/types.d.ts +52 -0
  201. package/dist-server/server/runtime-host/types.js +62 -0
  202. package/dist-server/server/structured-query-evidence.d.ts +7 -0
  203. package/dist-server/server/structured-query-evidence.js +47 -0
  204. package/dist-server/server/structured-runtime.d.ts +52 -0
  205. package/dist-server/server/structured-runtime.js +130 -0
  206. package/dist-server/server/structured-scope.d.ts +4 -0
  207. package/dist-server/server/structured-scope.js +12 -0
  208. package/dist-server/server/turn.d.ts +6 -1
  209. package/dist-server/server/turn.js +86 -26
  210. package/dist-server/server/wire-types.d.ts +67 -3
  211. package/dist-server/server/wren-profiles.js +1 -1
  212. package/managed-wren/README.md +60 -0
  213. package/managed-wren/dependency-wheels.json +282 -0
  214. package/managed-wren/manifest.json +373 -0
  215. package/managed-wren/release-inputs.json +45 -0
  216. package/managed-wren/runtime-tree.cjs +115 -0
  217. package/managed-wren/source-inputs.json +40 -0
  218. package/package.json +15 -6
  219. package/profiles/genbi-default/context/binding.yml +17 -7
  220. package/profiles/genbi-default/context/context.json +146 -0
  221. package/profiles/genbi-default/ir.golden.json +39 -43
  222. package/profiles/genbi-default/profile.yml +31 -0
  223. package/profiles/genbi-enrich-context/context/binding.yml +14 -3
  224. package/profiles/genbi-enrich-context/context/context.json +146 -0
  225. package/profiles/genbi-enrich-context/ir.golden.json +4 -5
  226. package/profiles/genbi-monitor/context/binding.yml +13 -5
  227. package/profiles/genbi-monitor/context/context.json +146 -0
  228. package/profiles/genbi-monitor/ir.golden.json +13 -7
  229. package/profiles/genbi-report/context/binding.yml +19 -0
  230. package/profiles/genbi-report/context/context.json +223 -0
  231. package/profiles/genbi-report/ir.golden.json +328 -0
  232. package/profiles/genbi-report/profile.yml +38 -0
  233. package/profiles/genbi-setup/ir.golden.json +3 -1
  234. package/dist/assets/index-Xs4q7pNo.css +0 -1
@@ -0,0 +1,245 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { mkdtemp, readFile, rm } from "node:fs/promises";
3
+ import os from "node:os";
4
+ import path from "node:path";
5
+ import { z } from "zod";
6
+ import { fingerprintSurfaces } from "@warble/claude-agent-sdk";
7
+ import { generatePreparedContext, generatePreparedContextAndCatalog, resolveContextLoader } from "../compile/context-loader.js";
8
+ import { hashDirectory } from "../compile/fingerprint.js";
9
+ import { resolveWarbleBinary } from "../compile/resolve-binary.js";
10
+ import { createAgentEventEmitter } from "../events/emitter.js";
11
+ import { createDefaultProviderRegistry, isZoneAwareBinding, resolveTierModel, resolveTierSpec } from "../providers/index.js";
12
+ import { deriveAdapterSpec } from "../route/adapter-spec.js";
13
+ import { resolveWrenBinary } from "../tools/index.js";
14
+ import { runAiComponentStep } from "./ai-step.js";
15
+ import { createComponentBroker } from "./broker.js";
16
+ import { buildCapabilityCard } from "./capability-card.js";
17
+ import { verifyEgress } from "./egress.js";
18
+ import { createModelJudge } from "./egress-judge.js";
19
+ import { normalizeComponentEvidence } from "./normalize.js";
20
+ import { buildSlotTable, isReportComponent, materialiseReportPlan, normalizeReportEvidence, parseReportPlan, reportSteps } from "./report.js";
21
+ import { planDigest } from "./plan.js";
22
+ import { ComponentRunner } from "./runner.js";
23
+ import { modelIdOf, summariseUsage } from "./usage.js";
24
+ import { captureWrenAccessIdentity, openWrenComponentAccess } from "./wren-access.js";
25
+ import { assertZoneGate } from "./zone-gate.js";
26
+ const data = z.object({ columns: z.array(z.string()), rows: z.array(z.record(z.string(), z.unknown())) });
27
+ /** Executable format 0.2 path; never flattened into the legacy agent tool union. */
28
+ export async function runInProcessComponents(plan, options) {
29
+ if (options.mcpServers)
30
+ throw new Error("Composed execution requires component-owned host bindings");
31
+ // The historical default entry is `answer_query`; a composed profile with exactly one entry
32
+ // (the report profile: `plan_report` alone is an entry) runs that entry when none is named.
33
+ const entry = options.agentId ?? defaultEntry(plan);
34
+ // The binding is fixed and gated here, before any scratch state, context
35
+ // generation, provider construction or child process exists. A rejected
36
+ // binding therefore produces none of them.
37
+ const binding = {
38
+ tiers: structuredClone(options.tierBinding ?? Object.fromEntries(Object.values(plan.components).flatMap((component) => component.steps.map((step) => [step.tier,
39
+ deriveAdapterSpec(options.authChoice, options.model ? { model: options.model } : {})])))),
40
+ ...(options.disclosurePolicy !== undefined ? { disclosurePolicy: structuredClone(options.disclosurePolicy) } : {}),
41
+ ...(options.zoneRoles !== undefined ? { roles: structuredClone(options.zoneRoles) } : {}),
42
+ };
43
+ assertZoneGate(plan, entry, binding);
44
+ const controller = new AbortController();
45
+ const signal = options.signal ? AbortSignal.any([options.signal, controller.signal]) : controller.signal;
46
+ const scratch = await mkdtemp(path.join(os.tmpdir(), "genbi-component-context-"));
47
+ const emitter = createAgentEventEmitter(options.onEvent);
48
+ const traceSteps = [];
49
+ const surfaceRecords = [];
50
+ const usageRecords = [];
51
+ const trace = () => ({ steps: traceSteps.sort((a, b) => a.ordinal - b.ordinal), surfaces: surfaceRecords, usage: summariseUsage(usageRecords) });
52
+ const toolOrder = new Map();
53
+ const zoneAware = isZoneAwareBinding(binding);
54
+ emitter.emit({ kind: "run.start", mode: "A", agentId: entry });
55
+ try {
56
+ signal.throwIfAborted();
57
+ const project = path.resolve(options.userProject);
58
+ const fingerprint = await hashDirectory(project);
59
+ const accessIdentity = await captureWrenAccessIdentity(project);
60
+ const snapshotPath = path.join(scratch, "context.json");
61
+ const catalogPath = path.join(scratch, "catalog.json");
62
+ // A zone-aware run also needs the capability catalog: the card public-zone steps
63
+ // receive instead of the snapshot. One generator process reads the project once.
64
+ if (zoneAware)
65
+ await generatePreparedContextAndCatalog(resolveContextLoader().bin, project, snapshotPath, catalogPath);
66
+ else
67
+ await generatePreparedContext(resolveContextLoader().bin, project, snapshotPath);
68
+ const snapshot = JSON.parse(await readFile(snapshotPath, "utf8"));
69
+ let card;
70
+ if (zoneAware) {
71
+ // The bound policy's shape limits go on the card, so the planner plans within what egress allows.
72
+ const maxBytes = options.capabilityCard?.maxBytes;
73
+ card = buildCapabilityCard(JSON.parse(await readFile(catalogPath, "utf8")), { ...(maxBytes !== undefined ? { maxBytes } : {}),
74
+ ...(binding.disclosurePolicy ? { disclosure: binding.disclosurePolicy } : {}) });
75
+ if (card.truncated)
76
+ traceSteps.push({ id: "capability-card", tool: "capability_card", outcome: "success", ordinal: -1,
77
+ detail: `warning: capability card truncated by the size bound (${card.omittedLines} lines omitted, ${card.bytes} bytes kept)` });
78
+ }
79
+ const zoneOf = (component, tier) => resolveTierSpec(binding, tier, component)?.spec.zone ?? "unbound";
80
+ const checkProject = async () => {
81
+ signal.throwIfAborted();
82
+ await accessIdentity.assertCurrent();
83
+ if (fingerprint !== await hashDirectory(project)) {
84
+ controller.abort();
85
+ throw new Error("Component project changed");
86
+ }
87
+ };
88
+ await checkProject();
89
+ const contexts = Object.fromEntries(Object.values(plan.components).map((component) => {
90
+ const binding = z.object({ project: z.string() }).passthrough().parse(component.declaration.context_binding);
91
+ if (path.resolve(binding.project) !== project)
92
+ throw new Error("Component is bound to another project");
93
+ return [component.id, { binding, snapshot }];
94
+ }));
95
+ const registry = createDefaultProviderRegistry();
96
+ const identity = { session: randomUUID(), vendor: "in-process", account: planDigest(binding), generation: randomUUID(),
97
+ project, bindingRevision: planDigest([fingerprint, accessIdentity.digest]), contextDigest: planDigest(contexts), planDigest: plan.identity };
98
+ const models = new Map();
99
+ // Counts only, attributed to the provider that served the call; the payload never enters the record.
100
+ const recordUsage = (at, spec, model, usage) => {
101
+ usageRecords.push({ ...at, zone: spec?.zone ?? "unbound", adapter: spec?.adapter ?? "unbound", model: model ? modelIdOf(model) : "unbound",
102
+ inputTokens: usage.inputTokens, outputTokens: usage.outputTokens });
103
+ };
104
+ // The egress verification seam is installed whenever a disclosure policy is
105
+ // bound. The gate has already required a private judge tier for it, so a
106
+ // missing judge here is an internal error, not a degrade.
107
+ const policy = binding.disclosurePolicy;
108
+ const judgeKey = binding.roles?.judge;
109
+ const judgeSpec = judgeKey !== undefined ? binding.tiers[judgeKey] : undefined;
110
+ if (policy && !judgeSpec)
111
+ throw new Error("Egress verification requires a bound judge tier");
112
+ const judgeModel = judgeSpec ? registry.create(judgeSpec.adapter, judgeSpec.config) : undefined;
113
+ const broker = createComponentBroker({ plan, contexts, verifierBinary: await resolveWarbleBinary(options.warbleBin),
114
+ identity, currentIdentity: () => signal.aborted ? undefined : identity,
115
+ ...(policy ? { verifyChild: async (context, result, parent) => {
116
+ await checkProject();
117
+ const judge = judgeModel ? createModelJudge(judgeModel, (usage) => recordUsage({ component: context.callee, step: "egress_judge", tier: judgeKey, depth: context.depth }, judgeSpec, judgeModel, usage)) : undefined;
118
+ return verifyEgress(context.request, result, { policy, ...(judge ? { judge } : {}), signal: parent });
119
+ } } : {}),
120
+ async prepare(component, _identity, parent) {
121
+ await checkProject();
122
+ // Keyed on (mount, tier): a caller and callee sharing a tier name can bind different models.
123
+ models.set(component.id, new Map([...new Set(component.steps.map((step) => step.tier))]
124
+ .map((tier) => [tier, resolveTierModel(binding, tier, registry, component.id)])));
125
+ if (component.steps.every((step) => step.tools.length === 0))
126
+ return {
127
+ async query() { throw new Error("No query grant"); }, async inspect() { throw new Error("No context grant"); }, async close() { },
128
+ };
129
+ await resolveWrenBinary();
130
+ return openWrenComponentAccess({ executable: "wren", project, fingerprint, signal: parent, identity: accessIdentity });
131
+ },
132
+ // Public-zone steps never see the prepared snapshot; they get the card. Private steps get the snapshot as before.
133
+ ...(card ? { contextSurface: (component, run) => {
134
+ if (zoneOf(component.id, run.tier) === "public")
135
+ return { kind: "card", text: card.text };
136
+ const context = contexts[component.id];
137
+ return context ? { kind: "snapshot", text: `Host semantic context:\n${JSON.stringify(context.snapshot)}` } : undefined;
138
+ } } : {}),
139
+ async step(run, component) {
140
+ await checkProject();
141
+ const model = models.get(component.id)?.get(run.tier);
142
+ if (!model)
143
+ throw new Error("Missing component tier binding");
144
+ // Host materialisation for a report's narrator: the layout it consumes is resolved from the
145
+ // slot table (the disclosed child answers) before the model reads it, so the values it sees
146
+ // are the verified ones and it never has to retype a number. The recorded product stays the
147
+ // planner's own output; only the consumed view is materialised (decision recorded in the design doc).
148
+ let consumes = run.consumes;
149
+ const report = isReportComponent(component) ? reportSteps(component) : undefined;
150
+ if (report && Object.hasOwn(run.consumes, report.layout.produces)) {
151
+ const layout = parseReportPlan(run.consumes[report.layout.produces]);
152
+ if (!layout)
153
+ throw new Error("The planner did not produce a report layout");
154
+ consumes = { ...run.consumes, [report.layout.produces]: materialiseReportPlan(layout, buildSlotTable(run.children)) };
155
+ }
156
+ // Every part of the assembled model input is fingerprinted, including the request and the
157
+ // consumed artifacts, so an audit of a public-tier prompt covers the values that crossed.
158
+ const surfaces = { ...(run.surfaces ?? { prompt: run.prompt }) };
159
+ if (plan.systemPrompt)
160
+ surfaces["system"] = plan.systemPrompt;
161
+ if (run.brief !== undefined)
162
+ surfaces["brief"] = run.brief;
163
+ surfaces["input"] = JSON.stringify({ request: run.request, input: run.input });
164
+ surfaces["consumes"] = JSON.stringify(consumes);
165
+ const fingerprint = fingerprintSurfaces(surfaces);
166
+ const stepName = plan.components[component.id]?.steps.find((step) => step.prompt === surfaces["prompt"] && step.tier === run.tier)?.name ?? "?";
167
+ surfaceRecords.push({ component: component.id, step: stepName, tier: run.tier, zone: zoneOf(component.id, run.tier), context: run.contextKind ?? "none",
168
+ algorithm: fingerprint.algorithm, digest: fingerprint.digest, surfaces: fingerprint.surfaces });
169
+ const result = await runAiComponentStep({ ...run, consumes, prompt: [plan.systemPrompt, run.prompt].filter(Boolean).join("\n\n") }, model);
170
+ await checkProject();
171
+ return result;
172
+ },
173
+ async normalize(component, evidence) {
174
+ await checkProject();
175
+ // A report's envelope is synthesised by the host from the slot table; every other component keeps the generic path.
176
+ return isReportComponent(component) ? normalizeReportEvidence(component, evidence) : normalizeComponentEvidence(component, evidence, contexts[component.id]?.snapshot);
177
+ },
178
+ onEvent(event) {
179
+ if (event.kind === "usage" && event.step) {
180
+ const tier = plan.components[event.component]?.steps.find((step) => step.name === event.step)?.tier ?? "unbound";
181
+ recordUsage({ component: event.component, step: event.step, tier, depth: event.depth }, resolveTierSpec(binding, tier, event.component)?.spec, models.get(event.component)?.get(tier), event.usage ?? { inputTokens: 0, outputTokens: 0 });
182
+ return;
183
+ }
184
+ if (event.kind === "egress") {
185
+ // Slot id, status and reason category only: the payload never enters the trace.
186
+ traceSteps.push({ id: `${event.callId}:egress:${event.slot}`, tool: "egress", outcome: event.egress === "refused" ? "error" : "success",
187
+ ordinal: toolOrder.get(event.callId ?? "") ?? traceSteps.length, detail: `${event.tool}/${event.slot}: ${event.egress}${event.reason ? ` (${event.reason})` : ""}` });
188
+ return;
189
+ }
190
+ if (event.callId && event.tool && event.kind === "tool.start")
191
+ toolOrder.set(event.callId, toolOrder.size);
192
+ if (event.callId && event.tool && event.kind === "tool.finish")
193
+ traceSteps.push({ id: event.callId, tool: event.tool,
194
+ outcome: event.status === "ok" ? "success" : "error", ordinal: toolOrder.get(event.callId) ?? traceSteps.length });
195
+ if (!event.step)
196
+ return;
197
+ const stepId = `${event.invocation}:${event.step}`;
198
+ if (event.kind === "step.start")
199
+ emitter.emit({ kind: "step.start", stepId, name: event.step,
200
+ tier: plan.components[event.component].steps.find((step) => step.name === event.step).tier,
201
+ ...(event.parent ? { parent: event.parent } : {}), depth: event.depth });
202
+ if (event.kind === "step.finish")
203
+ emitter.emit({ kind: "step.finish", stepId, name: event.step, status: event.status === "ok" ? "ok" : "error" });
204
+ if (event.tool && event.callId && event.kind === "tool.start")
205
+ emitter.emit({ kind: "tool.call", stepId, callId: event.callId, tool: event.tool, depth: event.depth, status: "running" });
206
+ if (event.tool && event.callId && event.kind === "tool.finish")
207
+ emitter.emit({ kind: "tool.result", stepId, callId: event.callId, tool: event.tool, status: event.status === "ok" ? "success" : "error" });
208
+ },
209
+ });
210
+ const result = await new ComponentRunner(plan, broker).run(entry, { request: options.question }, signal);
211
+ if (result.status === "error")
212
+ throw new Error("Component execution did not complete.");
213
+ if (result.status !== "ok") {
214
+ const envelope = { blocks: [], verified: false };
215
+ emitter.emit({ kind: "refusal", reason: result.message, envelope });
216
+ emitter.emit({ kind: "run.finish", status: "refusal" });
217
+ return { kind: "refusal", reason: result.message, envelope, trace: trace() };
218
+ }
219
+ const value = result.output.kind === "value" ? data.safeParse(result.output.value) : undefined;
220
+ const blocks = result.output.kind === "render" ? result.output.blocks : value?.success
221
+ ? [{ type: "table", columns: value.data.columns, rows: value.data.rows }]
222
+ : [{ type: "narrative", text: JSON.stringify(result.output.value) }];
223
+ const envelope = { blocks, verified: result.provenance?.verified === true,
224
+ ...(result.output.kind === "render" && result.output.summary !== undefined ? { summary: result.output.summary } : {}),
225
+ ...(result.provenance?.definition ? { definition: result.provenance.definition } : {}) };
226
+ emitter.emit({ kind: "answer", envelope });
227
+ emitter.emit({ kind: "run.finish", status: "answer" });
228
+ return { kind: "answer", envelope, trace: trace() };
229
+ }
230
+ catch (error) {
231
+ emitter.emit({ kind: "error", message: "Component preparation or execution failed." });
232
+ emitter.emit({ kind: "run.finish", status: "error" });
233
+ throw error;
234
+ }
235
+ finally {
236
+ controller.abort();
237
+ await rm(scratch, { recursive: true, force: true });
238
+ }
239
+ }
240
+ /** `answer_query` when the plan has it, else the plan's single entry; a multi-entry plan without `answer_query` keeps the historical default and fails as before. */
241
+ export function defaultEntry(plan) {
242
+ if (plan.entries.includes("answer_query"))
243
+ return "answer_query";
244
+ return plan.entries.length === 1 ? plan.entries[0] : "answer_query";
245
+ }
@@ -0,0 +1,35 @@
1
+ import { type ComponentInvocationResult } from "@warble/claude-agent-sdk";
2
+ import { z } from "zod";
3
+ import { type ComponentEvidence, type ComponentPlan } from "./runner.js";
4
+ declare const definitionSchema: z.ZodObject<{
5
+ sql: z.ZodString;
6
+ source_tables: z.ZodArray<z.ZodString>;
7
+ filters: z.ZodArray<z.ZodString>;
8
+ }, z.core.$strict>;
9
+ declare const table: z.ZodObject<{
10
+ columns: z.ZodArray<z.ZodString>;
11
+ rows: z.ZodArray<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
12
+ }, z.core.$strip>;
13
+ type Definition = z.infer<typeof definitionSchema>;
14
+ type Observation = ComponentEvidence["tools"][number];
15
+ export interface GroundedQuery {
16
+ readonly sql: string;
17
+ readonly queryId?: string;
18
+ readonly table: z.infer<typeof table>;
19
+ readonly definition: Definition;
20
+ }
21
+ export declare function groundedQuery(call: Pick<Observation, "input" | "output">): GroundedQuery | undefined;
22
+ /**
23
+ * The executed query a citation names, grounded, else `undefined`. A citation by host-assigned
24
+ * `query_id` resolves to that observation only: an id no executed query carries is never grounded
25
+ * on another query or on its SQL text. Otherwise the last observation of the same SQL, compared
26
+ * whitespace- and terminator-insensitively (the model may reformat the SQL it names). An
27
+ * observation that cannot ground (see `groundingDefinition`) never matches.
28
+ */
29
+ export declare function citedGroundedQuery(observations: readonly Pick<Observation, "input" | "output">[], citation: {
30
+ readonly queryId?: string | undefined;
31
+ readonly sql?: string | undefined;
32
+ }): GroundedQuery | undefined;
33
+ /** Child normalization is pure. Data evidence comes from observed tools, never a verified flag. */
34
+ export declare function normalizeComponentEvidence(component: ComponentPlan, evidence: ComponentEvidence, context?: unknown): ComponentInvocationResult;
35
+ export {};
@@ -0,0 +1,245 @@
1
+ import { normalizeComponentResult } from "@warble/claude-agent-sdk";
2
+ import { z } from "zod";
3
+ import { checkSemanticGuards, querySemantics } from "./semantic-guards.js";
4
+ import { namedAnswerQueryId, namedAnswerSql, selectAnsweringCandidate, sqlKey } from "../render/answering-query.js";
5
+ import { isQueryGrant } from "./runner.js";
6
+ import { batchEntry, parseTerminalJson } from "./terminal-json.js";
7
+ const definitionSchema = z.object({ sql: z.string(), source_tables: z.array(z.string()), filters: z.array(z.string()) }).strict();
8
+ /** A definition a render block may be grounded on: `filters` is optional in the block contract. */
9
+ const lineageSchema = z.object({ sql: z.string(), source_tables: z.array(z.string()), filters: z.array(z.string()).optional() }).strict();
10
+ const table = z.object({ columns: z.array(z.string()).min(1), rows: z.array(z.record(z.string(), z.unknown())) });
11
+ const blocks = z.array(z.object({ type: z.string(), fields: z.record(z.string(), z.string()) }).strict());
12
+ const capabilities = z.array(z.string());
13
+ /** The host-assigned id of an executed query (`q<N>`, see the runner), read from the observed output only. */
14
+ function queryIdOf(output) {
15
+ const id = output?.query_id;
16
+ return typeof id === "string" ? id : undefined;
17
+ }
18
+ /**
19
+ * The one test for whether a component's own tool observation may ground an answer, a render
20
+ * block or a batch entry: the result is a table, the tool itself proved a `definition` for the
21
+ * SQL it executed, and that definition reads at least one table. Returns the proven definition,
22
+ * else `undefined`. A definition the model claims never counts, so an observation without a
23
+ * proven one grounds nothing (fail closed).
24
+ *
25
+ * Known limit: this proves the query read a model, not where each projected value came from. A
26
+ * query that reads a model can still project literals (`SELECT CASE ... THEN 424 ... FROM orders`).
27
+ */
28
+ function groundingDefinition(call) {
29
+ const sql = call.input?.sql;
30
+ if (typeof sql !== "string" || !table.safeParse(call.output).success)
31
+ return undefined;
32
+ const proven = definitionSchema.safeParse(call.output?.definition);
33
+ if (!proven.success || proven.data.sql !== sql)
34
+ return undefined;
35
+ return proven.data.source_tables.some((name) => name.length > 0) ? proven.data : undefined;
36
+ }
37
+ function canGround(call) {
38
+ return groundingDefinition(call) !== undefined;
39
+ }
40
+ export function groundedQuery(call) {
41
+ const definition = groundingDefinition(call);
42
+ const parsed = table.safeParse(call.output);
43
+ if (!definition || !parsed.success)
44
+ return undefined;
45
+ const queryId = queryIdOf(call.output);
46
+ return { sql: definition.sql, ...(queryId !== undefined ? { queryId } : {}), table: parsed.data, definition };
47
+ }
48
+ /**
49
+ * The executed query a citation names, grounded, else `undefined`. A citation by host-assigned
50
+ * `query_id` resolves to that observation only: an id no executed query carries is never grounded
51
+ * on another query or on its SQL text. Otherwise the last observation of the same SQL, compared
52
+ * whitespace- and terminator-insensitively (the model may reformat the SQL it names). An
53
+ * observation that cannot ground (see `groundingDefinition`) never matches.
54
+ */
55
+ export function citedGroundedQuery(observations, citation) {
56
+ const { queryId } = citation;
57
+ const claimedSql = queryId === undefined ? citation.sql : undefined;
58
+ const observed = queryId !== undefined ? observations.find((call) => queryIdOf(call.output) === queryId && canGround(call))
59
+ : claimedSql === undefined ? undefined
60
+ : [...observations].reverse().find((call) => { const ran = call.input?.sql; return typeof ran === "string" && sqlKey(ran) === sqlKey(claimedSql) && canGround(call); });
61
+ return observed ? groundedQuery(observed) : undefined;
62
+ }
63
+ function refused() {
64
+ return { status: "refused", code: "callee_refused", message: "The component did not produce a grounded result." };
65
+ }
66
+ /** Child normalization is pure. Data evidence comes from observed tools, never a verified flag. */
67
+ export function normalizeComponentEvidence(component, evidence, context) {
68
+ const grants = component.steps.flatMap((step) => step.tools);
69
+ const queryNames = new Set(grants.filter(isQueryGrant).map((grant) => grant.name));
70
+ const observations = evidence.tools.filter((call) => queryNames.has(call.tool));
71
+ const guards = z.array(z.record(z.string(), z.unknown())).parse(component.declaration.guardrails ?? []);
72
+ const semanticGuard = guards.some((guard) => (guard.name === "additivity_guard" && guard.locked === true) || guard.name === "drill_depth_limit");
73
+ const proofs = [];
74
+ for (const call of observations) {
75
+ const proof = querySemantics.safeParse(call.output?.semantics);
76
+ if (proof.success && proof.data.sql === call.input?.sql)
77
+ proofs.push(proof.data);
78
+ else if (semanticGuard)
79
+ return refused();
80
+ }
81
+ // The public child-result contract carries no semantic query proof. Do not infer
82
+ // additivity from a child's generic verified flag or from its model-authored value.
83
+ if (semanticGuard && evidence.children.length > 0)
84
+ return refused();
85
+ if (!checkSemanticGuards(guards, proofs, context))
86
+ return refused();
87
+ // Only observations that can ground become evidence, for the single answer, a batch entry and
88
+ // render blocks alike; a table-less or unproven query is as if it never returned a table.
89
+ const queries = observations.map(groundedQuery).filter((query) => query !== undefined);
90
+ const definitions = queries.map((query) => query.definition);
91
+ const data = queries.map((query) => query.table);
92
+ const declared = capabilities.parse(component.declaration.required_capabilities);
93
+ const render = blocks.parse(component.declaration.effect.render_blocks ?? []);
94
+ const steps = component.steps.filter((step) => Object.hasOwn(evidence.steps, step.produces));
95
+ const terminal = steps.at(-1);
96
+ if (!terminal)
97
+ return refused();
98
+ const lastProduct = evidence.steps[terminal.produces];
99
+ // Tool evidence cannot turn an explicit terminal refusal or error into success.
100
+ let terminalValue = lastProduct;
101
+ if (typeof lastProduct === "string") {
102
+ // Ordinary prose is normalized below. Only the parse is tolerant: the value feeds the refusal
103
+ // check and the value branches; the render path still parses the raw text.
104
+ const parsed = parseTerminalJson(lastProduct);
105
+ terminalValue = parsed.ok ? parsed.value : lastProduct;
106
+ }
107
+ if (terminalValue && typeof terminalValue === "object" && "status" in terminalValue
108
+ && ["refused", "error"].includes(String(terminalValue.status)))
109
+ return refused();
110
+ if (declared.includes("sql_execution:read_only") && data.length === 0)
111
+ return refused();
112
+ // A batch-shaped callee (answer_batch) ends in an array with one entry per slot. Each tabular
113
+ // entry is rebuilt from the observed query result its SQL names; the model's copy of the rows
114
+ // is never read, and an entry no executed query backs is returned as unanswerable.
115
+ if (render.length === 0 && Array.isArray(terminalValue))
116
+ return normalizeBatchTerminal(terminalValue, observations);
117
+ if (render.length === 0 && data.length > 0) {
118
+ // The answer is the query the terminal value names, never simply the last table: a model
119
+ // that runs a stray check after the right query must not turn that check into the answer.
120
+ // A value naming no query (by id or SQL) keeps the historical last-observation rule; a value
121
+ // naming a query that never ran is refused rather than answered with another table.
122
+ let chosen;
123
+ if (namedAnswerQueryId(terminalValue) === undefined && namedAnswerSql(terminalValue) === undefined) {
124
+ chosen = groundedQuery(observations[observations.length - 1]);
125
+ }
126
+ else {
127
+ chosen = selectAnsweringCandidate(queries, terminalValue);
128
+ }
129
+ if (!chosen)
130
+ return refused();
131
+ const definition = chosen.definition;
132
+ return { status: "ok", output: { kind: "value", value: { ...chosen.table, verified: true, summary: "Query completed.", definition } },
133
+ provenance: { verified: true, definition } };
134
+ }
135
+ const result = normalizeComponentResult(typeof lastProduct === "string" ? lastProduct : JSON.stringify(lastProduct), render).value;
136
+ if (result.status !== "ok")
137
+ return result;
138
+ const composed = component.steps.some((step) => step.calls.length > 0);
139
+ if (composed) {
140
+ if (evidence.children.length === 0 || evidence.children.some((child) => child.status !== "ok" || child.provenance?.verified !== true))
141
+ return refused();
142
+ for (const child of evidence.children) {
143
+ if (child.status !== "ok" || child.output.kind !== "value")
144
+ return refused();
145
+ const parsed = table.safeParse(child.output.value);
146
+ if (!parsed.success)
147
+ return refused();
148
+ data.push(parsed.data);
149
+ const definition = lineageSchema.safeParse(child.provenance?.definition);
150
+ if (definition.success)
151
+ definitions.push(definition.data);
152
+ }
153
+ }
154
+ if (result.output.kind === "render" && data.length > 0 && !grounded(result.output.blocks, data, definitions))
155
+ return refused();
156
+ // A render with data-bearing blocks cannot earn provenance from prose alone.
157
+ if (result.output.kind === "render" && result.output.blocks.some((block) => ["table", "chart", "kpi_card"].includes(String(block.type))) && data.length === 0)
158
+ return refused();
159
+ return { ...result, provenance: { ...result.provenance, verified: data.length > 0 } };
160
+ }
161
+ function grounded(render, data, definitions) {
162
+ return render.every((block) => {
163
+ // `filters` is optional in the block contract: compared only when both sides carry it.
164
+ if (block.type === "definition")
165
+ return definitions.some((definition) => definition.sql === block.sql
166
+ && JSON.stringify(definition.source_tables) === JSON.stringify(block.source_tables)
167
+ && (block.filters === undefined || definition.filters === undefined || JSON.stringify(definition.filters) === JSON.stringify(block.filters)));
168
+ if (block.type === "kpi_card")
169
+ return typeof block.label === "string" && data.some((source) => source.columns.includes(block.label)
170
+ && source.rows.some((row) => row[block.label] === block.value
171
+ && (block.unit === undefined || (Object.hasOwn(row, "unit") && row.unit === block.unit))
172
+ && (block.delta === undefined || (Object.hasOwn(row, "delta") && row.delta === block.delta))));
173
+ if (block.type !== "table" && block.type !== "chart")
174
+ return true;
175
+ if (!Array.isArray(block.rows))
176
+ return false;
177
+ const columns = block.type === "table" ? block.columns : [block.x, ...(Array.isArray(block.series) ? block.series : [])];
178
+ if (!Array.isArray(columns) || !columns.length || new Set(columns).size !== columns.length || !columns.every((value) => typeof value === "string"))
179
+ return false;
180
+ const sources = data.filter((source) => columns.every((name) => source.columns.includes(name)));
181
+ if (!sources.length)
182
+ return false;
183
+ if (block.rows.length === 0)
184
+ return sources.some((source) => source.rows.length === 0);
185
+ return block.rows.every((row) => {
186
+ if (Array.isArray(row) && row.length !== columns.length)
187
+ return false;
188
+ const values = Array.isArray(row) ? Object.fromEntries(columns.map((name, index) => [name, row[index]])) : row;
189
+ if (!values || typeof values !== "object")
190
+ return false;
191
+ if (Object.keys(values).length !== columns.length || !columns.every((name) => Object.hasOwn(values, name)))
192
+ return false;
193
+ return sources.some((source) => source.rows.some((observed) => Object.entries(values).every(([name, value]) => Object.hasOwn(observed, name) && JSON.stringify(observed[name]) === JSON.stringify(value))));
194
+ });
195
+ });
196
+ }
197
+ const REASON_LIMIT = 200;
198
+ function sanitizedReason(value, fallback) {
199
+ const text = typeof value === "string" ? value.replace(/\s+/g, " ").trim() : "";
200
+ return (text.length > 0 ? text : fallback).slice(0, REASON_LIMIT);
201
+ }
202
+ /**
203
+ * One entry per slot, first entry per slot id wins. A tabular entry is
204
+ * accepted only when its `definition` cites a query this run executed with a
205
+ * result that can ground (see `groundingDefinition`): by `query_id` (the id the
206
+ * host attached to that tool result), else by `sql`. Its columns, rows and
207
+ * definition are then the observed ones. The model contributes `summary` only;
208
+ * every value comes from the tool. Entries the model marked
209
+ * `unanswerable` (or `refused`) pass through with a bounded reason.
210
+ */
211
+ function normalizeBatchTerminal(entries, observations) {
212
+ const seen = new Set();
213
+ const value = [];
214
+ let answered = 0;
215
+ for (const raw of entries) {
216
+ const entry = batchEntry.safeParse(raw);
217
+ if (!entry.success || seen.has(entry.data.slot_id))
218
+ continue;
219
+ seen.add(entry.data.slot_id);
220
+ const item = entry.data;
221
+ if (item.status === "unanswerable" || item.status === "refused") {
222
+ value.push({ slot_id: item.slot_id, status: "unanswerable", reason: sanitizedReason(item.reason, "the callee could not answer this slot") });
223
+ continue;
224
+ }
225
+ const claimed = item.definition && typeof item.definition === "object" ? item.definition : undefined;
226
+ // An id no executed query carries, or a cited query that cannot ground, is unanswerable.
227
+ const query = citedGroundedQuery(observations, {
228
+ queryId: typeof claimed?.query_id === "string" ? claimed.query_id : undefined,
229
+ sql: typeof claimed?.sql === "string" ? claimed.sql : undefined,
230
+ });
231
+ if (!query) {
232
+ value.push({ slot_id: item.slot_id, status: "unanswerable", reason: "no executed query backs this answer" });
233
+ continue;
234
+ }
235
+ // Provenance is the observed query's tool-proven definition; any definition fields the
236
+ // model sends besides the citation are ignored.
237
+ answered += 1;
238
+ value.push({ slot_id: item.slot_id, columns: query.table.columns, rows: query.table.rows, verified: true,
239
+ summary: typeof item.summary === "string" ? item.summary : "Query completed.", definition: query.definition });
240
+ }
241
+ if (value.length === 0)
242
+ return refused();
243
+ // Spec §6.2: an array value is preserved as-is with no outer provenance; per-entry `verified`/`definition` stay on the entries.
244
+ return { status: "ok", output: { kind: "value", value }, ...(answered > 0 ? { provenance: { verified: true } } : {}) };
245
+ }
@@ -0,0 +1,31 @@
1
+ import type { ExecutionPlan } from "./runner.js";
2
+ export declare const HOST_FEATURES: readonly ["immutable_step_authority", "isolated_component_tools", "fresh_child_context", "verified_context_preconditions", "component_owned_bindings", "exact_step_tiers", "exact_dataflow", "bounded_repair", "shared_admission_ledger", "deadline_and_descendant_cancellation", "normalized_child_results", "root_only_persistence", "redacted_usage_trace"];
3
+ export declare const VERCEL_HOST_CONTRACT: Readonly<{
4
+ protocol: "warble-component-host/1";
5
+ bundle_version: "0.2";
6
+ features: readonly ["immutable_step_authority", "isolated_component_tools", "fresh_child_context", "verified_context_preconditions", "component_owned_bindings", "exact_step_tiers", "exact_dataflow", "bounded_repair", "shared_admission_ledger", "deadline_and_descendant_cancellation", "normalized_child_results", "root_only_persistence", "redacted_usage_trace"];
7
+ guardrails: string[];
8
+ tool_authority: {
9
+ query: {
10
+ source: string;
11
+ capabilities: string[];
12
+ };
13
+ semantic_introspect: {
14
+ source: string;
15
+ capabilities: string[];
16
+ };
17
+ };
18
+ }>;
19
+ /** SHA over recursively sorted JSON, matching the producer's documented digest form. */
20
+ export declare function canonicalPlanJson(value: unknown): string;
21
+ export declare function planDigest(value: unknown): string;
22
+ export interface PlanReceipt {
23
+ /** Trusted host receipt captured from its successful producer invocation, never model input. */
24
+ readonly digest: string;
25
+ readonly inputIrDigest: string;
26
+ readonly contextBinding: Readonly<Record<string, unknown>>;
27
+ readonly declarations: Readonly<Record<string, unknown>>;
28
+ /** Exact host contract bytes passed to the direct-session producer. */
29
+ readonly directHostDocument?: string;
30
+ }
31
+ export declare function readExecutionPlan(document: string, receipt: PlanReceipt): ExecutionPlan;