@stigmer/server 3.22.1 → 3.23.1-dev.20260923075205

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 (329) 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 +4 -8
  10. package/dist/authorization/authorizer.d.ts.map +1 -1
  11. package/dist/authorization/authorizer.js +5 -9
  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 +10 -25
  15. package/dist/authorization/derived-tuples.js.map +1 -1
  16. package/dist/authorization/evaluator.d.ts +7 -8
  17. package/dist/authorization/evaluator.d.ts.map +1 -1
  18. package/dist/authorization/evaluator.js +4 -20
  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 +5 -9
  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 +4 -5
  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 +3 -4
  34. package/dist/authorization/model/plugin.js.map +1 -1
  35. package/dist/authorization/model/rewrite.d.ts +8 -10
  36. package/dist/authorization/model/rewrite.d.ts.map +1 -1
  37. package/dist/authorization/model/rewrite.js +0 -3
  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 +2 -3
  50. package/dist/authorization/organization-directory.js.map +1 -1
  51. package/dist/authorization/store-test-kit.d.ts +0 -6
  52. package/dist/authorization/store-test-kit.d.ts.map +1 -1
  53. package/dist/authorization/store-test-kit.js +16 -62
  54. package/dist/authorization/store-test-kit.js.map +1 -1
  55. package/dist/authorization/tuples.d.ts +13 -32
  56. package/dist/authorization/tuples.d.ts.map +1 -1
  57. package/dist/authorization/tuples.js +3 -11
  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 +3 -1
  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 +3 -1
  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 +3 -4
  80. package/dist/domain/agentinstance/controller.js.map +1 -1
  81. package/dist/domain/agentshare/constants.d.ts +6 -11
  82. package/dist/domain/agentshare/constants.d.ts.map +1 -1
  83. package/dist/domain/agentshare/constants.js +8 -16
  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 +3 -1
  87. package/dist/domain/agentshare/controller.js.map +1 -1
  88. package/dist/domain/agentshare/steps.d.ts +18 -25
  89. package/dist/domain/agentshare/steps.d.ts.map +1 -1
  90. package/dist/domain/agentshare/steps.js +41 -142
  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 +2 -3
  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 +2 -1
  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 +3 -4
  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 +9 -11
  104. package/dist/domain/plugin/controller.js.map +1 -1
  105. package/dist/domain/plugin/push.d.ts +7 -5
  106. package/dist/domain/plugin/push.d.ts.map +1 -1
  107. package/dist/domain/plugin/push.js +8 -14
  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 +3 -1
  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 +3 -2
  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 +33 -24
  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/agent-call-references.d.ts +54 -0
  140. package/dist/domain/workflow/agent-call-references.d.ts.map +1 -0
  141. package/dist/domain/workflow/agent-call-references.js +97 -0
  142. package/dist/domain/workflow/agent-call-references.js.map +1 -0
  143. package/dist/domain/workflow/controller.d.ts.map +1 -1
  144. package/dist/domain/workflow/controller.js +14 -6
  145. package/dist/domain/workflow/controller.js.map +1 -1
  146. package/dist/domain/workflow/registry/data/task-kind-registry.json +1 -1
  147. package/dist/domain/workflow/validation/task-config-constraints.d.ts +10 -1
  148. package/dist/domain/workflow/validation/task-config-constraints.d.ts.map +1 -1
  149. package/dist/domain/workflow/validation/task-config-constraints.js +3 -2
  150. package/dist/domain/workflow/validation/task-config-constraints.js.map +1 -1
  151. package/dist/domain/workflowexecution/controller.d.ts.map +1 -1
  152. package/dist/domain/workflowexecution/controller.js +4 -2
  153. package/dist/domain/workflowexecution/controller.js.map +1 -1
  154. package/dist/domain/workflowinstance/controller.d.ts.map +1 -1
  155. package/dist/domain/workflowinstance/controller.js +3 -4
  156. package/dist/domain/workflowinstance/controller.js.map +1 -1
  157. package/dist/extensions/authorization-queries.d.ts +6 -5
  158. package/dist/extensions/authorization-queries.d.ts.map +1 -1
  159. package/dist/extensions/list-read-scope.d.ts +1 -1
  160. package/dist/extensions/resource-authorization.d.ts +3 -2
  161. package/dist/extensions/resource-authorization.d.ts.map +1 -1
  162. package/dist/index.d.ts +1 -1
  163. package/dist/index.d.ts.map +1 -1
  164. package/dist/index.js.map +1 -1
  165. package/dist/pipeline/apiresource-meta.d.ts +12 -3
  166. package/dist/pipeline/apiresource-meta.d.ts.map +1 -1
  167. package/dist/pipeline/apiresource-meta.js +21 -10
  168. package/dist/pipeline/apiresource-meta.js.map +1 -1
  169. package/dist/pipeline/steps/authorization-tuples.d.ts +9 -3
  170. package/dist/pipeline/steps/authorization-tuples.d.ts.map +1 -1
  171. package/dist/pipeline/steps/authorization-tuples.js +9 -11
  172. package/dist/pipeline/steps/authorization-tuples.js.map +1 -1
  173. package/dist/pipeline/steps/references.d.ts +178 -20
  174. package/dist/pipeline/steps/references.d.ts.map +1 -1
  175. package/dist/pipeline/steps/references.js +344 -43
  176. package/dist/pipeline/steps/references.js.map +1 -1
  177. package/dist/pipeline/steps/resolve-artifact-source.d.ts +11 -8
  178. package/dist/pipeline/steps/resolve-artifact-source.d.ts.map +1 -1
  179. package/dist/pipeline/steps/resolve-artifact-source.js +8 -4
  180. package/dist/pipeline/steps/resolve-artifact-source.js.map +1 -1
  181. package/dist/query/search/controller.js +0 -1
  182. package/dist/query/search/controller.js.map +1 -1
  183. package/dist/query/search/criteria.d.ts +1 -5
  184. package/dist/query/search/criteria.d.ts.map +1 -1
  185. package/dist/query/search/criteria.js +3 -13
  186. package/dist/query/search/criteria.js.map +1 -1
  187. package/dist/query/search/handler.d.ts +5 -4
  188. package/dist/query/search/handler.d.ts.map +1 -1
  189. package/dist/query/search/handler.js +7 -6
  190. package/dist/query/search/handler.js.map +1 -1
  191. package/dist/query/search/query-store.d.ts.map +1 -1
  192. package/dist/query/search/query-store.js +0 -2
  193. package/dist/query/search/query-store.js.map +1 -1
  194. package/dist/store/interface.d.ts +3 -6
  195. package/dist/store/interface.d.ts.map +1 -1
  196. package/dist/store/interface.js.map +1 -1
  197. package/dist/store/postgres/migrations.d.ts +3 -1
  198. package/dist/store/postgres/migrations.d.ts.map +1 -1
  199. package/dist/store/postgres/migrations.js +35 -1
  200. package/dist/store/postgres/migrations.js.map +1 -1
  201. package/dist/store/postgres/store.d.ts.map +1 -1
  202. package/dist/store/postgres/store.js +4 -9
  203. package/dist/store/postgres/store.js.map +1 -1
  204. package/dist/store/public-visibility-retired.d.ts +67 -0
  205. package/dist/store/public-visibility-retired.d.ts.map +1 -0
  206. package/dist/store/public-visibility-retired.js +55 -0
  207. package/dist/store/public-visibility-retired.js.map +1 -0
  208. package/dist/store/sqlite/migrations.d.ts +3 -1
  209. package/dist/store/sqlite/migrations.d.ts.map +1 -1
  210. package/dist/store/sqlite/migrations.js +36 -1
  211. package/dist/store/sqlite/migrations.js.map +1 -1
  212. package/dist/store/sqlite/store.d.ts.map +1 -1
  213. package/dist/store/sqlite/store.js +4 -8
  214. package/dist/store/sqlite/store.js.map +1 -1
  215. package/package.json +6 -6
  216. package/src/artifactstorage/__tests__/artifact-storage.test.ts +36 -33
  217. package/src/artifactstorage/__tests__/r2-storage.test.ts +11 -13
  218. package/src/artifactstorage/artifact-storage.ts +37 -33
  219. package/src/artifactstorage/r2-storage.ts +6 -15
  220. package/src/authorization/README.md +24 -26
  221. package/src/authorization/__tests__/authorizer.test.ts +10 -9
  222. package/src/authorization/__tests__/derived-tuples.test.ts +4 -11
  223. package/src/authorization/__tests__/evaluator.test.ts +14 -73
  224. package/src/authorization/__tests__/facts.test.ts +2 -2
  225. package/src/authorization/__tests__/fixtures/fga/agent-instance-creation.fga.yaml +19 -27
  226. package/src/authorization/__tests__/fixtures/fga/blueprint-private-visibility.fga.yaml +6 -98
  227. package/src/authorization/__tests__/fixtures/fga/default-instance-inheritance.fga.yaml +4 -61
  228. package/src/authorization/__tests__/fixtures/fga/mcp-server-authoring.fga.yaml +1 -15
  229. package/src/authorization/__tests__/fixtures/fga/org-admin-owner-inheritance.fga.yaml +1 -21
  230. package/src/authorization/__tests__/fixtures/fga/platform-visibility.fga.yaml +1 -25
  231. package/src/authorization/__tests__/fixtures/fga/workflow-execution-sharing.fga.yaml +1 -3
  232. package/src/authorization/__tests__/list-read-scope.test.ts +18 -17
  233. package/src/authorization/__tests__/store-tests.test.ts +2 -6
  234. package/src/authorization/__tests__/tuples.test.ts +12 -11
  235. package/src/authorization/authorizer.ts +4 -9
  236. package/src/authorization/derived-tuples.ts +14 -31
  237. package/src/authorization/evaluator.ts +8 -44
  238. package/src/authorization/list-read-scope.ts +5 -11
  239. package/src/authorization/model/agent.ts +2 -5
  240. package/src/authorization/model/agent_instance.ts +0 -2
  241. package/src/authorization/model/mcp_server.ts +0 -2
  242. package/src/authorization/model/plugin.ts +1 -4
  243. package/src/authorization/model/rewrite.ts +8 -13
  244. package/src/authorization/model/skill.ts +0 -2
  245. package/src/authorization/model/workflow.ts +0 -2
  246. package/src/authorization/model/workflow_instance.ts +0 -2
  247. package/src/authorization/organization-directory.ts +1 -3
  248. package/src/authorization/store-test-kit.ts +16 -75
  249. package/src/authorization/tuples.ts +16 -39
  250. package/src/boot/__tests__/skill-transfer-origin.test.ts +72 -0
  251. package/src/boot/compose.ts +62 -25
  252. package/src/boot/config.ts +4 -3
  253. package/src/boot/skill-transfer-origin.ts +76 -0
  254. package/src/domain/agent/__tests__/agent.test.ts +11 -4
  255. package/src/domain/agent/controller.ts +7 -7
  256. package/src/domain/agentchannel/__tests__/agentchannel.test.ts +223 -48
  257. package/src/domain/agentchannel/controller.ts +6 -1
  258. package/src/domain/agentexecution/controller.ts +6 -1
  259. package/src/domain/agentinstance/controller.ts +6 -12
  260. package/src/domain/agentshare/__tests__/agentshare.test.ts +196 -172
  261. package/src/domain/agentshare/__tests__/create-authorization.test.ts +16 -31
  262. package/src/domain/agentshare/constants.ts +14 -26
  263. package/src/domain/agentshare/controller.ts +6 -1
  264. package/src/domain/agentshare/steps.ts +41 -194
  265. package/src/domain/environment/controller.ts +5 -8
  266. package/src/domain/executioncontext/controller.ts +5 -1
  267. package/src/domain/mcpserver/__tests__/mcpserver.test.ts +115 -28
  268. package/src/domain/mcpserver/__tests__/oauth-handshake.test.ts +53 -18
  269. package/src/domain/mcpserver/controller.ts +6 -12
  270. package/src/domain/plugin/controller.ts +16 -25
  271. package/src/domain/plugin/push.ts +8 -16
  272. package/src/domain/schedule/controller.ts +6 -1
  273. package/src/domain/session/controller.ts +12 -4
  274. package/src/domain/skill/__tests__/skill.test.ts +5 -3
  275. package/src/domain/skill/__tests__/slots.test.ts +113 -60
  276. package/src/domain/skill/__tests__/staging.test.ts +304 -0
  277. package/src/domain/skill/constants.ts +28 -3
  278. package/src/domain/skill/controller.ts +44 -42
  279. package/src/domain/skill/push.ts +3 -3
  280. package/src/domain/skill/transfer/handler.ts +33 -15
  281. package/src/domain/skill/transfer/slots.ts +89 -100
  282. package/src/domain/skill/transfer/staging.ts +191 -0
  283. package/src/domain/workflow/__tests__/agent-call-references.test.ts +222 -0
  284. package/src/domain/workflow/agent-call-references.ts +157 -0
  285. package/src/domain/workflow/controller.ts +28 -10
  286. package/src/domain/workflow/registry/data/task-kind-registry.json +1 -1
  287. package/src/domain/workflow/validation/task-config-constraints.ts +7 -3
  288. package/src/domain/workflowexecution/controller.ts +7 -2
  289. package/src/domain/workflowinstance/controller.ts +6 -12
  290. package/src/extensions/__tests__/built-in-authorization-composed.test.ts +32 -19
  291. package/src/extensions/__tests__/extension-composition.test.ts +4 -4
  292. package/src/extensions/__tests__/list-read-scope-composed.test.ts +9 -6
  293. package/src/extensions/authorization-queries.ts +6 -5
  294. package/src/extensions/list-read-scope.ts +1 -1
  295. package/src/extensions/resource-authorization.ts +3 -5
  296. package/src/index.ts +0 -1
  297. package/src/pipeline/__tests__/supports-visibility.test.ts +112 -0
  298. package/src/pipeline/apiresource-meta.ts +21 -10
  299. package/src/pipeline/steps/__tests__/authorization-tuples.test.ts +18 -31
  300. package/src/pipeline/steps/__tests__/references.test.ts +703 -0
  301. package/src/pipeline/steps/authorization-tuples.ts +9 -11
  302. package/src/pipeline/steps/references.ts +578 -70
  303. package/src/pipeline/steps/resolve-artifact-source.ts +20 -13
  304. package/src/query/search/__tests__/criteria.test.ts +0 -4
  305. package/src/query/search/__tests__/query-store.test.ts +11 -40
  306. package/src/query/search/controller.ts +0 -1
  307. package/src/query/search/criteria.ts +0 -14
  308. package/src/query/search/handler.ts +6 -7
  309. package/src/query/search/query-store.ts +0 -2
  310. package/src/store/__tests__/public-visibility-retired.test.ts +127 -0
  311. package/src/store/__tests__/store-contract.ts +6 -51
  312. package/src/store/interface.ts +3 -6
  313. package/src/store/postgres/__tests__/migrations.test.ts +187 -6
  314. package/src/store/postgres/__tests__/store-contract.test.ts +0 -2
  315. package/src/store/postgres/migrations.ts +48 -1
  316. package/src/store/postgres/store.ts +4 -11
  317. package/src/store/public-visibility-retired.ts +119 -0
  318. package/src/store/sqlite/__tests__/migrations.test.ts +191 -5
  319. package/src/store/sqlite/migrations.ts +45 -1
  320. package/src/store/sqlite/store.ts +4 -10
  321. package/src/transport/registry/__tests__/lanes.test.ts +1 -1
  322. package/dist/pipeline/steps/visibility-gates.d.ts +0 -27
  323. package/dist/pipeline/steps/visibility-gates.d.ts.map +0 -1
  324. package/dist/pipeline/steps/visibility-gates.js +0 -139
  325. package/dist/pipeline/steps/visibility-gates.js.map +0 -1
  326. package/src/authorization/__tests__/fixtures/fga/cross-org-share-creation.fga.yaml +0 -98
  327. package/src/authorization/__tests__/fixtures/fga/public-visibility-setter.fga.yaml +0 -52
  328. package/src/pipeline/steps/__tests__/visibility-gates.test.ts +0 -187
  329. package/src/pipeline/steps/visibility-gates.ts +0 -174
