@stigmer/server 3.22.1-dev.20260922095755 → 3.23.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 (330) hide show
  1. package/dist/artifactstorage/artifact-storage.d.ts +29 -31
  2. package/dist/artifactstorage/artifact-storage.d.ts.map +1 -1
  3. package/dist/artifactstorage/artifact-storage.js +5 -7
  4. package/dist/artifactstorage/artifact-storage.js.map +1 -1
  5. package/dist/artifactstorage/r2-storage.d.ts +12 -7
  6. package/dist/artifactstorage/r2-storage.d.ts.map +1 -1
  7. package/dist/artifactstorage/r2-storage.js +6 -14
  8. package/dist/artifactstorage/r2-storage.js.map +1 -1
  9. package/dist/authorization/authorizer.d.ts +8 -4
  10. package/dist/authorization/authorizer.d.ts.map +1 -1
  11. package/dist/authorization/authorizer.js +9 -5
  12. package/dist/authorization/authorizer.js.map +1 -1
  13. package/dist/authorization/derived-tuples.d.ts.map +1 -1
  14. package/dist/authorization/derived-tuples.js +25 -10
  15. package/dist/authorization/derived-tuples.js.map +1 -1
  16. package/dist/authorization/evaluator.d.ts +8 -7
  17. package/dist/authorization/evaluator.d.ts.map +1 -1
  18. package/dist/authorization/evaluator.js +20 -4
  19. package/dist/authorization/evaluator.js.map +1 -1
  20. package/dist/authorization/list-read-scope.d.ts.map +1 -1
  21. package/dist/authorization/list-read-scope.js +9 -5
  22. package/dist/authorization/list-read-scope.js.map +1 -1
  23. package/dist/authorization/model/agent.d.ts.map +1 -1
  24. package/dist/authorization/model/agent.js +5 -4
  25. package/dist/authorization/model/agent.js.map +1 -1
  26. package/dist/authorization/model/agent_instance.d.ts.map +1 -1
  27. package/dist/authorization/model/agent_instance.js +2 -2
  28. package/dist/authorization/model/agent_instance.js.map +1 -1
  29. package/dist/authorization/model/mcp_server.d.ts.map +1 -1
  30. package/dist/authorization/model/mcp_server.js +2 -2
  31. package/dist/authorization/model/mcp_server.js.map +1 -1
  32. package/dist/authorization/model/plugin.d.ts.map +1 -1
  33. package/dist/authorization/model/plugin.js +4 -3
  34. package/dist/authorization/model/plugin.js.map +1 -1
  35. package/dist/authorization/model/rewrite.d.ts +10 -8
  36. package/dist/authorization/model/rewrite.d.ts.map +1 -1
  37. package/dist/authorization/model/rewrite.js +3 -0
  38. package/dist/authorization/model/rewrite.js.map +1 -1
  39. package/dist/authorization/model/skill.d.ts.map +1 -1
  40. package/dist/authorization/model/skill.js +2 -2
  41. package/dist/authorization/model/skill.js.map +1 -1
  42. package/dist/authorization/model/workflow.d.ts.map +1 -1
  43. package/dist/authorization/model/workflow.js +2 -2
  44. package/dist/authorization/model/workflow.js.map +1 -1
  45. package/dist/authorization/model/workflow_instance.d.ts.map +1 -1
  46. package/dist/authorization/model/workflow_instance.js +2 -2
  47. package/dist/authorization/model/workflow_instance.js.map +1 -1
  48. package/dist/authorization/organization-directory.d.ts.map +1 -1
  49. package/dist/authorization/organization-directory.js +3 -2
  50. package/dist/authorization/organization-directory.js.map +1 -1
  51. package/dist/authorization/store-test-kit.d.ts +6 -0
  52. package/dist/authorization/store-test-kit.d.ts.map +1 -1
  53. package/dist/authorization/store-test-kit.js +62 -16
  54. package/dist/authorization/store-test-kit.js.map +1 -1
  55. package/dist/authorization/tuples.d.ts +32 -13
  56. package/dist/authorization/tuples.d.ts.map +1 -1
  57. package/dist/authorization/tuples.js +11 -3
  58. package/dist/authorization/tuples.js.map +1 -1
  59. package/dist/boot/compose.d.ts.map +1 -1
  60. package/dist/boot/compose.js +49 -24
  61. package/dist/boot/compose.js.map +1 -1
  62. package/dist/boot/config.d.ts +4 -3
  63. package/dist/boot/config.d.ts.map +1 -1
  64. package/dist/boot/config.js.map +1 -1
  65. package/dist/boot/skill-transfer-origin.d.ts +52 -0
  66. package/dist/boot/skill-transfer-origin.d.ts.map +1 -0
  67. package/dist/boot/skill-transfer-origin.js +54 -0
  68. package/dist/boot/skill-transfer-origin.js.map +1 -0
  69. package/dist/domain/agent/controller.d.ts.map +1 -1
  70. package/dist/domain/agent/controller.js +4 -4
  71. package/dist/domain/agent/controller.js.map +1 -1
  72. package/dist/domain/agentchannel/controller.d.ts.map +1 -1
  73. package/dist/domain/agentchannel/controller.js +1 -3
  74. package/dist/domain/agentchannel/controller.js.map +1 -1
  75. package/dist/domain/agentexecution/controller.d.ts.map +1 -1
  76. package/dist/domain/agentexecution/controller.js +1 -3
  77. package/dist/domain/agentexecution/controller.js.map +1 -1
  78. package/dist/domain/agentinstance/controller.d.ts.map +1 -1
  79. package/dist/domain/agentinstance/controller.js +4 -3
  80. package/dist/domain/agentinstance/controller.js.map +1 -1
  81. package/dist/domain/agentshare/constants.d.ts +11 -6
  82. package/dist/domain/agentshare/constants.d.ts.map +1 -1
  83. package/dist/domain/agentshare/constants.js +16 -8
  84. package/dist/domain/agentshare/constants.js.map +1 -1
  85. package/dist/domain/agentshare/controller.d.ts.map +1 -1
  86. package/dist/domain/agentshare/controller.js +1 -3
  87. package/dist/domain/agentshare/controller.js.map +1 -1
  88. package/dist/domain/agentshare/steps.d.ts +25 -18
  89. package/dist/domain/agentshare/steps.d.ts.map +1 -1
  90. package/dist/domain/agentshare/steps.js +142 -41
  91. package/dist/domain/agentshare/steps.js.map +1 -1
  92. package/dist/domain/environment/controller.d.ts.map +1 -1
  93. package/dist/domain/environment/controller.js +3 -2
  94. package/dist/domain/environment/controller.js.map +1 -1
  95. package/dist/domain/executioncontext/controller.d.ts.map +1 -1
  96. package/dist/domain/executioncontext/controller.js +1 -2
  97. package/dist/domain/executioncontext/controller.js.map +1 -1
  98. package/dist/domain/mcpserver/controller.d.ts.map +1 -1
  99. package/dist/domain/mcpserver/controller.js +4 -3
  100. package/dist/domain/mcpserver/controller.js.map +1 -1
  101. package/dist/domain/plugin/controller.d.ts +3 -7
  102. package/dist/domain/plugin/controller.d.ts.map +1 -1
  103. package/dist/domain/plugin/controller.js +11 -9
  104. package/dist/domain/plugin/controller.js.map +1 -1
  105. package/dist/domain/plugin/push.d.ts +5 -7
  106. package/dist/domain/plugin/push.d.ts.map +1 -1
  107. package/dist/domain/plugin/push.js +14 -8
  108. package/dist/domain/plugin/push.js.map +1 -1
  109. package/dist/domain/schedule/controller.d.ts.map +1 -1
  110. package/dist/domain/schedule/controller.js +1 -3
  111. package/dist/domain/schedule/controller.js.map +1 -1
  112. package/dist/domain/session/controller.d.ts.map +1 -1
  113. package/dist/domain/session/controller.js +2 -3
  114. package/dist/domain/session/controller.js.map +1 -1
  115. package/dist/domain/skill/constants.d.ts +26 -3
  116. package/dist/domain/skill/constants.d.ts.map +1 -1
  117. package/dist/domain/skill/constants.js +26 -3
  118. package/dist/domain/skill/constants.js.map +1 -1
  119. package/dist/domain/skill/controller.d.ts +6 -11
  120. package/dist/domain/skill/controller.d.ts.map +1 -1
  121. package/dist/domain/skill/controller.js +35 -22
  122. package/dist/domain/skill/controller.js.map +1 -1
  123. package/dist/domain/skill/push.d.ts +2 -2
  124. package/dist/domain/skill/push.d.ts.map +1 -1
  125. package/dist/domain/skill/push.js +2 -2
  126. package/dist/domain/skill/push.js.map +1 -1
  127. package/dist/domain/skill/transfer/handler.d.ts +17 -9
  128. package/dist/domain/skill/transfer/handler.d.ts.map +1 -1
  129. package/dist/domain/skill/transfer/handler.js +19 -8
  130. package/dist/domain/skill/transfer/handler.js.map +1 -1
  131. package/dist/domain/skill/transfer/slots.d.ts +26 -41
  132. package/dist/domain/skill/transfer/slots.d.ts.map +1 -1
  133. package/dist/domain/skill/transfer/slots.js +80 -87
  134. package/dist/domain/skill/transfer/slots.js.map +1 -1
  135. package/dist/domain/skill/transfer/staging.d.ts +48 -0
  136. package/dist/domain/skill/transfer/staging.d.ts.map +1 -0
  137. package/dist/domain/skill/transfer/staging.js +127 -0
  138. package/dist/domain/skill/transfer/staging.js.map +1 -0
  139. package/dist/domain/workflow/controller.d.ts.map +1 -1
  140. package/dist/domain/workflow/controller.js +6 -14
  141. package/dist/domain/workflow/controller.js.map +1 -1
  142. package/dist/domain/workflow/registry/data/task-kind-registry.json +1 -1
  143. package/dist/domain/workflow/validation/task-config-constraints.d.ts +1 -10
  144. package/dist/domain/workflow/validation/task-config-constraints.d.ts.map +1 -1
  145. package/dist/domain/workflow/validation/task-config-constraints.js +2 -3
  146. package/dist/domain/workflow/validation/task-config-constraints.js.map +1 -1
  147. package/dist/domain/workflowexecution/controller.d.ts.map +1 -1
  148. package/dist/domain/workflowexecution/controller.js +2 -4
  149. package/dist/domain/workflowexecution/controller.js.map +1 -1
  150. package/dist/domain/workflowinstance/controller.d.ts.map +1 -1
  151. package/dist/domain/workflowinstance/controller.js +4 -3
  152. package/dist/domain/workflowinstance/controller.js.map +1 -1
  153. package/dist/extensions/authorization-queries.d.ts +5 -6
  154. package/dist/extensions/authorization-queries.d.ts.map +1 -1
  155. package/dist/extensions/list-read-scope.d.ts +1 -1
  156. package/dist/extensions/resource-authorization.d.ts +2 -3
  157. package/dist/extensions/resource-authorization.d.ts.map +1 -1
  158. package/dist/index.d.ts +3 -1
  159. package/dist/index.d.ts.map +1 -1
  160. package/dist/index.js +13 -0
  161. package/dist/index.js.map +1 -1
  162. package/dist/pipeline/apiresource-meta.d.ts +3 -12
  163. package/dist/pipeline/apiresource-meta.d.ts.map +1 -1
  164. package/dist/pipeline/apiresource-meta.js +10 -21
  165. package/dist/pipeline/apiresource-meta.js.map +1 -1
  166. package/dist/pipeline/steps/authorization-tuples.d.ts +3 -9
  167. package/dist/pipeline/steps/authorization-tuples.d.ts.map +1 -1
  168. package/dist/pipeline/steps/authorization-tuples.js +11 -9
  169. package/dist/pipeline/steps/authorization-tuples.js.map +1 -1
  170. package/dist/pipeline/steps/references.d.ts +20 -178
  171. package/dist/pipeline/steps/references.d.ts.map +1 -1
  172. package/dist/pipeline/steps/references.js +43 -344
  173. package/dist/pipeline/steps/references.js.map +1 -1
  174. package/dist/pipeline/steps/resolve-artifact-source.d.ts +11 -8
  175. package/dist/pipeline/steps/resolve-artifact-source.d.ts.map +1 -1
  176. package/dist/pipeline/steps/resolve-artifact-source.js +8 -4
  177. package/dist/pipeline/steps/resolve-artifact-source.js.map +1 -1
  178. package/dist/pipeline/steps/visibility-gates.d.ts +27 -0
  179. package/dist/pipeline/steps/visibility-gates.d.ts.map +1 -0
  180. package/dist/pipeline/steps/visibility-gates.js +139 -0
  181. package/dist/pipeline/steps/visibility-gates.js.map +1 -0
  182. package/dist/query/search/controller.js +1 -0
  183. package/dist/query/search/controller.js.map +1 -1
  184. package/dist/query/search/criteria.d.ts +5 -1
  185. package/dist/query/search/criteria.d.ts.map +1 -1
  186. package/dist/query/search/criteria.js +13 -3
  187. package/dist/query/search/criteria.js.map +1 -1
  188. package/dist/query/search/handler.d.ts +4 -5
  189. package/dist/query/search/handler.d.ts.map +1 -1
  190. package/dist/query/search/handler.js +6 -7
  191. package/dist/query/search/handler.js.map +1 -1
  192. package/dist/query/search/query-store.d.ts.map +1 -1
  193. package/dist/query/search/query-store.js +2 -0
  194. package/dist/query/search/query-store.js.map +1 -1
  195. package/dist/store/interface.d.ts +6 -3
  196. package/dist/store/interface.d.ts.map +1 -1
  197. package/dist/store/interface.js.map +1 -1
  198. package/dist/store/postgres/migrations.d.ts +1 -3
  199. package/dist/store/postgres/migrations.d.ts.map +1 -1
  200. package/dist/store/postgres/migrations.js +1 -35
  201. package/dist/store/postgres/migrations.js.map +1 -1
  202. package/dist/store/postgres/store.d.ts.map +1 -1
  203. package/dist/store/postgres/store.js +9 -4
  204. package/dist/store/postgres/store.js.map +1 -1
  205. package/dist/store/sqlite/migrations.d.ts +1 -3
  206. package/dist/store/sqlite/migrations.d.ts.map +1 -1
  207. package/dist/store/sqlite/migrations.js +1 -36
  208. package/dist/store/sqlite/migrations.js.map +1 -1
  209. package/dist/store/sqlite/store.d.ts.map +1 -1
  210. package/dist/store/sqlite/store.js +8 -4
  211. package/dist/store/sqlite/store.js.map +1 -1
  212. package/package.json +6 -6
  213. package/src/artifactstorage/__tests__/artifact-storage.test.ts +36 -33
  214. package/src/artifactstorage/__tests__/r2-storage.test.ts +11 -13
  215. package/src/artifactstorage/artifact-storage.ts +37 -33
  216. package/src/artifactstorage/r2-storage.ts +6 -15
  217. package/src/authorization/README.md +26 -24
  218. package/src/authorization/__tests__/authorizer.test.ts +9 -10
  219. package/src/authorization/__tests__/derived-tuples.test.ts +11 -4
  220. package/src/authorization/__tests__/evaluator.test.ts +73 -14
  221. package/src/authorization/__tests__/facts.test.ts +2 -2
  222. package/src/authorization/__tests__/fixtures/fga/agent-instance-creation.fga.yaml +185 -0
  223. package/src/authorization/__tests__/fixtures/fga/blueprint-private-visibility.fga.yaml +98 -6
  224. package/src/authorization/__tests__/fixtures/fga/cross-org-share-creation.fga.yaml +98 -0
  225. package/src/authorization/__tests__/fixtures/fga/default-instance-inheritance.fga.yaml +61 -4
  226. package/src/authorization/__tests__/fixtures/fga/mcp-server-authoring.fga.yaml +143 -0
  227. package/src/authorization/__tests__/fixtures/fga/org-admin-owner-inheritance.fga.yaml +21 -1
  228. package/src/authorization/__tests__/fixtures/fga/platform-visibility.fga.yaml +25 -1
  229. package/src/authorization/__tests__/fixtures/fga/public-visibility-setter.fga.yaml +52 -0
  230. package/src/authorization/__tests__/fixtures/fga/workflow-execution-sharing.fga.yaml +3 -1
  231. package/src/authorization/__tests__/list-read-scope.test.ts +17 -18
  232. package/src/authorization/__tests__/store-tests.test.ts +8 -1
  233. package/src/authorization/__tests__/tuples.test.ts +11 -12
  234. package/src/authorization/authorizer.ts +9 -4
  235. package/src/authorization/derived-tuples.ts +31 -14
  236. package/src/authorization/evaluator.ts +44 -8
  237. package/src/authorization/list-read-scope.ts +11 -5
  238. package/src/authorization/model/agent.ts +5 -2
  239. package/src/authorization/model/agent_instance.ts +2 -0
  240. package/src/authorization/model/mcp_server.ts +2 -0
  241. package/src/authorization/model/plugin.ts +4 -1
  242. package/src/authorization/model/rewrite.ts +13 -8
  243. package/src/authorization/model/skill.ts +2 -0
  244. package/src/authorization/model/workflow.ts +2 -0
  245. package/src/authorization/model/workflow_instance.ts +2 -0
  246. package/src/authorization/organization-directory.ts +3 -1
  247. package/src/authorization/store-test-kit.ts +75 -16
  248. package/src/authorization/tuples.ts +39 -16
  249. package/src/boot/__tests__/skill-transfer-origin.test.ts +72 -0
  250. package/src/boot/compose.ts +62 -25
  251. package/src/boot/config.ts +4 -3
  252. package/src/boot/skill-transfer-origin.ts +76 -0
  253. package/src/domain/agent/__tests__/agent.test.ts +4 -11
  254. package/src/domain/agent/controller.ts +7 -7
  255. package/src/domain/agentchannel/__tests__/agentchannel.test.ts +48 -223
  256. package/src/domain/agentchannel/controller.ts +1 -6
  257. package/src/domain/agentexecution/controller.ts +1 -6
  258. package/src/domain/agentinstance/controller.ts +12 -6
  259. package/src/domain/agentshare/__tests__/agentshare.test.ts +172 -196
  260. package/src/domain/agentshare/__tests__/create-authorization.test.ts +31 -16
  261. package/src/domain/agentshare/constants.ts +26 -14
  262. package/src/domain/agentshare/controller.ts +1 -6
  263. package/src/domain/agentshare/steps.ts +194 -41
  264. package/src/domain/environment/controller.ts +8 -5
  265. package/src/domain/executioncontext/controller.ts +1 -5
  266. package/src/domain/mcpserver/__tests__/mcpserver.test.ts +28 -115
  267. package/src/domain/mcpserver/__tests__/oauth-handshake.test.ts +18 -53
  268. package/src/domain/mcpserver/controller.ts +12 -6
  269. package/src/domain/plugin/controller.ts +23 -18
  270. package/src/domain/plugin/push.ts +16 -8
  271. package/src/domain/schedule/controller.ts +1 -6
  272. package/src/domain/session/controller.ts +4 -12
  273. package/src/domain/skill/__tests__/skill.test.ts +5 -3
  274. package/src/domain/skill/__tests__/slots.test.ts +113 -60
  275. package/src/domain/skill/__tests__/staging.test.ts +304 -0
  276. package/src/domain/skill/constants.ts +28 -3
  277. package/src/domain/skill/controller.ts +51 -35
  278. package/src/domain/skill/push.ts +3 -3
  279. package/src/domain/skill/transfer/handler.ts +33 -15
  280. package/src/domain/skill/transfer/slots.ts +89 -100
  281. package/src/domain/skill/transfer/staging.ts +191 -0
  282. package/src/domain/workflow/controller.ts +10 -28
  283. package/src/domain/workflow/registry/data/task-kind-registry.json +1 -1
  284. package/src/domain/workflow/validation/task-config-constraints.ts +3 -7
  285. package/src/domain/workflowexecution/controller.ts +2 -7
  286. package/src/domain/workflowinstance/controller.ts +12 -6
  287. package/src/extensions/__tests__/built-in-authorization-composed.test.ts +19 -32
  288. package/src/extensions/__tests__/extension-composition.test.ts +4 -4
  289. package/src/extensions/__tests__/list-read-scope-composed.test.ts +6 -9
  290. package/src/extensions/authorization-queries.ts +5 -6
  291. package/src/extensions/list-read-scope.ts +1 -1
  292. package/src/extensions/resource-authorization.ts +5 -3
  293. package/src/index.ts +14 -0
  294. package/src/pipeline/apiresource-meta.ts +10 -21
  295. package/src/pipeline/steps/__tests__/authorization-tuples.test.ts +31 -18
  296. package/src/pipeline/steps/__tests__/visibility-gates.test.ts +187 -0
  297. package/src/pipeline/steps/authorization-tuples.ts +11 -9
  298. package/src/pipeline/steps/references.ts +70 -578
  299. package/src/pipeline/steps/resolve-artifact-source.ts +20 -13
  300. package/src/pipeline/steps/visibility-gates.ts +174 -0
  301. package/src/query/search/__tests__/criteria.test.ts +4 -0
  302. package/src/query/search/__tests__/query-store.test.ts +40 -11
  303. package/src/query/search/controller.ts +1 -0
  304. package/src/query/search/criteria.ts +14 -0
  305. package/src/query/search/handler.ts +7 -6
  306. package/src/query/search/query-store.ts +2 -0
  307. package/src/store/__tests__/store-contract.ts +51 -6
  308. package/src/store/interface.ts +6 -3
  309. package/src/store/postgres/__tests__/migrations.test.ts +6 -187
  310. package/src/store/postgres/__tests__/store-contract.test.ts +2 -0
  311. package/src/store/postgres/migrations.ts +1 -48
  312. package/src/store/postgres/store.ts +11 -4
  313. package/src/store/sqlite/__tests__/migrations.test.ts +5 -191
  314. package/src/store/sqlite/migrations.ts +1 -45
  315. package/src/store/sqlite/store.ts +10 -4
  316. package/src/transport/registry/__tests__/lanes.test.ts +1 -1
  317. package/dist/domain/workflow/agent-call-references.d.ts +0 -54
  318. package/dist/domain/workflow/agent-call-references.d.ts.map +0 -1
  319. package/dist/domain/workflow/agent-call-references.js +0 -97
  320. package/dist/domain/workflow/agent-call-references.js.map +0 -1
  321. package/dist/store/public-visibility-retired.d.ts +0 -67
  322. package/dist/store/public-visibility-retired.d.ts.map +0 -1
  323. package/dist/store/public-visibility-retired.js +0 -55
  324. package/dist/store/public-visibility-retired.js.map +0 -1
  325. package/src/domain/workflow/__tests__/agent-call-references.test.ts +0 -222
  326. package/src/domain/workflow/agent-call-references.ts +0 -157
  327. package/src/pipeline/__tests__/supports-visibility.test.ts +0 -112
  328. package/src/pipeline/steps/__tests__/references.test.ts +0 -703
  329. package/src/store/__tests__/public-visibility-retired.test.ts +0 -127
  330. package/src/store/public-visibility-retired.ts +0 -119
