primitive-admin 1.2.0-alpha.0 → 1.2.0-alpha.10

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 (412) hide show
  1. package/README.md +108 -136
  2. package/assets/skill/skills/primitive-platform/SKILL.md +37 -4
  3. package/dist/bin/primitive.js +30 -35
  4. package/dist/bin/primitive.js.map +1 -1
  5. package/dist/src/commands/agent-sessions.d.ts +133 -0
  6. package/dist/src/commands/agent-sessions.js +792 -0
  7. package/dist/src/commands/agent-sessions.js.map +1 -0
  8. package/dist/src/commands/analytics.js +0 -148
  9. package/dist/src/commands/analytics.js.map +1 -1
  10. package/dist/src/commands/apps-children.d.ts +54 -0
  11. package/dist/src/commands/apps-children.js +631 -0
  12. package/dist/src/commands/apps-children.js.map +1 -0
  13. package/dist/src/commands/apps.d.ts +6 -0
  14. package/dist/src/commands/apps.js +131 -9
  15. package/dist/src/commands/apps.js.map +1 -1
  16. package/dist/src/commands/auth-sessions.d.ts +3 -3
  17. package/dist/src/commands/auth-sessions.js +117 -23
  18. package/dist/src/commands/auth-sessions.js.map +1 -1
  19. package/dist/src/commands/auth.js +460 -36
  20. package/dist/src/commands/auth.js.map +1 -1
  21. package/dist/src/commands/collections.js +316 -256
  22. package/dist/src/commands/collections.js.map +1 -1
  23. package/dist/src/commands/config.d.ts +1 -1
  24. package/dist/src/commands/config.js +60 -28
  25. package/dist/src/commands/config.js.map +1 -1
  26. package/dist/src/commands/connections.js +12 -17
  27. package/dist/src/commands/connections.js.map +1 -1
  28. package/dist/src/commands/database-type-configs.js +2 -76
  29. package/dist/src/commands/database-type-configs.js.map +1 -1
  30. package/dist/src/commands/databases.d.ts +9 -0
  31. package/dist/src/commands/databases.js +228 -566
  32. package/dist/src/commands/databases.js.map +1 -1
  33. package/dist/src/commands/db3-types.d.ts +15 -0
  34. package/dist/src/commands/db3-types.js +157 -0
  35. package/dist/src/commands/db3-types.js.map +1 -0
  36. package/dist/src/commands/db3.d.ts +12 -0
  37. package/dist/src/commands/db3.js +1052 -0
  38. package/dist/src/commands/db3.js.map +1 -0
  39. package/dist/src/commands/documents.d.ts +121 -2
  40. package/dist/src/commands/documents.js +1601 -1251
  41. package/dist/src/commands/documents.js.map +1 -1
  42. package/dist/src/commands/env.d.ts +9 -1
  43. package/dist/src/commands/env.js +164 -49
  44. package/dist/src/commands/env.js.map +1 -1
  45. package/dist/src/commands/function-trigger-commands.d.ts +28 -0
  46. package/dist/src/commands/function-trigger-commands.js +341 -0
  47. package/dist/src/commands/function-trigger-commands.js.map +1 -0
  48. package/dist/src/commands/functions.js +297 -24
  49. package/dist/src/commands/functions.js.map +1 -1
  50. package/dist/src/commands/groups.js +150 -49
  51. package/dist/src/commands/groups.js.map +1 -1
  52. package/dist/src/commands/guides.js +5 -5
  53. package/dist/src/commands/init.d.ts +7 -0
  54. package/dist/src/commands/init.js +23 -5
  55. package/dist/src/commands/init.js.map +1 -1
  56. package/dist/src/commands/integrations.js +14 -8
  57. package/dist/src/commands/integrations.js.map +1 -1
  58. package/dist/src/commands/metadata-category-configs.d.ts +2 -2
  59. package/dist/src/commands/metadata-category-configs.js +2 -2
  60. package/dist/src/commands/prompts.d.ts +7 -0
  61. package/dist/src/commands/prompts.js +48 -5
  62. package/dist/src/commands/prompts.js.map +1 -1
  63. package/dist/src/commands/scripts.d.ts +9 -17
  64. package/dist/src/commands/scripts.js +24 -479
  65. package/dist/src/commands/scripts.js.map +1 -1
  66. package/dist/src/commands/sessions.js +3 -8
  67. package/dist/src/commands/sessions.js.map +1 -1
  68. package/dist/src/commands/sync-app-settings.d.ts +36 -8
  69. package/dist/src/commands/sync-app-settings.js +109 -23
  70. package/dist/src/commands/sync-app-settings.js.map +1 -1
  71. package/dist/src/commands/sync.d.ts +436 -613
  72. package/dist/src/commands/sync.js +2604 -5977
  73. package/dist/src/commands/sync.js.map +1 -1
  74. package/dist/src/commands/tokens.js +4 -9
  75. package/dist/src/commands/tokens.js.map +1 -1
  76. package/dist/src/commands/users.js +77 -2
  77. package/dist/src/commands/users.js.map +1 -1
  78. package/dist/src/commands/workflows.d.ts +5 -83
  79. package/dist/src/commands/workflows.js +57 -2282
  80. package/dist/src/commands/workflows.js.map +1 -1
  81. package/dist/src/lib/aggregate-output.d.ts +25 -0
  82. package/dist/src/lib/aggregate-output.js +53 -0
  83. package/dist/src/lib/aggregate-output.js.map +1 -0
  84. package/dist/src/lib/api-client.d.ts +681 -581
  85. package/dist/src/lib/api-client.js +880 -851
  86. package/dist/src/lib/api-client.js.map +1 -1
  87. package/dist/src/lib/app-settings-descriptor.d.ts +47 -1
  88. package/dist/src/lib/app-settings-descriptor.js +114 -10
  89. package/dist/src/lib/app-settings-descriptor.js.map +1 -1
  90. package/dist/src/lib/auth-fixtures.d.ts +44 -0
  91. package/dist/src/lib/auth-fixtures.js +61 -0
  92. package/dist/src/lib/auth-fixtures.js.map +1 -0
  93. package/dist/src/lib/auth-flow.d.ts +41 -1
  94. package/dist/src/lib/auth-flow.js +70 -16
  95. package/dist/src/lib/auth-flow.js.map +1 -1
  96. package/dist/src/lib/block-layout.d.ts +12 -23
  97. package/dist/src/lib/block-layout.js +26 -92
  98. package/dist/src/lib/block-layout.js.map +1 -1
  99. package/dist/src/lib/block-selector.d.ts +2 -2
  100. package/dist/src/lib/block-selector.js +2 -2
  101. package/dist/src/lib/child-apps-local.d.ts +164 -0
  102. package/dist/src/lib/child-apps-local.js +258 -0
  103. package/dist/src/lib/child-apps-local.js.map +1 -0
  104. package/dist/src/lib/ci-session-request.d.ts +22 -0
  105. package/dist/src/lib/ci-session-request.js +51 -0
  106. package/dist/src/lib/ci-session-request.js.map +1 -0
  107. package/dist/src/lib/codegen-shared/generatedFiles.d.ts +10 -8
  108. package/dist/src/lib/codegen-shared/generatedFiles.js +21 -15
  109. package/dist/src/lib/codegen-shared/generatedFiles.js.map +1 -1
  110. package/dist/src/lib/codegen-shared/prettierStable.d.ts +1 -1
  111. package/dist/src/lib/codegen-shared/prettierStable.js +1 -1
  112. package/dist/src/lib/codegen-shared/resolveCodegenSourceDir.d.ts +2 -2
  113. package/dist/src/lib/codegen-shared/resolveCodegenSourceDir.js +2 -2
  114. package/dist/src/lib/collection-export.d.ts +76 -15
  115. package/dist/src/lib/collection-export.js +116 -23
  116. package/dist/src/lib/collection-export.js.map +1 -1
  117. package/dist/src/lib/collection-principal.d.ts +22 -0
  118. package/dist/src/lib/collection-principal.js +35 -0
  119. package/dist/src/lib/collection-principal.js.map +1 -0
  120. package/dist/src/lib/config-object-descriptor.d.ts +3 -22
  121. package/dist/src/lib/config-object-descriptor.js +56 -291
  122. package/dist/src/lib/config-object-descriptor.js.map +1 -1
  123. package/dist/src/lib/config-payload.js +2 -3
  124. package/dist/src/lib/config-payload.js.map +1 -1
  125. package/dist/src/lib/config-surface.d.ts +5 -7
  126. package/dist/src/lib/config-surface.js +18 -40
  127. package/dist/src/lib/config-surface.js.map +1 -1
  128. package/dist/src/lib/connections-list.d.ts +10 -0
  129. package/dist/src/lib/connections-list.js +18 -0
  130. package/dist/src/lib/connections-list.js.map +1 -0
  131. package/dist/src/lib/credentials-home.d.ts +58 -0
  132. package/dist/src/lib/credentials-home.js +204 -0
  133. package/dist/src/lib/credentials-home.js.map +1 -0
  134. package/dist/src/lib/credentials-lock.d.ts +88 -0
  135. package/dist/src/lib/credentials-lock.js +302 -0
  136. package/dist/src/lib/credentials-lock.js.map +1 -0
  137. package/dist/src/lib/credentials-store.d.ts +179 -24
  138. package/dist/src/lib/credentials-store.js +712 -77
  139. package/dist/src/lib/credentials-store.js.map +1 -1
  140. package/dist/src/lib/csv.d.ts +8 -9
  141. package/dist/src/lib/csv.js +3 -4
  142. package/dist/src/lib/csv.js.map +1 -1
  143. package/dist/src/lib/custodian.d.ts +8 -0
  144. package/dist/src/lib/custodian.js +18 -0
  145. package/dist/src/lib/custodian.js.map +1 -0
  146. package/dist/src/lib/db-codegen/dbGenerator.js +5 -16
  147. package/dist/src/lib/db-codegen/dbGenerator.js.map +1 -1
  148. package/dist/src/lib/db-codegen/dbNaming.d.ts +5 -13
  149. package/dist/src/lib/db-codegen/dbNaming.js +5 -17
  150. package/dist/src/lib/db-codegen/dbNaming.js.map +1 -1
  151. package/dist/src/lib/db-codegen/dbTemplates.d.ts +7 -56
  152. package/dist/src/lib/db-codegen/dbTemplates.js +25 -125
  153. package/dist/src/lib/db-codegen/dbTemplates.js.map +1 -1
  154. package/dist/src/lib/db-codegen/dbTsTypes.d.ts +2 -2
  155. package/dist/src/lib/db-codegen/dbTsTypes.js +2 -2
  156. package/dist/src/lib/db-codegen/dbTypeIR.d.ts +67 -2
  157. package/dist/src/lib/db-codegen/dbTypeIR.js +13 -2
  158. package/dist/src/lib/db-codegen/dbTypeIR.js.map +1 -1
  159. package/dist/src/lib/db3-export.d.ts +231 -0
  160. package/dist/src/lib/db3-export.js +510 -0
  161. package/dist/src/lib/db3-export.js.map +1 -0
  162. package/dist/src/lib/db3-index-output.d.ts +27 -0
  163. package/dist/src/lib/db3-index-output.js +49 -0
  164. package/dist/src/lib/db3-index-output.js.map +1 -0
  165. package/dist/src/lib/document-export-lookups.d.ts +31 -0
  166. package/dist/src/lib/document-export-lookups.js +53 -0
  167. package/dist/src/lib/document-export-lookups.js.map +1 -0
  168. package/dist/src/lib/document-export-permissions.d.ts +7 -5
  169. package/dist/src/lib/document-export-permissions.js +7 -5
  170. package/dist/src/lib/document-export-permissions.js.map +1 -1
  171. package/dist/src/lib/document-export-record-log.d.ts +57 -0
  172. package/dist/src/lib/document-export-record-log.js +215 -0
  173. package/dist/src/lib/document-export-record-log.js.map +1 -0
  174. package/dist/src/lib/document-ingest-artifact.d.ts +26 -2
  175. package/dist/src/lib/document-ingest-artifact.js +42 -8
  176. package/dist/src/lib/document-ingest-artifact.js.map +1 -1
  177. package/dist/src/lib/document-ingest-format2.d.ts +36 -0
  178. package/dist/src/lib/document-ingest-format2.js +78 -0
  179. package/dist/src/lib/document-ingest-format2.js.map +1 -0
  180. package/dist/src/lib/document-ingest-input.d.ts +9 -0
  181. package/dist/src/lib/document-ingest-input.js +28 -1
  182. package/dist/src/lib/document-ingest-input.js.map +1 -1
  183. package/dist/src/lib/document-ingest-rows.d.ts +16 -0
  184. package/dist/src/lib/document-ingest-rows.js +17 -0
  185. package/dist/src/lib/document-ingest-rows.js.map +1 -1
  186. package/dist/src/lib/document-ingest.d.ts +33 -1
  187. package/dist/src/lib/document-ingest.js +49 -0
  188. package/dist/src/lib/document-ingest.js.map +1 -1
  189. package/dist/src/lib/document-schema-rows.d.ts +42 -0
  190. package/dist/src/lib/document-schema-rows.js +53 -0
  191. package/dist/src/lib/document-schema-rows.js.map +1 -0
  192. package/dist/src/lib/env-resolver-core.d.ts +70 -2
  193. package/dist/src/lib/env-resolver-core.js +137 -20
  194. package/dist/src/lib/env-resolver-core.js.map +1 -1
  195. package/dist/src/lib/env-resolver.d.ts +14 -2
  196. package/dist/src/lib/env-resolver.js +32 -10
  197. package/dist/src/lib/env-resolver.js.map +1 -1
  198. package/dist/src/lib/function-authorize-check.d.ts +142 -0
  199. package/dist/src/lib/function-authorize-check.js +310 -0
  200. package/dist/src/lib/function-authorize-check.js.map +1 -0
  201. package/dist/src/lib/function-authorize-view.d.ts +15 -0
  202. package/dist/src/lib/function-authorize-view.js +50 -0
  203. package/dist/src/lib/function-authorize-view.js.map +1 -0
  204. package/dist/src/lib/function-bundle.d.ts +38 -18
  205. package/dist/src/lib/function-bundle.js +96 -35
  206. package/dist/src/lib/function-bundle.js.map +1 -1
  207. package/dist/src/lib/function-collect.d.ts +75 -12
  208. package/dist/src/lib/function-collect.js +427 -12
  209. package/dist/src/lib/function-collect.js.map +1 -1
  210. package/dist/src/lib/function-db-types.d.ts +29 -0
  211. package/dist/src/lib/function-db-types.js +117 -7
  212. package/dist/src/lib/function-db-types.js.map +1 -1
  213. package/dist/src/lib/function-document-types.d.ts +5 -3
  214. package/dist/src/lib/function-document-types.js +10 -14
  215. package/dist/src/lib/function-document-types.js.map +1 -1
  216. package/dist/src/lib/function-lint.d.ts +34 -0
  217. package/dist/src/lib/function-lint.js +112 -0
  218. package/dist/src/lib/function-lint.js.map +1 -0
  219. package/dist/src/lib/function-log-row.js +21 -2
  220. package/dist/src/lib/function-log-row.js.map +1 -1
  221. package/dist/src/lib/function-run.d.ts +16 -21
  222. package/dist/src/lib/function-run.js +37 -30
  223. package/dist/src/lib/function-run.js.map +1 -1
  224. package/dist/src/lib/function-schema-codegen.d.ts +1 -1
  225. package/dist/src/lib/function-schema-codegen.js +5 -5
  226. package/dist/src/lib/function-schema-codegen.js.map +1 -1
  227. package/dist/src/lib/function-source-roots.d.ts +67 -0
  228. package/dist/src/lib/function-source-roots.js +148 -0
  229. package/dist/src/lib/function-source-roots.js.map +1 -0
  230. package/dist/src/lib/function-sync.d.ts +108 -22
  231. package/dist/src/lib/function-sync.js +127 -39
  232. package/dist/src/lib/function-sync.js.map +1 -1
  233. package/dist/src/lib/function-trigger-listing.d.ts +11 -6
  234. package/dist/src/lib/function-trigger-listing.js +32 -11
  235. package/dist/src/lib/function-trigger-listing.js.map +1 -1
  236. package/dist/src/lib/function-typecheck.d.ts +37 -2
  237. package/dist/src/lib/function-typecheck.js +210 -28
  238. package/dist/src/lib/function-typecheck.js.map +1 -1
  239. package/dist/src/lib/function-versions.d.ts +2 -0
  240. package/dist/src/lib/function-versions.js +1 -0
  241. package/dist/src/lib/function-versions.js.map +1 -1
  242. package/dist/src/lib/generated-allowlist.js +0 -20
  243. package/dist/src/lib/generated-allowlist.js.map +1 -1
  244. package/dist/src/lib/generated-config-surfaces.d.ts +703 -201
  245. package/dist/src/lib/generated-config-surfaces.js +2335 -2033
  246. package/dist/src/lib/generated-config-surfaces.js.map +1 -1
  247. package/dist/src/lib/generated-sdk-types.d.ts +1 -1
  248. package/dist/src/lib/generated-sdk-types.js +1 -1
  249. package/dist/src/lib/generated-sdk-types.js.map +1 -1
  250. package/dist/src/lib/init-adopt.js +12 -3
  251. package/dist/src/lib/init-adopt.js.map +1 -1
  252. package/dist/src/lib/init-login-note.d.ts +13 -0
  253. package/dist/src/lib/init-login-note.js +22 -0
  254. package/dist/src/lib/init-login-note.js.map +1 -0
  255. package/dist/src/lib/json-value.d.ts +6 -0
  256. package/dist/src/lib/json-value.js +13 -0
  257. package/dist/src/lib/json-value.js.map +1 -0
  258. package/dist/src/lib/load-routes.d.ts +33 -0
  259. package/dist/src/lib/load-routes.js +35 -0
  260. package/dist/src/lib/load-routes.js.map +1 -0
  261. package/dist/src/lib/local-state.d.ts +38 -7
  262. package/dist/src/lib/local-state.js +172 -49
  263. package/dist/src/lib/local-state.js.map +1 -1
  264. package/dist/src/lib/local-test-cases.d.ts +5 -2
  265. package/dist/src/lib/local-test-cases.js +7 -4
  266. package/dist/src/lib/local-test-cases.js.map +1 -1
  267. package/dist/src/lib/log-inspection.d.ts +42 -4
  268. package/dist/src/lib/log-inspection.js +37 -5
  269. package/dist/src/lib/log-inspection.js.map +1 -1
  270. package/dist/src/lib/login-exchange-check.d.ts +18 -0
  271. package/dist/src/lib/login-exchange-check.js +27 -0
  272. package/dist/src/lib/login-exchange-check.js.map +1 -0
  273. package/dist/src/lib/login-scope-lines.d.ts +25 -0
  274. package/dist/src/lib/login-scope-lines.js +45 -0
  275. package/dist/src/lib/login-scope-lines.js.map +1 -0
  276. package/dist/src/lib/logout-admin-session.d.ts +2 -1
  277. package/dist/src/lib/logout-admin-session.js +7 -1
  278. package/dist/src/lib/logout-admin-session.js.map +1 -1
  279. package/dist/src/lib/object-status-filter.d.ts +9 -1
  280. package/dist/src/lib/object-status-filter.js +9 -1
  281. package/dist/src/lib/object-status-filter.js.map +1 -1
  282. package/dist/src/lib/output.js +2 -0
  283. package/dist/src/lib/output.js.map +1 -1
  284. package/dist/src/lib/paginate.d.ts +0 -5
  285. package/dist/src/lib/paginate.js +0 -5
  286. package/dist/src/lib/paginate.js.map +1 -1
  287. package/dist/src/lib/pin-refusal-hint.d.ts +12 -0
  288. package/dist/src/lib/pin-refusal-hint.js +35 -0
  289. package/dist/src/lib/pin-refusal-hint.js.map +1 -0
  290. package/dist/src/lib/primitive-env-plugin.d.ts +99 -0
  291. package/dist/src/lib/primitive-env-plugin.js +408 -0
  292. package/dist/src/lib/primitive-env-plugin.js.map +1 -0
  293. package/dist/src/lib/project-config.d.ts +7 -0
  294. package/dist/src/lib/project-config.js +22 -1
  295. package/dist/src/lib/project-config.js.map +1 -1
  296. package/dist/src/lib/project-login-apps.d.ts +8 -0
  297. package/dist/src/lib/project-login-apps.js +27 -0
  298. package/dist/src/lib/project-login-apps.js.map +1 -0
  299. package/dist/src/lib/prompt-schema-codegen.d.ts +10 -3
  300. package/dist/src/lib/prompt-schema-codegen.js +52 -11
  301. package/dist/src/lib/prompt-schema-codegen.js.map +1 -1
  302. package/dist/src/lib/pull-write.d.ts +31 -0
  303. package/dist/src/lib/pull-write.js +49 -0
  304. package/dist/src/lib/pull-write.js.map +1 -0
  305. package/dist/src/lib/push-failure-report.d.ts +45 -0
  306. package/dist/src/lib/push-failure-report.js +57 -0
  307. package/dist/src/lib/push-failure-report.js.map +1 -0
  308. package/dist/src/lib/records-verbs.d.ts +69 -0
  309. package/dist/src/lib/records-verbs.js +40 -0
  310. package/dist/src/lib/records-verbs.js.map +1 -0
  311. package/dist/src/lib/refresh-admin-credentials.d.ts +7 -0
  312. package/dist/src/lib/refresh-admin-credentials.js +16 -4
  313. package/dist/src/lib/refresh-admin-credentials.js.map +1 -1
  314. package/dist/src/lib/root-import-decision.d.ts +65 -0
  315. package/dist/src/lib/root-import-decision.js +109 -0
  316. package/dist/src/lib/root-import-decision.js.map +1 -0
  317. package/dist/src/lib/run-status.d.ts +1 -1
  318. package/dist/src/lib/run-status.generated.d.ts +2 -2
  319. package/dist/src/lib/run-status.generated.js +3 -1
  320. package/dist/src/lib/run-status.generated.js.map +1 -1
  321. package/dist/src/lib/run-status.js +1 -1
  322. package/dist/src/lib/schema-codegen/generated-schema-descriptor.d.ts +129 -0
  323. package/dist/src/lib/schema-codegen/generated-schema-descriptor.js +269 -0
  324. package/dist/src/lib/schema-codegen/generated-schema-descriptor.js.map +1 -0
  325. package/dist/src/lib/schema-codegen/naming.d.ts +35 -0
  326. package/dist/src/lib/schema-codegen/naming.js +83 -0
  327. package/dist/src/lib/schema-codegen/naming.js.map +1 -0
  328. package/dist/src/lib/schema-codegen/schemaToTs.d.ts +82 -0
  329. package/dist/src/lib/schema-codegen/schemaToTs.js +305 -0
  330. package/dist/src/lib/schema-codegen/schemaToTs.js.map +1 -0
  331. package/dist/src/lib/scope-request.d.ts +84 -0
  332. package/dist/src/lib/scope-request.js +235 -0
  333. package/dist/src/lib/scope-request.js.map +1 -0
  334. package/dist/src/lib/session-id.d.ts +9 -0
  335. package/dist/src/lib/session-id.js +28 -0
  336. package/dist/src/lib/session-id.js.map +1 -0
  337. package/dist/src/lib/snapshot-build-rows.d.ts +21 -0
  338. package/dist/src/lib/snapshot-build-rows.js +22 -0
  339. package/dist/src/lib/snapshot-build-rows.js.map +1 -1
  340. package/dist/src/lib/snapshot-build.js +8 -3
  341. package/dist/src/lib/snapshot-build.js.map +1 -1
  342. package/dist/src/lib/snapshots.d.ts +17 -1
  343. package/dist/src/lib/snapshots.js +50 -2
  344. package/dist/src/lib/snapshots.js.map +1 -1
  345. package/dist/src/lib/swift-codegen/agentGenerator.d.ts +6 -2
  346. package/dist/src/lib/swift-codegen/agentGenerator.js +57 -5
  347. package/dist/src/lib/swift-codegen/agentGenerator.js.map +1 -1
  348. package/dist/src/lib/swift-codegen/banners.d.ts +34 -7
  349. package/dist/src/lib/swift-codegen/banners.js +37 -7
  350. package/dist/src/lib/swift-codegen/banners.js.map +1 -1
  351. package/dist/src/lib/swift-codegen/dbGenerator.d.ts +4 -3
  352. package/dist/src/lib/swift-codegen/dbGenerator.js +14 -117
  353. package/dist/src/lib/swift-codegen/dbGenerator.js.map +1 -1
  354. package/dist/src/lib/swift-codegen/functionGenerator.d.ts +3 -3
  355. package/dist/src/lib/swift-codegen/functionGenerator.js +23 -18
  356. package/dist/src/lib/swift-codegen/functionGenerator.js.map +1 -1
  357. package/dist/src/lib/swift-codegen/generator.d.ts +5 -5
  358. package/dist/src/lib/swift-codegen/generator.js +35 -40
  359. package/dist/src/lib/swift-codegen/generator.js.map +1 -1
  360. package/dist/src/lib/swift-codegen/schemaToSwift.d.ts +3 -3
  361. package/dist/src/lib/swift-codegen/schemaToSwift.js +4 -4
  362. package/dist/src/lib/swift-codegen/schemaToSwift.js.map +1 -1
  363. package/dist/src/lib/swift-codegen/swiftKeys.d.ts +19 -0
  364. package/dist/src/lib/swift-codegen/swiftKeys.js +31 -0
  365. package/dist/src/lib/swift-codegen/swiftKeys.js.map +1 -0
  366. package/dist/src/lib/sync-paths.d.ts +35 -0
  367. package/dist/src/lib/sync-paths.js +57 -5
  368. package/dist/src/lib/sync-paths.js.map +1 -1
  369. package/dist/src/lib/sync-resource-types.d.ts +26 -26
  370. package/dist/src/lib/sync-resource-types.js +61 -189
  371. package/dist/src/lib/sync-resource-types.js.map +1 -1
  372. package/dist/src/lib/sync-selectors.d.ts +5 -1
  373. package/dist/src/lib/sync-selectors.js +23 -13
  374. package/dist/src/lib/sync-selectors.js.map +1 -1
  375. package/dist/src/lib/test-case-file-names.d.ts +3 -5
  376. package/dist/src/lib/test-case-file-names.js +3 -5
  377. package/dist/src/lib/test-case-file-names.js.map +1 -1
  378. package/dist/src/lib/toml-database-config.d.ts +15 -75
  379. package/dist/src/lib/toml-database-config.js +17 -306
  380. package/dist/src/lib/toml-database-config.js.map +1 -1
  381. package/dist/src/lib/transient-retry.d.ts +43 -0
  382. package/dist/src/lib/transient-retry.js +94 -0
  383. package/dist/src/lib/transient-retry.js.map +1 -0
  384. package/dist/src/lib/vars-override.d.ts +105 -0
  385. package/dist/src/lib/vars-override.js +143 -0
  386. package/dist/src/lib/vars-override.js.map +1 -0
  387. package/dist/src/lib/webhook-capture.d.ts +43 -0
  388. package/dist/src/lib/webhook-capture.js +91 -0
  389. package/dist/src/lib/webhook-capture.js.map +1 -0
  390. package/dist/src/lib/webhook-deliver.d.ts +18 -20
  391. package/dist/src/lib/webhook-deliver.js +34 -89
  392. package/dist/src/lib/webhook-deliver.js.map +1 -1
  393. package/dist/src/lib/webhook-test-run.d.ts +32 -0
  394. package/dist/src/lib/webhook-test-run.js +108 -0
  395. package/dist/src/lib/webhook-test-run.js.map +1 -0
  396. package/dist/src/lib/workflow-codegen/generator.d.ts +7 -7
  397. package/dist/src/lib/workflow-codegen/generator.js +16 -23
  398. package/dist/src/lib/workflow-codegen/generator.js.map +1 -1
  399. package/dist/src/lib/workflow-codegen/invokerIR.d.ts +6 -12
  400. package/dist/src/lib/workflow-codegen/invokerIR.js +4 -5
  401. package/dist/src/lib/workflow-codegen/invokerIR.js.map +1 -1
  402. package/dist/src/lib/workflow-toml-validator.d.ts +2 -0
  403. package/dist/src/lib/workflow-toml-validator.js +28 -1
  404. package/dist/src/lib/workflow-toml-validator.js.map +1 -1
  405. package/dist/src/types/index.d.ts +30 -37
  406. package/dist/src/validators.d.ts +7 -55
  407. package/dist/src/validators.js +7 -53
  408. package/dist/src/validators.js.map +1 -1
  409. package/dist/src/vite.d.ts +17 -0
  410. package/dist/src/vite.js +18 -0
  411. package/dist/src/vite.js.map +1 -0
  412. package/package.json +20 -12
