@opengeni/core 0.28.1 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (224) hide show
  1. package/README.md +1 -1
  2. package/dist/access/external-actor-authority.d.ts +47 -0
  3. package/dist/access/index.d.ts +182 -3
  4. package/dist/access/service-initiator.d.ts +3 -0
  5. package/dist/application/api-integration-servers.d.ts +20 -0
  6. package/dist/application/archived-session-imports.d.ts +6 -0
  7. package/dist/application/composer-submit.d.ts +24 -0
  8. package/dist/application/connect-authority.d.ts +13 -0
  9. package/dist/application/connect-operation.d.ts +31 -0
  10. package/dist/application/external-continuation.d.ts +15 -0
  11. package/dist/application/external-identity-lifecycle.d.ts +20 -0
  12. package/dist/application/external-link-work-admission.d.ts +12 -0
  13. package/dist/application/external-workspace-members.d.ts +34 -0
  14. package/dist/application/modal-native-worker-host-transport.d.ts +297 -0
  15. package/dist/application/new-session-drafts.d.ts +39 -2
  16. package/dist/application/sandbox-recovery.d.ts +58 -0
  17. package/dist/application/session-commands.d.ts +26 -13
  18. package/dist/application/session-mcp-credential-rotation.d.ts +29 -0
  19. package/dist/application/session-tenancy.d.ts +19 -0
  20. package/dist/application/user-resource-grants.d.ts +20 -0
  21. package/dist/billing/limits.d.ts +11 -0
  22. package/dist/canonical-human-identities.d.ts +7 -2
  23. package/dist/canonical-human-identities.js +17 -4
  24. package/dist/canonical-human-identities.js.map +1 -1
  25. package/dist/{chunk-6WKPWE5S.js → chunk-2MKMRGSY.js} +108 -24
  26. package/dist/chunk-2MKMRGSY.js.map +1 -0
  27. package/dist/{chunk-NYPMQ7NO.js → chunk-GVO3N37G.js} +205 -37
  28. package/dist/chunk-GVO3N37G.js.map +1 -0
  29. package/dist/{chunk-JJU6I5XD.js → chunk-LCQXNR62.js} +36 -10
  30. package/dist/chunk-LCQXNR62.js.map +1 -0
  31. package/dist/chunk-MM6N7CTO.js +387 -0
  32. package/dist/chunk-MM6N7CTO.js.map +1 -0
  33. package/dist/chunk-QO5GVFFO.js +235 -0
  34. package/dist/chunk-QO5GVFFO.js.map +1 -0
  35. package/dist/codex-model-availability.d.ts +14 -0
  36. package/dist/default-session-model.d.ts +139 -0
  37. package/dist/dependencies.d.ts +87 -4
  38. package/dist/developer-setup-credential-ceiling.d.ts +10 -0
  39. package/dist/domain/agent-config-resolution.d.ts +49 -0
  40. package/dist/domain/automations.d.ts +58 -0
  41. package/dist/domain/capabilities.d.ts +22 -4
  42. package/dist/domain/company-profile-agent-admin.d.ts +25 -0
  43. package/dist/domain/connection-account-selection-error.d.ts +8 -0
  44. package/dist/domain/connector-tool-permissions.d.ts +36 -0
  45. package/dist/domain/direct-model-provider.d.ts +3 -0
  46. package/dist/domain/editable-artifacts/agent-application.d.ts +10 -1
  47. package/dist/domain/editable-artifacts/durable-export.d.ts +22 -1
  48. package/dist/domain/editable-artifacts/ports.d.ts +10 -0
  49. package/dist/domain/editable-artifacts/service.d.ts +4 -3
  50. package/dist/domain/environments.d.ts +5 -9
  51. package/dist/domain/external-creation-attribution.d.ts +6 -0
  52. package/dist/domain/file-owner.d.ts +8 -0
  53. package/dist/domain/github-action-policies.d.ts +36 -0
  54. package/dist/domain/github-repository-bindings.d.ts +117 -0
  55. package/dist/domain/github-skill-source.d.ts +5 -0
  56. package/dist/domain/governed-learning-slack-publication.d.ts +45 -0
  57. package/dist/domain/insights-timing.d.ts +16 -0
  58. package/dist/domain/insights-usage.d.ts +27 -0
  59. package/dist/domain/insights.d.ts +28 -1
  60. package/dist/domain/knowledge-files.d.ts +20 -0
  61. package/dist/domain/knowledge-messages.d.ts +39 -0
  62. package/dist/domain/knowledge-messages.test.d.ts +1 -0
  63. package/dist/domain/knowledge-preparation.d.ts +10 -0
  64. package/dist/domain/knowledge-preparation.test.d.ts +1 -0
  65. package/dist/domain/knowledge-search.d.ts +15 -0
  66. package/dist/domain/knowledge-search.test.d.ts +1 -0
  67. package/dist/domain/knowledge.d.ts +9 -0
  68. package/dist/domain/mcp-account-bindings.d.ts +19 -0
  69. package/dist/domain/mcp-account-routes.d.ts +21 -0
  70. package/dist/domain/native-mcp-connection-admission.d.ts +3 -0
  71. package/dist/domain/organization-integration-catalog.d.ts +15 -0
  72. package/dist/domain/organization-membership-lifecycle.d.ts +1 -0
  73. package/dist/domain/personal-connection-delegations.d.ts +87 -4
  74. package/dist/domain/pr-review.d.ts +249 -0
  75. package/dist/domain/resources.d.ts +24 -3
  76. package/dist/domain/scheduled-task-access.d.ts +247 -0
  77. package/dist/domain/scheduled-tasks.d.ts +103 -6
  78. package/dist/domain/session-connection-accounts.d.ts +16 -0
  79. package/dist/domain/session-file-access.d.ts +4 -0
  80. package/dist/domain/session-native-connection-replacement.d.ts +14 -0
  81. package/dist/domain/session-start-timing.d.ts +7 -0
  82. package/dist/domain/session-tool-policy.d.ts +93 -4
  83. package/dist/domain/sessions.d.ts +275 -28
  84. package/dist/domain/skill-imports.d.ts +1 -0
  85. package/dist/domain/skill-search.d.ts +30 -0
  86. package/dist/domain/skills.d.ts +117 -0
  87. package/dist/domain/slack-bot.d.ts +46 -1
  88. package/dist/domain/workspace-members.d.ts +8 -1
  89. package/dist/editable-artifact-live/application.d.ts +12 -3
  90. package/dist/editable-artifact-live/ports.d.ts +0 -1
  91. package/dist/editable-artifact-live/server.d.ts +5 -2
  92. package/dist/editable-artifact-live/ticket.d.ts +2 -2
  93. package/dist/editable-artifact-live/types.d.ts +14 -2
  94. package/dist/editable-artifact-live.js +2 -2
  95. package/dist/editable-artifacts.js +10 -4
  96. package/dist/index.d.ts +49 -3
  97. package/dist/index.js +18812 -6833
  98. package/dist/index.js.map +1 -1
  99. package/dist/managed-auth-session-sets.d.ts +131 -0
  100. package/dist/managed-auth-session-sets.js +51 -0
  101. package/dist/managed-auth-session-sets.js.map +1 -0
  102. package/dist/managed-session.d.ts +98 -11
  103. package/dist/model-catalog.d.ts +117 -0
  104. package/dist/product-usage-metrics.d.ts +30 -0
  105. package/dist/rigs/index.d.ts +22 -5
  106. package/dist/rigs/provider-images.d.ts +1 -1
  107. package/dist/sandbox/fleet.d.ts +59 -10
  108. package/dist/sandbox/routing.d.ts +22 -2
  109. package/dist/sandbox/runtime-settings.d.ts +56 -0
  110. package/dist/session-authorization.d.ts +38 -1
  111. package/dist/site-session-origin.d.ts +9 -0
  112. package/dist/transcription.d.ts +12 -1
  113. package/dist/turn-surface.d.ts +18 -0
  114. package/dist/user-presence.d.ts +32 -0
  115. package/package.json +18 -11
  116. package/src/access/external-actor-authority.ts +94 -0
  117. package/src/access/index.ts +1386 -44
  118. package/src/access/service-initiator.ts +50 -0
  119. package/src/application/api-integration-servers.ts +215 -0
  120. package/src/application/archived-session-imports.ts +161 -0
  121. package/src/application/composer-submit.ts +77 -0
  122. package/src/application/connect-authority.ts +77 -0
  123. package/src/application/connect-operation.ts +80 -0
  124. package/src/application/external-continuation.ts +129 -0
  125. package/src/application/external-identity-lifecycle.ts +48 -0
  126. package/src/application/external-link-work-admission.ts +90 -0
  127. package/src/application/external-workspace-members.ts +232 -0
  128. package/src/application/modal-native-worker-host-transport.ts +263 -0
  129. package/src/application/new-session-drafts.ts +222 -29
  130. package/src/application/sandbox-recovery.ts +78 -0
  131. package/src/application/session-commands.ts +430 -73
  132. package/src/application/session-mcp-credential-rotation.ts +217 -0
  133. package/src/application/session-tenancy.ts +445 -0
  134. package/src/application/user-resource-grants.ts +154 -0
  135. package/src/billing/limits.ts +103 -24
  136. package/src/canonical-human-identities.ts +22 -3
  137. package/src/codex-model-availability.ts +136 -0
  138. package/src/default-session-model.ts +656 -0
  139. package/src/dependencies.ts +97 -5
  140. package/src/developer-setup-credential-ceiling.ts +60 -0
  141. package/src/domain/agent-config-resolution.ts +148 -0
  142. package/src/domain/automations.ts +270 -0
  143. package/src/domain/capabilities.ts +244 -151
  144. package/src/domain/company-profile-agent-admin.ts +56 -0
  145. package/src/domain/connection-account-selection-error.ts +19 -0
  146. package/src/domain/connector-tool-permissions.ts +371 -0
  147. package/src/domain/direct-model-provider.ts +62 -0
  148. package/src/domain/editable-artifacts/agent-application.ts +61 -15
  149. package/src/domain/editable-artifacts/durable-export.ts +100 -5
  150. package/src/domain/editable-artifacts/in-memory.ts +6 -0
  151. package/src/domain/editable-artifacts/ports.ts +11 -0
  152. package/src/domain/editable-artifacts/service.ts +32 -1
  153. package/src/domain/editable-artifacts/snapshot-compaction.ts +9 -5
  154. package/src/domain/editable-artifacts/snapshot-verifier-genesis.ts +10 -8
  155. package/src/domain/editable-artifacts/snapshot-verifier.ts +10 -3
  156. package/src/domain/environments.ts +25 -45
  157. package/src/domain/external-creation-attribution.ts +22 -0
  158. package/src/domain/file-owner.ts +67 -0
  159. package/src/domain/github-action-policies.ts +169 -0
  160. package/src/domain/github-repository-bindings.ts +295 -0
  161. package/src/domain/github-skill-source.ts +233 -0
  162. package/src/domain/governed-learning-slack-publication.ts +157 -0
  163. package/src/domain/insights-timing.ts +95 -0
  164. package/src/domain/insights-usage.ts +67 -0
  165. package/src/domain/insights.ts +270 -169
  166. package/src/domain/knowledge-files.ts +46 -0
  167. package/src/domain/knowledge-messages.test.ts +91 -0
  168. package/src/domain/knowledge-messages.ts +106 -0
  169. package/src/domain/knowledge-preparation.test.ts +276 -0
  170. package/src/domain/knowledge-preparation.ts +147 -0
  171. package/src/domain/knowledge-search.test.ts +561 -0
  172. package/src/domain/knowledge-search.ts +256 -0
  173. package/src/domain/knowledge.ts +133 -0
  174. package/src/domain/mcp-account-bindings.ts +171 -0
  175. package/src/domain/mcp-account-routes.ts +121 -0
  176. package/src/domain/native-mcp-connection-admission.ts +14 -0
  177. package/src/domain/organization-integration-catalog.ts +88 -0
  178. package/src/domain/organization-membership-lifecycle.ts +10 -0
  179. package/src/domain/personal-connection-delegations.ts +758 -115
  180. package/src/domain/pr-review.ts +687 -0
  181. package/src/domain/resources.ts +182 -25
  182. package/src/domain/scheduled-task-access.ts +1052 -0
  183. package/src/domain/scheduled-tasks.ts +1952 -215
  184. package/src/domain/session-connection-accounts.ts +67 -0
  185. package/src/domain/session-file-access.ts +47 -0
  186. package/src/domain/session-native-connection-replacement.ts +64 -0
  187. package/src/domain/session-start-timing.ts +55 -0
  188. package/src/domain/session-tool-policy.ts +443 -19
  189. package/src/domain/sessions.ts +3417 -619
  190. package/src/domain/skill-imports.ts +137 -17
  191. package/src/domain/skill-search.ts +221 -0
  192. package/src/domain/skills.ts +104 -0
  193. package/src/domain/slack-bot.ts +128 -3
  194. package/src/domain/workspace-members.ts +34 -2
  195. package/src/editable-artifact-live/application.ts +29 -4
  196. package/src/editable-artifact-live/ports.ts +0 -1
  197. package/src/editable-artifact-live/server.ts +167 -13
  198. package/src/editable-artifact-live/ticket.ts +87 -6
  199. package/src/editable-artifact-live/types.ts +16 -2
  200. package/src/editable-artifact-live/wire.ts +0 -10
  201. package/src/index.ts +69 -4
  202. package/src/managed-auth-session-sets.ts +371 -0
  203. package/src/managed-session.ts +520 -2
  204. package/src/model-catalog.ts +873 -0
  205. package/src/product-usage-metrics.ts +101 -0
  206. package/src/rigs/index.ts +179 -63
  207. package/src/rigs/provider-images.ts +2 -3
  208. package/src/sandbox/fleet.ts +471 -59
  209. package/src/sandbox/routing.ts +160 -33
  210. package/src/sandbox/runtime-settings.ts +281 -0
  211. package/src/session-authorization.ts +262 -78
  212. package/src/site-session-origin.ts +22 -0
  213. package/src/transcription.ts +13 -1
  214. package/src/turn-surface.ts +45 -0
  215. package/src/user-presence.ts +134 -0
  216. package/dist/chunk-6WKPWE5S.js.map +0 -1
  217. package/dist/chunk-IBOEYG6N.js +0 -34
  218. package/dist/chunk-IBOEYG6N.js.map +0 -1
  219. package/dist/chunk-JJU6I5XD.js.map +0 -1
  220. package/dist/chunk-NYPMQ7NO.js.map +0 -1
  221. package/dist/domain/durable-learning-slack-publication.d.ts +0 -72
  222. package/dist/domain/packs.d.ts +0 -55
  223. package/src/domain/durable-learning-slack-publication.ts +0 -202
  224. package/src/domain/packs.ts +0 -556