@@ -41,18 +41,26 @@
41
41
  * `skipped` is therefore complete once every case has run; a consumer
42
42
  * pins it after the cases, which is the order a test file declares them.
43
43
  *
44
- * The parser is strict on purpose: an unknown key anywhere is a thrown
45
- * fault, so a change to the format upstream is loud here rather than
46
- * half-run. The model declares no condition, so the format's `condition`
47
- * on a tuple and `context` on a check are among the keys refused: a
48
- * document that still carries them is written against a model this
49
- * evaluator does not answer, and saying so beats running it half-blind.
44
+ * Context. A check's `context.allow` is the `allow_public` parameter
45
+ * (tuples.ts). A check without a context runs with `allow: false`, the
46
+ * listing posture: OpenFGA itself would fault on a conditional tuple it
47
+ * reached with the parameter missing, so the cloud's documents never do
48
+ * that — their CI would already fail — and the choice cannot change an
49
+ * answer the oracle gives. The parser is strict on purpose: an unknown
50
+ * key anywhere is a thrown fault, so a change to the format upstream is
51
+ * loud here rather than half-run.
50
52
  */
51
53
  import assert from "node:assert/strict";
52
54
 
53
55
  import { checkRelation } from "./evaluator.js";
54
56
  import type { Model } from "./model/index.js";