@@ -7,7 +7,9 @@ import { confirmPrompt } from "../lib/confirm-prompt.js";
7
7
  import { pageCursorOption, pageLimitOption, parsePageLimit, printEmptyPage, printPageHint, } from "../lib/list-options.js";
8
8
  import { normalizeCliListEnvelope, wholeListEnvelope, } from "../lib/paginate.js";
9
9
  import { resolveOwnerUserId } from "../lib/resolve-owner.js";
10
- import { COLLECTION_EXPORT_FILENAME, COLLECTION_EXPORT_VERSION, duplicateNameSources, emptyImportSummary, grantDisposition, grantUpdateLine, groupTargetsByName, importExitCode, planCollection, readCollectionExportFile, serverReason, } from "../lib/collection-export.js";
10
+ import { parseCustodianOrExit } from "../lib/custodian.js";
11
+ import { parseCollectionPrincipal, parseCollectionRole } from "../lib/collection-principal.js";
12
+ import { COLLECTION_EXPORT_FILENAME, COLLECTION_EXPORT_VERSION, duplicateNameSources, emptyImportSummary, grantDisposition, grantUpdateLine, sameGrantGroup, groupTargetsByName, importExitCode, planCollection, readCollectionExportFile, rowCustodian, custodianUserId, permissionForRole, serverReason, } from "../lib/collection-export.js";
11
13
  export function registerCollectionsCommands(program) {
12
14
  const collections = program
13
15
  .command("collections")
@@ -18,12 +20,14 @@ Examples:
18
20
  $ primitive collections create "Sprint 12"
19
21
  $ primitive collections create "Sprint 12" --owner user@example.com
20
22
  $ primitive collections get <collection-id>
23
+ $ primitive collections list --custodian user:<user-id>
24
+ $ primitive collections transfer <collection-id> user:<user-id>
21
25
  $ primitive collections documents list <collection-id>
22
26
  $ primitive collections documents add <collection-id> <document-id>
23
- $ primitive collections access <collection-id>
24
- $ primitive collections share <collection-id> --group team/engineering --permission read-write
25
27
  $ primitive collections members list <collection-id>
26
- $ primitive collections members add <collection-id> <user-id> --permission reader
28
+ $ primitive collections members add <collection-id> <user-id> --role contributor
29
+ $ primitive collections members add <collection-id> group:<group-id> --role viewer
30
+ $ primitive collections members set-role <collection-id> <user-id> editor
27
31
  $ primitive collections for-document <document-id>
28
32
  `);
29
33
  // List collections
@@ -34,18 +38,26 @@ Examples:
34
38
  .argument("[app-id]", "App ID (uses current app if not specified)")
35
39
  .option("--app <app-id>", "App ID")
36
40
  .option("--all", "List every collection in the app (admin-only). Default is collections you're a direct member of.")
41
+ .option("--custodian <custodian>", 'Only this custodian\'s collections: "app" or "user:<user-id>" (needs an admin token)')
37
42
  .addOption(pageLimitOption())
38
43
  .addOption(pageCursorOption())
39
44
  .option("--json", "Output as JSON")
40
45
  .action(async (appId, options) => {
41
46
  const limit = parsePageLimit(options.limit);
47
+ const custodian = options.custodian === undefined ? undefined : parseCustodianOrExit(options.custodian, "--custodian");
48
+ if (custodian && options.all) {
49
+ error("--custodian and --all are alternatives; pass one.");
50
+ process.exit(1);
51
+ }
42
52
  const resolvedAppId = resolveAppId(appId, options);
43
53
  const client = new ApiClient();
44
54
  try {
45
55
  const params = { limit, cursor: options.cursor };
46
- const page = normalizeCliListEnvelope(options.all
47
- ? await client.listAllCollections(resolvedAppId, params)
48
- : await client.listCollections(resolvedAppId, params));
56
+ const page = normalizeCliListEnvelope(custodian
57
+ ? await client.listCollectionsByCustodian(resolvedAppId, custodian, params)
58
+ : options.all
59
+ ? await client.listAllCollections(resolvedAppId, params)
60
+ : await client.listCollections(resolvedAppId, params));
49
61
  const list = page.items;
50
62
  if (options.json) {
51
63
  json(page);
@@ -63,7 +75,10 @@ Examples:
63
75
  { header: "CREATED", key: "createdAt", format: formatDate },
64
76
  ];
65
77
  // `permission` is only returned by the default list() endpoint
66
- if (!options.all) {
78
+ if (custodian) {
79
+ columns.splice(2, 0, { header: "CUSTODIAN", key: "custodian" });
80
+ }
81
+ else if (!options.all) {
67
82
  columns.splice(4, 0, { header: "PERMISSION", key: "permission", format: (v) => v || "—" });
68
83
  }
69
84
  console.log(formatTable(list, columns));
@@ -82,25 +97,21 @@ Examples:
82
97
  // Create collection
83
98
  collections
84
99
  .command("create")
85
- .description("Create a new collection")
100
+ .description("Create a new collection, held by the caller or by --owner")
86
101
  .argument("<name>", "Collection name")
87
102
  .option("--description <description>", "Collection description")
88
- .option("--initial-metadata <json>", "Create-time resource metadata as JSON, keyed by category name (issue #1420)")
103
+ .option("--initial-metadata <json>", "Create-time resource metadata as JSON, keyed by category name")
89
104
  .option("--app <app-id>", "App ID")
90
- .option("--owner <userId-or-email>", "User ID or email to own the collection (super-admin or assigned-console-admin tokens only)")
105
+ .option("--owner <userId-or-email>", "The user who holds the collection (its custodian and owner); needs an admin token")
91
106
  .option("--json", "Output as JSON")
92
107
  .addHelpText("after", `
93
- Ownership:
94
- An app-user token — member, admin, or owner — always creates the collection
95
- owned by the caller: the server ignores --owner for those tokens.
96
- A super-admin token, or a console-admin token assigned to this app, acts
97
- through an admin shadow app user — without --owner that shadow user owns the
98
- collection, with --owner the named user does.
99
- A console admin not assigned to this app has no access to the app at all.
100
-
101
- The owner is the collection's creator, so under the default rules they — and
102
- no other non-admin — can edit it, delete it, and manage its documents and
103
- members.
108
+ Custody:
109
+ Without --owner the caller holds the new collection: an app user's token
110
+ makes them its custodian and owner; a console admin's token gives the app
111
+ custody.
112
+ With --owner the collection is created through the admin API for that user,
113
+ who becomes its custodian and owner; this needs an admin token assigned to
114
+ the app.
104
115
 
105
116
  An email given to --owner is resolved to a user id before anything is
106
117
  created; a user who is not in the app fails the command without creating a
@@ -111,7 +122,7 @@ Ownership:
111
122
  const client = new ApiClient();
112
123
  // Resolve --owner to a userId through the one shared lookup (#2763),
113
124
  // before the create: an email nobody answers to must fail with nothing
114
- // created, not leave a collection owned by the admin.
125
+ // created.
115
126
  let ownerUserId;
116
127
  if (options.owner) {
117
128
  try {
@@ -124,8 +135,6 @@ Ownership:
124
135
  }
125
136
  try {
126
137
  const data = { name };
127
- if (ownerUserId)
128
- data.createdBy = ownerUserId;
129
138
  if (options.description)
130
139
  data.description = options.description;
131
140
  // Create-time per-category ResourceMetadata rows (issue #1420).
@@ -138,7 +147,9 @@ Ownership:
138
147
  process.exit(1);
139
148
  }
140
149
  }
141
- const result = await client.createCollection(resolvedAppId, data);
150
+ const result = ownerUserId
151
+ ? await client.createCollectionAsAdmin(resolvedAppId, { ...data, custodian: `user:${ownerUserId}` })
152
+ : await client.createCollection(resolvedAppId, data);
142
153
  if (options.json) {
143
154
  json(result);
144
155
  return;
@@ -146,9 +157,8 @@ Ownership:
146
157
  success("Collection created.");
147
158
  keyValue("Collection ID", result.collectionId);
148
159
  keyValue("Name", result.name);
149
- // Read back from the RESPONSE, not from --owner: whom the server
150
- // credited is the answer, and for a non-admin token it is the caller.
151
- keyValue("Owner", result.createdBy);
160
+ // Read back from the RESPONSE: who holds it is the server's answer.
161
+ keyValue("Custodian", result.custodian ?? "(none)");
152
162
  if (result.description)
153
163
  keyValue("Description", result.description);
154
164
  }
@@ -184,6 +194,38 @@ Ownership:
184
194
  process.exit(1);
185
195
  }
