@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,328 @@
1
+ {
2
+ "components": [
3
+ {
4
+ "borrowed_actions": [],
5
+ "brief": "You plan reports for business readers who want the figures laid out and explained, not the\nqueries behind them. Keep every cell's question self-contained and answerable from the\ncapability card in your context, keep the preamble (period, currency, shared filters)\nexplicit, ask for the smallest series or table that still answers a cell, and keep the\nreport readable when a cell is unavailable.",
6
+ "context_binding": {
7
+ "binding_mode": "runtime_selected",
8
+ "project": "../../../../examples/v5-jaffle"
9
+ },
10
+ "context_precondition": [],
11
+ "context_requirements": [
12
+ "a bound semantic context (semantic layer) whose questions the mounted batch-answer behavior can answer"
13
+ ],
14
+ "description": "Plan and narrate a report on a topic: lay out KPI cards, charts, tables and narrative cells with one self-contained data question per cell under a shared period/currency/filter preamble, obtain all the answers in one batch through the profile's bound batch-answer behavior, and write the summary and per-cell notes over the verified values without holding query authority itself. Use it when someone wants a laid-out report whose planning and narration may run separately from the data access that fills it.",
15
+ "effect": {
16
+ "outcome": {
17
+ "kind": "none"
18
+ },
19
+ "render_blocks": [
20
+ {
21
+ "fields": {
22
+ "label": "string",
23
+ "note": "string?",
24
+ "slot_id": "string?",
25
+ "unit": "string?",
26
+ "value": "number|string"
27
+ },
28
+ "type": "kpi_card"
29
+ },
30
+ {
31
+ "fields": {
32
+ "columns": "string[]",
33
+ "note": "string?",
34
+ "rows": "row[]",
35
+ "slot_id": "string?",
36
+ "title": "string?"
37
+ },
38
+ "type": "table"
39
+ },
40
+ {
41
+ "fields": {
42
+ "chart_type": "bar|line|pie|area|scatter",
43
+ "note": "string?",
44
+ "rows": "row[]",
45
+ "series": "string[]",
46
+ "slot_id": "string?",
47
+ "title": "string?",
48
+ "x": "string"
49
+ },
50
+ "type": "chart"
51
+ },
52
+ {
53
+ "fields": {
54
+ "slot_id": "string?",
55
+ "text": "string",
56
+ "title": "string?"
57
+ },
58
+ "type": "narrative"
59
+ },
60
+ {
61
+ "fields": {
62
+ "block_type": "kpi_card|table|chart|narrative",
63
+ "label": "string",
64
+ "note": "string?",
65
+ "reason_category": "string",
66
+ "slot_id": "string?"
67
+ },
68
+ "type": "unavailable"
69
+ },
70
+ {
71
+ "fields": {
72
+ "filters": "string[]?",
73
+ "source_tables": "string[]",
74
+ "sql": "string"
75
+ },
76
+ "type": "definition"
77
+ }
78
+ ]
79
+ },
80
+ "entrypoint": true,
81
+ "eval_ref": "plan_report.eval",
82
+ "examples": [
83
+ "Build me an annual revenue report.",
84
+ "Prepare a quarterly customer-retention report for the board.",
85
+ "Report on how each region contributed to this year's growth."
86
+ ],
87
+ "guardrails": [
88
+ {
89
+ "locked": true,
90
+ "name": "read_only_execution"
91
+ },
92
+ {
93
+ "locked": true,
94
+ "name": "artifact_write",
95
+ "scope": "."
96
+ }
97
+ ],
98
+ "id": "plan_report",
99
+ "llm_calls": [
100
+ {
101
+ "component_calls": [
102
+ {
103
+ "alias": "ask",
104
+ "component": "answer_batch"
105
+ }
106
+ ],
107
+ "conditional": false,
108
+ "consumes": [],
109
+ "name": "plan_layout",
110
+ "produces": "report_plan",
111
+ "prompt": "You plan data reports over the bound semantic context `v5-jaffle` (a semantic layer at\n`../../../../examples/v5-jaffle`). You do not access data yourself: every data cell of the report is a question you\nhand to the logical `ask` alias, in one batch, and the values come back to you already answered.\nThis step produces `report_plan`, a layout of typed placeholders.\n\nGiven the user's request:\n\n- If the request refers back to a report already in play (\"the report\", \"it\", \"that one\") or is a\n bare \"build it\" follow-up, take the topic from the conversation so far; if there is truly none,\n fall back to an overview of the project's key metrics. This step always ends with a\n `report_plan`, never a clarifying question.\n- Decide the report's **preamble** — the framing every cell shares: the period (e.g. a fiscal\n year), the currency, and any filters that apply to the whole report. Write it once; every\n question is read under it.\n- Lay out the report as blocks in the render-contract types: `kpi_card` for a headline number,\n `chart` for a trend or breakdown, `table` for small detail, `narrative` for a grounded prose\n finding. Every data cell is one **slot**:\n `{\"slot_id\": \"<unique snake_case id>\", \"block_type\": \"kpi_card|chart|table|narrative\",\n \"expected_shape\": \"scalar|series|table|narrative\", \"question\": \"<one self-contained\n natural-language data question>\", \"unit\": \"<optional>\", \"max_rows\": <optional integer>}`.\n Shapes pair with block types: a `kpi_card` asks for a `scalar`, a `chart` for one `series`\n (ask for the whole series in one question; never split a chart into several scalars), a `table`\n for a `table`, a `narrative` for a `narrative`. Set `max_rows` on every `series` and `table`\n slot, as small as still answers the question.\n- Ask only for **answers**. Never ask for the query text, the definition or the provenance behind\n an answer, never ask for raw rows beyond what a cell displays, and never ask for more rows than\n a slot's `max_rows`. Provenance is attached to the report by the host, not requested by you.\n- A per-entity listing (one row per customer, order or user) is not a report cell, and neither is\n a ranking of named individual entities (a \"top 10 customers\"): each of its rows describes one\n entity. Ask instead for a grouped aggregate over a dimension the context offers (a segment,\n status, region, product category or month), so that every row summarises many entities, with\n `max_rows` no higher than 25, and have `summary_brief` note that per-entity detail is not\n available here.\n- If your context states disclosure limits (a minimum group size, a row limit, allowed shapes),\n plan every slot within them: each grouped row must cover at least the minimum group size, and no\n slot may ask for more rows than the limit. A slot outside the limits comes back refused.\n- Call the logical `ask` alias **exactly once** with a concise request and this structured input:\n `{\"preamble\": <the preamble>, \"questions\": [<every slot, in layout order>]}`. Do not call it\n once per slot. The call returns, when successful, `output.kind: \"value\"` whose `value` is an\n array with one entry per `slot_id`: either a verified tabular answer or\n `{\"slot_id\", \"status\": \"unanswerable\" | \"refused\", \"reason\" | \"reason_category\"}`.\n- A slot that comes back unavailable does not fail the report. If its reason says the question was\n too fine-grained or asked for too many rows, you may issue **one** follow-up call to `ask` with\n coarser questions for those slots only, under new `slot_id`s; never re-ask a question that was\n refused for policy reasons, and never guess a value for it. Otherwise keep the block and let the\n host mark it unavailable.\n- Produce `report_plan` as exactly one JSON object with this shape and no extra keys:\n ```\n {\"title\": \"<report title>\",\n \"preamble\": {\"period\": \"...\", \"currency\": \"...\", \"filters\": [\"...\"]},\n \"slots\": [<every slot you asked, including any follow-up slots>],\n \"blocks\": [{\"type\": \"kpi_card\", \"label\": \"...\", \"slot_id\": \"...\"},\n {\"type\": \"chart\", \"title\": \"...\", \"chart_type\": \"bar|line|pie|area|scatter\", \"slot_id\": \"...\"},\n {\"type\": \"table\", \"title\": \"...\", \"slot_id\": \"...\"},\n {\"type\": \"narrative\", \"title\": \"...\", \"slot_id\": \"...\"}],\n \"summary_brief\": \"<one sentence on what the report summary should address>\"}\n ```\n Blocks reference their value by `slot_id` **only**. Do not copy any number, row, series or prose\n answer into a block — even though you have seen the answers — the host materialises the verified\n values into the layout before the next step reads it.",
112
+ "tier": "strong",
113
+ "when": null
114
+ },
115
+ {
116
+ "conditional": false,
117
+ "consumes": [
118
+ "report_plan"
119
+ ],
120
+ "name": "narrate",
121
+ "produces": "report",
122
+ "prompt": "Given `report_plan` — the layout from the previous step, with the verified values already\nmaterialised into its blocks by the host — write the report's summary and per-block notes and\nproduce `report`.\n\n- Each block in `report_plan.blocks` now arrives in one of two states:\n - **filled**: it carries the fields its type renders — a `kpi_card` has `value` (and `unit`\n when known), a `chart` has `x`, `series` and `rows`, a `table` has `columns` and `rows`, a\n `narrative` has `text` — plus its `slot_id`;\n - **unavailable**: it carries `\"status\": \"unavailable\"` and a `reason_category` instead of a\n value, because its answer was refused or could not be given.\n The plan may also carry `definition` blocks the host attached as provenance.\n- **Never alter a value.** Copy every `value`, `unit`, `x`, `series`, `rows`, `columns` and `text`\n into the output exactly as materialised — same numbers, same rows, same order. Do not compute\n totals, deltas, ratios or growth rates that are not already in a block, do not round, and do not\n drop or add rows. Do not fill an unavailable cell with an estimate, a recollection or a\n placeholder number. If a value looks wrong, say so in its `note`; do not fix it.\n- Write `summary`: a few sentences addressing `summary_brief`, grounded only in the materialised\n values, naming the preamble's period and currency. Where a cell is unavailable, say so plainly and\n name its reason category; never speculate about what the value would have been.\n- Write a short `note` on each filled `kpi_card`, `chart` and `table` block: the useful reading of\n that cell, grounded only in its own values. You may adjust a block's `label` or `title` for\n clarity; you may not change its type, `slot_id` or position.\n- Map each unavailable block to an `unavailable` block:\n `{\"type\": \"unavailable\", \"label\": \"<the block's label or title>\", \"block_type\": \"<its original\n type>\", \"reason_category\": \"<as materialised>\", \"slot_id\": \"<as materialised>\",\n \"note\": \"<optional one-line explanation>\"}`. Keep it in the block's original position.\n- Pass every `definition` block through unchanged. Do not write one yourself.\n- Produce `report`: follow the \"Render output\" instructions the dispatcher appends below for the\n active render flavor — emit the `{ blocks, summary, verified }` envelope (programmatic), where\n `verified` is true only if every filled block came from a verified answer. The rendered report IS\n the artifact: never ask the user what kind of artifact they want, and never offer alternatives.",
123
+ "tier": "cheap",
124
+ "when": null
125
+ }
126
+ ],
127
+ "params": [],
128
+ "precondition_result": {
129
+ "checks": [],
130
+ "status": "pass"
131
+ },
132
+ "prompt_fragment": "## plan_layout\n\nYou plan data reports over the bound semantic context `v5-jaffle` (a semantic layer at\n`../../../../examples/v5-jaffle`). You do not access data yourself: every data cell of the report is a question you\nhand to the logical `ask` alias, in one batch, and the values come back to you already answered.\nThis step produces `report_plan`, a layout of typed placeholders.\n\nGiven the user's request:\n\n- If the request refers back to a report already in play (\"the report\", \"it\", \"that one\") or is a\n bare \"build it\" follow-up, take the topic from the conversation so far; if there is truly none,\n fall back to an overview of the project's key metrics. This step always ends with a\n `report_plan`, never a clarifying question.\n- Decide the report's **preamble** — the framing every cell shares: the period (e.g. a fiscal\n year), the currency, and any filters that apply to the whole report. Write it once; every\n question is read under it.\n- Lay out the report as blocks in the render-contract types: `kpi_card` for a headline number,\n `chart` for a trend or breakdown, `table` for small detail, `narrative` for a grounded prose\n finding. Every data cell is one **slot**:\n `{\"slot_id\": \"<unique snake_case id>\", \"block_type\": \"kpi_card|chart|table|narrative\",\n \"expected_shape\": \"scalar|series|table|narrative\", \"question\": \"<one self-contained\n natural-language data question>\", \"unit\": \"<optional>\", \"max_rows\": <optional integer>}`.\n Shapes pair with block types: a `kpi_card` asks for a `scalar`, a `chart` for one `series`\n (ask for the whole series in one question; never split a chart into several scalars), a `table`\n for a `table`, a `narrative` for a `narrative`. Set `max_rows` on every `series` and `table`\n slot, as small as still answers the question.\n- Ask only for **answers**. Never ask for the query text, the definition or the provenance behind\n an answer, never ask for raw rows beyond what a cell displays, and never ask for more rows than\n a slot's `max_rows`. Provenance is attached to the report by the host, not requested by you.\n- A per-entity listing (one row per customer, order or user) is not a report cell, and neither is\n a ranking of named individual entities (a \"top 10 customers\"): each of its rows describes one\n entity. Ask instead for a grouped aggregate over a dimension the context offers (a segment,\n status, region, product category or month), so that every row summarises many entities, with\n `max_rows` no higher than 25, and have `summary_brief` note that per-entity detail is not\n available here.\n- If your context states disclosure limits (a minimum group size, a row limit, allowed shapes),\n plan every slot within them: each grouped row must cover at least the minimum group size, and no\n slot may ask for more rows than the limit. A slot outside the limits comes back refused.\n- Call the logical `ask` alias **exactly once** with a concise request and this structured input:\n `{\"preamble\": <the preamble>, \"questions\": [<every slot, in layout order>]}`. Do not call it\n once per slot. The call returns, when successful, `output.kind: \"value\"` whose `value` is an\n array with one entry per `slot_id`: either a verified tabular answer or\n `{\"slot_id\", \"status\": \"unanswerable\" | \"refused\", \"reason\" | \"reason_category\"}`.\n- A slot that comes back unavailable does not fail the report. If its reason says the question was\n too fine-grained or asked for too many rows, you may issue **one** follow-up call to `ask` with\n coarser questions for those slots only, under new `slot_id`s; never re-ask a question that was\n refused for policy reasons, and never guess a value for it. Otherwise keep the block and let the\n host mark it unavailable.\n- Produce `report_plan` as exactly one JSON object with this shape and no extra keys:\n ```\n {\"title\": \"<report title>\",\n \"preamble\": {\"period\": \"...\", \"currency\": \"...\", \"filters\": [\"...\"]},\n \"slots\": [<every slot you asked, including any follow-up slots>],\n \"blocks\": [{\"type\": \"kpi_card\", \"label\": \"...\", \"slot_id\": \"...\"},\n {\"type\": \"chart\", \"title\": \"...\", \"chart_type\": \"bar|line|pie|area|scatter\", \"slot_id\": \"...\"},\n {\"type\": \"table\", \"title\": \"...\", \"slot_id\": \"...\"},\n {\"type\": \"narrative\", \"title\": \"...\", \"slot_id\": \"...\"}],\n \"summary_brief\": \"<one sentence on what the report summary should address>\"}\n ```\n Blocks reference their value by `slot_id` **only**. Do not copy any number, row, series or prose\n answer into a block — even though you have seen the answers — the host materialises the verified\n values into the layout before the next step reads it.\n\n## narrate\n\nGiven `report_plan` — the layout from the previous step, with the verified values already\nmaterialised into its blocks by the host — write the report's summary and per-block notes and\nproduce `report`.\n\n- Each block in `report_plan.blocks` now arrives in one of two states:\n - **filled**: it carries the fields its type renders — a `kpi_card` has `value` (and `unit`\n when known), a `chart` has `x`, `series` and `rows`, a `table` has `columns` and `rows`, a\n `narrative` has `text` — plus its `slot_id`;\n - **unavailable**: it carries `\"status\": \"unavailable\"` and a `reason_category` instead of a\n value, because its answer was refused or could not be given.\n The plan may also carry `definition` blocks the host attached as provenance.\n- **Never alter a value.** Copy every `value`, `unit`, `x`, `series`, `rows`, `columns` and `text`\n into the output exactly as materialised — same numbers, same rows, same order. Do not compute\n totals, deltas, ratios or growth rates that are not already in a block, do not round, and do not\n drop or add rows. Do not fill an unavailable cell with an estimate, a recollection or a\n placeholder number. If a value looks wrong, say so in its `note`; do not fix it.\n- Write `summary`: a few sentences addressing `summary_brief`, grounded only in the materialised\n values, naming the preamble's period and currency. Where a cell is unavailable, say so plainly and\n name its reason category; never speculate about what the value would have been.\n- Write a short `note` on each filled `kpi_card`, `chart` and `table` block: the useful reading of\n that cell, grounded only in its own values. You may adjust a block's `label` or `title` for\n clarity; you may not change its type, `slot_id` or position.\n- Map each unavailable block to an `unavailable` block:\n `{\"type\": \"unavailable\", \"label\": \"<the block's label or title>\", \"block_type\": \"<its original\n type>\", \"reason_category\": \"<as materialised>\", \"slot_id\": \"<as materialised>\",\n \"note\": \"<optional one-line explanation>\"}`. Keep it in the block's original position.\n- Pass every `definition` block through unchanged. Do not write one yourself.\n- Produce `report`: follow the \"Render output\" instructions the dispatcher appends below for the\n active render flavor — emit the `{ blocks, summary, verified }` envelope (programmatic), where\n `verified` is true only if every filled block came from a verified answer. The rendered report IS\n the artifact: never ask the user what kind of artifact they want, and never offer alternatives.",
133
+ "realization_kind": "skill",
134
+ "required_capabilities": [
135
+ "render_contract",
136
+ "artifact_write",
137
+ "llm:per_step_tier",
138
+ "llm:strong",
139
+ "llm:cheap",
140
+ "component_invocation"
141
+ ],
142
+ "trigger": {
143
+ "kind": "one_shot"
144
+ },
145
+ "type": "analytical",
146
+ "verb": "plan_report"
147
+ },
148
+ {
149
+ "borrowed_actions": [],
150
+ "brief": "Data access in this deployment goes through the `wren` CLI. If you need to introspect the\nschema, run `wren context show`. Query the semantic layer with `wren -q -o json -s '<SQL>'`,\nwhich returns JSON; object-shaped rows are also valid — preserve their values exactly. Never\nhand-write SQL against raw tables outside the model.",
151
+ "context_binding": {
152
+ "binding_mode": "runtime_selected",
153
+ "project": "../../../../examples/v5-jaffle"
154
+ },
155
+ "context_precondition": [
156
+ {
157
+ "predicate": "mdl_parseable"
158
+ }
159
+ ],
160
+ "context_requirements": [
161
+ "a bound semantic context (semantic layer) to query"
162
+ ],
163
+ "description": "Answer a batch of natural-language questions about the bound semantic model in one run, under one shared preamble (period, currency, filters), so the answers agree with each other: resolve every question's intent, generate and run read-only queries through the semantic layer, repair what fails, and return one tabular answer per question with the definitions it relied on. Use it when a caller has several data cells to fill from one consistent view of the data, not for a single standalone question.",
164
+ "effect": {
165
+ "outcome": {
166
+ "kind": "none"
167
+ },
168
+ "render_blocks": []
169
+ },
170
+ "entrypoint": false,
171
+ "eval_ref": "answer_batch.eval",
172
+ "examples": [
173
+ "For fiscal 2025 in USD: total revenue; revenue by quarter; the ten largest customers by revenue.",
174
+ "Under the same filters, answer these five KPI questions so the figures reconcile."
175
+ ],
176
+ "guardrails": [
177
+ {
178
+ "locked": true,
179
+ "name": "read_only_execution"
180
+ },
181
+ {
182
+ "locked": false,
183
+ "name": "row_limit",
184
+ "threshold": 1000
185
+ },
186
+ {
187
+ "locked": false,
188
+ "name": "statement_timeout",
189
+ "threshold": 30
190
+ },
191
+ {
192
+ "locked": true,
193
+ "name": "deterministic_gate"
194
+ }
195
+ ],
196
+ "id": "answer_batch",
197
+ "llm_calls": [
198
+ {
199
+ "capabilities": [
200
+ "llm:cheap"
201
+ ],
202
+ "conditional": false,
203
+ "consumes": [],
204
+ "name": "resolve_intent",
205
+ "produces": "batch_intent",
206
+ "prompt": "You answer a batch of data questions over the bound semantic context `v5-jaffle` (a semantic\nlayer at `../../../../examples/v5-jaffle`). The request's `input` carries `preamble` (report-level framing such as the\nperiod, the currency, and filters every answer must share) and `questions`: a list of slots, each\n`{slot_id, question, expected_shape, unit?, max_rows?}` where `expected_shape` is one of `scalar`\n(one value), `series` (one ordered set of points for a chart), `table` (a small detail table), or\n`narrative` (a grounded prose answer). This first step resolves every slot into a concrete query\nintent.\n\n- Do not answer the questions in this step and do not call any tool: this step declares no data\n capability. Resolve every slot from the semantic context you were given; the next step writes and\n runs the queries.\n- Apply the preamble to every slot: the same period, currency, and filters, so the figures foot.\n Resolve ambiguous business terms once and reuse the resolution across slots that share them.\n- For each slot identify the model(s), metric(s), dimension(s), filters, grouping, and ordering its\n question implies, and the row bound: a `scalar` is one row; a `series` or `table` never exceeds\n the slot's `max_rows` when given, and stays as small as still answers the question when not.\n- A slot whose question cannot be resolved against this semantic context is not an error for the\n batch. Record it as unresolvable with a short reason so the next step reports it as unanswerable\n instead of guessing.\n- Produce `batch_intent`: one entry per slot, in the order received, each naming its `slot_id`,\n what to compute (measures, grouping, filters, ordering, row limit), and its `expected_shape` —\n enough for the next step to write every query without re-reading the questions.\n `batch_intent` is your final message, not a tool: a JSON array with one object per slot —\n `{\"slot_id\", \"compute\", \"expected_shape\", \"max_rows\"?}` or\n `{\"slot_id\", \"status\": \"unresolvable\", \"reason\"}` — and nothing else.",
207
+ "tier": "cheap",
208
+ "when": null
209
+ },
210
+ {
211
+ "conditional": false,
212
+ "consumes": [
213
+ "batch_intent"
214
+ ],
215
+ "name": "generate_sql",
216
+ "produces": "batch_result",
217
+ "prompt": "Given the resolved `batch_intent`, write and execute the query for every slot through the semantic\nlayer, in this one run so the answers share one consistent view of the data.\n\n- Query ONLY through the bound query capability, which returns structured (JSON) results. Never\n hand-write SQL against raw tables outside the model — always go through the semantic layer.\n- Respect the guardrails: read-only only, keep within the row limit, honour each slot's `max_rows`\n (never return more rows than a slot asked for), and prefer a deterministic ordering when a\n question implies a ranking, a top-N, or a time series.\n- **Verify every result set (deterministic gate — required before answering).** After each query\n runs, check it is legitimate: it actually executed (no error), it is non-empty where a value is\n expected, the types/units are sane, the grain matches the question, and the shape matches the\n slot's `expected_shape` (a `scalar` is exactly one row and one value column). Record whether it\n passed, per slot.\n- Produce `batch_result` as exactly one JSON ARRAY with one entry per slot, in the order received,\n and no other top-level keys. A verified entry has exactly this shape and no extra keys:\n ```\n {\"slot_id\": \"<the slot's id>\",\n \"columns\": [\"col1\", ...], \"rows\": [[v1, ...], ...],\n \"summary\": \"<a concise prose answer grounded only in the returned rows>\",\n \"verified\": true,\n \"definition\": {\"query_id\": \"<the query_id the tool result returned>\"}}\n ```\n Object-shaped rows are also valid; preserve their values exactly. Emit numbers as numbers.\n Set `verified: true` only after both execution and the deterministic result-set validation pass.\n The summary must state the useful conclusion for that slot, not merely describe the columns.\n `definition` is run-level provenance only; do not invent formal lineage.\n- When the tool result carries a `query_id`, never copy SQL, filters or source tables into\n `definition`: cite `{\"query_id\": …}` only. When it carries none, cite\n `{\"sql\": \"<the exact SQL you ran>\", \"source_tables\": [\"...\"]}` and nothing else. No shape of\n `definition` has a `filters` key.\n- A slot that cannot be answered — unresolvable intent, a query that still fails after a bounded\n number of attempts, a result that cannot be validated — does NOT fail the batch. Emit it as\n `{\"slot_id\": \"<id>\", \"status\": \"unanswerable\", \"reason\": \"<short, stable, non-secret reason>\"}`\n and go on with the other slots. Never fabricate a number to fill a slot.\n- Only when the batch as a whole could not run — no query executed at all, or the semantic context\n is unusable — keep the attempted SQL, execution/validation evidence, and stable non-secret error\n in `batch_result` so the declared repair step can diagnose it. Never mark a failed entry verified.\n- Your final message is `batch_result` as JSON and nothing else: no heading, no sentence before or\n after it, no Markdown fence, no reasoning tags. Every string closed and every bracket matched; it\n must parse as JSON.",
218
+ "tier": "strong",
219
+ "when": null
220
+ },
221
+ {
222
+ "conditional": true,
223
+ "consumes": [
224
+ "batch_result"
225
+ ],
226
+ "name": "repair_sql",
227
+ "produces": "repaired_batch",
228
+ "prompt": "Repair step — runs ONLY in one of these cases:\n\n- When `batch_result` is `{\"status\": \"error\", \"code\": \"step_failed\", \"reason\":\n \"terminal_unparseable: …\", \"text\": …}`, the previous answers are in `text`; when `batch_result`\n is otherwise not valid JSON (prose around it, an unterminated string, a stray reasoning tag),\n the previous answers are that raw text. Re-emit those SAME answers as one JSON array in the canonical\n shape below, citing each query by its `query_id` (or, when its tool result carried none,\n `{\"sql\": \"<the exact SQL you ran>\", \"source_tables\": [\"...\"]}`).\n Run no new query, and put nothing else in the message.\n- `batch_result` came back as a batch-level failure (no query executed, an execution error that\n stopped the run, or an obviously wrong result across the batch).\n\nIf the previous step produced a valid array — even one with `unanswerable` entries — do nothing and\npass it through unchanged.\n\n- For an execution failure (the second case above): diagnose the failure from the error text (unknown column, bad join, type mismatch, wrong grain),\n fix the affected queries, and re-run them through the bound query capability, still under the\n batch's shared preamble and each slot's `max_rows`. Bound your attempts — a few retries at most;\n do not loop indefinitely (retry depth is this step's concern, not the profile's).\n- A slot whose result still cannot be validated becomes\n `{\"slot_id\": \"<id>\", \"status\": \"unanswerable\", \"reason\": \"<why it could not be verified>\"}`.\n Do not fabricate a number.\n- **If nothing in the batch can be validated, REFUSE.** Emit\n `{\"verified\": false, \"refused\": true, \"reason\": \"<why the batch could not be answered>\"}` and stop.\n- Produce `repaired_batch`. On success return exactly the same canonical array shape as\n `generate_sql`: one entry per slot, in the order received, each either the verified tabular shape\n ```\n {\"slot_id\": \"<the slot's id>\",\n \"columns\": [\"col1\", ...], \"rows\": [[v1, ...], ...],\n \"summary\": \"<a concise prose answer grounded only in the returned rows>\",\n \"verified\": true,\n \"definition\": {\"query_id\": \"<the query_id the tool result returned>\"}}\n ```\n or the `unanswerable` entry above, with no extra keys. Object-shaped rows are also valid;\n preserve their values exactly and emit numbers as numbers. Set `verified: true` only when the\n repaired query ran and its result set passed validation. The `definition` is run-level provenance\n only (the query behind that answer) — do not invent unit/owner/formal-metric lineage.\n When the tool result carries a `query_id`, never copy SQL, filters or source tables into\n `definition`: cite `{\"query_id\": …}` only. When it carries none, cite\n `{\"sql\": \"<the exact SQL you ran>\", \"source_tables\": [\"...\"]}` and nothing else. No shape of\n `definition` has a `filters` key.\n Your final message is the JSON array (or the refusal object) and nothing else: no heading, no\n sentence before or after it, no Markdown fence, no reasoning tags. Every string closed and every\n bracket matched; it must parse as JSON.",
229
+ "tier": "strong",
230
+ "when": {
231
+ "guard": "on_failure",
232
+ "target": "generate_sql"
233
+ }
234
+ }
235
+ ],
236
+ "params": [],
237
+ "precondition_result": {
238
+ "checks": [
239
+ {
240
+ "outcome": "pass",
241
+ "predicate": "mdl_parseable"
242
+ }
243
+ ],
244
+ "status": "pass"
245
+ },
246
+ "prompt_fragment": "## resolve_intent\n\nYou answer a batch of data questions over the bound semantic context `v5-jaffle` (a semantic\nlayer at `../../../../examples/v5-jaffle`). The request's `input` carries `preamble` (report-level framing such as the\nperiod, the currency, and filters every answer must share) and `questions`: a list of slots, each\n`{slot_id, question, expected_shape, unit?, max_rows?}` where `expected_shape` is one of `scalar`\n(one value), `series` (one ordered set of points for a chart), `table` (a small detail table), or\n`narrative` (a grounded prose answer). This first step resolves every slot into a concrete query\nintent.\n\n- Do not answer the questions in this step and do not call any tool: this step declares no data\n capability. Resolve every slot from the semantic context you were given; the next step writes and\n runs the queries.\n- Apply the preamble to every slot: the same period, currency, and filters, so the figures foot.\n Resolve ambiguous business terms once and reuse the resolution across slots that share them.\n- For each slot identify the model(s), metric(s), dimension(s), filters, grouping, and ordering its\n question implies, and the row bound: a `scalar` is one row; a `series` or `table` never exceeds\n the slot's `max_rows` when given, and stays as small as still answers the question when not.\n- A slot whose question cannot be resolved against this semantic context is not an error for the\n batch. Record it as unresolvable with a short reason so the next step reports it as unanswerable\n instead of guessing.\n- Produce `batch_intent`: one entry per slot, in the order received, each naming its `slot_id`,\n what to compute (measures, grouping, filters, ordering, row limit), and its `expected_shape` —\n enough for the next step to write every query without re-reading the questions.\n `batch_intent` is your final message, not a tool: a JSON array with one object per slot —\n `{\"slot_id\", \"compute\", \"expected_shape\", \"max_rows\"?}` or\n `{\"slot_id\", \"status\": \"unresolvable\", \"reason\"}` — and nothing else.\n\n## generate_sql\n\nGiven the resolved `batch_intent`, write and execute the query for every slot through the semantic\nlayer, in this one run so the answers share one consistent view of the data.\n\n- Query ONLY through the bound query capability, which returns structured (JSON) results. Never\n hand-write SQL against raw tables outside the model — always go through the semantic layer.\n- Respect the guardrails: read-only only, keep within the row limit, honour each slot's `max_rows`\n (never return more rows than a slot asked for), and prefer a deterministic ordering when a\n question implies a ranking, a top-N, or a time series.\n- **Verify every result set (deterministic gate — required before answering).** After each query\n runs, check it is legitimate: it actually executed (no error), it is non-empty where a value is\n expected, the types/units are sane, the grain matches the question, and the shape matches the\n slot's `expected_shape` (a `scalar` is exactly one row and one value column). Record whether it\n passed, per slot.\n- Produce `batch_result` as exactly one JSON ARRAY with one entry per slot, in the order received,\n and no other top-level keys. A verified entry has exactly this shape and no extra keys:\n ```\n {\"slot_id\": \"<the slot's id>\",\n \"columns\": [\"col1\", ...], \"rows\": [[v1, ...], ...],\n \"summary\": \"<a concise prose answer grounded only in the returned rows>\",\n \"verified\": true,\n \"definition\": {\"query_id\": \"<the query_id the tool result returned>\"}}\n ```\n Object-shaped rows are also valid; preserve their values exactly. Emit numbers as numbers.\n Set `verified: true` only after both execution and the deterministic result-set validation pass.\n The summary must state the useful conclusion for that slot, not merely describe the columns.\n `definition` is run-level provenance only; do not invent formal lineage.\n- When the tool result carries a `query_id`, never copy SQL, filters or source tables into\n `definition`: cite `{\"query_id\": …}` only. When it carries none, cite\n `{\"sql\": \"<the exact SQL you ran>\", \"source_tables\": [\"...\"]}` and nothing else. No shape of\n `definition` has a `filters` key.\n- A slot that cannot be answered — unresolvable intent, a query that still fails after a bounded\n number of attempts, a result that cannot be validated — does NOT fail the batch. Emit it as\n `{\"slot_id\": \"<id>\", \"status\": \"unanswerable\", \"reason\": \"<short, stable, non-secret reason>\"}`\n and go on with the other slots. Never fabricate a number to fill a slot.\n- Only when the batch as a whole could not run — no query executed at all, or the semantic context\n is unusable — keep the attempted SQL, execution/validation evidence, and stable non-secret error\n in `batch_result` so the declared repair step can diagnose it. Never mark a failed entry verified.\n- Your final message is `batch_result` as JSON and nothing else: no heading, no sentence before or\n after it, no Markdown fence, no reasoning tags. Every string closed and every bracket matched; it\n must parse as JSON.\n\n## repair_sql\n\nRepair step — runs ONLY in one of these cases:\n\n- When `batch_result` is `{\"status\": \"error\", \"code\": \"step_failed\", \"reason\":\n \"terminal_unparseable: …\", \"text\": …}`, the previous answers are in `text`; when `batch_result`\n is otherwise not valid JSON (prose around it, an unterminated string, a stray reasoning tag),\n the previous answers are that raw text. Re-emit those SAME answers as one JSON array in the canonical\n shape below, citing each query by its `query_id` (or, when its tool result carried none,\n `{\"sql\": \"<the exact SQL you ran>\", \"source_tables\": [\"...\"]}`).\n Run no new query, and put nothing else in the message.\n- `batch_result` came back as a batch-level failure (no query executed, an execution error that\n stopped the run, or an obviously wrong result across the batch).\n\nIf the previous step produced a valid array — even one with `unanswerable` entries — do nothing and\npass it through unchanged.\n\n- For an execution failure (the second case above): diagnose the failure from the error text (unknown column, bad join, type mismatch, wrong grain),\n fix the affected queries, and re-run them through the bound query capability, still under the\n batch's shared preamble and each slot's `max_rows`. Bound your attempts — a few retries at most;\n do not loop indefinitely (retry depth is this step's concern, not the profile's).\n- A slot whose result still cannot be validated becomes\n `{\"slot_id\": \"<id>\", \"status\": \"unanswerable\", \"reason\": \"<why it could not be verified>\"}`.\n Do not fabricate a number.\n- **If nothing in the batch can be validated, REFUSE.** Emit\n `{\"verified\": false, \"refused\": true, \"reason\": \"<why the batch could not be answered>\"}` and stop.\n- Produce `repaired_batch`. On success return exactly the same canonical array shape as\n `generate_sql`: one entry per slot, in the order received, each either the verified tabular shape\n ```\n {\"slot_id\": \"<the slot's id>\",\n \"columns\": [\"col1\", ...], \"rows\": [[v1, ...], ...],\n \"summary\": \"<a concise prose answer grounded only in the returned rows>\",\n \"verified\": true,\n \"definition\": {\"query_id\": \"<the query_id the tool result returned>\"}}\n ```\n or the `unanswerable` entry above, with no extra keys. Object-shaped rows are also valid;\n preserve their values exactly and emit numbers as numbers. Set `verified: true` only when the\n repaired query ran and its result set passed validation. The `definition` is run-level provenance\n only (the query behind that answer) — do not invent unit/owner/formal-metric lineage.\n When the tool result carries a `query_id`, never copy SQL, filters or source tables into\n `definition`: cite `{\"query_id\": …}` only. When it carries none, cite\n `{\"sql\": \"<the exact SQL you ran>\", \"source_tables\": [\"...\"]}` and nothing else. No shape of\n `definition` has a `filters` key.\n Your final message is the JSON array (or the refusal object) and nothing else: no heading, no\n sentence before or after it, no Markdown fence, no reasoning tags. Every string closed and every\n bracket matched; it must parse as JSON.",
247
+ "realization_kind": "skill",
248
+ "required_capabilities": [
249
+ "sql_execution:read_only",
250
+ "llm:per_step_tier",
251
+ "llm:strong",
252
+ "llm:cheap"
253
+ ],
254
+ "trigger": {
255
+ "kind": "one_shot"
256
+ },
257
+ "type": "analytical",
258
+ "verb": "answer_batch"
259
+ }
260
+ ],
261
+ "config": {},
262
+ "context_binding": {
263
+ "binding_mode": "runtime_selected",
264
+ "project": "../../../../examples/v5-jaffle",
265
+ "resolved": {
266
+ "dimensions": [
267
+ {
268
+ "name": "customer_id",
269
+ "temporal": false
270
+ },
271
+ {
272
+ "name": "name",
273
+ "temporal": false
274
+ },
275
+ {
276
+ "name": "order_date",
277
+ "temporal": true
278
+ }
279
+ ],
280
+ "lineage": {
281
+ "consumers": {
282
+ "dashboards": 0,
283
+ "queries": 1
284
+ },
285
+ "edges": 7,
286
+ "nodes": 8,
287
+ "resolvable": true
288
+ },
289
+ "metrics": [
290
+ {
291
+ "additivity": "additive",
292
+ "declared": true,
293
+ "name": "total_revenue"
294
+ },
295
+ {
296
+ "additivity": "additive",
297
+ "declared": true,
298
+ "name": "order_count"
299
+ },
300
+ {
301
+ "declared": false,
302
+ "name": "id"
303
+ },
304
+ {
305
+ "declared": false,
306
+ "name": "id"
307
+ },
308
+ {
309
+ "declared": false,
310
+ "name": "customer_id"
311
+ },
312
+ {
313
+ "declared": false,
314
+ "name": "amount"
315
+ }
316
+ ],
317
+ "models": [
318
+ "customers",
319
+ "orders"
320
+ ],
321
+ "time_dimensions": [
322
+ "order_date"
323
+ ]
324
+ }
325
+ },
326
+ "profile": "genbi-report",
327
+ "warble_ir_version": "0.9"
328
+ }
@@ -0,0 +1,38 @@
1
+ # genbi-report — the two-stage hybrid report profile (cloud planner, local oracle).
2
+ #
3
+ # Mounts the Hub's report pair as one behavior surface bound to one semantic context. `plan_report`
4
+ # is the entry: a planner that lays the report out as typed placeholders, obtains every answer
5
+ # through ONE composition call to `answer_batch`, and narrates over the values the host has
6
+ # materialised. It holds no data authority of its own. `answer_batch` is callee-only: a session
7
+ # cannot start it directly, only the planner's `ask` edge reaches it, and it alone owns query
8
+ # execution. The harness runs the pair on a zone-aware tier binding (see RUNNING.md, "Zone-aware
9
+ # tier binding"): the planner's steps may sit on a public tier, the callee, the judge and the
10
+ # render role must be private, and every callee answer crosses the egress verification step.
11
+ #
12
+ # Like genbi-default, this profile mounts Hub components by id and owns no component library.
13
+
14
+ profile: genbi-report
15
+
16
+ context:
17
+ project: ./context/binding.yml
18
+
19
+ components:
20
+ # The planner's brief is its charter, not a data-access framing: it reaches data only through the
21
+ # `ask` alias, and the composition contract denies it any query surface regardless of the brief.
22
+ # Nothing here names the `wren` CLI, so the public-tier prompt carries no deployment detail.
23
+ - use: plan_report
24
+ brief: |
25
+ You plan reports for business readers who want the figures laid out and explained, not the
26
+ queries behind them. Keep every cell's question self-contained and answerable from the
27
+ capability card in your context, keep the preamble (period, currency, shared filters)
28
+ explicit, ask for the smallest series or table that still answers a cell, and keep the
29
+ report readable when a cell is unavailable.
30
+ # Callee-only mount. Its brief carries this deployment's concrete data-access framing, exactly
31
+ # like the neutralised Hub components mounted in genbi-default.
32
+ - use: answer_batch
33
+ entrypoint: false
34
+ brief: |
35
+ Data access in this deployment goes through the `wren` CLI. If you need to introspect the
36
+ schema, run `wren context show`. Query the semantic layer with `wren -q -o json -s '<SQL>'`,
37
+ which returns JSON; object-shaped rows are also valid — preserve their values exactly. Never
38
+ hand-write SQL against raw tables outside the model.
@@ -16,6 +16,7 @@
16
16
  },