@@ -0,0 +1,263 @@
1
+ import { createHash, createHmac, timingSafeEqual } from "node:crypto";
2
+ import { types } from "node:util";
3
+ import type { Settings } from "@opengeni/config";
4
+ import {
5
+ ModalNativeConfigurationCaptureRefV2,
6
+ ModalNativeOriginalScopeV2,
7
+ ModalNativeProofGrantRefV2,
8
+ } from "@opengeni/contracts/modal-native-proof-v2";
9
+ import { z } from "zod";
10
+
11
+ // Private transport only. This module is not an ordinary access-token entry,
12
+ // native acquisition issuer, custody authorizer, public registrar or effect CAS.
13
+ // A successful MAC authenticates this host transport, NOT the named human,
14
+ // live origin, grant, configuration, provider response or physical settlement.
15
+ const PREFIX = "ogmnp2_";
16
+ const SUBKEY_DOMAIN = "opengeni:modal-native-worker-host:transport:subkey:v2";
17
+ const MESSAGE_DOMAIN = "opengeni:modal-native-worker-host:transport:message:v2\0";
18
+ const TRANSPORT_TTL_SECONDS = 60;
19
+ const MAX_BODY_BYTES = 4 * 1024 * 1024;
20
+ const MAX_ENVELOPE_BYTES = 16_384;
21
+ const typedArrayPrototype = Object.getPrototypeOf(Uint8Array.prototype);
22
+ const nativeByteLength = Object.getOwnPropertyDescriptor(typedArrayPrototype, "byteLength")!.get!;
23
+ const nativeByteOffset = Object.getOwnPropertyDescriptor(typedArrayPrototype, "byteOffset")!.get!;
24
+ const nativeBuffer = Object.getOwnPropertyDescriptor(typedArrayPrototype, "buffer")!.get!;
25
+ const uuid = z.string().uuid();
26
+ const revision = z.number().int().positive().safe();
27
+ const base = {
28
+ protocol: z.literal("opengeni-modal-native-worker-host-proof"),
29
+ version: z.literal(2),
30
+ principalKind: z.literal("worker_host"),
31
+ purpose: z.literal("original-native-custody-proof"),
32
+ scope: ModalNativeOriginalScopeV2,
33
+ configurationCaptureRef: ModalNativeConfigurationCaptureRefV2,
34
+ proofGrantRef: ModalNativeProofGrantRefV2,
35
+ requestId: uuid,
36
+ };
37
+ const acquisition = z
38
+ .object({
39
+ claimId: uuid,
40
+ nonce: uuid,
41
+ claimRevision: revision,
42
+ recordRevision: revision,
43
+ captureId: uuid,
44
+ acquisitionId: uuid,
45
+ })
46
+ .strict();
47
+ const intent = z.discriminatedUnion("action", [
48
+ z.object({ ...base, action: z.literal("declare_preparation") }).strict(),
49
+ z
50
+ .object({ ...base, action: z.literal("claim_namespace_read"), recordRevision: revision })
51
+ .strict(),
52
+ z
53
+ .object({ ...base, action: z.literal("claim_original_command_read"), recordRevision: revision })
54
+ .strict(),
55
+ z.object({ ...base, action: z.literal("capture_namespace_binding"), acquisition }).strict(),
56
+ z.object({ ...base, action: z.literal("capture_original_page"), acquisition }).strict(),
57
+ z.object({ ...base, action: z.literal("reconcile_same_capture"), acquisition }).strict(),
58
+ z.object({ ...base, action: z.literal("settle_original_acquisition"), acquisition }).strict(),
59
+ ]);
60
+ const timing = z
61
+ .object({
62
+ bodySha256: z.string().regex(/^[a-f0-9]{64}$/u),
63
+ issuedAt: z.number().int().nonnegative().safe(),
64
+ expiresAt: z.number().int().positive().safe(),
65
+ })
66
+ .strict();
67
+ type Intent = z.infer<typeof intent>;
68
+ export type NativeWorkerHostTransportDescriptionV2 = Intent & z.infer<typeof timing>;
69
+
70
+ declare const transportBrand: unique symbol;
71
+ export type NativeWorkerHostTransport = Readonly<{ [transportBrand]: true }>;
72
+ declare const verifiedBrand: unique symbol;
73
+ export type VerifiedNativeWorkerHostTransport = Readonly<{ [verifiedBrand]: true }>;
74
+ const transports = new WeakMap<NativeWorkerHostTransport, Buffer>();
75
+ const verified = new WeakMap<
76
+ VerifiedNativeWorkerHostTransport,
77
+ {
78
+ transport: NativeWorkerHostTransport;
79
+ description: NativeWorkerHostTransportDescriptionV2;
80
+ }
81
+ >();
82
+
83
+ /** Inspect before schema parsing; getters/Proxies must not execute inside the
84
+ * signing boundary. This is JSON correlation, never an authority constructor. */
85
+ function ownJson(value: unknown, depth = 0): void {
86
+ if (depth > 12) throw new Error("Unsupported native host transport input");
87
+ if (value === null || typeof value === "string" || typeof value === "boolean") return;
88
+ if (typeof value === "number" && Number.isFinite(value) && !Object.is(value, -0)) return;
89
+ if (typeof value !== "object" || types.isProxy(value))
90
+ throw new Error("Unsupported native host transport input");
91
+ const array = Array.isArray(value);
92
+ const prototype = Object.getPrototypeOf(value);
93
+ if (array ? prototype !== Array.prototype : prototype !== Object.prototype && prototype !== null)
94
+ throw new Error("Unsupported native host transport input");
95
+ const descriptors = Object.getOwnPropertyDescriptors(value);
96
+ const keys = Reflect.ownKeys(descriptors).filter((key) => !(array && key === "length"));
97
+ if (array && keys.length !== (value as unknown[]).length)
98
+ throw new Error("Unsupported native host transport input");
99
+ for (const key of keys) {
100
+ if (typeof key !== "string" || (array && !/^(0|[1-9][0-9]*)$/u.test(key)))
101
+ throw new Error("Unsupported native host transport input");
102
+ const descriptor = descriptors[key]!;
103
+ if (!descriptor.enumerable || !("value" in descriptor))
104
+ throw new Error("Unsupported native host transport input");
105
+ ownJson(descriptor.value, depth + 1);
106
+ }
107
+ }
108
+
109
+ function bodyHash(body: Uint8Array): string {
110
+ if (types.isProxy(body) || !types.isUint8Array(body))
111
+ throw new Error("Unsupported native host transport body");
112
+ // Native getters ignore caller-owned byteLength/buffer/offset overrides. Reject
113
+ // shared backing storage: its bytes could change concurrently during the copy.
114
+ const length = nativeByteLength.call(body) as number;
115
+ const backing = nativeBuffer.call(body) as ArrayBuffer;
116
+ if (length > MAX_BODY_BYTES || !types.isArrayBuffer(backing))
117
+ throw new Error("Unsupported native host transport body");
118
+ const snapshot = new Uint8Array(length);
119
+ try {
120
+ snapshot.set(new Uint8Array(backing, nativeByteOffset.call(body) as number, length));
121
+ return createHash("sha256").update(snapshot).digest("hex");
122
+ } catch {
123
+ throw new Error("Unsupported native host transport body");
124
+ } finally {
125
+ snapshot.fill(0);
126
+ }
127
+ }
128
+
129
+ function keyFor(transport: NativeWorkerHostTransport): Buffer {
130
+ const key = transports.get(transport);
131
+ if (!key) throw new Error("Native host transport is unavailable");
132
+ return key;
133
+ }
134
+
135
+ function mac(key: Buffer, encoded: string): Buffer {
136
+ return createHmac("sha256", key).update(MESSAGE_DOMAIN).update(PREFIX).update(encoded).digest();
137
+ }
138
+
139
+ /** Host startup only. Deliberately DOES NOT call resolveFirstPartyDelegationSecret:
140
+ * neither configured access-key substitution nor the public local/test fallback
141
+ * can select this new purpose root. Missing explicit deployment secret is OFF.
142
+ * No variable lookup, provider request, config mutation or persistence occurs. */
143
+ export function configuredNativeWorkerHostTransport(
144
+ settings: Pick<Settings, "delegationSecret">,
145
+ ): NativeWorkerHostTransport | null {
146
+ if (!settings || typeof settings !== "object" || types.isProxy(settings)) return null;
147
+ const descriptor = Object.getOwnPropertyDescriptor(settings, "delegationSecret");
148
+ if (!descriptor || !("value" in descriptor) || typeof descriptor.value !== "string") return null;
149
+ const secret = descriptor.value.trim();
150
+ if (!secret) return null;
151
+ const transient = Buffer.from(secret, "utf8");
152
+ try {
153
+ const key = createHmac("sha256", transient).update(SUBKEY_DOMAIN).digest();
154
+ const handle = Object.freeze(Object.create(null)) as NativeWorkerHostTransport;
155
+ transports.set(handle, key);
156
+ return handle;
157
+ } finally {
158
+ transient.fill(0);
159
+ }
160
+ }
161
+
162
+ /** Release only this transport's derived-key copy, not a config/grant/custody
163
+ * lifecycle or observation slot. Outstanding native I/O needs its own join. */
164
+ export function disposeNativeWorkerHostTransport(transport: NativeWorkerHostTransport): void {
165
+ const key = transports.get(transport);
166
+ if (key) key.fill(0);
167
+ transports.delete(transport);
168
+ }
169
+
170
+ /** Private worker/core transport serialization. The caller MUST first establish
171
+ * canonical original-host admission. This helper cannot establish that admission
172
+ * and is intentionally not exported from the core package's public entrypoints. */
173
+ export function signNativeWorkerHostTransport(
174
+ transport: NativeWorkerHostTransport,
175
+ input: Intent,
176
+ body: Uint8Array,
177
+ nowSeconds = Math.floor(Date.now() / 1000),
178
+ ): string {
179
+ const key = keyFor(transport);
180
+ ownJson(input);
181
+ const parsed = intent.safeParse(input);
182
+ const clock = z.number().int().nonnegative().safe().safeParse(nowSeconds);
183
+ if (!parsed.success || !clock.success) throw new Error("Unsupported native host transport input");
184
+ const timed = timing.safeParse({
185
+ bodySha256: bodyHash(body),
186
+ issuedAt: clock.data,
187
+ expiresAt: clock.data + TRANSPORT_TTL_SECONDS,
188
+ });
189
+ if (!timed.success) throw new Error("Unsupported native host transport input");
190
+ const description: NativeWorkerHostTransportDescriptionV2 = {
191
+ ...parsed.data,
192
+ ...timed.data,
193
+ };
194
+ const encoded = Buffer.from(JSON.stringify(description), "utf8").toString("base64url");
195
+ if (PREFIX.length + encoded.length + 1 + 43 > MAX_ENVELOPE_BYTES)
196
+ throw new Error("Unsupported native host transport input");
197
+ return `${PREFIX}${encoded}.${mac(key, encoded).toString("base64url")}`;
198
+ }
199
+
200
+ /** Only host transport authenticity is verified. A later narrow authorizer must
201
+ * independently source-join original live/maintenance authority, configuration,
202
+ * grant, claim, acquisition, body fields/cursors and exact action-specific CAS. */
203
+ export function verifyNativeWorkerHostTransport(
204
+ transport: NativeWorkerHostTransport,
205
+ envelope: string,
206
+ body: Uint8Array,
207
+ nowSeconds = Math.floor(Date.now() / 1000),
208
+ ): VerifiedNativeWorkerHostTransport | null {
209
+ const key = keyFor(transport);
210
+ if (
211
+ typeof envelope !== "string" ||
212
+ envelope.length > MAX_ENVELOPE_BYTES ||
213
+ !envelope.startsWith(PREFIX)
214
+ )
215
+ return null;
216
+ const pieces = envelope.slice(PREFIX.length).split(".");
217
+ if (pieces.length !== 2) return null;
218
+ const [encoded, signature] = pieces as [string, string];
219
+ if (!/^[A-Za-z0-9_-]+$/u.test(encoded) || !/^[A-Za-z0-9_-]{43}$/u.test(signature)) return null;
220
+ const received = Buffer.from(signature, "base64url");
221
+ if (received.length !== 32 || received.toString("base64url") !== signature) return null;
222
+ if (!timingSafeEqual(received, mac(key, encoded))) return null;
223
+ try {
224
+ const decoded = JSON.parse(Buffer.from(encoded, "base64url").toString("utf8")) as Record<
225
+ string,
226
+ unknown
227
+ >;
228
+ const { bodySha256, issuedAt, expiresAt, ...rawIntent } = decoded;
229
+ const description = {
230
+ ...intent.parse(rawIntent),
231
+ ...timing.parse({ bodySha256, issuedAt, expiresAt }),
232
+ };
233
+ if (
234
+ !Number.isSafeInteger(nowSeconds) ||
235
+ nowSeconds < 0 ||
236
+ description.issuedAt > nowSeconds ||
237
+ description.expiresAt <= nowSeconds ||
238
+ description.expiresAt - description.issuedAt !== TRANSPORT_TTL_SECONDS ||
239
+ description.bodySha256 !== bodyHash(body) ||
240
+ Buffer.from(JSON.stringify(description), "utf8").toString("base64url") !== encoded
241
+ )
242
+ return null;
243
+ const proof = Object.freeze(Object.create(null)) as VerifiedNativeWorkerHostTransport;
244
+ verified.set(proof, { transport, description });
245
+ return proof;
246
+ } catch {
247
+ return null;
248
+ }
249
+ }
250
+
251
+ /** Request-local inspection of a previously verified MAC, NOT current admission.
252
+ * The narrow host authorizer must still recheck expiry and current authority/CAS.
253
+ * Copying a description, token or ref cannot mint grant or native-I/O authority. */
254
+ export function nativeWorkerHostTransportDescription(
255
+ transport: NativeWorkerHostTransport,
256
+ proof: VerifiedNativeWorkerHostTransport,
257
+ ): NativeWorkerHostTransportDescriptionV2 {
258
+ keyFor(transport);
259
+ const record = verified.get(proof);
260
+ if (!record || record.transport !== transport)
261
+ throw new Error("Native host transport proof is unavailable");
262
+ return structuredClone(record.description);
263
+ }
@@ -1,3 +1,5 @@
1
+ import { fileOwnerContextForAccess } from "../domain/file-owner";
2
+ import { withSessionRlsActorContext } from "@opengeni/db";
1
3
  import {
2
4
  NewSessionDraft,
3
5
  SaveNewSessionDraftRequest,
@@ -11,9 +13,12 @@ import {
11
13
  getSandbox,
12
14
  getVariableSet,
13
15
  NewSessionDraftAccessError,
16
+ newSessionDraftModelProvided,
17
+ newSessionDraftSelectedProjectChannelId,
14
18
  newSessionDraftToolsProvided,
19
+ newSessionSelectionHistory,
15
20
  publicNewSessionDraftOptions,
16
- requireFile,
21
+ requireFileForSubject,
17
22
  saveNewSessionDraftInTransaction,
18
23
  withWorkspaceSubjectRls,
19
24
  } from "@opengeni/db";
@@ -27,8 +32,14 @@ import {
27
32
  validateGitHubRepositorySelection,
28
33
  validateToolRefs,
29
34
  } from "../domain/resources";
30
- import { hasPermission } from "../access";
35
+ import {
36
+ hasPermission,
37
+ hasVerifiedOwningUserAuthorization,
38
+ type AccessGrantAuthorization,
39
+ } from "../access";
31
40
  import { assertConfiguredModel, assertWorkspaceModelPolicyAllows } from "../domain/sessions";
41
+ import { resolveDefaultSessionModel } from "../default-session-model";
42
+ import { canonicalizeConfiguredModelId, type Settings } from "@opengeni/config";
32
43
 
33
44
  type NewSessionDraftDependencies = Pick<AppDependencies, "settings" | "db" | "objectStorage">;
34
45
 
@@ -40,6 +51,7 @@ function mapNewSessionDraft(
40
51
  row: Awaited<ReturnType<typeof getNewSessionDraftInTransaction>>,
41
52
  ): NewSessionDraftValue | null {
42
53
  if (!row) return null;
54
+ const selectedProjectChannelId = newSessionDraftSelectedProjectChannelId(row);
43
55
  return NewSessionDraft.parse({
44
56
  revision: row.revision,
45
57
  text: row.text,
@@ -49,20 +61,81 @@ function mapNewSessionDraft(
49
61
  model: row.model,
50
62
  reasoningEffort: row.reasoningEffort,
51
63
  latencyMode: row.latencyMode,
64
+ ...(newSessionDraftModelProvided(row) !== undefined
65
+ ? { modelProvided: newSessionDraftModelProvided(row) }
66
+ : {}),
67
+ ...(selectedProjectChannelId !== undefined ? { selectedProjectChannelId } : {}),
52
68
  options: publicNewSessionDraftOptions(row),
69
+ selectionHistory: newSessionSelectionHistory(row),
53
70
  updatedAt: row.updatedAt.toISOString(),
54
71
  });
55
72
  }
56
73
 
74
+ /**
75
+ * Whether a stored draft's model policy was the person's choice. A row written
76
+ * before the marker existed (or by an older client) counts as a choice unless
77
+ * it is exactly the deployment default policy (model, reasoning, standard
78
+ * speed), which is what an untouched composer used to save.
79
+ */
80
+ export function draftModelProvided(
81
+ settings: Settings,
82
+ draft: Pick<NewSessionDraftValue, "model" | "reasoningEffort" | "latencyMode" | "modelProvided">,
83
+ ): boolean {
84
+ if (draft.modelProvided !== undefined) return draft.modelProvided;
85
+ return !(
86
+ canonicalizeConfiguredModelId(settings, draft.model) ===
87
+ canonicalizeConfiguredModelId(settings, settings.openaiModel) &&
88
+ draft.reasoningEffort === settings.openaiReasoningEffort &&
89
+ draft.latencyMode === "standard"
90
+ );
91
+ }
92
+
93
+ /** The resolved new-chat default for this actor and workspace. */
94
+ async function actorDefaultModel(
95
+ deps: Pick<NewSessionDraftDependencies, "db" | "settings">,
96
+ grant: AccessGrant,
97
+ workspaceId: string,
98
+ ) {
99
+ return await resolveDefaultSessionModel(deps.db, deps.settings, {
100
+ accountId: grant.accountId,
101
+ workspaceId,
102
+ subjectId: grant.subjectId,
103
+ });
104
+ }
105
+
106
+ type NewSessionDraftReadOptions = {
107
+ /**
108
+ * Project a draft that follows the default onto today's resolved default.
109
+ * Callers that only reuse a chosen model (Slack defaults) skip the
110
+ * resolution; session creation resolves the default itself.
111
+ */
112
+ projectDefaultModel?: boolean;
113
+ };
114
+
57
115
  async function hydrateNewSessionDraft(
58
116
  deps: Pick<NewSessionDraftDependencies, "db" | "settings">,
59
117
  grant: AccessGrant,
60
118
  workspaceId: string,
61
119
  row: Awaited<ReturnType<typeof getNewSessionDraftInTransaction>>,
120
+ readOptions: NewSessionDraftReadOptions = {},
62
121
  ): Promise<NewSessionDraftValue | null> {
63
122
  if (!row) return null;
64
- const mapped = mapNewSessionDraft(row);
65
- if (!mapped) return null;
123
+ const stored = mapNewSessionDraft(row);
124
+ if (!stored) return null;
125
+ // A draft that follows the default is projected onto today's default, so a
126
+ // later subscription connect or credit purchase replaces an untouched free
127
+ // model. The stored row is unchanged until the person saves again.
128
+ const modelProvided = draftModelProvided(deps.settings, stored);
129
+ let mapped: NewSessionDraftValue = { ...stored, modelProvided };
130
+ if (!modelProvided && readOptions.projectDefaultModel !== false) {
131
+ const resolved = await actorDefaultModel(deps, grant, workspaceId);
132
+ mapped = {
133
+ ...mapped,
134
+ model: resolved.model,
135
+ reasoningEffort: resolved.reasoningEffort,
136
+ latencyMode: resolved.model === stored.model ? stored.latencyMode : "standard",
137
+ };
138
+ }
66
139
  const runtimeSettings = await settingsWithEnabledCapabilityMcpServers(
67
140
  deps.db,
68
141
  workspaceId,
@@ -88,7 +161,12 @@ async function hydrateNewSessionDraft(
88
161
  continue;
89
162
  }
90
163
  try {
91
- const file = await requireFile(deps.db, workspaceId, resource.fileId);
164
+ const file = await requireFileForSubject(deps.db, {
165
+ accountId: grant.accountId,
166
+ workspaceId,
167
+ subjectId: grant.subjectId,
168
+ fileId: resource.fileId,
169
+ });
92
170
  if (file.status === "ready") resources.push(resource);
93
171
  } catch {
94
172
  // Missing, foreign, failed, and pending files are stale draft state.
@@ -96,13 +174,32 @@ async function hydrateNewSessionDraft(
96
174
  }
97
175
 
98
176
  const options = { ...mapped.options };
99
- if (options.variableSetId) {
100
- if (
101
- !hasPermission(grant.permissions, "variable-sets:use") ||
102
- !(await getVariableSet(deps.db, workspaceId, options.variableSetId))
103
- ) {
177
+ const variableSetIds = options.variableSetIds ?? [];
178
+ if (variableSetIds.length > 0) {
179
+ const selectionsAuthorized =
180
+ hasPermission(grant.permissions, "variable-sets:attach") &&
181
+ hasPermission(grant.permissions, "variable-sets:use") &&
182
+ (
183
+ await Promise.all(
184
+ variableSetIds.map((variableSetId) =>
185
+ getVariableSet(
186
+ deps.db,
187
+ { accountId: grant.accountId, workspaceId, subjectId: grant.subjectId },
188
+ variableSetId,
189
+ ),
190
+ ),
191
+ )
192
+ ).every(Boolean);
193
+ if (selectionsAuthorized) {
194
+ options.variableSetIds = variableSetIds;
195
+ options.variableSetId = variableSetIds[variableSetIds.length - 1];
196
+ } else {
197
+ delete options.variableSetIds;
104
198
  delete options.variableSetId;
105
199
  }
200
+ } else {
201
+ delete options.variableSetIds;
202
+ delete options.variableSetId;
106
203
  }
107
204
  if (options.rigId) {
108
205
  const rig = await getRig(deps.db, workspaceId, options.rigId);
@@ -151,10 +248,11 @@ async function hydrateNewSessionDraft(
151
248
  }
152
249
 
153
250
  /** Read the authenticated actor's server-authoritative pre-session composer state. */
154
- export async function getActorNewSessionDraft(
251
+ async function getActorNewSessionDraftInFileScope(
155
252
  deps: Pick<NewSessionDraftDependencies, "settings" | "db">,
156
253
  grant: AccessGrant,
157
254
  workspaceId: string,
255
+ readOptions: NewSessionDraftReadOptions = {},
158
256
  ): Promise<NewSessionDraftValue> {
159
257
  const row = await withWorkspaceSubjectRls(deps.db, workspaceId, grant.subjectId, (scoped) =>
160
258
  getNewSessionDraftInTransaction(scoped, {
@@ -162,20 +260,26 @@ export async function getActorNewSessionDraft(
162
260
  subjectId: grant.subjectId,
163
261
  }),
164
262
  );
165
- return (
166
- (await hydrateNewSessionDraft(deps, grant, workspaceId, row)) ?? {
167
- revision: 0,
168
- text: "",
169
- resources: [],
170
- tools: [],
171
- toolsProvided: false,
172
- model: deps.settings.openaiModel,
173
- reasoningEffort: deps.settings.openaiReasoningEffort,
174
- latencyMode: "standard",
175
- options: {},
176
- updatedAt: null,
177
- }
178
- );
263
+ const hydrated = await hydrateNewSessionDraft(deps, grant, workspaceId, row, readOptions);
264
+ if (hydrated) return hydrated;
265
+ const resolved =
266
+ readOptions.projectDefaultModel === false
267
+ ? { model: deps.settings.openaiModel, reasoningEffort: deps.settings.openaiReasoningEffort }
268
+ : await actorDefaultModel(deps, grant, workspaceId);
269
+ return {
270
+ revision: 0,
271
+ text: "",
272
+ resources: [],
273
+ tools: [],
274
+ toolsProvided: false,
275
+ model: resolved.model,
276
+ reasoningEffort: resolved.reasoningEffort,
277
+ latencyMode: "standard",
278
+ modelProvided: false,
279
+ options: {},
280
+ selectionHistory: { projects: [] },
281
+ updatedAt: null,
282
+ };
179
283
  }
180
284
 
181
285
  /**
@@ -185,11 +289,19 @@ export async function getActorNewSessionDraft(
185
289
  * draft may represent incomplete options, while no invalid option can become a
186
290
  * session without passing that single canonical create boundary.
187
291
  */
188
- export async function saveActorNewSessionDraft(
292
+ async function saveActorNewSessionDraftInFileScope(
189
293
  deps: NewSessionDraftDependencies,
190
294
  grant: AccessGrant,
191
295
  workspaceId: string,
192
296
  rawInput: unknown,
297
+ /**
298
+ * `AccessGrantAuthorization.canonicalManagedHumanSession` — did this request
299
+ * authenticate as the canonical managed cookie that owns `grant.subjectId`?
300
+ * Defaults to false so every caller that has not proven it fails closed onto
301
+ * the historical bare-membership fence.
302
+ */
303
+ canonicalManagedHumanSession = false,
304
+ externalAuthorization?: AccessGrantAuthorization,
193
305
  ): Promise<NewSessionDraftValue> {
194
306
  const input = SaveNewSessionDraftRequest.parse(rawInput);
195
307
  // The pre-marker client contract required `tools` and had no
@@ -207,9 +319,20 @@ export async function saveActorNewSessionDraft(
207
319
  const tools = toolsProvided ? validateToolRefs(input.tools, runtimeSettings) : [];
208
320
  await validateGitHubRepositorySelection(deps.db, workspaceId, resources);
209
321
  if (resources.some((resource) => resource.kind === "file") && !deps.objectStorage) {
210
- throw new HTTPException(503, { message: "object storage is not configured" });
322
+ throw new HTTPException(503, {
323
+ message: "object storage is not configured",
324
+ });
211
325
  }
212
- await validateFileResources(deps.db, workspaceId, resources);
326
+ await validateFileResources(
327
+ deps.db,
328
+ grant.accountId,
329
+ workspaceId,
330
+ grant.subjectId,
331
+ resources,
332
+ externalAuthorization
333
+ ? await fileOwnerContextForAccess(deps, externalAuthorization, "sessions:create")
334
+ : undefined,
335
+ );
213
336
  assertConfiguredModel(deps.settings, input.model);
214
337
  await assertWorkspaceModelPolicyAllows(deps.db, deps.settings, workspaceId, input.model);
215
338
 
@@ -228,16 +351,31 @@ export async function saveActorNewSessionDraft(
228
351
  model: input.model,
229
352
  reasoningEffort: input.reasoningEffort,
230
353
  latencyMode: input.latencyMode,
354
+ ...(input.modelProvided !== undefined ? { modelProvided: input.modelProvided } : {}),
355
+ ...(input.selectedProjectChannelId !== undefined
356
+ ? { selectedProjectChannelId: input.selectedProjectChannelId }
357
+ : {}),
231
358
  options: input.options,
232
359
  // Only managed people are removed through removeWorkspaceMember().
233
360
  // API keys and delegated service actors (for example the first-party
234
361
  // worker MCP principal) legitimately have no workspace_memberships
235
362
  // row, so they must not be rejected by the human-removal fence.
236
363
  requireWorkspaceMembership: grant.subjectId.startsWith("user:"),
364
+ // A managed human's own personal workspace has no membership row at
365
+ // all, so the human-removal fence above must fall back to the
366
+ // organization-membership pointer for them — and only for the
367
+ // verified owning-user authorization for this exact grant.
368
+ personalWorkspaceOwnerException:
369
+ canonicalManagedHumanSession ||
370
+ (externalAuthorization?.grant === grant &&
371
+ hasVerifiedOwningUserAuthorization(externalAuthorization)),
237
372
  }),
238
373
  ),
239
374
  );
240
- return mapNewSessionDraft(saved)!;
375
+ // Report the same model-policy marker a read of this row reports, so the
376
+ // save response and the next GET agree for old and new clients alike.
377
+ const mapped = mapNewSessionDraft(saved)!;
378
+ return { ...mapped, modelProvided: draftModelProvided(deps.settings, mapped) };
241
379
  } catch (error) {
242
380
  if (error instanceof NewSessionDraftAccessError) {
243
381
  throw new HTTPException(403, { message: error.message });
@@ -245,3 +383,58 @@ export async function saveActorNewSessionDraft(
245
383
  throw error;
246
384
  }
247
385
  }
386
+
387
+ export async function getActorNewSessionDraft(
388
+ deps: Parameters<typeof getActorNewSessionDraftInFileScope>[0],
389
+ grant: AccessGrant,
390
+ workspaceId: string,
391
+ authorization?: AccessGrantAuthorization,
392
+ readOptions: NewSessionDraftReadOptions = {},
393
+ ): Promise<NewSessionDraftValue> {
394
+ const actor = authorization
395
+ ? await fileOwnerContextForAccess(deps, authorization, "sessions:read")
396
+ : { subjectId: grant.subjectId, privateFileOwnerSubjectId: null };
397
+ return withSessionRlsActorContext(actor, () =>
398
+ getActorNewSessionDraftInFileScope(deps, grant, workspaceId, readOptions),
399
+ );
400
+ }
401
+ export async function saveActorNewSessionDraft(
402
+ ...args: Parameters<typeof saveActorNewSessionDraftInFileScope>
403
+ ): Promise<NewSessionDraftValue> {
404
+ const [deps, grant, , , , authorization] = args;
405
+ const actor = authorization
406
+ ? await fileOwnerContextForAccess(deps, authorization, "sessions:create")
407
+ : { subjectId: grant.subjectId, privateFileOwnerSubjectId: null };
408
+ return withSessionRlsActorContext(actor, () => saveActorNewSessionDraftInFileScope(...args));
409
+ }
410
+
411
+ /**
412
+ * The model policy the person explicitly chose in the website composer, or an
413
+ * empty object when their draft follows the default.
414
+ *
415
+ * Only the model policy is reused. Connectors, repositories, Variable Sets,
416
+ * Sandbox Environment and compute target in the draft are the person's last
417
+ * explicit narrowing of one website chat, so surfaces that start work without
418
+ * the composer (Slack) resolve those from workspace defaults instead of
419
+ * silently inheriting that narrowing.
420
+ */
421
+ export async function getActorNewSessionModelChoice(
422
+ deps: Parameters<typeof getActorNewSessionDraft>[0],
423
+ grant: AccessGrant,
424
+ workspaceId: string,
425
+ ): Promise<
426
+ Pick<NewSessionDraftValue, "model" | "reasoningEffort" | "latencyMode"> | Record<string, never>
427
+ > {
428
+ // A draft that follows the default is not projected here; session creation
429
+ // resolves the default itself, once.
430
+ const draft = await getActorNewSessionDraft(deps, grant, workspaceId, undefined, {
431
+ projectDefaultModel: false,
432
+ });
433
+ return draft.revision > 0 && draft.modelProvided === true
434
+ ? {
435
+ model: draft.model,
436
+ reasoningEffort: draft.reasoningEffort,
437
+ latencyMode: draft.latencyMode,
438
+ }
439
+ : {};
440
+ }