186
196
  });
197
+ // Transfer collection
198
+ collections
199
+ .command("transfer")
200
+ .description("Transfer a collection to another user or to the app")
201
+ .argument("<collection-id>", "Collection ID")
202
+ .argument("<custodian>", '"user:<user-id>" (a member of the app) or "app"')
203
+ .option("--app <app-id>", "App ID")
204
+ .option("--json", "Output as JSON")
205
+ .allowExcessArguments(false)
206
+ .addHelpText("after", `
207
+ The custodian and the owner row move together: the new custodian holds the
208
+ owner role, the previous owner stays as a manager (or, moving to the app,
209
+ loses the owner row), and no other role changes.
210
+ `)
211
+ .action(async (collectionId, custodianArg, options) => {
212
+ const to = parseCustodianOrExit(custodianArg, "custodian");
213
+ const resolvedAppId = resolveAppId(undefined, options);
214
+ const client = new ApiClient();
215
+ try {
216
+ const result = await client.transferCollection(resolvedAppId, collectionId, to);
217
+ if (options.json) {
218
+ json(result);
219
+ return;
220
+ }
221
+ success(`Collection ${collectionId} transferred.`);
222
+ printResult("Custodian", result.custodian);
223
+ }
224
+ catch (err) {
225
+ error(err.message);
226
+ process.exit(1);
227
+ }
228
+ });
187
229
  // Update collection
