@wrenai/genbi 0.0.4 → 0.0.5

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 (230) hide show
  1. package/README.md +64 -2
  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 +69 -30
  93. package/dist-server/harness/route/codex-host-query.d.ts +52 -0
  94. package/dist-server/harness/route/codex-host-query.js +191 -0
  95. package/dist-server/harness/route/codex-local-manifest.d.ts +9 -1
  96. package/dist-server/harness/route/codex-local-manifest.js +96 -15
  97. package/dist-server/harness/route/codex-wren-proxy.d.ts +33 -0
  98. package/dist-server/harness/route/codex-wren-proxy.js +227 -0
  99. package/dist-server/harness/route/describe.js +45 -6
  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 +5 -2
  103. package/dist-server/harness/route/types.d.ts +30 -1
  104. package/dist-server/harness/session/run.js +30 -10
  105. package/dist-server/harness/setup/runner.d.ts +2 -0
  106. package/dist-server/harness/setup/runner.js +38 -17
  107. package/dist-server/harness/setup/yaml-scalar.d.ts +23 -0
  108. package/dist-server/harness/setup/yaml-scalar.js +62 -0
  109. package/dist-server/server/adopt.js +1 -17
  110. package/dist-server/server/app.d.ts +1 -0
  111. package/dist-server/server/app.js +163 -14
  112. package/dist-server/server/auth-choice.d.ts +13 -1
  113. package/dist-server/server/auth-choice.js +66 -1
  114. package/dist-server/server/bin.js +177 -25
  115. package/dist-server/server/codex-local-grounding.d.ts +18 -0
  116. package/dist-server/server/codex-local-grounding.js +92 -0
  117. package/dist-server/server/codex-native-session.d.ts +25 -0
  118. package/dist-server/server/codex-native-session.js +1 -0
  119. package/dist-server/server/codex-runtime-composition.d.ts +34 -0
  120. package/dist-server/server/codex-runtime-composition.js +250 -0
  121. package/dist-server/server/codex-structured-ask.d.ts +23 -0
  122. package/dist-server/server/codex-structured-ask.js +102 -0
  123. package/dist-server/server/conn-config.js +4 -16
  124. package/dist-server/server/conversation-socket.d.ts +16 -0
  125. package/dist-server/server/conversation-socket.js +65 -0
  126. package/dist-server/server/db.d.ts +46 -0
  127. package/dist-server/server/db.js +88 -9
  128. package/dist-server/server/env-detect.d.ts +6 -0
  129. package/dist-server/server/env-detect.js +7 -0
  130. package/dist-server/server/fold.d.ts +22 -4
  131. package/dist-server/server/fold.js +37 -5
  132. package/dist-server/server/harness.d.ts +2 -2
  133. package/dist-server/server/harness.js +33 -7
  134. package/dist-server/server/interactive-terminal.d.ts +10 -6
  135. package/dist-server/server/interactive-terminal.js +33 -8
  136. package/dist-server/server/managed-wren-runtime.d.ts +116 -0
  137. package/dist-server/server/managed-wren-runtime.js +670 -0
  138. package/dist-server/server/native-artifacts.d.ts +23 -7
  139. package/dist-server/server/native-artifacts.js +43 -12
  140. package/dist-server/server/native-component-context.d.ts +19 -0
  141. package/dist-server/server/native-component-context.js +76 -0
  142. package/dist-server/server/native-component-preparation.d.ts +20 -0
  143. package/dist-server/server/native-component-preparation.js +129 -0
  144. package/dist-server/server/native-component-runtime.d.ts +36 -0
  145. package/dist-server/server/native-component-runtime.js +92 -0
  146. package/dist-server/server/native-components.d.ts +84 -0
  147. package/dist-server/server/native-components.js +160 -0
  148. package/dist-server/server/native-dispatch-process.d.ts +8 -0
  149. package/dist-server/server/native-dispatch-process.js +65 -0
  150. package/dist-server/server/native-runtime-spec.d.ts +89 -0
  151. package/dist-server/server/native-runtime-spec.js +169 -0
  152. package/dist-server/server/native-sessions.d.ts +172 -19
  153. package/dist-server/server/native-sessions.js +839 -244
  154. package/dist-server/server/native-shutdown.d.ts +6 -0
  155. package/dist-server/server/native-shutdown.js +28 -0
  156. package/dist-server/server/native-wren-home.d.ts +18 -0
  157. package/dist-server/server/native-wren-home.js +262 -0
  158. package/dist-server/server/node-pty-host.d.ts +2 -1
  159. package/dist-server/server/node-pty-host.js +3 -2
  160. package/dist-server/server/profile-registry.d.ts +139 -0
  161. package/dist-server/server/profile-registry.js +620 -0
  162. package/dist-server/server/runtime-binding.d.ts +4 -1
  163. package/dist-server/server/runtime-binding.js +50 -2
  164. package/dist-server/server/runtime-host/claude-component-step.d.ts +25 -0
  165. package/dist-server/server/runtime-host/claude-component-step.js +186 -0
  166. package/dist-server/server/runtime-host/codex-app-server.d.ts +75 -0
  167. package/dist-server/server/runtime-host/codex-app-server.js +239 -0
  168. package/dist-server/server/runtime-host/codex-compatibility.d.ts +110 -0
  169. package/dist-server/server/runtime-host/codex-compatibility.js +162 -0
  170. package/dist-server/server/runtime-host/codex-component-step.d.ts +19 -0
  171. package/dist-server/server/runtime-host/codex-component-step.js +308 -0
  172. package/dist-server/server/runtime-host/codex-conversation.d.ts +109 -0
  173. package/dist-server/server/runtime-host/codex-conversation.js +295 -0
  174. package/dist-server/server/runtime-host/codex-direct-tools.d.ts +23 -0
  175. package/dist-server/server/runtime-host/codex-direct-tools.js +59 -0
  176. package/dist-server/server/runtime-host/codex-events.d.ts +647 -0
  177. package/dist-server/server/runtime-host/codex-events.js +66 -0
  178. package/dist-server/server/runtime-host/codex-policy.d.ts +16 -0
  179. package/dist-server/server/runtime-host/codex-policy.js +151 -0
  180. package/dist-server/server/runtime-host/codex-process.d.ts +8 -0
  181. package/dist-server/server/runtime-host/codex-process.js +96 -0
  182. package/dist-server/server/runtime-host/codex-rpc.d.ts +47 -0
  183. package/dist-server/server/runtime-host/codex-rpc.js +182 -0
  184. package/dist-server/server/runtime-host/codex-session.d.ts +74 -0
  185. package/dist-server/server/runtime-host/codex-session.js +492 -0
  186. package/dist-server/server/runtime-host/codex-terminal-protocol.d.ts +35 -0
  187. package/dist-server/server/runtime-host/codex-terminal-protocol.js +243 -0
  188. package/dist-server/server/runtime-host/codex-terminal.d.ts +16 -0
  189. package/dist-server/server/runtime-host/codex-terminal.js +120 -0
  190. package/dist-server/server/runtime-host/index.d.ts +5 -0
  191. package/dist-server/server/runtime-host/index.js +3 -0
  192. package/dist-server/server/runtime-host/local.d.ts +29 -0
  193. package/dist-server/server/runtime-host/local.js +90 -0
  194. package/dist-server/server/runtime-host/policy.d.ts +8 -0
  195. package/dist-server/server/runtime-host/policy.js +78 -0
  196. package/dist-server/server/runtime-host/types.d.ts +52 -0
  197. package/dist-server/server/runtime-host/types.js +62 -0
  198. package/dist-server/server/structured-query-evidence.d.ts +7 -0
  199. package/dist-server/server/structured-query-evidence.js +47 -0
  200. package/dist-server/server/structured-runtime.d.ts +52 -0
  201. package/dist-server/server/structured-runtime.js +130 -0
  202. package/dist-server/server/structured-scope.d.ts +4 -0
  203. package/dist-server/server/structured-scope.js +12 -0
  204. package/dist-server/server/turn.d.ts +6 -1
  205. package/dist-server/server/turn.js +82 -25
  206. package/dist-server/server/wire-types.d.ts +67 -3
  207. package/dist-server/server/wren-profiles.js +1 -1
  208. package/managed-wren/README.md +60 -0
  209. package/managed-wren/dependency-wheels.json +282 -0
  210. package/managed-wren/manifest.json +373 -0
  211. package/managed-wren/release-inputs.json +45 -0
  212. package/managed-wren/runtime-tree.cjs +115 -0
  213. package/managed-wren/source-inputs.json +40 -0
  214. package/package.json +15 -6
  215. package/profiles/genbi-default/context/binding.yml +17 -7
  216. package/profiles/genbi-default/context/context.json +146 -0
  217. package/profiles/genbi-default/ir.golden.json +39 -43
  218. package/profiles/genbi-default/profile.yml +31 -0
  219. package/profiles/genbi-enrich-context/context/binding.yml +14 -3
  220. package/profiles/genbi-enrich-context/context/context.json +146 -0
  221. package/profiles/genbi-enrich-context/ir.golden.json +4 -5
  222. package/profiles/genbi-monitor/context/binding.yml +13 -5
  223. package/profiles/genbi-monitor/context/context.json +146 -0
  224. package/profiles/genbi-monitor/ir.golden.json +13 -7
  225. package/profiles/genbi-report/context/binding.yml +19 -0
  226. package/profiles/genbi-report/context/context.json +223 -0
  227. package/profiles/genbi-report/ir.golden.json +328 -0
  228. package/profiles/genbi-report/profile.yml +38 -0
  229. package/profiles/genbi-setup/ir.golden.json +3 -1
  230. package/dist/assets/index-Xs4q7pNo.css +0 -1