55
- import type { Person, Subject, Tuple, TupleSource } from "./tuples.js";
57
+ import type {
58
+ CheckContext,
59
+ Person,
60
+ Subject,
61
+ Tuple,
62
+ TupleSource,
63
+ } from "./tuples.js";
56
64
  import {
57
65
  ACCOUNT_TYPE,
58
66
  newInMemoryTupleSource,
@@ -68,17 +76,20 @@ export interface StoreTestTuple {
68
76
  readonly user: string;
69
77
  readonly relation: string;
70
78
  readonly object: string;
79
+ readonly condition?: { readonly name: string };
71
80
  }
72
81
 
73
82
  export interface StoreTestCheck {
74
83
  readonly user: string;
75
84
  readonly object: string;
85
+ readonly context?: CheckContext;
76
86
  readonly assertions: Readonly<Record<string, boolean>>;
77
87
  }
78
88
 
79
89
  export interface StoreTestListObjects {
80
90
  readonly user: string;
81
91
  readonly type: string;
92
+ readonly context?: CheckContext;
82
93
  readonly assertions: Readonly<Record<string, ReadonlyArray<string>>>;
83
94
  }
84
95
 
@@ -121,12 +132,28 @@ export function parseStoreTestDocument(
121
132
  }
122
133
 
123
134
  function parseTuple(value: unknown, where: string): StoreTestTuple {
124
- const entry = record(value, where, ["user", "relation", "object"]);
125
- return {
135
+ const entry = record(value, where, [
136
+ "user",
137
+ "relation",
138
+ "object",
139
+ "condition",
140
+ ]);
141
+ const tuple: StoreTestTuple = {
126
142
  user: text(entry["user"], `${where}.user`),
127
143
  relation: text(entry["relation"], `${where}.relation`),
128
144
  object: text(entry["object"], `${where}.object`),
129
145
  };
146
+ if (entry["condition"] === undefined) {
147
+ return tuple;
148
+ }
149
+ const condition = record(entry["condition"], `${where}.condition`, [
150
+ "name",
151
+ "context",
152
+ ]);
153
+ return {
154
+ ...tuple,
155
+ condition: { name: text(condition["name"], `${where}.condition.name`) },
156
+ };
130
157
  }
131
158
 
132
159
  function parseTest(value: unknown, where: string): StoreTest {
@@ -146,7 +173,12 @@ function parseTest(value: unknown, where: string): StoreTest {
146
173
  }
147
174
 
148
175
  function parseCheck(value: unknown, where: string): StoreTestCheck {
149
- const entry = record(value, where, ["user", "object", "assertions"]);
176
+ const entry = record(value, where, [
177
+ "user",
178
+ "object",
179
+ "context",
180
+ "assertions",
181
+ ]);
150
182
  const assertions = record(entry["assertions"], `${where}.assertions`);
151
183
  const parsed: Record<string, boolean> = {};
152
184
  for (const [relation, expected] of Object.entries(assertions)) {
@@ -155,15 +187,17 @@ function parseCheck(value: unknown, where: string): StoreTestCheck {
155
187
  }
156
188
  parsed[relation] = expected;
157
189
  }
190
+ const context = parseContext(entry["context"], `${where}.context`);
158
191
  return {
159
192
  user: text(entry["user"], `${where}.user`),
160
193
  object: text(entry["object"], `${where}.object`),
194
+ ...(context === undefined ? {} : { context }),
161
195
  assertions: parsed,
162
196
  };
163
197
  }
164
198
 
165
199
  function parseListObjects(value: unknown, where: string): StoreTestListObjects {
166
- const entry = record(value, where, ["user", "type", "assertions"]);
200
+ const entry = record(value, where, ["user", "type", "context", "assertions"]);
167
201
  const assertions = record(entry["assertions"], `${where}.assertions`);
168
202
  const parsed: Record<string, ReadonlyArray<string>> = {};
169
203
  for (const [relation, expected] of Object.entries(assertions)) {
@@ -171,13 +205,26 @@ function parseListObjects(value: unknown, where: string): StoreTestListObjects {
171
205
  (item, index) => text(item, `${where}.assertions.${relation}[${index}]`),
172
206
  );
173
207
  }
208
+ const context = parseContext(entry["context"], `${where}.context`);
174
209
  return {
175
210
  user: text(entry["user"], `${where}.user`),
176
211
  type: text(entry["type"], `${where}.type`),
212
+ ...(context === undefined ? {} : { context }),
177
213
  assertions: parsed,
178
214
  };
179
215
  }
180
216
 
217
+ function parseContext(value: unknown, where: string): CheckContext | undefined {
218
+ if (value === undefined) {
219
+ return undefined;
220
+ }
221
+ const entry = record(value, where, ["allow"]);
222
+ if (typeof entry["allow"] !== "boolean") {
223
+ throw new Error(`${where}.allow: expected a boolean`);
224
+ }
225
+ return { allow: entry["allow"] };
226
+ }
227
+
181
228
  function record(
182
229
  value: unknown,
183
230
  where: string,
@@ -250,6 +297,9 @@ export interface StoreTestKit {
250
297
  readonly skipped: ReadonlyArray<StoreTestSkip>;
251
298
  }
252
299
 
300
+ /** The listing posture, for a check the document leaves without a context (the module header). */
301
+ const CONTEXT_WHEN_ABSENT: CheckContext = { allow: false };
302
+
253
303
  /**
254
304
  * The document's tests as runnable cases over `model`, plus every
255
305
  * assertion group the kit does not run.
@@ -296,6 +346,7 @@ export function storeTestCases(
296
346
  for (const item of runnable) {
297
347
  const object = parseObjectRef(item.object);
298
348
  const person = personOf(item.user);
349
+ const context = item.context ?? CONTEXT_WHEN_ABSENT;
299
350
  for (const [relation, expected] of Object.entries(item.assertions)) {
300
351
  const recording = recordUndeclaredTypes(source, model);
301
352
  const actual = await checkRelation(
@@ -303,6 +354,7 @@ export function storeTestCases(
303
354
  object,
304
355
  relation,
305
356
  person,
357
+ context,
306
358
  );
307
359
  if (recording.reached.size > 0) {
308
360
  skipped.push({
@@ -319,7 +371,7 @@ export function storeTestCases(
319
371
  assert.equal(
320
372
  actual,
321
373
  expected,
322
- `${document.name} :: ${test.name}: ${item.user} ${relation} ${item.object} expected ${String(expected)}, got ${String(actual)}`,
374
+ `${document.name} :: ${test.name}: ${item.user} ${relation} ${item.object} (allow: ${String(context.allow)}) expected ${String(expected)}, got ${String(actual)}`,
323
375
  );
324
376
  }
325
377
  }
@@ -337,9 +389,10 @@ interface RecordingTupleSource extends TupleSource {
337
389
  /**
338
390
  * A view of `inner` that records, for one walk, every subject type the
339
391
  * evaluator read that `model` does not declare. A person
340
- * (`identity_account:<id>`) is a subject the evaluator compares, never a
341
- * type it resolves, so it is not a "reach" whatever the model declares; a
342
- * userset or a parent object of an undeclared type is.
392
+ * (`identity_account:<id>`, the wildcard) is a subject the evaluator
393
+ * compares, never a type it resolves, so it is not a "reach" whatever
394
+ * the model declares; a userset or a parent object of an undeclared
395
+ * type is.
343
396
  */
344
397
  function recordUndeclaredTypes(
345
398
  inner: TupleSource,
@@ -370,6 +423,8 @@ function resolvedTypeOf(subject: Subject): string | undefined {
370
423
  : subject.object.type;
371
424
  case "userset":
372
425
  return subject.object.type;
426
+ case "wildcard":
427
+ return undefined;
373
428
  default: {
374
429
  const exhaustive: never = subject;
375
430
  throw new Error(`unknown subject form: ${JSON.stringify(exhaustive)}`);
@@ -378,10 +433,14 @@ function resolvedTypeOf(subject: Subject): string | undefined {
378
433
  }
379
434
 
380
435
  function tupleOf(entry: StoreTestTuple): Tuple {
436
+ const subject = parseSubject(entry.user);
381
437
  return {
382
438
  object: parseObjectRef(entry.object),
383
439
  relation: entry.relation,
384
- subject: parseSubject(entry.user),
440
+ subject:
441
+ subject.form === "wildcard" && entry.condition !== undefined
442
+ ? { ...subject, condition: entry.condition.name }
443
+ : subject,
385
444
  };
386
445
  }
387
446
 
@@ -1,11 +1,9 @@
1
1
  /**
2
2
  * The tuple vocabulary of the built-in authorizer — the OpenFGA notions
3
3
  * the cloud's model is written in (object, relation, subject; a subject
4
- * being an object or a userset `type:id#relation`), typed once so the
5
- * evaluator, the derivation and the store-test kit speak one language. The
6
- * model declares no wildcard subject and no condition: every reader of a
7
- * row is an account or the holder of a relation on an object, which is
8
- * why a check needs no context parameter. Nothing here is
4
+ * being an object, a userset `type:id#relation`, or the public wildcard
5
+ * `type:*` under a condition), typed once so the evaluator, the
6
+ * derivation and the store-test kit speak one language. Nothing here is
9
7
  * stored: open source derives every tuple from a row (derived-tuples.ts)
10
8
  * or reads it from an IamPolicy row, and this module only names what a
11
9
  * tuple IS.
@@ -20,18 +18,28 @@
20
18
  * The type is named here; the construction (`personFor`, `resolvePerson`)
21
19
  * is person.ts's, so this module stays vocabulary with no I/O.
22
20
  *
23
- * The string forms (`agent:x`, `organization:acme#viewer`) are the
24
- * notation the cloud's store tests and the IamPolicy proto both use; parse
25
- * and format are here so the kit and the tests never spell them a second
26
- * way. The wildcard notation `type:*` is refused by the parser: the model
27
- * has no line that admits one, so a fixture or a policy that spells it is
28
- * a mistake to surface, never a subject to carry.
21
+ * `CheckContext` is the FGA condition context. The one condition the
22
+ * model declares is `allow_public(allow: bool)` on the public wildcard,
23
+ * and the cloud's own posture (stigmer-cloud authorizer/fga-authorizer.ts,
24
+ * the Java BuildCheckRequest) is `allow: true` on a point check — a public
25
+ * resource reads for any authenticated caller — and `allow: false` in a
26
+ * listing, which keeps a public wildcard from flooding an organization's
27
+ * catalog. The parameter is required so a caller states which posture it
28
+ * is in; there is no default because either one is wrong for the other.
29
+ *
30
+ * The string forms (`agent:x`, `organization:acme#viewer`,
31
+ * `identity_account:*`) are the notation the cloud's store tests and the
32
+ * IamPolicy proto both use; parse and format are here so the kit and the
33
+ * tests never spell them a second way.
29
34
  */
30
35
  import type { Message } from "@bufbuild/protobuf";
31
36
 
32
37
  /** The FGA type name of the kind every person is: `identity_account:<id>`. */
33
38
  export const ACCOUNT_TYPE = "identity_account";
34
39
 
40
+ /** The one condition the model declares; it guards the public wildcard. */
41
+ export const ALLOW_PUBLIC_CONDITION = "allow_public";
42
+
35
43
  /** `type:id` — an object of the model. */
36
44
  export interface ObjectRef {
37
45
  readonly type: string;
@@ -47,6 +55,12 @@ export type Subject =
47
55
  readonly form: "userset";
48
56
  readonly object: ObjectRef;
49
57
  readonly relation: string;
58
+ }
59
+ /** `type:*` — every object of the type, honoured only when its condition holds. */
60
+ | {
61
+ readonly form: "wildcard";
62
+ readonly type: string;
63
+ readonly condition: string | undefined;
50
64
  };
51
65
 
52
66
  export interface Tuple {
@@ -83,6 +97,11 @@ export interface Person {
83
97
  readonly aliases: ReadonlySet<string>;
84
98
  }
85
99
 
100
+ /** The condition parameters of a check (the module header). */
101
+ export interface CheckContext {
102
+ readonly allow: boolean;
103
+ }
104
+
86
105
  // ---------------------------------------------------------------------------
87
106
  // The string notation.
88
107
  // ---------------------------------------------------------------------------
@@ -101,8 +120,10 @@ export function formatObjectRef(object: ObjectRef): string {
101
120
  }
102
121
 
103
122
  /**
104
- * `type:id` or `type:id#relation` → Subject. `type:*` is refused (the
105
- * module header): no line of the model admits a wildcard subject.
123
+ * `type:id`, `type:id#relation` or `type:*` → Subject. A wildcard parsed
124
+ * from text carries no condition; the caller attaches one when its source
125
+ * says so (the store tests carry it on the tuple, the derivation attaches
126
+ * the model's).
106
127
  */
107
128
  export function parseSubject(text: string): Subject {
108
129
  const hash = text.indexOf("#");
@@ -115,9 +136,7 @@ export function parseSubject(text: string): Subject {
115
136
  }
116
137
  const object = parseObjectRef(text);
117
138
  if (object.id === "*") {
118
- throw new Error(
119
- `wildcard subject '${text}' is not admitted: the model declares no wildcard line`,
120
- );
139
+ return { form: "wildcard", type: object.type, condition: undefined };
121
140
  }
122
141
  return { form: "object", object };
123
142
  }
@@ -128,6 +147,10 @@ export function formatSubject(subject: Subject): string {
128
147
  return formatObjectRef(subject.object);
129
148
  case "userset":
130
149
  return `${formatObjectRef(subject.object)}#${subject.relation}`;
150
+ case "wildcard":
151
+ return subject.condition === undefined
152
+ ? `${subject.type}:*`
153
+ : `${subject.type}:* with ${subject.condition}`;
131
154
  default: {
132
155
  const exhaustive: never = subject;
133
156
  throw new Error(`unknown subject form: ${JSON.stringify(exhaustive)}`);
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Pins the skill transfer origin verdict (boot/skill-transfer-origin.ts):
3
+ * loopback on the relay is fine for a single-operator install and a
4
+ * warning under the require-authentication posture; a public origin is
5
+ * reachable whatever the posture; a bucket driver makes the base URL
6
+ * unused; an unparseable base URL is not mistaken for loopback.
7
+ */
8
+ import { describe, expect, it } from "vitest";
9
+
10
+ import { assessSkillTransferOrigin } from "../skill-transfer-origin.js";
11
+
12
+ describe("assessSkillTransferOrigin", () => {
13
+ it("the default loopback on a single-operator install is correct", () => {
14
+ expect(
15
+ assessSkillTransferOrigin({
16
+ baseUrl: "http://localhost:7234",
17
+ relaysThroughServer: true,
18
+ requireAuthentication: false,
19
+ }),
20
+ ).toEqual({ kind: "loopback-local" });
21
+ });
22
+
23
+ it.each([
24
+ "http://localhost:7234",
25
+ "http://127.0.0.1:7234",
26
+ "http://[::1]:7234",
27
+ "http://0.0.0.0:7234",
28
+ ])(
29
+ "loopback (%s) under the require-authentication posture is the warning, naming the URL",
30
+ (baseUrl) => {
31
+ expect(
32
+ assessSkillTransferOrigin({
33
+ baseUrl,
34
+ relaysThroughServer: true,
35
+ requireAuthentication: true,
36
+ }),
37
+ ).toEqual({ kind: "loopback-hosted", baseUrl });
38
+ },
39
+ );
40
+
41
+ it("a public origin is reachable under either posture", () => {
42
+ for (const requireAuthentication of [true, false]) {
43
+ expect(
44
+ assessSkillTransferOrigin({
45
+ baseUrl: "https://api.example.com",
46
+ relaysThroughServer: true,
47
+ requireAuthentication,
48
+ }),
49
+ ).toEqual({ kind: "reachable" });
50
+ }
51
+ });
52
+
53
+ it("a driver that signs its own URLs makes the base URL unused, loopback or not", () => {
54
+ expect(
55
+ assessSkillTransferOrigin({
56
+ baseUrl: "http://localhost:7234",
57
+ relaysThroughServer: false,
58
+ requireAuthentication: true,
59
+ }),
60
+ ).toEqual({ kind: "unused" });
61
+ });
62
+
63
+ it("an unparseable base URL is not loopback; its failure is loud on the first mint instead", () => {
64
+ expect(
65
+ assessSkillTransferOrigin({
66
+ baseUrl: "not a url",
67
+ relaysThroughServer: true,
68
+ requireAuthentication: true,
69
+ }),
70
+ ).toEqual({ kind: "reachable" });
71
+ });
72
+ });
@@ -119,14 +119,20 @@ import { registerSkillServices } from "../domain/skill/controller.js";
119
119
  import {
120
120
  DEFAULT_SLOT_TTL_MS,
121
121
  MAX_ZIP_SIZE,
122
+ STAGING_KEY_PREFIX,
122
123
  } from "../domain/skill/constants.js";
123
124
  import { newSkillArtifactStorage } from "../domain/skill/storage/artifact-storage.js";
124
125
  import { newContentAddressedArchiveStore } from "../archive/content-store.js";
125
126
  import {
126
127
  newSkillTransferLane,
128
+ transferServeUrl,
127
129
  uploadUrl as skillUploadUrl,
128
130
  } from "../domain/skill/transfer/handler.js";
129
131
  import { UploadSlots } from "../domain/skill/transfer/slots.js";
132
+ import {
133
+ newArchiveStaging,
134
+ uploadRefOf,
135
+ } from "../domain/skill/transfer/staging.js";
130
136
  import {
131
137
  DEFAULT_WRITE_VERSION,
132
138
  ENCRYPTION_KEY_ENV_VAR,
@@ -166,6 +172,7 @@ import { SqliteStore } from "../store/sqlite/store.js";
166
172
  import type { Store } from "../store/interface.js";
167
173
  import { resolveExtensions } from "../extensions/registry.js";
168
174
  import { resolveOAuthRedirectUri } from "./oauth-redirect-uri.js";
175
+ import { assessSkillTransferOrigin } from "./skill-transfer-origin.js";
169
176
  import type { ServerExtension } from "../extensions/registry.js";
170
177
  import { registerWorkflowServices } from "../domain/workflow/controller.js";
171
178
  import {
@@ -926,7 +933,7 @@ export async function composeServer(
926
933
  : undefined;
927
934
  // The skill artifact store + transfer lane (Go server.go:318-340). The
928
935
  // store is content-addressed and never-GC at {storagePath}/skills/;
929
- // the slots registry wipes {storagePath}/skills-staging at boot (orphans
936
+ // the slots registry wipes {storagePath}/skills/staging at boot (orphans
930
937
  // from a dead process are useless — the registry died with it). On OSS
931
938
  // the lane is ALWAYS configured (Go wires it unconditionally too): the
932
939
  // FailedPrecondition lane-absent arms exist for construction-order
@@ -938,16 +945,25 @@ export async function composeServer(
938
945
  // deployment changes nothing for skill artifacts until it opts in
939
946
  // explicitly, and the Go-written skills/ directory keeps serving in
940
947
  // place on the default arm.
948
+ //
949
+ // Capability URLs come from the driver through the staging port
950
+ // (domain/skill/transfer/staging.ts, stigmer#1219): the local driver's
951
+ // presignPut reserves a slot here and renders the lane URL below, a
952
+ // bucket driver's signs a PUT straight to the bucket, and the
953
+ // controllers cannot tell which. The slots are constructed on every
954
+ // arm so the HTTP lane always has a registry to consult; on a bucket
955
+ // arm nothing is ever reserved in it.
941
956
  const skillUploadSlots = new UploadSlots(
942
- path.join(config.storagePath, "skills-staging"),
957
+ config.storagePath,
958
+ STAGING_KEY_PREFIX,
943
959
  DEFAULT_SLOT_TTL_MS,
944
960
  MAX_ZIP_SIZE,
945
961
  );
962
+ const skillType =
963
+ config.skillArtifactStorageType === ""
964
+ ? "local"
965
+ : config.skillArtifactStorageType;
946
966
  const skillStorageDriver = ((): ArtifactStorage => {
947
- const skillType =
948
- config.skillArtifactStorageType === ""
949
- ? "local"
950
- : config.skillArtifactStorageType;
951
967
  if (skillType === "local") {
952
968
  // Go layout invariant: {storagePath}/skills exists from boot with
953
969
  // 0755 (the retired LocalFileStorage constructor's mkdir; the
@@ -958,16 +974,23 @@ export async function composeServer(
958
974
  });
959
975
  // The local presigned-PUT arm rides the skill transfer lane's slot
960
976
  // mechanism (§6b Q1 ruling: one upload surface, no new lane). The
961
- // slots stage inside THIS driver's root, so a minted stagingKey
962
- // reads back through the same instance; the empty serve URL is the
963
- // honest state — skill downloads ride the transfer lane, never
964
- // getSignedUrl.
965
- return new LocalArtifactStorage(config.storagePath, "", {
966
- mint: (declaredSizeBytes) => skillUploadSlots.mint(declaredSizeBytes),
967
- uploadUrl: (ref) => skillUploadUrl(config.skillTransferBaseUrl, ref),
968
- stagedKey: (ref) =>
969
- `skills-staging/${skillUploadSlots.stagedFileName(ref)}`,
970
- });
977
+ // slots stage inside THIS driver's root under the domain's staging
978
+ // key, so download(key) reads back what the lane received; the
979
+ // serve URL is the lane's download route, so getSignedUrl renders
980
+ // the very URL the lane dispatches.
981
+ return new LocalArtifactStorage(
982
+ config.storagePath,
983
+ transferServeUrl(config.skillTransferBaseUrl),
984
+ {
985
+ reserve: (key, declaredSizeBytes) => {
986
+ const { ttlMs } = skillUploadSlots.reserve(key, declaredSizeBytes);
987
+ return {
988
+ url: skillUploadUrl(config.skillTransferBaseUrl, uploadRefOf(key)),
989
+ ttlMs,
990
+ };
991
+ },
992
+ },
993
+ );
971
994
  }
972
995
  // Non-local skill backends share the artifact store's R2 settings for
973
996
  // the built-in arm and the composition's registered drivers beyond it.
@@ -994,11 +1017,31 @@ export async function composeServer(
994
1017
  skillStorageDriver,
995
1018
  PLUGIN_ARTIFACT_KEY_PREFIX,
996
1019
  );
1020
+ // One staging port for every archive kind, over the one skill blob
1021
+ // driver: the controllers mint, consume and sign through it and never
1022
+ // meet the slots or the bucket.
1023
+ const skillArchiveStaging = newArchiveStaging(skillStorageDriver, logger);
997
1024
  const skillTransferLane = newSkillTransferLane(
998
1025
  skillUploadSlots,
999
1026
  skillArtifactStorage,
1000
1027
  logger,
1001
1028
  );
1029
+ // A hosted server relaying bytes through itself from its loopback
1030
+ // default mints URLs no caller can reach (stigmer#1219); nothing inside
1031
+ // the process can observe that, so the boot says it. Never fatal: the
1032
+ // inline push and every other RPC work, and a self-host behind a tunnel
1033
+ // it forgot to name deserves a pointer, not an outage.
1034
+ const transferOrigin = assessSkillTransferOrigin({
1035
+ baseUrl: config.skillTransferBaseUrl,
1036
+ relaysThroughServer: skillType === "local",
1037
+ requireAuthentication,
1038
+ });
1039
+ if (transferOrigin.kind === "loopback-hosted") {
1040
+ logger.warn(
1041
+ "skill artifact transfer lane mints capability URLs on the server's own loopback while the server requires authentication; pushes above the gRPC message cap and sandbox skill downloads will fail — set SKILL_TRANSFER_BASE_URL to the origin callers reach this server on",
1042
+ { baseUrl: transferOrigin.baseUrl },
1043
+ );
1044
+ }
1002
1045
  // The console lane (lane 4, DD-012): present only when a static export
1003
1046
  // is bundled (slim artifacts) or configured (STIGMER_CONSOLE_DIR) —
1004
1047
  // "not bundled" is a modeled state, logged once, with the router
@@ -1463,14 +1506,11 @@ export async function composeServer(
1463
1506
  // The agentexecution blob store (server.go:362-363) — read side of
1464
1507
  // pushFromExecutionArtifact.
1465
1508
  executionArtifactStorage: artifactStorage,
1466
- transferLane: {
1467
- slots: skillUploadSlots,
1468
- baseUrl: config.skillTransferBaseUrl,
1469
- },
1509
+ staging: skillArchiveStaging,
1470
1510
  });
1471
1511
  // Plugins materialise their members through the in-process lane as
1472
1512
  // the installing caller; the lazy provider resolves the clients built
1473
- // from these same routes after boot. The upload slots are the skill
1513
+ // from these same routes after boot. The staging port is the skill
1474
1514
  // lane's: one upload surface for every archive.
1475
1515
  registerPluginServices(router, {
1476
1516
  store,
@@ -1479,10 +1519,7 @@ export async function composeServer(
1479
1519
  authorizationLifecycle,
1480
1520
  artifactStorage: pluginArtifactStorage,
1481
1521
  materializerProvider: () => requireInProcess().pluginMaterializer,
1482
- transferLane: {
1483
- slots: skillUploadSlots,
1484
- baseUrl: config.skillTransferBaseUrl,
1485
- },
1522
+ staging: skillArchiveStaging,
1486
1523
  });
1487
1524
  // Artifact CRUD shares the ONE blob store with agentexecution's
1488
1525
  // attachment lanes (Go server.go 347–374).
@@ -121,9 +121,10 @@ export interface ServerConfig {
121
121
  readonly oauthRedirectUri: string;
122
122
  /**
123
123
  * Skill artifact storage root (STORAGE_PATH; Go defaultStoragePath
124
- * ~/.stigmer/storage). Artifacts live at {storagePath}/skills/,
125
- * upload staging at {storagePath}/skills-staging/ — byte-identical to
126
- * Go's layout, so a Go-written directory is served in place at cutover.
124
+ * ~/.stigmer/storage). Artifacts live at {storagePath}/skills/ —
125
+ * byte-identical to Go's layout, so a Go-written directory is served in
126
+ * place at cutover — and upload staging at {storagePath}/skills/staging/,
127
+ * the prefix the domain names (domain/skill/constants.ts).
127
128
  */
128
129
  readonly storagePath: string;
129
130
  /**
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Whether the skill transfer lane's origin can be reached by the callers
3
+ * it is minted for.
4
+ *
5
+ * On the local blob driver the server itself carries artifact bytes over
6
+ * HTTP, and every upload and download capability URL it mints is rendered
7
+ * from `SKILL_TRANSFER_BASE_URL`, which defaults to the server's own
8
+ * loopback. That default is right for a single-operator install reached
9
+ * on the machine it runs on, and silently wrong for a hosted one: the
10
+ * server boots, serves, and passes every in-process check while handing
11
+ * the CLI, the SDKs and the sandbox runner a host none of them can reach —
12
+ * every push above the gRPC cap fails on the PUT and every sandbox mount
13
+ * of a skill with supporting files loses them (stigmer#1219). No test
14
+ * inside the process can see it, so the process says it at boot.
15
+ *
16
+ * The hosted signal is the require-authentication posture (boot/compose.ts
17
+ * combines its two sources): a server that requires credentials serves
18
+ * callers it does not share a machine with. A bucket driver signs URLs
19
+ * straight to its bucket and never renders the base URL, so the verdict
20
+ * is moot there and says so rather than warning about a knob nothing
21
+ * reads.
22
+ *
23
+ * Proven by __tests__/skill-transfer-origin.test.ts.
24
+ */
25
+
26
+ /** Hosts that name the process's own machine and nothing beyond it. */
27
+ const LOOPBACK_HOSTS = new Set([
28
+ "localhost",
29
+ "127.0.0.1",
30
+ "[::1]",
31
+ "0.0.0.0",
32
+ "[::]",
33
+ ]);
34
+
35
+ export type SkillTransferOriginVerdict =
36
+ /** The base URL names a host other than the server's own loopback. */
37
+ | { readonly kind: "reachable" }
38
+ /** Loopback on a single-operator install: the default, and correct. */
39
+ | { readonly kind: "loopback-local" }
40
+ /** Loopback on a server that requires authentication: minted URLs are dead on arrival. */
41
+ | { readonly kind: "loopback-hosted"; readonly baseUrl: string }
42
+ /** The blob driver signs its own URLs; the base URL is never rendered. */
43
+ | { readonly kind: "unused" };
44
+
45
+ export interface SkillTransferOriginInputs {
46
+ /** The configured or defaulted `SKILL_TRANSFER_BASE_URL`. */
47
+ readonly baseUrl: string;
48
+ /** Whether the local driver relays artifact bytes through this server. */
49
+ readonly relaysThroughServer: boolean;
50
+ /** The composed require-authentication posture. */
51
+ readonly requireAuthentication: boolean;
52
+ }
53
+
54
+ export function assessSkillTransferOrigin(
55
+ inputs: SkillTransferOriginInputs,
56
+ ): SkillTransferOriginVerdict {
57
+ if (!inputs.relaysThroughServer) {
58
+ return { kind: "unused" };
59
+ }
60
+ if (!namesLoopback(inputs.baseUrl)) {
61
+ return { kind: "reachable" };
62
+ }
63
+ return inputs.requireAuthentication
64
+ ? { kind: "loopback-hosted", baseUrl: inputs.baseUrl }
65
+ : { kind: "loopback-local" };
66
+ }
67
+
68
+ function namesLoopback(baseUrl: string): boolean {
69
+ try {
70
+ return LOOPBACK_HOSTS.has(new URL(baseUrl).hostname);
71
+ } catch {
72
+ // An unparseable base URL renders into every capability URL as-is; it
73
+ // is not loopback, and the failure it causes is loud on the first mint.
74
+ return false;
75
+ }
76
+ }