188
230
  collections
189
231
  .command("update")
@@ -259,130 +301,6 @@ Ownership:
259
301
  process.exit(1);
260
302
  }
261
303
  });
262
- // ---- Access command ----
263
- collections
264
- .command("access")
265
- .description("Combined view of the groups and members with access to a collection " +
266
- "(members only: 'collections members list'; group grants: 'collections share'/'unshare')")
267
- .argument("<collection-id>", "Collection ID")
268
- .option("--app <app-id>", "App ID")
269
- .option("--json", "Output as JSON")
270
- .action(async (collectionId, options) => {
271
- const resolvedAppId = resolveAppId(undefined, options);
272
- const client = new ApiClient();
273
- try {
274
- const result = await client.getCollectionAccess(resolvedAppId, collectionId);
275
- if (options.json) {
276
- json(result);
277
- return;
278
- }
279
- const groups = result.groups || [];
280
- const members = result.members || [];
281
- if (groups.length === 0 && members.length === 0) {
282
- info("No groups or members have access to this collection.");
283
- return;
284
- }
285
- if (groups.length > 0) {
286
- console.log("\nGroups:");
287
- console.log(formatTable(groups, [
288
- { header: "TYPE", key: "groupType" },
289
- { header: "ID", key: "groupId" },
290
- { header: "PERMISSION", key: "permission" },
291
- { header: "GRANTED", key: "grantedAt", format: formatDate },
292
- ]));
293
- }
294
- if (members.length > 0) {
295
- console.log("\nMembers:");
296
- console.log(formatTable(members, [
297
- { header: "USER_ID", key: "userId", format: formatId },
298
- { header: "PERMISSION", key: "permission" },
299
- ]));
300
- }
301
- }
302
- catch (err) {
303
- error(err.message);
304
- process.exit(1);
305
- }
306
- });
307
- // ---- Share / unshare (group permissions) ----
308
- collections
309
- .command("share")
310
- .description("Grant a group access to a collection")
311
- .argument("<collection-id>", "Collection ID")
312
- .requiredOption("--group <group>", "Group in format type/id (e.g. team/engineering)")
313
- .requiredOption("--permission <permission>", "Permission level (reader or read-write)")
314
- .option("--app <app-id>", "App ID")
315
- .option("--json", "Output as JSON")
316
- .action(async (collectionId, options) => {
317
- const resolvedAppId = resolveAppId(undefined, options);
318
- const client = new ApiClient();
319
- const parts = options.group.split("/");
320
- if (parts.length !== 2) {
321
- error("Group must be in format type/id (e.g. team/engineering).");
322
- process.exit(1);
323
- }
324
- const [groupType, groupId] = parts;
325
- try {
326
- const result = await client.grantCollectionGroupPermission(resolvedAppId, collectionId, {
327
- groupType,
328
- groupId,
329
- permission: options.permission,
330
- });
331
- if (options.json) {
332
- json(result);
333
- return;
334
- }
335
- success(`Granted ${options.permission} access to ${groupType}/${groupId} on collection.`);
336
- }
337
- catch (err) {
338
- error(err.message);
339
- process.exit(1);
340
- }
341
- });
342
- collections
343
- .command("unshare")
344
- .description("Revoke a group's access to a collection")
345
- .argument("<collection-id>", "Collection ID")
346
- .requiredOption("--group <group>", "Group in format type/id (e.g. team/engineering)")
347
- .option("--app <app-id>", "App ID")
348
- .option("-y, --yes", "Skip confirmation prompt")
349
- .option("--json", "Output as JSON")
350
- .action(async (collectionId, options) => {
351
- const resolvedAppId = resolveAppId(undefined, options);
352
- const parts = options.group.split("/");
353
- if (parts.length !== 2) {
354
- error("Group must be in format type/id (e.g. team/engineering).");
355
- process.exit(1);
356
- }
357
- const [groupType, groupId] = parts;
358
- if (!options.yes) {
359
- let confirm;
360
- try {
361
- confirm = await confirmPrompt(`Revoke ${groupType}/${groupId} access to collection ${collectionId}?`);
362
- }
363
- catch (err) {
364
- error(err.message);
365
- process.exit(1);
366
- }
367
- if (!confirm) {
368
- info("Cancelled.");
369
- return;
370
- }
371
- }
372
- const client = new ApiClient();
373
- try {
374
- await client.revokeCollectionGroupPermission(resolvedAppId, collectionId, groupType, groupId);
375
- if (options.json) {
376
- json({ success: true, collectionId, groupType, groupId });
377
- return;
378
- }
379
- success(`Revoked ${groupType}/${groupId} access from collection.`);
380
- }
381
- catch (err) {
382
- error(err.message);
383
- process.exit(1);
384
- }
385
- });
386
304
  // ---- Documents subcommand group ----