@@ -20,7 +20,6 @@ export declare const NATIVE_SAVE_DASHBOARD_SEMANTIC_CONSTRAINTS: {
20
20
  readonly positionalRows: "Positional scalar rows in [x, ...series] order. Each row must contain exactly one x cell plus one cell per series entry.";
21
21
  };
22
22
  };
23
- /** The exact public MCP shape advertised to native clients and accepted by the host. */
24
23
  export declare const NATIVE_SAVE_DASHBOARD_INPUT_SCHEMA: {
25
24
  readonly type: "object";
26
25
  readonly additionalProperties: false;
@@ -90,7 +89,7 @@ export declare const NATIVE_SAVE_DASHBOARD_INPUT_SCHEMA: {
90
89
  };
91
90
  readonly answer_selection: {
92
91
  readonly const: "latest";
93
- readonly description: "Resolve the newest verified answer retained by this native session when its opaque answer_ref is no longer available in the conversation.";
92
+ readonly description: "Resolve the newest answer retained by this native session when its opaque answer_ref is no longer available in the conversation.";
94
93
  };
95
94
  readonly envelope: {
96
95
  readonly type: "object";
@@ -98,7 +97,8 @@ export declare const NATIVE_SAVE_DASHBOARD_INPUT_SCHEMA: {
98
97
  readonly required: readonly ["blocks", "verified"];
99
98
  readonly properties: {
100
99
  readonly verified: {
101
- readonly const: true;
100
+ readonly type: "boolean";
101
+ readonly description: "Ignored: the host records verified only for an answer it produced from a grounded component run; anything submitted here is stored as unverified.";
102
102
  };
103
103
  readonly summary: {
104
104
  readonly anyOf: readonly [{
@@ -349,7 +349,8 @@ export declare const NATIVE_PERSIST_ANSWER_INPUT_SCHEMA: {
349
349
  readonly required: readonly ["blocks", "verified"];
350
350
  readonly properties: {
351
351
  readonly verified: {
352
- readonly const: true;
352
+ readonly type: "boolean";
353
+ readonly description: "Ignored: the host records verified only for an answer it produced from a grounded component run; anything submitted here is stored as unverified.";
353
354
  };
354
355
  readonly blocks: {
355
356
  readonly type: "array";
@@ -580,7 +581,7 @@ export declare const NATIVE_SAVE_DASHBOARD_CONTRACT: {
580
581
  };
581
582
  readonly answer_selection: {
582
583
  readonly const: "latest";
583
- readonly description: "Resolve the newest verified answer retained by this native session when its opaque answer_ref is no longer available in the conversation.";
584
+ readonly description: "Resolve the newest answer retained by this native session when its opaque answer_ref is no longer available in the conversation.";
584
585
  };
585
586
  readonly envelope: {
586
587
  readonly type: "object";
@@ -588,7 +589,8 @@ export declare const NATIVE_SAVE_DASHBOARD_CONTRACT: {
588
589
  readonly required: readonly ["blocks", "verified"];
589
590
  readonly properties: {
590
591
  readonly verified: {
591
- readonly const: true;
592
+ readonly type: "boolean";
593
+ readonly description: "Ignored: the host records verified only for an answer it produced from a grounded component run; anything submitted here is stored as unverified.";
592
594
  };
593
595
  readonly summary: {
594
596
  readonly anyOf: readonly [{
@@ -848,7 +850,8 @@ export declare const NATIVE_PERSIST_ANSWER_CONTRACT: {
848
850
  readonly required: readonly ["blocks", "verified"];
849
851
  readonly properties: {
850
852
  readonly verified: {
851
- readonly const: true;
853
+ readonly type: "boolean";
854
+ readonly description: "Ignored: the host records verified only for an answer it produced from a grounded component run; anything submitted here is stored as unverified.";
852
855
  };
853
856
  readonly blocks: {
854
857
  readonly type: "array";
@@ -970,6 +973,7 @@ export interface NativeArtifactServiceOptions {
970
973
  readonly idempotencyKey: string;
971
974
  readonly envelopeJson: string;
972
975
  readonly digest: string;
976
+ readonly grounded: boolean;
973
977
  }) => {
974
978
  readonly row: NativeStructuredAnswerRow;
975
979
  readonly created: boolean;
@@ -1000,5 +1004,17 @@ export declare class NativeArtifactService {
1000
1004
  * a later follow-up can only reference this stored result, never recompute.
1001
1005
  */
1002
1006
  persistAnswer(credential: string | undefined, input: unknown): PersistedStructuredAnswer;
1007
+ /**
1008
+ * Host-only entry for a grounded component root (never exposed over MCP).
1009
+ * The caller must already have checked the component's verified provenance.
1010
+ */
1011
+ persistGroundedAnswer(credential: string | undefined, input: unknown): PersistedStructuredAnswer;
1012
+ private persistStructured;
1003
1013
  save(credential: string | undefined, input: unknown): SavedNativeArtifact;
1014
+ /**
1015
+ * Host-only entry for a grounded component render root (never exposed over
1016
+ * MCP). The caller must already have checked verified provenance.
1017
+ */
1018
+ saveGrounded(credential: string | undefined, input: unknown): SavedNativeArtifact;
1019
+ private saveArtifact;
1004
1020
  }
@@ -53,6 +53,7 @@ const POSITIONAL_ROWS_SCHEMA = {
53
53
  items: { type: "array", items: SCALAR_SCHEMA },
54
54
  };
55
55
  /** The exact public MCP shape advertised to native clients and accepted by the host. */
56
+ const VERIFIED_FIELD_SCHEMA = { type: "boolean", description: "Ignored: the host records verified only for an answer it produced from a grounded component run; anything submitted here is stored as unverified." };
56
57
  export const NATIVE_SAVE_DASHBOARD_INPUT_SCHEMA = {
57
58
  type: "object",
58
59
  additionalProperties: false,
@@ -67,11 +68,11 @@ export const NATIVE_SAVE_DASHBOARD_INPUT_SCHEMA = {
67
68
  name: { ...SAFE_TEXT_SCHEMA, minLength: 1, maxLength: MAX_NAME_LENGTH, pattern: "\\S" },
68
69
  idempotency_key: { ...SAFE_TEXT_SCHEMA, minLength: 8, maxLength: MAX_IDEMPOTENCY_KEY_LENGTH, pattern: "^[A-Za-z0-9][A-Za-z0-9._:-]+$" },
69
70
  answer_ref: { ...SAFE_TEXT_SCHEMA, pattern: "^answer-[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$", description: "Opaque reference returned by persist_answer for this native session." },
70
- answer_selection: { const: "latest", description: "Resolve the newest verified answer retained by this native session when its opaque answer_ref is no longer available in the conversation." },
71
+ answer_selection: { const: "latest", description: "Resolve the newest answer retained by this native session when its opaque answer_ref is no longer available in the conversation." },
71
72
  envelope: {
72
73
  type: "object", additionalProperties: false, required: ["blocks", "verified"],
73
74
  properties: {
74
- verified: { const: true }, summary: NULLABLE_SAFE_TEXT_SCHEMA, estimate: { type: ["boolean", "null"] },
75
+ verified: VERIFIED_FIELD_SCHEMA, summary: NULLABLE_SAFE_TEXT_SCHEMA, estimate: { type: ["boolean", "null"] },
75
76
  blocks: {
76
77
  type: "array", minItems: 1,
77
78
  items: { oneOf: [
@@ -97,7 +98,7 @@ const NATIVE_STRUCTURED_ANSWER_ENVELOPE_SCHEMA = {
97
98
  additionalProperties: false,
98
99
  required: ["blocks", "verified"],
99
100
  properties: {
100
- verified: { const: true },
101
+ verified: VERIFIED_FIELD_SCHEMA,
101
102
  blocks: {
102
103
  type: "array",
103
104
  minItems: 1,
@@ -211,8 +212,8 @@ function validBlock(block) {
211
212
  function validateDashboardEnvelope(envelope) {
212
213
  if (!isRecord(envelope) || !Object.hasOwn(envelope, "blocks") || !Object.hasOwn(envelope, "verified") || !Object.keys(envelope).every((key) => ["blocks", "summary", "verified", "estimate"].includes(key)))
213
214
  throw new NativeArtifactError("invalid dashboard render envelope: expected blocks and verified with optional summary or estimate");
214
- if (envelope.verified !== true)
215
- throw new NativeArtifactError("invalid dashboard render envelope: verified must be true");
215
+ if (typeof envelope.verified !== "boolean")
216
+ throw new NativeArtifactError("invalid dashboard render envelope: verified must be a boolean");
216
217
  if (!Array.isArray(envelope.blocks) || envelope.blocks.length === 0)
217
218
  throw new NativeArtifactError("invalid dashboard render envelope: blocks must be a non-empty array");
218
219
  const invalidBlock = envelope.blocks.findIndex((block) => !validBlock(block));
@@ -275,6 +276,14 @@ function validatePersistedStructuredAnswer(value) {
275
276
  }
276
277
  return { version: "1", idempotency_key: value.idempotency_key, envelope: value.envelope };
277
278
  }
279
+ /**
280
+ * The stored `verified` flag is host provenance, never the caller's claim: the
281
+ * persisted envelope carries exactly the flag the host recorded, so its own
282
+ * badge cannot disagree with the artifact row.
283
+ */
284
+ function withHostVerified(envelope, verified) {
285
+ return { ...envelope, verified };
286
+ }
278
287
  function structuredAnswerDigest(envelopeJson) {
279
288
  return `sha256:${createHash("sha256").update(envelopeJson).digest("hex")}`;
280
289
  }
@@ -377,11 +386,21 @@ export class NativeArtifactService {
377
386
  * a later follow-up can only reference this stored result, never recompute.
378
387
  */
379
388
  persistAnswer(credential, input) {
389
+ return this.persistStructured(credential, input, false);
390
+ }
391
+ /**
392
+ * Host-only entry for a grounded component root (never exposed over MCP).
393
+ * The caller must already have checked the component's verified provenance.
394
+ */
395
+ persistGroundedAnswer(credential, input) {
396
+ return this.persistStructured(credential, input, true);
397
+ }
398
+ persistStructured(credential, input, grounded) {
380
399
  const session = this.authorize(credential);
381
400
  if (session.purpose !== "analysis")
382
401
  throw new NativeArtifactError("structured answer persistence is unavailable for this native session", 409);
383
402
  const payload = validatePersistedStructuredAnswer(input);
384
- const envelopeJson = JSON.stringify(payload.envelope);
403
+ const envelopeJson = JSON.stringify(withHostVerified(payload.envelope, grounded));
385
404
  const digest = structuredAnswerDigest(envelopeJson);
386
405
  const existing = this.options.store.getNativeStructuredAnswerByIdempotency(session.id, payload.idempotency_key);
387
406
  if (existing) {
@@ -392,7 +411,7 @@ export class NativeArtifactService {
392
411
  }
393
412
  try {
394
413
  const persisted = (this.options.persistStructuredAnswer ?? ((params) => this.options.store.createNativeStructuredAnswer(params)))({
395
- id: newId("answer"), nativeSessionId: session.id, idempotencyKey: payload.idempotency_key, envelopeJson, digest,
414
+ id: newId("answer"), nativeSessionId: session.id, idempotencyKey: payload.idempotency_key, envelopeJson, digest, grounded,
396
415
  });
397
416
  if (persisted.row.digest !== digest || persisted.row.envelopeJson !== envelopeJson) {
398
417
  throw new NativeArtifactError("structured answer idempotency key is already bound to a different answer", 409);
@@ -409,6 +428,16 @@ export class NativeArtifactService {
409
428
  }
410
429
  }
411
430
  save(credential, input) {
431
+ return this.saveArtifact(credential, input, false);
432
+ }
433
+ /**
434
+ * Host-only entry for a grounded component render root (never exposed over
435
+ * MCP). The caller must already have checked verified provenance.
436
+ */
437
+ saveGrounded(credential, input) {
438
+ return this.saveArtifact(credential, input, true);
439
+ }
440
+ saveArtifact(credential, input, grounded) {
412
441
  const context = credential === undefined ? undefined : this.credentials.get(credential);
413
442
  if (!context)
414
443
  throw new NativeArtifactError("GenBI MCP bearer credential is invalid. Restart this native session to refresh its GenBI MCP connection.", 401);
@@ -419,7 +448,6 @@ export class NativeArtifactService {
419
448
  throw new NativeArtifactError("GenBI MCP session binding is stale. Start a new native session.", 409);
420
449
  }
421
450
  const payload = validateInput(input);
422
- const referenceSave = "answer_ref" in payload || "answer_selection" in payload;
423
451
  const source = "answer_ref" in payload
424
452
  ? this.options.store.getNativeStructuredAnswer(payload.answer_ref)
425
453
  : "answer_selection" in payload
@@ -431,13 +459,16 @@ export class NativeArtifactService {
431
459
  throw new NativeArtifactError("no persisted answer is available for this native session", 404);
432
460
  if (source)
433
461
  validateStoredStructuredAnswer(source, session.id);
434
- const contents = referenceSave ? source.envelopeJson : JSON.stringify(payload.envelope);
435
- const digest = referenceSave ? source.digest : structuredAnswerDigest(contents);
462
+ // A reference save inherits the retained answer's provenance; an envelope
463
+ // save is verified only through the host-only grounded entry.
464
+ const verified = source ? source.grounded : grounded;
465
+ const contents = JSON.stringify(withHostVerified(source ? JSON.parse(source.envelopeJson) : "envelope" in payload ? payload.envelope : undefined, verified));
466
+ const digest = structuredAnswerDigest(contents);
436
467
  const sameSaveRequest = (artifact) => artifact !== undefined &&
437
468
  artifact.name === payload.name.trim() &&
438
469
  artifact.projectIdentity === binding.identity && artifact.bindingGeneration === binding.generation && artifact.projectRevision === binding.revision &&
439
470
  artifact.nativeVendor === session.vendor && artifact.nativeAgent === session.agent &&
440
- artifact.contentDigest === digest && artifact.sourceAnswerId === (source?.id ?? null);
471
+ artifact.contentDigest === digest && artifact.sourceAnswerId === (source?.id ?? null) && artifact.verified === verified;
441
472
  const existing = this.options.store.getNativeArtifactByIdempotency(session.id, payload.idempotency_key);
442
473
  if (existing) {
443
474
  if (!sameSaveRequest(existing))
@@ -450,7 +481,7 @@ export class NativeArtifactService {
450
481
  try {
451
482
  const safeTarget = assertSafeContainedTarget(this.options.artifactsRoot, target);
452
483
  (this.options.writeAtomic ?? writeContainedAtomically)(safeTarget, contents);
453
- const inserted = this.options.store.createNativeArtifact({ id, sessionId: session.id, nativeSessionId: session.id, name: payload.name.trim(), location, projectIdentity: binding.identity, bindingGeneration: binding.generation, projectRevision: binding.revision, vendor: session.vendor, agent: session.agent, digest, idempotencyKey: payload.idempotency_key, ...(source ? { sourceAnswerId: source.id } : {}) });
484
+ const inserted = this.options.store.createNativeArtifact({ id, sessionId: session.id, nativeSessionId: session.id, name: payload.name.trim(), location, projectIdentity: binding.identity, bindingGeneration: binding.generation, projectRevision: binding.revision, vendor: session.vendor, agent: session.agent, digest, idempotencyKey: payload.idempotency_key, verified, ...(source ? { sourceAnswerId: source.id } : {}) });
454
485
  if (!inserted.created && !sameSaveRequest(inserted.row)) {
455
486
  rmSync(target, { force: true });
456
487
  throw new NativeArtifactError("artifact idempotency key is already bound to a different save request", 409);
@@ -0,0 +1,19 @@
1
+ import { type CodexSessionTools } from "./runtime-host/codex-direct-tools.js";
2
+ import { type NativeComponentRuntimeOptions } from "./native-component-runtime.js";
3
+ import type { NativeComponentPreparation } from "./native-components.js";
4
+ export interface NativeComponentContextOptions extends Omit<NativeComponentRuntimeOptions, "contexts" | "wren"> {
5
+ /** All executables and the provider are resolved by the certified server provisioner. */
6
+ readonly contextLoaderBinary: string;
7
+ /** Explicit host-only credential environment supplied by the provisioner. */
8
+ readonly wrenEnvironment?: Readonly<NodeJS.ProcessEnv>;
9
+ readonly wrenBinary: string;
10
+ readonly project: string;
11
+ readonly signal: AbortSignal;
12
+ }
13
+ /** Capture file-only context before host admission; never opens a vendor or Wren process. */
14
+ export declare function prepareCapturedNativeComponentHost(irDocument: string, scope: Readonly<Record<string, unknown>>, options: NativeComponentContextOptions): Promise<NativeComponentPreparation | undefined>;
15
+ /** Direct-session variant shares the same captured project/credential provenance. */
16
+ export declare function prepareCapturedCodexDirectTools(irDocument: string, scope: Readonly<Record<string, unknown>>, options: NativeComponentContextOptions & {
17
+ readonly producerBinary: string;
18
+ readonly expiresAt: number;
19
+ }): Promise<CodexSessionTools>;
@@ -0,0 +1,76 @@
1
+ import { mkdtemp, readFile, rm } from "node:fs/promises";
2
+ import os from "node:os";
3
+ import path from "node:path";
4
+ import { isDeepStrictEqual } from "node:util";
5
+ import { z } from "zod";
6
+ import { generatePreparedContext } from "../harness/compile/context-loader.js";
7
+ import { hashDirectory } from "../harness/compile/fingerprint.js";
8
+ import { captureWrenAccessIdentity } from "../harness/components/wren-access.js";
9
+ import { prepareCodexDirectTools } from "./runtime-host/codex-direct-tools.js";
10
+ import { prepareNativeComponentHost } from "./native-component-preparation.js";
11
+ import { createNativeComponentRuntimeBindings } from "./native-component-runtime.js";
12
+ /** Capture file-only context before host admission; never opens a vendor or Wren process. */
13
+ export async function prepareCapturedNativeComponentHost(irDocument, scope, options) {
14
+ const capturedScope = structuredClone(scope);
15
+ const bindings = await captureBindings(irDocument, options);
16
+ return prepareNativeComponentHost(irDocument, capturedScope, bindings);
17
+ }
18
+ /** Direct-session variant shares the same captured project/credential provenance. */
19
+ export async function prepareCapturedCodexDirectTools(irDocument, scope, options) {
20
+ if (options.vendor.vendor !== "codex" || options.identity.vendor !== "codex")
21
+ throw new Error("Direct session requires Codex");
22
+ const capturedScope = structuredClone(scope);
23
+ const accountEmail = options.vendor.accountEmail;
24
+ const bindings = await captureBindings(irDocument, options);
25
+ return prepareCodexDirectTools({ irDocument, scope: capturedScope, bindings, producerBinary: options.producerBinary,
26
+ accountEmail, expiresAt: options.expiresAt, signal: options.signal });
27
+ }
28
+ async function captureBindings(irDocument, options) {
29
+ const identity = structuredClone(options.identity);
30
+ const project = path.resolve(options.project);
31
+ const signal = options.signal;
32
+ const currentIdentity = options.currentIdentity.bind(options);
33
+ const assertBinding = options.assertCurrent.bind(options);
34
+ const assertVendor = options.vendor.assertCurrent.bind(options.vendor);
35
+ const check = () => {
36
+ signal.throwIfAborted();
37
+ assertBinding();
38
+ assertVendor();
39
+ if (!isDeepStrictEqual(currentIdentity(), identity))
40
+ throw new Error("Native component context binding expired");
41
+ };
42
+ check();
43
+ const ir = z.object({ warble_ir_version: z.literal("0.9"), components: z.array(z.object({
44
+ id: z.string(), context_binding: z.record(z.string(), z.unknown()).optional(),
45
+ }).passthrough()) }).passthrough().parse(JSON.parse(irDocument));
46
+ if (new Set(ir.components.map((component) => component.id)).size !== ir.components.length)
47
+ throw new Error("Duplicate component identity");
48
+ const fingerprint = await hashDirectory(project);
49
+ const accessIdentity = await captureWrenAccessIdentity(project, options.wrenEnvironment);
50
+ check();
51
+ const scratch = await mkdtemp(path.join(os.tmpdir(), "genbi-native-context-"));
52
+ try {
53
+ const snapshotPath = path.join(scratch, "context.json");
54
+ await generatePreparedContext(options.contextLoaderBinary, project, snapshotPath, signal);
55
+ const snapshot = z.object({ context_version: z.literal(2), parseable: z.literal(true) }).passthrough()
56
+ .parse(JSON.parse(await readFile(snapshotPath, "utf8")));
57
+ await accessIdentity.assertCurrent();
58
+ if (fingerprint !== await hashDirectory(project))
59
+ throw new Error("Native component project changed during context capture");
60
+ check();
61
+ const contexts = Object.fromEntries(ir.components.flatMap((component) => {
62
+ const binding = component.context_binding;
63
+ // Other, unreachable components cannot contribute a project or context grant.
64
+ // Preparation below requires a captured context for every reachable component.
65
+ if (!binding || typeof binding.project !== "string" || path.resolve(binding.project) !== project)
66
+ return [];
67
+ return [[component.id, { binding, snapshot }]];
68
+ }));
69
+ const bindings = createNativeComponentRuntimeBindings({ ...options, identity, contexts, currentIdentity, assertCurrent: check,
70
+ wren: { executable: options.wrenBinary, project, fingerprint, identity: accessIdentity } });
71
+ return bindings;
72
+ }
73
+ finally {
74
+ await rm(scratch, { recursive: true, force: true });
75
+ }
76
+ }
@@ -0,0 +1,20 @@
1
+ import { type ComponentBrokerOptions } from "../harness/components/broker.js";
2
+ import type { NativeComponentIdentity, NativeComponentPreparation } from "./native-components.js";
3
+ /** The certified provisioner supplies bindings; this module derives scope/closure authority. */
4
+ export interface NativeComponentBindings {
5
+ readonly identity: NativeComponentIdentity;
6
+ readonly contexts: ComponentBrokerOptions["contexts"];
7
+ readonly verifierBinary: string;
8
+ readonly prepare: ComponentBrokerOptions["prepare"];
9
+ readonly step: ComponentBrokerOptions["step"];
10
+ readonly normalize: ComponentBrokerOptions["normalize"];
11
+ readonly persistRoot?: ComponentBrokerOptions["persistRoot"];
12
+ readonly onEvent?: ComponentBrokerOptions["onEvent"];
13
+ currentIdentity(): NativeComponentIdentity | undefined;
14
+ /** Includes exact executable/account/generation and context snapshot provenance. */
15
+ assertCurrent(): void;
16
+ }
17
+ /** Select only composed eligible roots; direct non-composed vendor entries retain their path. */
18
+ export declare function prepareNativeComponentHost(irDocument: string, scope: Readonly<Record<string, unknown>>, bindings: NativeComponentBindings): NativeComponentPreparation | undefined;
19
+ /** Direct Codex sessions pin one root even when it has no child components. */
20
+ export declare function prepareDirectCodexComponentHost(irDocument: string, scope: Readonly<Record<string, unknown>>, bindings: NativeComponentBindings): NativeComponentPreparation;
@@ -0,0 +1,129 @@
1
+ import { isDeepStrictEqual } from "node:util";
2
+ import { z } from "zod";
3
+ import { createComponentBroker } from "../harness/components/broker.js";
4
+ import { HOST_FEATURES, planDigest } from "../harness/components/plan.js";
5
+ const nodeSchema = z.object({ id: z.string().min(1), verb: z.string().min(1), entrypoint: z.boolean().optional(),
6
+ type: z.literal("analytical"), realization_kind: z.literal("skill"), trigger: z.object({ kind: z.literal("one_shot") }),
7
+ required_capabilities: z.array(z.string()), guardrails: z.array(z.record(z.string(), z.unknown())),
8
+ context_binding: z.record(z.string(), z.unknown()),
9
+ llm_calls: z.array(z.object({ tier: z.string().min(1), component_calls: z.array(z.object({ alias: z.string(), component: z.string() })).optional() }).passthrough()),
10
+ }).passthrough();
11
+ const irSchema = z.object({ warble_ir_version: z.literal("0.9"), components: z.array(z.record(z.string(), z.unknown())) }).passthrough();
12
+ const supportedGuards = new Set(["read_only_execution", "row_limit", "statement_timeout", "deterministic_gate", "artifact_write", "additivity_guard", "drill_depth_limit"]);
13
+ const execution = ["ordered_steps", "isolated_step_tools", "artifact_provenance", "per_step_tiers", "bounded_repair", "render_contract"];
14
+ function freeze(value) {
15
+ if (value && typeof value === "object" && !Object.isFrozen(value)) {
16
+ for (const child of Object.values(value))
17
+ freeze(child);
18
+ Object.freeze(value);
19
+ }
20
+ return value;
21
+ }
22
+ /** Select only composed eligible roots; direct non-composed vendor entries retain their path. */
23
+ export function prepareNativeComponentHost(irDocument, scope, bindings) {
24
+ return prepareHost(irDocument, scope, bindings, false);
25
+ }
26
+ /** Direct Codex sessions pin one root even when it has no child components. */
27
+ export function prepareDirectCodexComponentHost(irDocument, scope, bindings) {
28
+ if (bindings.identity.vendor !== "codex")
29
+ throw new Error("Direct session requires Codex");
30
+ const prepared = prepareHost(irDocument, scope, bindings, true);
31
+ if (!prepared || Object.keys(prepared.hostRoots).length !== 1)
32
+ throw new Error("Direct session requires one entry");
33
+ return prepared;
34
+ }
35
+ function prepareHost(irDocument, scope, bindings, direct) {
36
+ bindings.assertCurrent();
37
+ const identity = freeze(structuredClone(bindings.identity));
38
+ const contexts = freeze(structuredClone(bindings.contexts));
39
+ const assertCurrent = bindings.assertCurrent.bind(bindings);
40
+ const currentIdentity = bindings.currentIdentity.bind(bindings);
41
+ const prepare = bindings.prepare, step = bindings.step, normalize = bindings.normalize;
42
+ const persistRoot = bindings.persistRoot, onEvent = bindings.onEvent, verifierBinary = bindings.verifierBinary;
43
+ const check = () => {
44
+ assertCurrent();
45
+ if (!isDeepStrictEqual(currentIdentity(), identity))
46
+ throw new Error("Native component binding expired");
47
+ };
48
+ check();
49
+ const ir = irSchema.parse(JSON.parse(irDocument));
50
+ const nodes = new Map(ir.components.map((node) => [z.string().min(1).parse(node.id), node]));
51
+ if (nodes.size !== ir.components.length)
52
+ throw new Error("Duplicate component identity");
53
+ const entry = z.union([
54
+ z.object({ kind: z.literal("scope"), prompt: z.string() }).strict(),
55
+ z.object({ kind: z.literal("agent").default("agent"), verb: z.string(), prompt: z.string() }).strict(),
56
+ ]).parse(scope.entry);
57
+ if (identity.vendor === "codex" && entry.kind !== "agent")
58
+ throw new Error("Codex requires a single pinned entry");
59
+ if (!isDeepStrictEqual(scope.binding, identity.binding))
60
+ throw new Error("Native component scope binding mismatch");
61
+ const eligible = [...nodes.values()].filter((node) => node.entrypoint !== false && (entry.kind === "scope" || node.verb === entry.verb));
62
+ if (entry.kind === "agent" && eligible.length !== 1)
63
+ throw new Error("Unknown or ambiguous pinned entry");
64
+ const roots = eligible.filter((node) => direct || z.array(z.object({ component_calls: z.array(z.unknown()).optional() }).passthrough()).parse(node.llm_calls)
65
+ .some((step) => (step.component_calls?.length ?? 0) > 0));
66
+ if (!roots.length)
67
+ return undefined;
68
+ const needed = new Set();
69
+ const hostRoots = Object.fromEntries(roots.map((root) => {
70
+ const visited = new Set();
71
+ const visiting = new Set();
72
+ const visit = (id, depth) => {
73
+ if (depth > 8 || visiting.has(id))
74
+ throw new Error("Invalid component invocation closure");
75
+ if (visited.has(id))
76
+ return;
77
+ const node = nodeSchema.parse(nodes.get(id));
78
+ visiting.add(id);
79
+ for (const guard of node.guardrails)
80
+ if (!supportedGuards.has(String(guard.name)))
81
+ throw new Error("Unsupported native component guard");
82
+ for (const call of node.llm_calls)
83
+ for (const edge of call.component_calls ?? [])
84
+ visit(edge.component, depth + 1);
85
+ visiting.delete(id);
86
+ visited.add(id);
87
+ needed.add(id);
88
+ };
89
+ visit(String(root.id), 0);
90
+ const components = Object.fromEntries([...visited].sort().map((id) => {
91
+ const node = nodeSchema.parse(nodes.get(id));
92
+ const context = contexts[id];
93
+ if (!context || !isDeepStrictEqual(context.binding, node.context_binding))
94
+ throw new Error("Missing or mismatched component context");
95
+ const capabilities = Object.fromEntries(node.required_capabilities.filter((capability) => !capability.startsWith("llm:")).map((capability) => {
96
+ if (capability === "sql_execution:read_only")
97
+ return [capability, { tool: "query_read_only" }];
98
+ if (capability === "semantic_introspection")
99
+ return [capability, { tool: "inspect_context" }];
100
+ if (["component_invocation", "artifact_write", "render_contract"].includes(capability))
101
+ return [capability, {}];
102
+ throw new Error("Unsupported native component capability");
103
+ }));
104
+ return [id, { version: "2", tiers: [...new Set(node.llm_calls.map((call) => call.tier))], guardrails: node.guardrails, capabilities, execution }];
105
+ }));
106
+ return [String(root.id), { version: "2", protocol: "warble-component-host/1", execution: HOST_FEATURES, components }];
107
+ }));
108
+ freeze(hostRoots);
109
+ const selectedContexts = Object.fromEntries([...needed].sort().map((id) => [id, contexts[id]]));
110
+ const snapshots = Object.fromEntries(Object.entries(selectedContexts).map(([id, value]) => [id, value.snapshot]));
111
+ return freeze({
112
+ identity, hostRoots, contexts: freeze(snapshots), assertCurrent: check,
113
+ async host(root, plan, signal) {
114
+ check();
115
+ signal.throwIfAborted();
116
+ const descriptor = hostRoots[root];
117
+ if (!descriptor || plan.entries.length !== 1 || plan.entries[0] !== root || !isDeepStrictEqual(Object.keys(plan.components).sort(), Object.keys(descriptor.components).sort()))
118
+ throw new Error("Native component root mismatch");
119
+ const rootContexts = Object.fromEntries(Object.keys(plan.components).map((id) => [id, selectedContexts[id]]));
120
+ const brokerIdentity = { session: identity.session_id, vendor: identity.vendor, account: identity.auth_identity,
121
+ generation: identity.runtime_generation, project: identity.binding.project_identity, bindingRevision: identity.binding.revision,
122
+ contextDigest: planDigest(rootContexts), planDigest: plan.identity };
123
+ return createComponentBroker({ plan, contexts: rootContexts, verifierBinary,
124
+ identity: brokerIdentity, currentIdentity: () => { check(); return signal.aborted ? undefined : brokerIdentity; },
125
+ prepare, step, normalize, ...(persistRoot ? { persistRoot } : {}), ...(onEvent ? { onEvent } : {}),
126
+ });
127
+ },
128
+ });
129
+ }
@@ -0,0 +1,36 @@
1
+ import { type WrenAccessIdentity } from "../harness/components/wren-access.js";
2
+ import type { NativeComponentBindings } from "./native-component-preparation.js";
3
+ import { type ClaudeComponentRuntime } from "./runtime-host/claude-component-step.js";
4
+ import { type CodexComponentPolicy } from "./runtime-host/codex-component-step.js";
5
+ import type { RpcTransport } from "./runtime-host/codex-rpc.js";
6
+ /** Injected only after vendor certification. This factory does not confer certification. */
7
+ export type NativeComponentVendor = {
8
+ readonly authIdentity: string;
9
+ readonly accountEmail: string;
10
+ readonly generation: string;
11
+ readonly models: Readonly<Record<string, string>>;
12
+ assertCurrent(): void;
13
+ assertLive?(): void;
14
+ } & ({
15
+ readonly vendor: "claude";
16
+ open(model: string, signal: AbortSignal): Promise<ClaudeComponentRuntime>;
17
+ } | {
18
+ readonly vendor: "codex";
19
+ open(model: string, signal: AbortSignal): Promise<{
20
+ readonly transport: RpcTransport;
21
+ readonly policy: CodexComponentPolicy;
22
+ }>;
23
+ });
24
+ export interface NativeComponentRuntimeOptions extends Pick<NativeComponentBindings, "identity" | "contexts" | "verifierBinary" | "currentIdentity" | "assertCurrent" | "persistRoot" | "onEvent"> {
25
+ readonly vendor: NativeComponentVendor;
26
+ /** Optional cheap scope guard; integrity remains mandatory at protected operations. */
27
+ assertLive?(): void;
28
+ readonly wren: {
29
+ readonly executable: string;
30
+ readonly project: string;
31
+ readonly fingerprint: string;
32
+ readonly identity: WrenAccessIdentity;
33
+ };
34
+ }
35
+ /** Bind governed steps to one approved account and captured tier map; no outer-session transport. */
36
+ export declare function createNativeComponentRuntimeBindings(options: NativeComponentRuntimeOptions): NativeComponentBindings;
@@ -0,0 +1,92 @@
1
+ import { isDeepStrictEqual } from "node:util";
2
+ import { hashDirectory } from "../harness/compile/fingerprint.js";
3
+ import { normalizeComponentEvidence } from "../harness/components/normalize.js";
4
+ import { openWrenComponentAccess } from "../harness/components/wren-access.js";
5
+ import { runClaudeComponentStep } from "./runtime-host/claude-component-step.js";
6
+ import { runCodexComponentStep } from "./runtime-host/codex-component-step.js";
7
+ /** Bind governed steps to one approved account and captured tier map; no outer-session transport. */
8
+ export function createNativeComponentRuntimeBindings(options) {
9
+ const identity = structuredClone(options.identity);
10
+ const contexts = structuredClone(options.contexts);
11
+ const provider = options.vendor;
12
+ const vendor = provider.vendor;
13
+ const account = { authIdentity: provider.authIdentity, accountEmail: provider.accountEmail, generation: provider.generation };
14
+ const models = structuredClone(provider.models);
15
+ const assertBinding = options.assertCurrent.bind(options);
16
+ const currentIdentity = options.currentIdentity.bind(options);
17
+ const assertVendor = provider.assertCurrent.bind(provider);
18
+ const open = provider.open.bind(provider);
19
+ const wren = { ...options.wren };
20
+ const persistRoot = options.persistRoot;
21
+ const used = new WeakSet();
22
+ const assertBindingLive = options.assertLive?.bind(options) ?? assertBinding;
23
+ const assertVendorLive = provider.assertLive?.bind(provider) ?? assertVendor;
24
+ const checkIdentity = () => {
25
+ if (identity.vendor !== vendor || identity.auth_identity !== account.authIdentity || identity.runtime_generation !== account.generation
26
+ || !account.accountEmail || !isDeepStrictEqual(currentIdentity(), identity)
27
+ || provider.vendor !== vendor || provider.authIdentity !== account.authIdentity || provider.accountEmail !== account.accountEmail
28
+ || provider.generation !== account.generation || !isDeepStrictEqual(provider.models, models))
29
+ throw new Error("Native component runtime binding expired");
30
+ };
31
+ const check = () => { assertBinding(); assertVendor(); checkIdentity(); };
32
+ const checkLive = () => { assertBindingLive(); assertVendorLive(); checkIdentity(); };
33
+ check();
34
+ const checkAccess = async (signal) => {
35
+ signal.throwIfAborted();
36
+ check();
37
+ await wren.identity.assertCurrent();
38
+ if (await hashDirectory(wren.project) !== wren.fingerprint)
39
+ throw new Error("Native component project changed");
40
+ signal.throwIfAborted();
41
+ check();
42
+ };
43
+ return {
44
+ identity, contexts, verifierBinary: options.verifierBinary, currentIdentity, assertCurrent: check,
45
+ ...(persistRoot ? { persistRoot: async (...args) => { await checkAccess(args[2]); await persistRoot(...args); } } : {}), ...(options.onEvent ? { onEvent: options.onEvent } : {}),
46
+ async prepare(component, _identity, signal) {
47
+ await checkAccess(signal);
48
+ if (component.steps.some((step) => !models[step.tier]))
49
+ throw new Error("Missing native component tier binding");
50
+ if (component.steps.every((step) => step.tools.length === 0))
51
+ return {
52
+ async query() { throw new Error("No component query grant"); }, async inspect() { throw new Error("No component context grant"); }, async close() { },
53
+ };
54
+ return openWrenComponentAccess({ ...wren, signal });
55
+ },
56
+ async step(run, component) {
57
+ await checkAccess(run.signal);
58
+ const model = models[run.tier];
59
+ if (!model || !component.steps.some((step) => step.tier === run.tier))
60
+ throw new Error("Missing native component step binding");
61
+ const resource = await open(model, run.signal);
62
+ const transport = "transport" in resource ? resource.transport : resource;
63
+ let closing;
64
+ const close = () => closing ??= transport.close();
65
+ try {
66
+ await checkAccess(run.signal);
67
+ if (used.has(transport))
68
+ throw new Error("Native component step requires a fresh runtime");
69
+ used.add(transport);
70
+ if (vendor === "codex" && "transport" in resource) {
71
+ if (resource.policy.accountEmail !== account.accountEmail || resource.policy.model !== model)
72
+ throw new Error("Native component account or model mismatch");
73
+ const originalCheck = resource.policy.assertCurrent.bind(resource.policy);
74
+ const originalLive = resource.policy.assertLive?.bind(resource.policy) ?? originalCheck;
75
+ return await runCodexComponentStep({ listen: resource.transport.listen.bind(resource.transport), write: resource.transport.write.bind(resource.transport), close }, { ...resource.policy, assertCurrent() { check(); originalCheck(); },
76
+ assertLive() { checkLive(); originalLive(); } }, run);
77
+ }
78
+ if (vendor === "claude" && !("transport" in resource)) {
79
+ if (resource.account.email !== account.accountEmail || resource.model !== model)
80
+ throw new Error("Native component account or model mismatch");
81
+ const originalCheck = resource.assertCurrent.bind(resource);
82
+ return await runClaudeComponentStep({ ...resource, query: resource.query.bind(resource), close, assertCurrent() { check(); originalCheck(); } }, run);
83
+ }
84
+ throw new Error("Native component vendor mismatch");
85
+ }
86
+ finally {
87
+ await close();
88
+ }
89
+ },
90
+ async normalize(component, evidence, signal, context) { await checkAccess(signal); return normalizeComponentEvidence(component, evidence, context); },
91
+ };
92
+ }