17
17
  "render_blocks": []
18
18
  },
19
+ "entrypoint": true,
19
20
  "eval": {
20
21
  "metrics": [
21
22
  "connect_success"
@@ -75,6 +76,7 @@
75
76
  },
76
77
  "render_blocks": []
77
78
  },
79
+ "entrypoint": true,
78
80
  "eval": {
79
81
  "metrics": [
80
82
  "build_success"
@@ -136,5 +138,5 @@
136
138
  }
137
139
  },
138
140
  "profile": "genbi-setup",
139
- "warble_ir_version": "0.6"
141
+ "warble_ir_version": "0.9"
140
142
  }
@@ -1 +0,0 @@
1
- .xterm{cursor:text;-webkit-user-select:none;user-select:none;position:relative}.xterm.focus,.xterm:focus{outline:none}.xterm .xterm-helpers{z-index:5;position:absolute;top:0}.xterm .xterm-helper-textarea{opacity:0;z-index:-5;white-space:nowrap;resize:none;border:0;width:0;height:0;margin:0;padding:0;position:absolute;top:0;left:-9999em;overflow:hidden}.xterm .composition-view{color:#fff;white-space:nowrap;z-index:1;background:#000;display:none;position:absolute}.xterm .composition-view.active{display:block}.xterm .xterm-viewport{cursor:default;background-color:#000;position:absolute;inset:0;overflow-y:scroll}.xterm .xterm-screen{position:relative}.xterm .xterm-screen canvas{position:absolute;top:0;left:0}.xterm-char-measure-element{visibility:hidden;line-height:normal;display:inline-block;position:absolute;top:0;left:-9999em}.xterm.enable-mouse-events{cursor:default}.xterm.xterm-cursor-pointer,.xterm .xterm-cursor-pointer{cursor:pointer}.xterm.column-select.focus{cursor:crosshair}.xterm .xterm-accessibility:not(.debug),.xterm .xterm-message{z-index:10;color:#0000;pointer-events:none;position:absolute;inset:0}.xterm .xterm-accessibility-tree:not(.debug) ::selection{color:#0000}.xterm .xterm-accessibility-tree{-webkit-user-select:text;user-select:text;white-space:pre;font-family:monospace}.xterm .xterm-accessibility-tree>div{transform-origin:0;width:fit-content}.xterm .live-region{width:1px;height:1px;position:absolute;left:-9999px;overflow:hidden}.xterm-dim{opacity:1!important}.xterm-underline-1{text-decoration:underline}.xterm-underline-2{-webkit-text-decoration:underline double;text-decoration:underline double}.xterm-underline-3{-webkit-text-decoration:underline wavy;text-decoration:underline wavy}.xterm-underline-4{-webkit-text-decoration:underline dotted;text-decoration:underline dotted}.xterm-underline-5{-webkit-text-decoration:underline dashed;text-decoration:underline dashed}.xterm-overline{text-decoration:overline}.xterm-overline.xterm-underline-1{text-decoration:underline overline}.xterm-overline.xterm-underline-2{-webkit-text-decoration:overline double underline;text-decoration:overline double underline}.xterm-overline.xterm-underline-3{-webkit-text-decoration:overline wavy underline;text-decoration:overline wavy underline}.xterm-overline.xterm-underline-4{-webkit-text-decoration:overline dotted underline;text-decoration:overline dotted underline}.xterm-overline.xterm-underline-5{-webkit-text-decoration:overline dashed underline;text-decoration:overline dashed underline}.xterm-strikethrough{text-decoration:line-through}.xterm-screen .xterm-decoration-container .xterm-decoration{z-index:6;position:absolute}.xterm-screen .xterm-decoration-container .xterm-decoration.xterm-decoration-top-layer{z-index:7}.xterm-decoration-overview-ruler{z-index:8;pointer-events:none;position:absolute;top:0;right:0}.xterm-decoration-top{z-index:2;position:relative}.xterm .xterm-scrollable-element>.scrollbar{cursor:default}.xterm .xterm-scrollable-element>.scrollbar>.scra{cursor:pointer;font-size:11px!important}.xterm .xterm-scrollable-element>.visible{opacity:1;z-index:11;background:0 0;transition:opacity .1s linear}.xterm .xterm-scrollable-element>.invisible{opacity:0;pointer-events:none}.xterm .xterm-scrollable-element>.invisible.fade{transition:opacity .8s linear}.xterm .xterm-scrollable-element>.shadow{display:none;position:absolute}.xterm .xterm-scrollable-element>.shadow.top{width:100%;height:3px;box-shadow:var(--vscode-scrollbar-shadow,#000) 0 6px 6px -6px inset;display:block;top:0;left:3px}.xterm .xterm-scrollable-element>.shadow.left{width:3px;height:100%;box-shadow:var(--vscode-scrollbar-shadow,#000) 6px 0 6px -6px inset;display:block;top:3px;left:0}.xterm .xterm-scrollable-element>.shadow.top-left-corner{width:3px;height:3px;display:block;top:0;left:0}.xterm .xterm-scrollable-element>.shadow.top.left{box-shadow:var(--vscode-scrollbar-shadow,#000) 6px 0 6px -6px inset}.sessions-workbench{background:var(--ant-color-bg-container);flex-direction:column;height:100%;min-height:0;display:flex}.sessions-toolbar{border-bottom:1px solid var(--ant-color-border-secondary);background:var(--ant-color-bg-container);align-items:center;gap:10px;min-height:42px;padding:6px 12px;display:flex}.sessions-toolbar-title{flex:1;align-items:baseline;gap:8px;min-width:0;display:flex}.sessions-toolbar-title strong{white-space:nowrap;text-overflow:ellipsis;overflow:hidden}.sessions-toolbar-title span{color:var(--ant-color-text-tertiary);white-space:nowrap;text-overflow:ellipsis;font-size:12px;overflow:hidden}.sessions-toolbar-actions{gap:4px;display:flex}.sessions-toolbar-error{color:var(--ant-color-error);font-size:12px}.sessions-terminal-wrap{box-sizing:border-box;flex:1;min-height:0;padding:12px;position:relative;overflow:hidden}.sessions-terminal{outline:none;height:100%}.sessions-terminal .xterm{height:100%}.sessions-terminal .xterm-viewport{scrollbar-color:var(--ant-color-border) transparent;scrollbar-width:thin}.sessions-terminal .xterm:focus-within{outline:2px solid var(--ant-color-primary);outline-offset:-2px}.sessions-terminal-message{z-index:1;border:1px solid var(--ant-color-border-secondary);background:var(--ant-color-bg-elevated);max-width:340px;color:var(--ant-color-text-secondary);border-radius:4px;padding:6px 8px;font-size:12px;position:absolute;top:12px;right:12px}.sessions-terminal-state,.sessions-empty{flex:1;place-items:center;min-height:0;padding:24px;display:grid}.sessions-empty{text-align:center;color:var(--ant-color-text-secondary);align-content:center}.sessions-empty h1{color:var(--ant-color-text);margin:0}.sessions-sidebar{flex-direction:column;min-width:0;height:100%;display:flex;overflow:hidden auto}.sessions-sidebar-new{min-width:0;padding:12px 8px 4px}.sessions-sidebar-group{min-width:0;padding:12px 8px 0}.sessions-sidebar-heading{letter-spacing:.06em;text-transform:uppercase;padding:0 8px 6px;font-size:11px;display:block}.sessions-sidebar-item{box-sizing:border-box;width:100%;min-width:0;color:inherit;text-align:left;cursor:pointer;background:0 0;border:0;border-radius:6px;padding:7px 8px}.sessions-sidebar-item:hover,.sessions-sidebar-item.is-selected{background:var(--ant-color-fill-secondary)}.sessions-sidebar-item:focus-visible{outline:2px solid var(--ant-color-primary);outline-offset:-2px}.sessions-sidebar-title{overflow-wrap:anywhere;-webkit-line-clamp:2;-webkit-box-orient:vertical;min-width:0;font-size:13px;line-height:1.35;display:-webkit-box;overflow:hidden}.sessions-sidebar-title-row{align-items:start;gap:6px;display:flex}.sessions-sidebar-purpose{overflow-wrap:anywhere;-webkit-line-clamp:2;-webkit-box-orient:vertical;flex:auto;min-width:0;display:-webkit-box;overflow:hidden}.sessions-sidebar-id{color:var(--ant-color-text-tertiary);font-family:var(--ant-font-family-code);white-space:nowrap;flex:none;font-size:11px;line-height:1.6}.sessions-sidebar-meta{min-width:0;color:var(--ant-color-text-tertiary);flex-wrap:wrap;gap:0 5px;font-size:11px;line-height:1.4;display:flex}.sessions-sidebar-meta>span+span:before{content:"·";margin-right:5px}.sessions-sidebar-state{color:var(--ant-color-text-tertiary);padding:12px 16px;font-size:11px;line-height:1.45}.sessions-new-popover{max-width:calc(100vw - 16px)}.sessions-new-popover .ant-popover-content{box-sizing:border-box;width:min(220px,100vw - 16px);max-width:100%}.sessions-new-menu{flex-direction:column;gap:10px;width:100%;min-width:0;display:flex;overflow-x:hidden}.sessions-new-menu section,.sessions-new-menu .ant-space{min-width:0}.sessions-new-menu section{flex-direction:column;gap:6px;display:flex}.sessions-new-menu .ant-typography{overflow-wrap:anywhere}.sessions-new-menu .ant-btn{white-space:normal;text-align:left;justify-content:flex-start;width:100%;height:auto;min-height:32px;line-height:1.35}.sessions-new-menu .ant-btn>span{overflow-wrap:anywhere;min-width:0}.sessions-workbench.is-compact .sessions-toolbar{gap:6px;padding-inline:8px}.sessions-workbench.is-compact .sessions-toolbar-title span{display:none}.sessions-workbench.is-compact .sessions-terminal-wrap{padding:8px}.sessions-workbench.is-compact .sessions-toolbar-actions .ant-btn{padding-inline:6px}.genbi-page{box-sizing:border-box;width:100%;margin:0 auto;padding:24px 26px 40px}.genbi-page-head{align-items:flex-start;gap:12px;margin-bottom:16px;display:flex}.genbi-page-head-text{flex:1;min-width:0}.genbi-page-title.ant-typography{margin:0!important}.genbi-page-lead{max-width:64ch;color:var(--ant-color-text-tertiary);margin:4px 0 0;font-size:13.5px;line-height:1.5}.genbi-page-actions{flex:none;align-items:center;gap:8px;margin-left:auto;display:flex}.genbi-page-body{flex-direction:column;gap:16px;min-width:0;display:flex}.genbi-artifacts-list{min-width:0;margin:0;padding:8px 0;list-style:none}.genbi-arow{width:100%;min-width:0;font:inherit;color:var(--ant-color-text-secondary);text-align:left;cursor:pointer;border-radius:var(--ant-border-radius-sm);background:0 0;border:0;grid-template-columns:auto minmax(0,1fr);align-items:start;gap:8px;padding:7px 12px;font-size:13px;line-height:1.4;display:grid}.genbi-arow:hover{background:var(--genbi-surface-2)}.genbi-arow:focus-visible{outline:2px solid var(--ant-color-primary);outline-offset:-2px}.genbi-arow.is-sel{background:color-mix(in srgb, var(--ant-color-primary) 12%, transparent);color:var(--ant-color-primary);font-weight:500}.genbi-arow.is-sel .genbi-aicon{color:var(--ant-color-primary)}.genbi-aicon{color:var(--ant-color-text-tertiary);grid-row:1/span 2;align-items:center;margin-top:3px;font-size:14px;display:inline-flex}.genbi-atext{flex-direction:column;gap:2px;min-width:0;display:flex}.genbi-aname{overflow-wrap:anywhere;-webkit-line-clamp:2;-webkit-box-orient:vertical;min-width:0;line-height:1.35;display:-webkit-box;overflow:hidden}.genbi-abadges{flex-wrap:wrap;grid-column:2;align-items:center;gap:4px;min-width:0;display:flex}.genbi-canvas-er{border:1px solid var(--ant-color-border-secondary);border-radius:var(--ant-border-radius);box-shadow:var(--ant-box-shadow-tertiary);background:radial-gradient(circle at 1px 1px, var(--ant-color-border-secondary) 1px, transparent 0) 0 0 / 22px 22px, var(--ant-color-bg-container);position:relative;overflow:auto}.genbi-er-inner{position:relative}.genbi-er-edges{position:absolute;inset:0}.genbi-er-edges path{fill:none;stroke:var(--ant-color-border);stroke-width:1.5px}.genbi-er-edges circle{fill:var(--ant-color-link)}.genbi-er-edges text{fill:var(--ant-color-text-tertiary);font-size:10px}.genbi-er-edges.is-impact path.is-active{stroke:var(--ant-color-primary);stroke-width:2px}.genbi-er-edges.is-impact circle.is-active{fill:var(--ant-color-primary)}.genbi-mnode{background:var(--ant-color-bg-container);border:1px solid var(--ant-color-border);box-shadow:var(--ant-box-shadow-tertiary);font:inherit;text-align:left;color:inherit;border-radius:9px;margin:0;padding:0;position:absolute;overflow:hidden}.genbi-mnode.is-interactive{cursor:pointer}.genbi-mnode.is-interactive:hover{border-color:var(--ant-color-primary)}.genbi-mnode.is-interactive:focus-visible{outline:2px solid var(--ant-color-primary);outline-offset:2px}.genbi-mnode .genbi-mh{background:var(--genbi-surface-2);border-bottom:1px solid var(--ant-color-border-secondary);color:var(--ant-color-text);align-items:center;gap:6px;padding:8px 10px;font-size:12px;font-weight:600;display:flex}.genbi-mnode .genbi-cnt{color:var(--ant-color-text-tertiary);margin-left:auto;font-size:11px;font-weight:400}.genbi-mnode ul{margin:0;padding:0;list-style:none}.genbi-mnode li{font-family:var(--ant-font-family-code);border-top:1px solid var(--ant-color-border-secondary);align-items:center;gap:6px;padding:4px 10px;font-size:11px;display:flex}.genbi-mnode li:first-child{border-top:none}.genbi-mnode .genbi-fn{color:var(--ant-color-text-secondary);text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.genbi-mnode .genbi-ft{color:var(--ant-color-text-tertiary);margin-left:auto}.genbi-mnode .genbi-key{border-radius:4px;flex:none;padding:0 5px;font-size:10px;font-weight:700;line-height:16px}.genbi-mnode .genbi-key-pk{background:color-mix(in srgb, var(--ant-color-primary) 16%, transparent);color:var(--ant-color-primary-active)}.genbi-mnode .genbi-key-fk{background:color-mix(in srgb, var(--ant-color-link) 16%, transparent);color:var(--ant-color-link)}.genbi-mnode.is-selected{outline:2px solid var(--ant-color-primary);outline-offset:1px;box-shadow:0 0 0 4px color-mix(in srgb, var(--ant-color-primary) 16%, transparent)}.genbi-mnode.is-affected{outline:2px solid color-mix(in srgb, var(--ant-color-warning) 72%, transparent);outline-offset:1px}.genbi-mdl-wrap{flex-direction:column;gap:16px;display:flex}.genbi-project-identity{align-items:baseline;gap:10px;font-size:13px;display:flex}.genbi-project-name{color:var(--ant-color-text);font-size:15px;font-weight:600}.genbi-project-path{color:var(--ant-color-text-secondary)}.genbi-project-path code{font-family:var(--ant-font-family-code);font-size:12px}.genbi-project-path-unbound{color:var(--ant-color-text-tertiary);font-style:italic}.genbi-mdl-toolbar{border:1px solid var(--ant-color-border-secondary);border-radius:var(--ant-border-radius);background:var(--ant-color-bg-container);color:var(--ant-color-text-secondary);align-items:center;gap:10px;padding:8px 12px;font-size:13px;display:flex}.genbi-mdl-stat b{color:var(--ant-color-text)}.genbi-mdl-stat-sep{color:var(--ant-color-border)}.genbi-mdl-layout{grid-template-columns:minmax(0,1fr) 296px;align-items:start;gap:16px;display:grid}@media (width<=900px){.genbi-mdl-layout{grid-template-columns:1fr}}.genbi-mdl-side{flex-direction:column;gap:14px;min-width:0;display:flex}.genbi-measure{border-top:1px solid var(--ant-color-border-secondary);align-items:flex-start;gap:8px;padding:8px 0;display:flex}.genbi-measure:first-child{border-top:none;padding-top:0}.genbi-mtxt{flex:1;min-width:0}.genbi-mname{color:var(--ant-color-text);font-size:13px;font-weight:600}.genbi-mexpr{font-family:var(--ant-font-family-code);color:var(--ant-color-text-tertiary);text-overflow:ellipsis;white-space:nowrap;margin-top:2px;font-size:11px;overflow:hidden}.genbi-measure-actions{flex-direction:column;flex:none;align-items:flex-end;gap:2px;display:flex}.genbi-relrow{border-top:1px solid var(--ant-color-border-secondary);justify-content:space-between;align-items:center;gap:8px;padding:6px 0;font-size:12px;display:flex}.genbi-relrow:first-child{border-top:none;padding-top:0}.genbi-relname{text-overflow:ellipsis;white-space:nowrap;min-width:0;overflow:hidden}.genbi-kstat{gap:20px;display:flex}.genbi-kstat-item .genbi-num{color:var(--ant-color-text);font-size:22px;font-weight:700;line-height:1.2}.genbi-kstat-item .genbi-lbl{color:var(--ant-color-text-tertiary);font-size:11px}.genbi-num-muted{color:var(--ant-color-text-tertiary);font-size:13px;font-style:italic;font-weight:400}.genbi-outscope{flex-direction:column;gap:8px;display:flex}.genbi-filetree{min-width:0;padding:10px 0}.genbi-rail-proj{border-bottom:1px solid var(--ant-color-border-secondary);min-width:0;color:var(--ant-color-text);align-items:center;gap:8px;margin-bottom:4px;padding:2px 12px 10px;font-size:13px;font-weight:600;display:flex}.genbi-rail-proj .genbi-tname{text-overflow:ellipsis;white-space:nowrap;flex:1;min-width:0;overflow:hidden}.genbi-ttree,.genbi-tchildren{min-width:0;margin:0;padding:0;list-style:none}.genbi-trow{width:100%;min-width:0;font:inherit;color:var(--ant-color-text-secondary);text-align:left;cursor:pointer;border-radius:var(--ant-border-radius-sm);background:0 0;border:0;align-items:center;gap:7px;padding:5px 12px;font-size:13px;line-height:1.4;display:flex}.genbi-trow:hover{background:var(--genbi-surface-2)}.genbi-trow:focus-visible{outline:2px solid var(--ant-color-primary);outline-offset:-2px}.genbi-tdirhdr{color:var(--ant-color-text);font-weight:500}.genbi-tfile.is-sel{background:color-mix(in srgb, var(--ant-color-primary) 12%, transparent);color:var(--ant-color-primary);font-weight:500}.genbi-tfile.is-sel .genbi-fi{color:var(--ant-color-primary)}.genbi-chev{color:var(--ant-color-text-tertiary);flex:none;align-items:center;font-size:10px;transition:transform .15s;display:inline-flex}.genbi-chev.is-open{transform:rotate(90deg)}.genbi-fi{color:var(--ant-color-text-tertiary);flex:none;align-items:center;font-size:14px;display:inline-flex}.genbi-tname{text-overflow:ellipsis;white-space:nowrap;flex:1;min-width:0;overflow:hidden}.harness-execution-path{grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:12px;margin-bottom:8px;display:grid}.harness-execution-path>div{flex-direction:column;gap:2px;min-width:0;display:flex}.harness-execution-path strong{overflow-wrap:anywhere}.harness-diagnostic-grid{grid-template-columns:repeat(2,minmax(0,1fr));gap:24px;display:grid}@media (width<=640px){.harness-execution-path,.harness-diagnostic-grid{grid-template-columns:1fr;gap:8px}}html,body,#root{height:100%;margin:0}body{font-family:var(--ant-font-family);overflow:hidden}.ant-tag{border-radius:999px}.ant-table-thead>tr>th{text-transform:uppercase;letter-spacing:.04em;font-size:11px;font-weight:600}