387
305
  const documents = collections
388
306
  .command("documents")
@@ -526,18 +444,17 @@ Ownership:
526
444
  }
527
445
  });
528
446
  // ---- Members subcommand group ----
447
+ //
448
+ // The one home for who holds a role on a collection: users and groups,
449
+ // addressed as principals (`<user-id>`, `group:<group-id>`). Writes go
450
+ // through the admin API, as the console's do, so the owner row can be
451
+ // restored and demoted from here.
529
452
  const members = collections
530
453
  .command("members")
531
- .description("Manage individual user access to a collection");
532
- // List members
533
- //
534
- // The server has no GET /collections/:id/members route — membership is read
535
- // from the access endpoint, which returns groups and members together. This
536
- // command is the members half of it, so reading "who is on this collection"
537
- // doesn't require knowing about a command named for neither (#2764).
454
+ .description("Manage a collection's members — users and groups — and their roles");
538
455
  members
539
456
  .command("list")
540
- .description("List users with direct access to a collection")
457
+ .description("List a collection's members, users and groups, with their roles")
541
458
  .argument("<collection-id>", "Collection ID")
542
459
  .option("--app <app-id>", "App ID")
543
460
  .option("--json", "Output as JSON")
@@ -545,21 +462,20 @@ Ownership:
545
462
  const resolvedAppId = resolveAppId(undefined, options);
546
463
  const client = new ApiClient();