@@ -32,8 +32,7 @@
32
32
  * annotation, which is why this driver meets that caller at all: a
33
33
  * skip lane hands the directory the identity the interceptor stamped,
34
34
  * and no Authorize arm ran before it. Every other caller gets the
35
- * evaluated list. The listing context (`allow: false`) is the cloud's,
36
- * though no organization line admits a wildcard.
35
+ * evaluated list.
37
36
  * The controller loads each id it is handed and skips the ones whose row
38
37
  * is gone (grants can outlive rows); the source has already loaded the
39
38
  * row once for the evaluation — two primary-key reads per organization,
@@ -102,7 +101,6 @@ export function newBuiltInOrganizationDirectory(
102
101
  { type: ORGANIZATION, id },
103
102
  "can_view",
104
103
  person,
105
- { allow: false },
106
104
  )
107
105
  ) {
108
106
  visible.push(id);
@@ -41,26 +41,18 @@
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
- * 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.
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.
52
50
  */
53
51
  import assert from "node:assert/strict";
54
52
 
55
53
  import { checkRelation } from "./evaluator.js";
56
54
  import type { Model } from "./model/index.js";
57
- import type {
58
- CheckContext,
59
- Person,
60
- Subject,
61
- Tuple,
62
- TupleSource,
63
- } from "./tuples.js";
55
+ import type { Person, Subject, Tuple, TupleSource } from "./tuples.js";
64
56
  import {
65
57
  ACCOUNT_TYPE,
66
58
  newInMemoryTupleSource,
@@ -76,20 +68,17 @@ export interface StoreTestTuple {
76
68
  readonly user: string;
77
69
  readonly relation: string;
78
70
  readonly object: string;
79
- readonly condition?: { readonly name: string };
80
71
  }
81
72
 
82
73
  export interface StoreTestCheck {
83
74
  readonly user: string;
84
75
  readonly object: string;
85
- readonly context?: CheckContext;
86
76
  readonly assertions: Readonly<Record<string, boolean>>;
87
77
  }
88
78
 
89
79
  export interface StoreTestListObjects {
90
80
  readonly user: string;
91
81
  readonly type: string;
92
- readonly context?: CheckContext;
93
82
  readonly assertions: Readonly<Record<string, ReadonlyArray<string>>>;
94
83
  }
95
84
 
@@ -132,28 +121,12 @@ export function parseStoreTestDocument(
132
121
  }
133
122
 
134
123
  function parseTuple(value: unknown, where: string): StoreTestTuple {
135
- const entry = record(value, where, [
136
- "user",
137
- "relation",
138
- "object",
139
- "condition",
140
- ]);
141
- const tuple: StoreTestTuple = {
124
+ const entry = record(value, where, ["user", "relation", "object"]);
125
+ return {
142
126
  user: text(entry["user"], `${where}.user`),
143
127
  relation: text(entry["relation"], `${where}.relation`),
144
128
  object: text(entry["object"], `${where}.object`),
145
129
  };
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
- };
157
130
  }
158
131
 
159
132
  function parseTest(value: unknown, where: string): StoreTest {
@@ -173,12 +146,7 @@ function parseTest(value: unknown, where: string): StoreTest {
173
146
  }
174
147
 
175
148
  function parseCheck(value: unknown, where: string): StoreTestCheck {
176
- const entry = record(value, where, [
177
- "user",
178
- "object",
179
- "context",
180
- "assertions",
181
- ]);
149
+ const entry = record(value, where, ["user", "object", "assertions"]);
182
150
  const assertions = record(entry["assertions"], `${where}.assertions`);
183
151
  const parsed: Record<string, boolean> = {};
184
152
  for (const [relation, expected] of Object.entries(assertions)) {
@@ -187,17 +155,15 @@ function parseCheck(value: unknown, where: string): StoreTestCheck {
187
155
  }
188
156
  parsed[relation] = expected;
189
157
  }
190
- const context = parseContext(entry["context"], `${where}.context`);
191
158
  return {
192
159
  user: text(entry["user"], `${where}.user`),
193
160
  object: text(entry["object"], `${where}.object`),
194
- ...(context === undefined ? {} : { context }),
195
161
  assertions: parsed,
196
162
  };
197
163
  }
198
164
 
199
165
  function parseListObjects(value: unknown, where: string): StoreTestListObjects {
200
- const entry = record(value, where, ["user", "type", "context", "assertions"]);
166
+ const entry = record(value, where, ["user", "type", "assertions"]);
201
167
  const assertions = record(entry["assertions"], `${where}.assertions`);
202
168
  const parsed: Record<string, ReadonlyArray<string>> = {};
203
169
  for (const [relation, expected] of Object.entries(assertions)) {
@@ -205,26 +171,13 @@ function parseListObjects(value: unknown, where: string): StoreTestListObjects {
205
171
  (item, index) => text(item, `${where}.assertions.${relation}[${index}]`),
206
172
  );
207
173
  }
208
- const context = parseContext(entry["context"], `${where}.context`);
209
174
  return {
210
175
  user: text(entry["user"], `${where}.user`),
211
176
  type: text(entry["type"], `${where}.type`),
212
- ...(context === undefined ? {} : { context }),
213
177
  assertions: parsed,
214
178
  };
215
179
  }
216
180
 
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
-
228
181
  function record(
229
182
  value: unknown,
230
183
  where: string,
@@ -297,9 +250,6 @@ export interface StoreTestKit {
297
250
  readonly skipped: ReadonlyArray<StoreTestSkip>;
298
251
  }
299
252
 
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
-
303
253
  /**
304
254
  * The document's tests as runnable cases over `model`, plus every
305
255
  * assertion group the kit does not run.
@@ -346,7 +296,6 @@ export function storeTestCases(
346
296
  for (const item of runnable) {
347
297
  const object = parseObjectRef(item.object);
348
298
  const person = personOf(item.user);
349
- const context = item.context ?? CONTEXT_WHEN_ABSENT;
350
299
  for (const [relation, expected] of Object.entries(item.assertions)) {
351
300
  const recording = recordUndeclaredTypes(source, model);
352
301
  const actual = await checkRelation(
@@ -354,7 +303,6 @@ export function storeTestCases(
354
303
  object,
355
304
  relation,
356
305
  person,
357
- context,
358
306
  );
359
307
  if (recording.reached.size > 0) {
360
308
  skipped.push({
@@ -371,7 +319,7 @@ export function storeTestCases(
371
319
  assert.equal(
372
320
  actual,
373
321
  expected,
374
- `${document.name} :: ${test.name}: ${item.user} ${relation} ${item.object} (allow: ${String(context.allow)}) expected ${String(expected)}, got ${String(actual)}`,
322
+ `${document.name} :: ${test.name}: ${item.user} ${relation} ${item.object} expected ${String(expected)}, got ${String(actual)}`,
375
323
  );
376
324
  }
377
325
  }
@@ -389,10 +337,9 @@ interface RecordingTupleSource extends TupleSource {
389
337
  /**
390
338
  * A view of `inner` that records, for one walk, every subject type the
391
339
  * evaluator read that `model` does not declare. A person
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.
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.
396
343
  */
397
344
  function recordUndeclaredTypes(
398
345
  inner: TupleSource,
@@ -423,8 +370,6 @@ function resolvedTypeOf(subject: Subject): string | undefined {
423
370
  : subject.object.type;
424
371
  case "userset":
425
372
  return subject.object.type;
426
- case "wildcard":
427
- return undefined;
428
373
  default: {
429
374
  const exhaustive: never = subject;
430
375
  throw new Error(`unknown subject form: ${JSON.stringify(exhaustive)}`);
@@ -433,14 +378,10 @@ function resolvedTypeOf(subject: Subject): string | undefined {
433
378
  }
434
379
 
435
380
  function tupleOf(entry: StoreTestTuple): Tuple {
436
- const subject = parseSubject(entry.user);
437
381
  return {
438
382
  object: parseObjectRef(entry.object),
439
383
  relation: entry.relation,
440
- subject:
441
- subject.form === "wildcard" && entry.condition !== undefined
442
- ? { ...subject, condition: entry.condition.name }
443
- : subject,
384
+ subject: parseSubject(entry.user),
444
385
  };
445
386
  }
446
387
 
@@ -1,9 +1,11 @@
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, 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
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
7
9
  * stored: open source derives every tuple from a row (derived-tuples.ts)
8
10
  * or reads it from an IamPolicy row, and this module only names what a
9
11
  * tuple IS.
@@ -18,28 +20,18 @@
18
20
  * The type is named here; the construction (`personFor`, `resolvePerson`)
19
21
  * is person.ts's, so this module stays vocabulary with no I/O.
20
22
  *
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.
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.
34
29
  */
35
30
  import type { Message } from "@bufbuild/protobuf";
36
31
 
37
32
  /** The FGA type name of the kind every person is: `identity_account:<id>`. */
38
33
  export const ACCOUNT_TYPE = "identity_account";
39
34
 
40
- /** The one condition the model declares; it guards the public wildcard. */
41
- export const ALLOW_PUBLIC_CONDITION = "allow_public";
42
-
43
35
  /** `type:id` — an object of the model. */
44
36
  export interface ObjectRef {
45
37
  readonly type: string;
@@ -55,12 +47,6 @@ export type Subject =
55
47
  readonly form: "userset";
56
48
  readonly object: ObjectRef;
57
49
  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;
64
50
  };
65
51
 
66
52
  export interface Tuple {
@@ -97,11 +83,6 @@ export interface Person {
97
83
  readonly aliases: ReadonlySet<string>;
98
84
  }
99
85
 
100
- /** The condition parameters of a check (the module header). */
101
- export interface CheckContext {
102
- readonly allow: boolean;
103
- }
104
-
105
86
  // ---------------------------------------------------------------------------
106
87
  // The string notation.
107
88
  // ---------------------------------------------------------------------------
@@ -120,10 +101,8 @@ export function formatObjectRef(object: ObjectRef): string {
120
101
  }
121
102
 
122
103
  /**
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).
104
+ * `type:id` or `type:id#relation` → Subject. `type:*` is refused (the
105
+ * module header): no line of the model admits a wildcard subject.
127
106
  */
128
107
  export function parseSubject(text: string): Subject {
129
108
  const hash = text.indexOf("#");
@@ -136,7 +115,9 @@ export function parseSubject(text: string): Subject {
136
115
  }
137
116
  const object = parseObjectRef(text);
138
117
  if (object.id === "*") {
139
- return { form: "wildcard", type: object.type, condition: undefined };
118
+ throw new Error(
119
+ `wildcard subject '${text}' is not admitted: the model declares no wildcard line`,
120
+ );
140
121
  }
141
122
  return { form: "object", object };
142
123
  }
@@ -147,10 +128,6 @@ export function formatSubject(subject: Subject): string {
147
128
  return formatObjectRef(subject.object);
148
129
  case "userset":
149
130
  return `${formatObjectRef(subject.object)}#${subject.relation}`;
150
- case "wildcard":
151
- return subject.condition === undefined
152
- ? `${subject.type}:*`
153
- : `${subject.type}:* with ${subject.condition}`;
154
131
  default: {
155
132
  const exhaustive: never = subject;
156
133
  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
+ }