547
464
  try {
548
- const result = await client.getCollectionAccess(resolvedAppId, collectionId);
549
- const list = result.members ?? [];
465
+ const { items } = await client.listCollectionMembers(resolvedAppId, collectionId);
550
466
  if (options.json) {
551
- // The members half of the access payload, in the one list envelope
552
- // (#3646) — the same shape `groups members list` prints (#2764).
553
- json(wholeListEnvelope(list));
467
+ // The whole membership, in the one list envelope (#3646).
468
+ json(wholeListEnvelope(items));
554
469
  return;
555
470
  }
556
- if (list.length === 0) {
557
- info("No members have direct access to this collection. Groups may still have access — see 'collections access'.");
471
+ if (items.length === 0) {
472
+ info("No user or group holds a role on this collection.");
558
473
  return;
559
474
  }
560
- console.log(formatTable(list, [
561
- { header: "USER_ID", key: "userId", format: formatId },
562
- { header: "PERMISSION", key: "permission" },
475
+ console.log(formatTable(items, [
476
+ { header: "PRINCIPAL", key: "principal" },
477
+ { header: "ROLE", key: "role" },
478
+ { header: "ADDED", key: "addedAt", format: formatDate },
563
479
  ]));
564
480
  }
565
481
  catch (err) {
@@ -567,49 +483,97 @@ Ownership:
567
483
  process.exit(1);
568
484
  }
569
485
  });
570
- // Add member
571
486
  members
572
487
  .command("add")
573
- .description("Add a user to a collection")
488
+ .description("Give a user or a group a role on a collection")
574
489
  .argument("<collection-id>", "Collection ID")
575
- .argument("<user-id>", "User ID")
576
- .requiredOption("--permission <permission>", "Permission level (reader or read-write)")
490
+ .argument("<principal>", "A user id, or group:<group-id>")
491
+ .requiredOption("--role <role>", "viewer, contributor, editor or manager (a group holds up to manager); owner restores the custodian's owner row")
577
492
  .option("--app <app-id>", "App ID")
578
493
  .option("--json", "Output as JSON")
579
- .action(async (collectionId, userId, options) => {
494
+ .allowExcessArguments(false)
495
+ .action(async (collectionId, principalArg, options) => {
496
+ let principal;
497
+ let role;
498
+ try {
499
+ principal = parseCollectionPrincipal(principalArg);
500
+ role = parseCollectionRole(options.role);
501
+ }
502
+ catch (err) {
503
+ error(err.message);
504
+ process.exit(1);
505
+ }
580
506
  const resolvedAppId = resolveAppId(undefined, options);
581
507
  const client = new ApiClient();
582
508
  try {
583
- const result = await client.addCollectionMember(resolvedAppId, collectionId, {
584
- userId,
585
- permission: options.permission,
586
- });
509
+ const result = await client.addCollectionMember(resolvedAppId, collectionId, { ...principal, role: role });
587
510
  if (options.json) {
588
511
  json(result);
589
512
  return;
590
513
  }
591
- success(`User ${userId} added to collection with ${options.permission} access.`);
514
+ success(`${principalArg} holds ${result.role ?? role} on collection ${collectionId}.`);
515
+ }
516
+ catch (err) {
517
+ error(err.message);
518
+ process.exit(1);
519
+ }
520
+ });
521
+ members
522
+ .command("set-role")
523
+ .description("Change a collection member's role")
524
+ .argument("<collection-id>", "Collection ID")
525
+ .argument("<principal>", "A user id, or group:<group-id>")
526
+ .argument("<role>", "owner, manager, editor, contributor or viewer")
527
+ .option("--app <app-id>", "App ID")
528
+ .option("--json", "Output as JSON")
529
+ .allowExcessArguments(false)
530
+ .action(async (collectionId, principalArg, roleArg, options) => {
531
+ let role;
532
+ try {
533
+ parseCollectionPrincipal(principalArg);
534
+ role = parseCollectionRole(roleArg);
535
+ }
536
+ catch (err) {
537
+ error(err.message);
538
+ process.exit(1);
539
+ }
540
+ const resolvedAppId = resolveAppId(undefined, options);
541
+ const client = new ApiClient();
542
+ try {
543
+ const result = await client.setCollectionMemberRole(resolvedAppId, collectionId, principalArg, role);
544
+ if (options.json) {
545
+ json(result);
546
+ return;
547
+ }
548
+ success(`${principalArg} is now ${result.role ?? role} on collection ${collectionId}.`);
592
549
  }
593
550
  catch (err) {
594
551
  error(err.message);
595
552
  process.exit(1);
596
553
  }
597
554
  });
598
- // Remove member
599
555
  members
600
556
  .command("remove")
601
- .description("Remove a user from a collection")
557
+ .description("Remove a user or a group from a collection")
602
558
  .argument("<collection-id>", "Collection ID")
603
- .argument("<user-id>", "User ID")
559
+ .argument("<principal>", "A user id, or group:<group-id>")
604
560
  .option("--app <app-id>", "App ID")
605
561
  .option("-y, --yes", "Skip confirmation prompt")
606
562
  .option("--json", "Output as JSON")
607
- .action(async (collectionId, userId, options) => {
563
+ .allowExcessArguments(false)
564
+ .action(async (collectionId, principalArg, options) => {
565
+ try {
566
+ parseCollectionPrincipal(principalArg);
567
+ }
568
+ catch (err) {
569
+ error(err.message);
570
+ process.exit(1);
571
+ }
608
572
  const resolvedAppId = resolveAppId(undefined, options);
609
573
  if (!options.yes) {
610
574
  let confirm;
611
575
  try {
612
- confirm = await confirmPrompt(`Remove user ${userId} from collection ${collectionId}?`);
576
+ confirm = await confirmPrompt(`Remove ${principalArg} from collection ${collectionId}?`);
613
577
  }
614
578
  catch (err) {
615
579
  error(err.message);
@@ -622,12 +586,12 @@ Ownership:
622
586
  }
623
587
  const client = new ApiClient();
624
588
  try {
625
- await client.removeCollectionMember(resolvedAppId, collectionId, userId);
589
+ await client.removeCollectionMember(resolvedAppId, collectionId, principalArg);
626
590
  if (options.json) {
627
- json({ success: true, collectionId, userId });
591
+ json({ success: true, collectionId, principal: principalArg });
628
592
  return;
629
593
  }
630
- success(`User ${userId} removed from collection.`);
594
+ success(`${principalArg} removed from collection.`);
631
595
  }
632
596
  catch (err) {
633
597
  error(err.message);
@@ -648,12 +612,13 @@ Ownership:
648
612
  * whole app. Owner and member identities travel as EMAILS wherever the server
649
613
  * can resolve one: users are app-scoped (`models.yaml`), so the same person is
650
614
  * a different userId in every app and a recorded source userId means nothing
651
- * in the target.
615
+ * in the target. Custody and roles travel as they are: `app` or the owner,
616
+ * and each member's and group's role.
652
617
  */
653
618
  function registerCollectionsExport(collections) {
654
619
  collections
655
620
  .command("export")
656
- .description("Export every collection in an app (name, owner, members, documents, group grants)")
621
+ .description("Export every collection in an app (name, custodian, members and groups with roles, documents)")
657
622
  .option("--app <app-id>", "App ID")
658
623
  .option("--output <dir>", "Output directory", "./primitive-export")
659
624
  .option("--json", "Output as JSON")
@@ -665,6 +630,9 @@ that 'documents export-all' writes, so one directory is one app's migration.
665
630
  Import it with 'primitive collections import' AFTER 'documents import', which
666
631
  preserves document ids.
667
632
 
633
+ The file records each collection's custodian (the app, or the user holding
634
+ the owner row), its settings, and every member's and group's role.
635
+
668
636
  Owner and member identities are recorded as emails wherever the server can
669
637
  resolve one — users are app-scoped, so a source app's userId means nothing in
670
638
  the target. A userId with no resolvable email is recorded as-is and will only
@@ -750,30 +718,34 @@ async function exportCollections(client, appId, options) {
750
718
  const collectionId = row.collectionId;
751
719
  try {
752
720
  const documents = await readAllCollectionDocuments(client, appId, collectionId);
753
- const access = await client.getCollectionAccess(appId, collectionId);
721
+ const roster = await client.listCollectionMembers(appId, collectionId);
754
722
  const members = [];
755
- for (const m of access?.members ?? []) {
756
- members.push({
757
- ...(await identity(m.userId)),
758
- permission: m.permission,
759
- });
723
+ const groups = [];
724
+ for (const m of roster.items) {
725
+ // `permission` and `groupType` stay beside `role`: a CLI written
726
+ // before roles reads them, and a group written before the role model
727
+ // resolves only with its type.
728
+ const permission = m.permission ?? permissionForRole(m.role);
729
+ if (m.groupId) {
730
+ // The server already leaves system groups out of the member list.
731
+ groups.push({ groupType: m.groupType, groupId: m.groupId, permission, role: m.role });
732
+ }
733
+ else if (m.userId) {
734
+ members.push({ ...(await identity(m.userId)), permission, role: m.role });
735
+ }
760
736
  }
761
- const groups = (access?.groups ?? [])
762
- // The server already filters system groups out of this response; a
763
- // `_`-prefixed type could never be re-granted anyway (it is reserved).
764
- .filter((g) => !String(g.groupType).startsWith("_"))
765
- .map((g) => ({
766
- groupType: g.groupType,
767
- groupId: g.groupId,
768
- permission: g.permission,
769
- }));
737
+ // A row created before custody has none (null); its creator holds the
738
+ // owner row and is recorded as the owner.
739
+ const ownerUserId = custodianUserId(rowCustodian(row));
770
740
  exported.push({
771
741
  collectionId,
772
742
  name: row.name,
773
743
  description: row.description ?? null,
774
744
  collectionType: row.collectionType ?? null,
775
745
  createdAt: row.createdAt ?? null,
776
- owner: row.createdBy ? await identity(row.createdBy) : null,
746
+ custodian: typeof row.custodian === "string" && row.custodian ? row.custodian : null,
747
+ owner: ownerUserId ? await identity(ownerUserId) : null,
748
+ settings: row.settings ?? null,
777
749
  documents,
778
750
  members,
779
751
  groups,
@@ -845,21 +817,34 @@ async function exportCollections(client, appId, options) {
845
817
  * go first ON PURPOSE: `addDocument` materializes every existing grant
846
818
  * onto a newly added document, so a fresh collection's access never
847
819
  * depends on the grant's own fan-out over the document list (F4).
820
+ *
821
+ * A collection is created through the admin route with its custodian — the
822
+ * owner resolved by email, or the app — so the server writes the owner row;
823
+ * members and groups are then replayed by role through the admin member
824
+ * route (#4731).
848
825
  */
849
826
  function registerCollectionsImport(collections) {
850
827
  collections
851
828
  .command("import")
852
- .description("Recreate collections from a collections.json export (owner, members, documents, group grants)")
829
+ .description("Recreate collections from a collections.json export (custodian, members and groups with roles, documents)")
853
830
  .argument("<path>", "Export directory holding collections.json, or the file itself")
854
831
  .option("--app <app-id>", "App ID")
855
- .option("--owner <userId-or-email>", "Own every imported collection with this user, instead of the owner each one records")
832
+ .option("--owner <userId-or-email>", "Give every imported collection this user as custodian and owner, instead of the custodian each one records")
856
833
  .option("--overwrite", "Merge into a collection that already carries the same name")
857
834
  .option("--dry-run", "Show the plan and the problems without writing anything")
858
835
  .option("--json", "Output as JSON")
859
836
  .addHelpText("after", `
860
- For admin tokens. Assigning an owner is admin-only, so under an app-user token
861
- the server creates every collection owned by the caller and each one whose
862
- recorded owner is somebody else is reported as an 'owner' problem.
837
+ Needs an admin token assigned to the app, checked before anything is read or
838
+ written: each collection is created through the admin API with the custodian
839
+ the file records — the app, or the owner, who holds the owner row — and
840
+ members and groups are added at the role the file records, through the admin
841
+ member routes. The custodian's own row follows the file too: a role below
842
+ owner is replayed, and a newly created collection whose file records no row
843
+ for its custodian has the owner row removed again. A member recorded as owner
844
+ who is not the custodian (with --owner, the recorded owner) is reported as an
845
+ 'owner' problem and not imported. A file written before roles is read with
846
+ reader as viewer and read-write as editor, and its owner's own read-write row
847
+ as the owner row.
863
848
 
864
849
  Run 'primitive documents import' FIRST: it preserves document ids, which is
865
850
  what the collections file refers to.
@@ -869,16 +854,16 @@ app's userId means nothing here. A recorded userId is used only when the file
869
854
  came from this same app.
870
855
 
871
856
  Matching is by name. Without --overwrite a name already in the target is
872
- skipped; with it, the collection is merged — but only when its owner, type and
873
- context id match, since none of those can be changed after creation.
857
+ skipped; with it, the collection is merged — but only when its custodian and
858
+ type match, since neither can be changed by a merge.
874
859
  A name more than one collection in the target carries is ambiguous: that
875
860
  collection is refused as a 'collection' problem naming every matching id, with
876
861
  or without --overwrite, and nothing is written for it. A name repeated inside
877
862
  the file is refused for the second and later occurrences, since matching by
878
863
  name could not tell them apart.
879
864
 
880
- A group grant the target already holds at another level is UPDATED to the level
881
- the file records, and what the group can reach follows the change. On a
865
+ A group or member the target already holds at another role is UPDATED to the
866
+ role the file records, and what the group can reach follows the change. On a
882
867
  collection holding more than 200 documents the server refuses a collection-wide
883
868
  group change whole (COLLECTION_FANOUT_LIMIT): that is reported as a 'group'
884
869
  problem, and the rest of the collection is still merged.
@@ -905,8 +890,39 @@ Exit code 1 when any per-item problem was reported. A skip is not a problem.
905
890
  function isNotFound(err) {
906
891
  return err?.statusCode === 404;
907
892
  }
893
+ /**
894
+ * The recorded type of a grant's group, sent beside its id: a group written
895
+ * before the role model carries no public id, and resolves only by its type.
896
+ */
897
+ function groupTypeOf(grant) {
898
+ return grant.groupType ? { groupType: grant.groupType } : {};
899
+ }
900
+ /**
901
+ * The import's writes all go through the admin API, so an app user's token —
902
+ * the app owner's included — could apply only part of a file (a merge's
903
+ * documents through the app route, and nothing else). Refused before any read
904
+ * of the target app (E14). The probe is one row of an admin-API read on the
905
+ * app, behind the same app-assignment gate as the admin create.
906
+ */
907
+ async function requireAdminAppAccess(client, appId) {
908
+ try {
909
+ await client.listGroupsByCustodian(appId, "app", { limit: 1 });
910
+ }
911
+ catch (err) {
912
+ const status = err?.statusCode;
913
+ if (status === 401 || status === 403 || status === 404) {
914
+ throw new Error(`collections import needs an admin token assigned to app ${appId}: it ` +
915
+ `creates collections and replays members through the admin API ` +
916
+ `(${serverReason(err)}). Log in as an admin of the app and re-run. ` +
917
+ `Nothing has been written.`);
918
+ }
919
+ throw new Error(`Checking admin access to app ${appId} failed: ${err?.message ?? err}. ` +
920
+ `Nothing has been written.`);
921
+ }
922
+ }
908
923
  async function importCollections(client, appId, importPath, options) {
909
924
  const file = readCollectionExportFile(importPath);
925
+ await requireAdminAppAccess(client, appId);
910
926
  // A file exported before the platform removed collection `contextId` (#3926)
911
927
  // still carries it. Said once for the run, on stderr, and never sent: the
912
928
  // server refuses the key, and the binding belongs in a metadata category.
@@ -1033,7 +1049,9 @@ async function importCollections(client, appId, importPath, options) {
1033
1049
  };
1034
1050
  let ownerUserId = explicitOwnerUserId ?? null;
1035
1051
  let ownerReason;
1036
- if (!ownerUserId) {
1052
+ // App custody needs no owner; `--owner` gives every collection one.
1053
+ const appCustody = !explicitOwnerUserId && source.custodian === "app";
1054
+ if (!ownerUserId && !appCustody) {
1037
1055
  try {
1038
1056
  const resolved = await resolveIdentity(source.owner);
1039
1057
  ownerUserId = resolved.userId;
@@ -1047,6 +1065,7 @@ async function importCollections(client, appId, importPath, options) {
1047
1065
  const row = planCollection(source, {
1048
1066
  ownerUserId,
1049
1067
  ownerReason,
1068
+ appCustody,
1050
1069
  existing: matches.length === 1 ? matches[0] : null,
1051
1070
  ambiguousTargetIds: matches.length > 1 ? matches.map((m) => m.collectionId) : null,
1052
1071
  duplicateOfSourceId: duplicates.get(source.collectionId) ?? null,
@@ -1058,33 +1077,36 @@ async function importCollections(client, appId, importPath, options) {
1058
1077
  planned.push({ row, source, grants: [], members: [], documents: [] });
1059
1078
  continue;
1060
1079
  }
1061
- // What the target already grants, so a merge can compare levels.
1080
+ // What the target already grants, so a merge can compare roles.
1062
1081
  let targetGrants = [];
1063
1082
  if (row.action === "merge" && row.targetCollectionId) {
1064
1083
  try {
1065
- const access = await client.getCollectionAccess(appId, row.targetCollectionId);
1066
- targetGrants = (access?.groups ?? []).map((g) => ({
1067
- groupType: g.groupType,
1068
- groupId: g.groupId,
1069
- permission: g.permission,
1084
+ const roster = await client.listCollectionMembers(appId, row.targetCollectionId);
1085
+ targetGrants = roster.items
1086
+ .filter((m) => m.groupId)
1087
+ .map((m) => ({
1088
+ groupId: m.groupId,
1089
+ ...(m.groupType ? { groupType: m.groupType } : {}),
1090
+ role: m.role,
1070
1091
  }));
1071
1092
  }
1072
1093
  catch (err) {
1073
- fatal(`the access of ${row.targetCollectionId}`, err);
1094
+ fatal(`the members of ${row.targetCollectionId}`, err);
1074
1095
  }
1075
1096
  }
1076
1097
  const grants = [];
1077
1098
  for (const grant of source.groups ?? []) {
1078
- const label = `${grant.groupType}/${grant.groupId}`;
1079
- // The server reserves the `_` prefix for its own system groups; an
1080
- // export never writes one, and re-granting one is a 400.
1081
- if (String(grant.groupType).startsWith("_")) {
1082
- warn(`${source.name}: skipping the reserved group type ${grant.groupType} ` +
1099
+ const label = `group:${grant.groupId}`;
1100
+ // The server reserves the `_` group-type prefix for its own system
1101
+ // groups; an export never writes one, and re-granting one is a 400. A
1102
+ // group id may start with `_` (only the type is reserved).
1103
+ if (String(grant.groupType ?? "").startsWith("_")) {
1104
+ warn(`${source.name}: skipping the reserved group ${grant.groupType ? `type ${grant.groupType} ` : ""}` +
1083
1105
  `(${label}); the server manages those itself.`);
1084
1106
  continue;
1085
1107
  }
1086
1108
  try {
1087
- await client.getGroup(appId, grant.groupType, grant.groupId);
1109
+ await client.getGroup(appId, grant.groupId, groupTypeOf(grant));
1088
1110
  }
1089
1111
  catch (err) {
1090
1112
  if (isNotFound(err)) {
@@ -1094,14 +1116,16 @@ async function importCollections(client, appId, importPath, options) {
1094
1116
  fatal(`group ${label}`, err);
1095
1117
  }
1096
1118
  const disposition = grantDisposition(grant, targetGrants);
1097
- const existing = targetGrants.find((g) => g.groupType === grant.groupType && g.groupId === grant.groupId);
1119
+ const existing = targetGrants.find((g) => sameGrantGroup(g, grant));
1098
1120
  grants.push({
1099
1121
  grant,
1100
1122
  disposition,
1101
- ...(existing ? { existingPermission: existing.permission } : {}),
1123
+ ...(existing ? { existingRole: existing.role } : {}),
1102
1124
  });
1103
1125
  }
1126
+ const custodianUser = row.custodian ? custodianUserId(row.custodian) : null;
1104
1127
  const members = [];
1128
+ let custodianRecorded = false;
1105
1129
  for (const member of source.members ?? []) {
1106
1130
  let resolved;
1107
1131
  try {
@@ -1115,8 +1139,32 @@ async function importCollections(client, appId, importPath, options) {
1115
1139
  note("member", resolved.id, resolved.reason);
1116
1140
  continue;
1117
1141
  }
1118
- members.push({ userId: resolved.userId, permission: member.permission });
1142
+ if (resolved.userId === custodianUser) {
1143
+ // The custodian's own row is replayed as the file records it: a role
1144
+ // below owner demotes the owner row custody wrote, and on a merge an
1145
+ // owner entry restores it. A create's owner entry is already written.
1146
+ custodianRecorded = true;
1147
+ if (row.action === "create" && member.role === "owner")
1148
+ continue;
1149
+ members.push({ userId: resolved.userId, role: member.role });
1150
+ continue;
1151
+ }
1152
+ if (member.role === "owner") {
1153
+ // The owner row is the custodian's alone; anyone else recorded as
1154
+ // owner is invalid data, reported rather than granted another role.
1155
+ note("owner", resolved.id, `recorded as owner, but the owner row belongs to the custodian ` +
1156
+ `(${row.custodian}); not imported`);
1157
+ continue;
1158
+ }
1159
+ members.push({ userId: resolved.userId, role: member.role });
1119
1160
  }
1161
+ // A user custodian the file records no row for had it removed in the
1162
+ // source; a create writes one, so it goes again. `--owner` names a new
1163
+ // custodian the file cannot speak for, and a merge leaves rows it does not
1164
+ // record alone.
1165
+ const removeOwnerRowOf = row.action === "create" && custodianUser && !custodianRecorded && !explicitOwnerUserId
1166
+ ? custodianUser
1167
+ : undefined;
1120
1168
  const documents = [];
1121
1169
  for (const document of source.documents ?? []) {
1122
1170
  try {
@@ -1132,7 +1180,7 @@ async function importCollections(client, appId, importPath, options) {
1132
1180
  }
1133
1181
  documents.push(document.documentId);
1134
1182
  }
1135
- planned.push({ row, source, grants, members, documents });
1183
+ planned.push({ row, source, grants, members, ...(removeOwnerRowOf ? { removeOwnerRowOf } : {}), documents });
1136
1184
  }
1137
1185
  // ── Apply phase ────────────────────────────────────────────────────────
1138
1186
  for (const entry of planned) {
@@ -1167,38 +1215,41 @@ async function importCollections(client, appId, importPath, options) {
1167
1215
  summary.merged += 1;
1168
1216
  info(`[dry-run] Would merge ${source.name} into ${row.targetCollectionId}`);
1169
1217
  }
1170
- for (const { grant, disposition, existingPermission } of entry.grants) {
1218
+ for (const { grant, disposition, existingRole } of entry.grants) {
1171
1219
  if (disposition === "already-applied")
1172
1220
  continue;
1173
- const label = `${grant.groupType}/${grant.groupId}`;
1221
+ const label = `group:${grant.groupId}`;
1174
1222
  info(disposition === "update"
1175
- ? `[dry-run] Would update ${grantUpdateLine(label, existingPermission, grant.permission)}`
1176
- : `[dry-run] Would grant ${label} ${grant.permission}`);
1223
+ ? `[dry-run] Would update ${grantUpdateLine(label, existingRole, grant.role)}`
1224
+ : `[dry-run] Would grant ${label} ${grant.role}`);
1177
1225
  summary.groupsGranted += 1;
1178
1226
  }
1179
1227
  summary.membersAdded += entry.members.length;
1228
+ if (entry.removeOwnerRowOf) {
1229
+ info(`[dry-run] Would remove the owner row of ${entry.removeOwnerRowOf} ` +
1230
+ `(the export records no membership for the custodian)`);
1231
+ }
1180
1232
  summary.documentsAdded += entry.documents.length;
1181
1233
  continue;
1182
1234
  }
1183
1235
  let targetCollectionId = row.targetCollectionId;
1184
1236
  if (row.action === "create") {
1185
1237
  try {
1186
- const created = await client.createCollection(appId, {
1238
+ const created = await client.createCollectionAsAdmin(appId, {
1187
1239
  name: source.name,
1188
1240
  ...(source.description ? { description: source.description } : {}),
1189
1241
  ...(source.collectionType ? { collectionType: source.collectionType } : {}),
1190
- createdBy: row.ownerUserId,
1242
+ custodian: row.custodian,
1243
+ ...(source.settings ? { settings: source.settings } : {}),
1191
1244
  });
1192
1245
  targetCollectionId = created.collectionId;
1193
1246
  row.targetCollectionId = targetCollectionId;
1194
1247
  summary.created += 1;
1195
1248
  info(`Created ${source.name} as ${targetCollectionId}`);
1196
- // Whom the server CREDITED is the answer. A server predating #3641
1197
- // ignores the field, and so does any server for a non-admin token.
1198
- if (created.createdBy && created.createdBy !== row.ownerUserId) {
1199
- note("owner", row.ownerUserId, `the server did not assign the owner (it credited ` +
1200
- `${created.createdBy}); the token is not an admin one, or the ` +
1201
- `server predates #3641`);
1249
+ // The custodian the server RECORDED is the answer.
1250
+ if (created.custodian && created.custodian !== row.custodian) {
1251
+ note("owner", row.custodian, `the server recorded ${created.custodian} as custodian, not ` +
1252
+ `${row.custodian}`);
1202
1253
  }
1203
1254
  }
1204
1255
  catch (err) {
@@ -1210,21 +1261,21 @@ async function importCollections(client, appId, importPath, options) {
1210
1261
  summary.merged += 1;
1211
1262
  info(`Merged ${source.name} into ${targetCollectionId}`);
1212
1263
  }
1213
- for (const { grant, disposition, existingPermission } of entry.grants) {
1264
+ for (const { grant, disposition, existingRole } of entry.grants) {
1214
1265
  if (disposition === "already-applied")
1215
1266
  continue;
1216
- const label = `${grant.groupType}/${grant.groupId}`;
1267
+ const label = `group:${grant.groupId}`;
1217
1268
  try {
1218
- await client.grantCollectionGroupPermission(appId, targetCollectionId, {
1219
- groupType: grant.groupType,
1269
+ await client.addCollectionMember(appId, targetCollectionId, {
1220
1270
  groupId: grant.groupId,
1221
- permission: grant.permission,
1271
+ ...groupTypeOf(grant),
1272
+ role: grant.role,
1222
1273
  });
1223
1274
  summary.groupsGranted += 1;
1224
- // A level change is worth saying out loud: it is the one grant write
1275
+ // A role change is worth saying out loud: it is the one grant write
1225
1276
  // that can take access away from people who already had it.
1226
1277
  if (disposition === "update") {
1227
- info(`Updated ${grantUpdateLine(label, existingPermission, grant.permission)}`);
1278
+ info(`Updated ${grantUpdateLine(label, existingRole, grant.role)}`);
1228
1279
  }
1229
1280
  }
1230
1281
  catch (err) {
@@ -1235,10 +1286,9 @@ async function importCollections(client, appId, importPath, options) {
1235
1286
  try {
1236
1287
  const result = await client.addCollectionMember(appId, targetCollectionId, {
1237
1288
  userId: member.userId,
1238
- permission: member.permission,
1289
+ role: member.role,
1239
1290
  });
1240
- // `already_member` is the server saying it was already so — including
1241
- // the owner's own `_col-writer` enrolment, which every export records.
1291
+ // `already_member` is the server saying it already held that role.
1242
1292
  if (result?.status !== "already_member")
1243
1293
  summary.membersAdded += 1;
1244
1294
  }
@@ -1246,6 +1296,16 @@ async function importCollections(client, appId, importPath, options) {
1246
1296
  note("member", member.userId, serverReason(err));
1247
1297
  }
1248
1298
  }
1299
+ if (entry.removeOwnerRowOf) {
1300
+ try {
1301
+ await client.removeCollectionMember(appId, targetCollectionId, entry.removeOwnerRowOf);
1302
+ info(`Removed the owner row of ${entry.removeOwnerRowOf} from ${source.name}: ` +
1303
+ `the export records no membership for the custodian`);
1304
+ }
1305
+ catch (err) {
1306
+ note("owner", entry.removeOwnerRowOf, serverReason(err));
1307
+ }
1308
+ }
1249
1309
  for (const documentId of entry.documents) {
1250
1310
  try {
1251
1311
  await client.addCollectionDocument(appId, targetCollectionId, { documentId });