@inneranimalmedia/agentsam-sdk 2.6.5 → 2.6.8

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 (400) hide show
  1. package/AGENTSAM.md +29 -0
  2. package/README.md +37 -0
  3. package/apps/README.md +5 -0
  4. package/apps/cad-creator/README.md +4 -4
  5. package/apps/cad-creator/shared/cad/package.json +1 -1
  6. package/apps/client-cms-editor/AGENTS.md +12 -7
  7. package/apps/client-cms-editor/README.md +76 -48
  8. package/apps/client-cms-editor/agentsam.app.json +26 -9
  9. package/apps/client-cms-editor/backend/package.json +20 -3
  10. package/apps/client-cms-editor/backend/src/api/client.ts +134 -101
  11. package/apps/client-cms-editor/backend/src/index.ts +1 -1
  12. package/apps/client-cms-editor/backend/src/model.ts +11 -115
  13. package/apps/client-cms-editor/backend/src/preview/bridge.ts +1 -1
  14. package/apps/client-cms-editor/backend/tsconfig.json +6 -1
  15. package/apps/client-cms-editor/bin/agentsam-cms.js +262 -0
  16. package/apps/client-cms-editor/bin/agentsam-cms.mjs +2 -228
  17. package/apps/client-cms-editor/frontend/package.json +16 -7
  18. package/apps/client-cms-editor/frontend/src/AgentSamDrawer.tsx +9 -41
  19. package/apps/client-cms-editor/frontend/src/CmsAgentSurface.tsx +19 -174
  20. package/apps/client-cms-editor/frontend/src/adapters/heuristic.ts +29 -0
  21. package/apps/client-cms-editor/frontend/src/adapters/http.ts +628 -0
  22. package/apps/client-cms-editor/frontend/src/adapters/memory.ts +510 -0
  23. package/apps/client-cms-editor/frontend/src/editor/CmsEditor.tsx +213 -0
  24. package/apps/client-cms-editor/frontend/src/editor/CmsEditorProvider.tsx +20 -0
  25. package/apps/client-cms-editor/frontend/src/editor/canvas/PreviewCanvas.tsx +53 -0
  26. package/apps/client-cms-editor/frontend/src/{CmsEditor.tsx → editor/legacy/CmsEditorPrototype.tsx} +52 -23
  27. package/apps/client-cms-editor/frontend/src/editor/useCmsEditorController.ts +218 -0
  28. package/apps/client-cms-editor/frontend/src/index.ts +19 -5
  29. package/apps/client-cms-editor/frontend/src/lib/agent-host.ts +24 -0
  30. package/apps/client-cms-editor/frontend/src/main.tsx +82 -8
  31. package/apps/client-cms-editor/frontend/src/mount.tsx +92 -30
  32. package/apps/client-cms-editor/frontend/src/styles/agentsam-drawer.css +1 -1
  33. package/apps/client-cms-editor/frontend/src/styles/cmsShell.css +8 -8
  34. package/apps/client-cms-editor/frontend/src/styles/studio.css +55 -3
  35. package/apps/client-cms-editor/frontend/tsconfig.json +12 -1
  36. package/apps/client-cms-editor/frontend/vite.config.ts +12 -0
  37. package/apps/client-cms-editor/package-lock.json +973 -134
  38. package/apps/client-cms-editor/package.json +118 -6
  39. package/apps/client-cms-editor/reference/harvest/HARVEST_RECEIPT.json +201 -0
  40. package/apps/client-cms-editor/reference/harvest/PROVENANCE_HISTORY.txt +10 -0
  41. package/apps/client-cms-editor/reference/harvest/README.md +8 -0
  42. package/apps/client-cms-editor/reference/harvest/plans/ARCHIVE-INTAKE.md +135 -0
  43. package/apps/client-cms-editor/reference/harvest/plans/CHATGPT-IDENTITY-HARVEST.md +83 -0
  44. package/apps/client-cms-editor/reference/harvest/plans/CMS-EDITOR-HARVEST.md +166 -0
  45. package/apps/client-cms-editor/reference/harvest/plans/HARVEST-INDEX.md +196 -0
  46. package/apps/client-cms-editor/reference/harvest/plans/INNERANIMALS-CONTENT-001.md +137 -0
  47. package/apps/client-cms-editor/reference/harvest/plans/NO-IMPLICIT-SIDE-EFFECTS.md +87 -0
  48. package/apps/client-cms-editor/reference/harvest/plans/STOREFRONT-SURFACE-MAP.md +140 -0
  49. package/apps/client-cms-editor/reference/harvest/plans/THEME-INNERANIMALS-HARVEST.md +137 -0
  50. package/apps/client-cms-editor/reference/harvest/plans/TOOLING-LAB.md +63 -0
  51. package/apps/client-cms-editor/reference/harvest/plans/harvest-manifest.json +97 -0
  52. package/apps/client-cms-editor/reference/harvest/plans/local-cms-harvest-schema.sql +328 -0
  53. package/apps/client-cms-editor/shared/cms/package.json +17 -7
  54. package/apps/client-cms-editor/shared/cms/src/adapter.ts +204 -0
  55. package/apps/client-cms-editor/shared/cms/src/agent-context.ts +7 -5
  56. package/apps/client-cms-editor/shared/cms/src/editor-types.ts +32 -0
  57. package/apps/client-cms-editor/shared/cms/src/host.ts +82 -0
  58. package/apps/client-cms-editor/shared/cms/src/index.ts +4 -0
  59. package/apps/client-cms-editor/shared/cms/src/map.ts +115 -0
  60. package/apps/client-cms-editor/shared/cms/src/starter-pack.ts +158 -0
  61. package/contracts/source-types.v1.json +353 -0
  62. package/docs/CMS_CONTEXTUAL_COMPOSER.md +1 -1
  63. package/docs/PRODUCT_LIFECYCLE.md +566 -0
  64. package/docs/REPO_FILE_MAP.txt +132 -0
  65. package/docs/THEME_REFINERY.md +2 -2
  66. package/docs/architecture/AGENTSAM_RUNTIME_PROTOCOL.md +40 -20
  67. package/docs/content-studio/REVISION_GATE_2026-09-27.md +1 -1
  68. package/docs/plans/LOCAL_STUDIO_DESKTOP_RELEASE_2026-09-29.md +150 -0
  69. package/migrations/d1/0017_identity_native_oauth.sql +21 -0
  70. package/migrations/d1/0018_goap_control_plane_remaster.sql +40 -0
  71. package/migrations/d1/0019_agentsam_agent_run_plan_todo.sql +14 -0
  72. package/migrations/d1/README_PORTABLE_CONTROL_PLANE.md +20 -9
  73. package/migrations/runtime/0002_goap_blackboard_v1.sql +15 -0
  74. package/migrations/runtime/0004_agent_run_plan_linkage.sql +8 -0
  75. package/native/agentsam-machine/Cargo.lock +426 -0
  76. package/native/agentsam-machine/Cargo.toml +9 -0
  77. package/native/agentsam-machine/cli/Cargo.toml +21 -0
  78. package/native/agentsam-machine/cli/README.md +21 -0
  79. package/native/agentsam-machine/cli/src/main.rs +77 -0
  80. package/native/agentsam-machine/core/Cargo.toml +22 -0
  81. package/native/agentsam-machine/core/README.md +14 -0
  82. package/native/agentsam-machine/core/build.rs +33 -0
  83. package/native/agentsam-machine/core/contracts/source-types.v1.json +353 -0
  84. package/native/agentsam-machine/core/src/assets.rs +790 -0
  85. package/native/agentsam-machine/core/src/bound.rs +298 -0
  86. package/native/agentsam-machine/core/src/enrich.rs +706 -0
  87. package/native/agentsam-machine/core/src/frontend.rs +442 -0
  88. package/native/agentsam-machine/core/src/lib.rs +1092 -0
  89. package/native/agentsam-machine/core/src/liquid.rs +528 -0
  90. package/native/agentsam-machine/core/src/source_types.rs +78 -0
  91. package/package.json +64 -24
  92. package/packages/agentsam-brand/README.md +1 -1
  93. package/packages/agentsam-brand/package.json +4 -4
  94. package/packages/agentsam-brand/src/index.js +1 -1
  95. package/packages/agentsam-contracts/package.json +13 -3
  96. package/packages/agentsam-contracts/src/tools.ts +22 -0
  97. package/packages/agentsam-errors/package.json +13 -3
  98. package/packages/agentsam-errors/src/index.js +1 -0
  99. package/packages/agentsam-errors/src/tool.js +56 -0
  100. package/packages/agentsam-errors/test/tool.test.mjs +58 -0
  101. package/packages/agentsam-goap/README.md +52 -0
  102. package/packages/agentsam-goap/package.json +37 -0
  103. package/packages/agentsam-goap/src/adapters/d1-sqlite.js +570 -0
  104. package/packages/agentsam-goap/src/adapters/memory.js +199 -0
  105. package/packages/agentsam-goap/src/contracts.js +226 -0
  106. package/packages/agentsam-goap/src/control-plane.js +112 -0
  107. package/packages/agentsam-goap/src/errors.js +157 -0
  108. package/packages/agentsam-goap/src/index.js +5 -0
  109. package/packages/agentsam-goap/src/world.js +202 -0
  110. package/packages/agentsam-goap/test/goap-control-plane.test.mjs +245 -0
  111. package/packages/agentsam-goap/test/world.test.mjs +79 -0
  112. package/packages/agentsam-knowledge/package.json +18 -4
  113. package/packages/agentsam-knowledge/src/autorag/index.js +10 -3
  114. package/packages/agentsam-knowledge/src/backends/index.js +33 -1
  115. package/packages/agentsam-knowledge/test/autorag.test.mjs +60 -1
  116. package/packages/agentsam-nav/dist/browser.global.js +21 -18
  117. package/packages/agentsam-nav/dist/browser.js +6807 -130
  118. package/packages/agentsam-nav/dist/index.js +6799 -122
  119. package/packages/agentsam-nav/package.json +24 -6
  120. package/packages/agentsam-queue-control/README.md +1 -1
  121. package/packages/agentsam-queue-control/package.json +7 -2
  122. package/packages/agentsam-rapid-rust/src/main.rs +1 -1
  123. package/packages/agentsam-repository/package.json +13 -3
  124. package/packages/agentsam-repository/src/goap.js +13 -14
  125. package/packages/agentsam-repository/src/source-types.js +8 -0
  126. package/packages/agentsam-repository/src/tickets.js +15 -22
  127. package/packages/agentsam-repository/test/goap-tickets-workspace-state.test.mjs +19 -8
  128. package/packages/agentsam-vault/package.json +14 -4
  129. package/packages/agentsam-workbench/package.json +15 -4
  130. package/packages/agentsam-workbench/src/agent/ToolPermissionRequest.tsx +94 -0
  131. package/packages/agentsam-workbench/src/agent/index.ts +2 -0
  132. package/packages/agentsam-workbench/src/agent/tool-permission.css +134 -0
  133. package/packages/connectors/cloudflare/package.json +15 -3
  134. package/packages/connectors/cloudflare/src/api-client.js +3 -2
  135. package/packages/connectors/cloudflare/src/families/vectorize.js +127 -0
  136. package/packages/connectors/cloudflare/src/index.js +1 -0
  137. package/packages/heuristic-theme/README.md +2 -2
  138. package/packages/heuristic-theme/package.json +8 -3
  139. package/packages/heuristic-theme/src/registry.js +2 -2
  140. package/packages/heuristic-theme/src/theme.js +1 -1
  141. package/packages/heuristic-theme/test/theme.test.mjs +1 -1
  142. package/packages/identity/.agentsam/features/oauth-login-portal/agentsam.feature.json +1 -1
  143. package/packages/identity/.agentsam/features/oauth-login-portal/providers.json +27 -7
  144. package/packages/identity/README.md +2 -2
  145. package/packages/identity/migrations/D1_SCHEMA_MAPPING.md +18 -15
  146. package/packages/identity/migrations/apply-portable.js +109 -0
  147. package/packages/identity/migrations/sqlite/004_identity_oauth_consent_catalog.sql +1 -1
  148. package/packages/identity/migrations/sqlite/005_identity_company_native.sql +52 -0
  149. package/packages/identity/package.json +27 -6
  150. package/packages/identity/schema/agentsam.identity/manifest.json +101 -0
  151. package/packages/identity/scripts/local-identity-bridge.mjs +13 -7
  152. package/packages/identity/scripts/preview-auth-portal.mjs +6 -6
  153. package/packages/identity/src/adapters/cloudflare-d1/index.js +8 -401
  154. package/packages/identity/src/adapters/iam-compat/index.js +540 -0
  155. package/packages/identity/src/adapters/portable-d1/index.js +18 -0
  156. package/packages/identity/src/adapters/portable-store.js +479 -0
  157. package/packages/identity/src/adapters/sqlite/index.js +13 -312
  158. package/packages/identity/src/contracts/auth-config.js +0 -1
  159. package/packages/identity/src/contracts/identity-store.js +56 -5
  160. package/packages/identity/src/contracts/provider.js +1 -0
  161. package/packages/identity/src/contracts/sign-in-options.js +104 -0
  162. package/packages/identity/src/core/constants.js +1 -1
  163. package/packages/identity/src/core/session-policy.js +31 -0
  164. package/packages/identity/src/frontend/auth-portal/pages/login.html +6 -6
  165. package/packages/identity/src/frontend/auth-portal/pages/reset.html +5 -5
  166. package/packages/identity/src/frontend/auth-portal/pages/signup.html +5 -5
  167. package/packages/identity/src/frontend/auth-portal/shared/company-branding.js +1 -1
  168. package/packages/identity/src/frontend/auth-portal/shared/desktop-identity-bridge.js +6 -37
  169. package/packages/identity/src/index.js +28 -1
  170. package/packages/identity/src/migrations/README.md +23 -23
  171. package/packages/identity/src/oauth/README.md +1 -1
  172. package/packages/identity/src/oauth/credentials.js +10 -11
  173. package/packages/identity/src/oauth/google-desktop-exchange.js +195 -27
  174. package/packages/identity/src/oauth/iam-platform.js +21 -9
  175. package/packages/identity/src/oauth/native-handoff.js +136 -0
  176. package/packages/identity/src/providers/chatgpt/headers.js +65 -0
  177. package/packages/identity/src/providers/chatgpt/index.js +86 -0
  178. package/packages/identity/src/providers/chatgpt/mapper.js +43 -0
  179. package/packages/identity/src/providers/chatgpt/paths.js +45 -0
  180. package/packages/identity/src/providers/github/oauth.js +1 -2
  181. package/packages/identity/src/providers/github/profile.js +16 -2
  182. package/packages/identity/src/providers/index.js +11 -3
  183. package/packages/identity/src/server/identity-service.js +2 -1
  184. package/packages/identity/src/server/worker-router.js +71 -18
  185. package/packages/identity/tests/chatgpt-provider.test.mjs +75 -0
  186. package/packages/identity/tests/google-desktop-identity-login.test.mjs +210 -0
  187. package/packages/identity/tests/identity-init-scaffold.test.mjs +15 -4
  188. package/packages/identity/tests/lane1-identity-store-parity.test.mjs +527 -0
  189. package/packages/identity/tests/lane1-packed-consumer.test.mjs +175 -0
  190. package/packages/identity/tests/native-oauth-handoff.test.mjs +292 -0
  191. package/packages/identity/tests/oauth-credentials.test.mjs +4 -4
  192. package/packages/identity/tests/preview-auth-portal.test.mjs +4 -2
  193. package/packages/identity/tests/session-routes-policy.test.mjs +13 -1
  194. package/packages/identity/tests/worker-runtime-boundary.test.mjs +11 -0
  195. package/packages/providers/completeful/package.json +7 -2
  196. package/packages/theme-church-site/package.json +17 -6
  197. package/packages/theme-church-site/src/harvest.json +4 -2
  198. package/packages/theme-church-site/src/index.js +7 -3
  199. package/packages/theme-church-site/test/theme.test.mjs +4 -2
  200. package/packages/theme-companions-site/package.json +17 -6
  201. package/packages/theme-companions-site/src/harvest.json +4 -2
  202. package/packages/theme-companions-site/src/index.js +7 -3
  203. package/packages/theme-companions-site/test/theme.test.mjs +4 -2
  204. package/packages/theme-floors-site/package.json +17 -6
  205. package/packages/theme-floors-site/src/harvest.json +4 -2
  206. package/packages/theme-floors-site/src/index.js +7 -3
  207. package/packages/theme-floors-site/test/theme.test.mjs +4 -2
  208. package/packages/theme-fuelnfree-site/package.json +17 -6
  209. package/packages/theme-fuelnfree-site/src/harvest.json +4 -2
  210. package/packages/theme-fuelnfree-site/src/index.js +7 -3
  211. package/packages/theme-fuelnfree-site/test/theme.test.mjs +4 -2
  212. package/packages/theme-handyman-site/package.json +17 -6
  213. package/packages/theme-handyman-site/src/harvest.json +4 -2
  214. package/packages/theme-handyman-site/src/index.js +7 -3
  215. package/packages/theme-handyman-site/test/theme.test.mjs +4 -2
  216. package/packages/theme-insurance-site/package.json +17 -6
  217. package/packages/theme-insurance-site/src/harvest.json +4 -2
  218. package/packages/theme-insurance-site/src/index.js +7 -3
  219. package/packages/theme-insurance-site/test/theme.test.mjs +4 -2
  220. package/packages/theme-scenes/README.md +1 -1
  221. package/packages/theme-scenes/package.json +4 -3
  222. package/packages/theme-scenes/src/registry.js +187 -0
  223. package/packages/theme-shinshu-site/package.json +17 -6
  224. package/packages/theme-shinshu-site/src/harvest.json +4 -2
  225. package/packages/theme-shinshu-site/src/index.js +7 -3
  226. package/packages/theme-shinshu-site/test/theme.test.mjs +4 -2
  227. package/packages/work-graph/package.json +14 -4
  228. package/protocol/FILEMETA_V1.md +2 -2
  229. package/protocol/capabilities/manifest.json +1 -1
  230. package/protocol/database/agentsam.schema-pack.v1.schema.json +72 -0
  231. package/protocol/presets/catalog.json +2 -2
  232. package/protocol/theme-refinery/lineage-registry.json +105 -0
  233. package/protocol/theme-refinery/product-metadata.schema.json +21 -1
  234. package/python/agentsam_sdk/repository/intelligence/discovery.py +27 -2
  235. package/python/agentsam_sdk/repository/intelligence/git.py +4 -0
  236. package/python/tests/test_repository_intelligence.py +30 -0
  237. package/src/cli/command-catalog.js +48 -5
  238. package/src/cli/dispatch.js +1 -0
  239. package/src/cli.js +20 -1
  240. package/src/cloudflare/runtime-status.js +2 -1
  241. package/src/commands/autorag.js +67 -13
  242. package/src/commands/cheat-sheet.js +29 -4
  243. package/src/commands/codebaseindex.js +45 -2
  244. package/src/commands/goap-world.js +134 -0
  245. package/src/commands/goap.js +11 -4
  246. package/src/commands/knowledge.js +59 -2
  247. package/src/commands/machine-binary.js +109 -0
  248. package/src/commands/machine-runtime.js +266 -0
  249. package/src/commands/machine.js +208 -0
  250. package/src/commands/package.js +530 -0
  251. package/src/commands/plan-ledger.js +370 -0
  252. package/src/commands/plan.js +183 -0
  253. package/src/commands/security.js +67 -4
  254. package/src/commands/setup.js +33 -16
  255. package/src/commands/skill.js +78 -17
  256. package/src/commands/status.js +8 -1
  257. package/src/features/resolve.js +1 -0
  258. package/src/knowledge/engine.js +21 -2
  259. package/src/knowledge/runtime-discovery.js +127 -0
  260. package/src/knowledge/source.js +1 -1
  261. package/src/lib/identity-scaffold.js +53 -13
  262. package/src/lib/setup/recipes.js +1 -1
  263. package/src/lib/slash-commands.js +1 -1
  264. package/src/local/runtime-store.js +24 -2
  265. package/src/local/sqlite.js +17 -0
  266. package/src/models/discovery.js +40 -22
  267. package/src/models/inventory-core.d.ts +105 -0
  268. package/src/models/inventory-core.js +54 -6
  269. package/src/sam/operations/package-audit.js +41 -0
  270. package/src/sam/seed.js +2 -0
  271. package/src/security/logs.js +3 -0
  272. package/src/ui/cli/help.js +41 -0
  273. package/apps/client-cms-editor/backend/src/demo-bootstrap.ts +0 -145
  274. package/test/account-session.test.mjs +0 -101
  275. package/test/agent-capabilities.test.mjs +0 -67
  276. package/test/app-authority-context.test.mjs +0 -32
  277. package/test/app-building-skills.test.mjs +0 -61
  278. package/test/apps-scaffold-contract.test.mjs +0 -114
  279. package/test/blender-cad.test.mjs +0 -146
  280. package/test/cad-docker.test.mjs +0 -96
  281. package/test/capabilities.test.mjs +0 -133
  282. package/test/cli/api-key-env-whoami.test.mjs +0 -129
  283. package/test/cli/codebaseindex-plan-ux.test.mjs +0 -37
  284. package/test/cli/eval-live.test.mjs +0 -219
  285. package/test/cli/go.test.mjs +0 -128
  286. package/test/cli/local-vault-and-provider-setup.test.mjs +0 -268
  287. package/test/cli/mcp-client.test.mjs +0 -256
  288. package/test/cli/preferences-runtime.test.mjs +0 -11
  289. package/test/cli/runtime-persist.test.mjs +0 -56
  290. package/test/cli/runtime-ui.test.mjs +0 -141
  291. package/test/cli/rust-command.test.mjs +0 -21
  292. package/test/cli/skill-npm-and-env.test.mjs +0 -52
  293. package/test/cli/status-actions.test.mjs +0 -88
  294. package/test/cli/wireframes-go-registry.test.mjs +0 -139
  295. package/test/cli-preferences.test.mjs +0 -46
  296. package/test/cloudflare-connector.test.mjs +0 -93
  297. package/test/cloudflare-runtime.test.mjs +0 -82
  298. package/test/context.test.mjs +0 -159
  299. package/test/credentials-audit.test.mjs +0 -113
  300. package/test/deploy-health-scan.test.mjs +0 -95
  301. package/test/deploy-receipt.test.mjs +0 -91
  302. package/test/dockerize.test.mjs +0 -93
  303. package/test/error-diagnostics.test.mjs +0 -124
  304. package/test/eval-context.test.mjs +0 -37
  305. package/test/execution-approvals.test.mjs +0 -27
  306. package/test/fixtures/cad/courtyard-house.json +0 -208
  307. package/test/fixtures/knowledge-rpc-worker.mjs +0 -16
  308. package/test/gcloud-auth.test.mjs +0 -111
  309. package/test/go/build-source-identity.test.mjs +0 -31
  310. package/test/go/cloudflare-probe.test.mjs +0 -317
  311. package/test/go/distribution.test.mjs +0 -28
  312. package/test/google-cloud-connection.test.mjs +0 -83
  313. package/test/google-cloud-inventory.test.mjs +0 -64
  314. package/test/indexing-provider.test.mjs +0 -29
  315. package/test/init-options.test.mjs +0 -35
  316. package/test/integration/ai-access-onboarding.test.mjs +0 -47
  317. package/test/integration/app-command.test.mjs +0 -85
  318. package/test/integration/cad-discovery.test.mjs +0 -158
  319. package/test/integration/cad-docker-executor.test.mjs +0 -249
  320. package/test/integration/cad-execution.test.mjs +0 -184
  321. package/test/integration/cad-project-app.test.mjs +0 -31
  322. package/test/integration/cad-project-d1.test.mjs +0 -25
  323. package/test/integration/cad-project-native.test.mjs +0 -24
  324. package/test/integration/cad-project-workflow.test.mjs +0 -9
  325. package/test/integration/cad-project.test.mjs +0 -85
  326. package/test/integration/cli-help.test.mjs +0 -41
  327. package/test/integration/cms-app-scaffold.test.mjs +0 -71
  328. package/test/integration/cms-site-tenancy-contract.test.mjs +0 -257
  329. package/test/integration/codebaseindex-ingest.test.mjs +0 -214
  330. package/test/integration/core-client.integration.test.mjs +0 -55
  331. package/test/integration/error-runtime-rpc.test.mjs +0 -71
  332. package/test/integration/features-resolve.test.mjs +0 -147
  333. package/test/integration/icon-registry.test.mjs +0 -67
  334. package/test/integration/ingest-discover-models.test.mjs +0 -30
  335. package/test/integration/inspect-path-cli.test.mjs +0 -46
  336. package/test/integration/install-script.test.mjs +0 -110
  337. package/test/integration/inventory-core.test.mjs +0 -53
  338. package/test/integration/inventory-evidence.test.mjs +0 -125
  339. package/test/integration/knowledge-rpc.test.mjs +0 -112
  340. package/test/integration/local-fs.test.mjs +0 -113
  341. package/test/integration/merkle-cli.test.mjs +0 -61
  342. package/test/integration/merkle-persistence-identity.test.mjs +0 -48
  343. package/test/integration/plugins-cloudflare.integration.test.mjs +0 -28
  344. package/test/integration/provider-env-cli.test.mjs +0 -50
  345. package/test/integration/provider-factory.test.mjs +0 -197
  346. package/test/integration/repository-company-graph.test.mjs +0 -90
  347. package/test/integration/runtime-migrations.test.mjs +0 -87
  348. package/test/integration/runtime-status.integration.test.mjs +0 -212
  349. package/test/integration/sam-activity-recovery.test.mjs +0 -147
  350. package/test/integration/sam-decision.test.mjs +0 -584
  351. package/test/integration/sam-kernel.test.mjs +0 -99
  352. package/test/integration/sam-planning-astar.test.mjs +0 -279
  353. package/test/integration/scaffold-cloudflare.test.mjs +0 -46
  354. package/test/integration/skill-runtime.test.mjs +0 -240
  355. package/test/integration/storage-architecture.test.mjs +0 -154
  356. package/test/integration/store-discovery.test.mjs +0 -44
  357. package/test/integration/studio-fs-pty-e2e.test.mjs +0 -294
  358. package/test/integration/terminal-exec.integration.test.mjs +0 -86
  359. package/test/integration/tool-runtime.test.mjs +0 -124
  360. package/test/integration/tool-schema.test.mjs +0 -59
  361. package/test/jr-dev-skill.test.mjs +0 -26
  362. package/test/knowledge-context-pack.test.mjs +0 -19
  363. package/test/knowledge-postgres.test.mjs +0 -40
  364. package/test/knowledge-service.test.mjs +0 -110
  365. package/test/knowledge.test.mjs +0 -206
  366. package/test/live/terminal-transport.live.test.mjs +0 -24
  367. package/test/local-sessions.test.mjs +0 -57
  368. package/test/local-studio-deploy.test.mjs +0 -83
  369. package/test/loopback-oauth.test.mjs +0 -53
  370. package/test/merkle-render.test.mjs +0 -31
  371. package/test/mini.test.mjs +0 -140
  372. package/test/model-catalog.test.mjs +0 -70
  373. package/test/models.test.mjs +0 -309
  374. package/test/npm10-lock.test.mjs +0 -29
  375. package/test/ollama.test.mjs +0 -137
  376. package/test/open-url.test.mjs +0 -64
  377. package/test/openai-responses.test.mjs +0 -155
  378. package/test/portable-context.test.mjs +0 -94
  379. package/test/project-config.test.mjs +0 -81
  380. package/test/project-rules.test.mjs +0 -44
  381. package/test/provider-credentials.test.mjs +0 -106
  382. package/test/rehydrate.test.mjs +0 -25
  383. package/test/release-hygiene.test.mjs +0 -42
  384. package/test/repository-snapshot-view.test.mjs +0 -112
  385. package/test/responses-runner.test.mjs +0 -465
  386. package/test/runtime-activity.test.mjs +0 -98
  387. package/test/sdk-worker-contract.test.mjs +0 -72
  388. package/test/security.test.mjs +0 -252
  389. package/test/setup-planner.test.mjs +0 -74
  390. package/test/setup-runtime.test.mjs +0 -51
  391. package/test/shell.test.mjs +0 -281
  392. package/test/skills.test.mjs +0 -22
  393. package/test/smoke.mjs +0 -122
  394. package/test/telemetry.test.mjs +0 -79
  395. package/test/terminal/local-pty.mock.test.mjs +0 -153
  396. package/test/terminal-machine-identity.test.mjs +0 -88
  397. package/test/theme-portability.test.mjs +0 -14
  398. package/test/tools-search.test.mjs +0 -73
  399. package/test/unit/website-assets-bindings.test.mjs +0 -60
  400. package/test/whoami-resume.test.mjs +0 -157
@@ -0,0 +1,479 @@
1
+ import {
2
+ IDENTITY_STORE_SCHEMA_VERSION,
3
+ IdentitySchemaError,
4
+ } from '../contracts/identity-store.js';
5
+ import { DEFAULT_COMPANY_ID, DEFAULT_COMPANY_SLUG, normalizeCompanyRow } from '../contracts/company.js';
6
+ import { SESSION_POLICY, SESSION_TYPES, NATIVE_HANDOFF_TTL_SECONDS, shouldRenewDesktopSession } from '../core/session-policy.js';
7
+ import {
8
+ newAccountIdentityId,
9
+ newAuthEventId,
10
+ newAuthUserId,
11
+ newSessionId,
12
+ nowUnix,
13
+ } from './cloudflare-d1/ids.js';
14
+
15
+ async function hashValue(value) {
16
+ const v = String(value || '').trim();
17
+ if (!v) return null;
18
+ const bytes = new TextEncoder().encode(v);
19
+ const digest = await crypto.subtle.digest('SHA-256', bytes);
20
+ return Array.from(new Uint8Array(digest)).map((b) => b.toString(16).padStart(2, '0')).join('');
21
+ }
22
+
23
+ async function requirePortableSchema(db) {
24
+ let row;
25
+ try {
26
+ row = await db.prepare(
27
+ `SELECT value FROM identity_schema_meta WHERE key = 'schema_version' LIMIT 1`,
28
+ ).bind().first();
29
+ } catch (err) {
30
+ throw new IdentitySchemaError(
31
+ 'IDENTITY_SCHEMA_MIGRATION_REQUIRED',
32
+ 'agentsam.identity portable migrations not applied — run applyPortableIdentityMigrations()',
33
+ { cause: String(err?.message || err) },
34
+ );
35
+ }
36
+ const version = Number(row?.value || 0);
37
+ if (version < IDENTITY_STORE_SCHEMA_VERSION) {
38
+ throw new IdentitySchemaError(
39
+ 'IDENTITY_SCHEMA_MIGRATION_REQUIRED',
40
+ `identity schema_version=${version}, required=${IDENTITY_STORE_SCHEMA_VERSION}`,
41
+ { version, required: IDENTITY_STORE_SCHEMA_VERSION },
42
+ );
43
+ }
44
+ }
45
+
46
+ /**
47
+ * Shared IdentityStore over portable identity_* tables (SQLite + portable D1).
48
+ * @param {{ prepare: Function }} db
49
+ * @param {{ backend: string, sessionTtlSeconds?: number, skipSchemaCheck?: boolean }} options
50
+ */
51
+ export function createPortableIdentityStore(db, options = {}) {
52
+ if (!db?.prepare) throw new Error('portable_identity_store_requires_db');
53
+ const backend = options.backend || 'portable';
54
+ const sessionTtlSeconds = options.sessionTtlSeconds ?? SESSION_POLICY.browser.ttlSeconds;
55
+ let schemaReady = Boolean(options.skipSchemaCheck);
56
+
57
+ async function ensure() {
58
+ if (schemaReady) return;
59
+ await requirePortableSchema(db);
60
+ schemaReady = true;
61
+ }
62
+
63
+ return Object.freeze({
64
+ schemaVersion: IDENTITY_STORE_SCHEMA_VERSION,
65
+ sessionTtlSeconds,
66
+ backend,
67
+
68
+ async findUserByEmail(email) {
69
+ await ensure();
70
+ const row = await db.prepare(
71
+ `SELECT id, email, display_name, password_hash, salt, status, created_at, updated_at
72
+ FROM identity_users WHERE email = ? COLLATE NOCASE LIMIT 1`,
73
+ ).bind(String(email || '').trim().toLowerCase()).first();
74
+ return row || null;
75
+ },
76
+
77
+ async findUserById(userId) {
78
+ await ensure();
79
+ const row = await db.prepare(
80
+ `SELECT id, email, display_name, password_hash, salt, status, created_at, updated_at
81
+ FROM identity_users WHERE id = ? LIMIT 1`,
82
+ ).bind(userId).first();
83
+ return row || null;
84
+ },
85
+
86
+ async createUser({ email, passwordHash, salt, displayName }) {
87
+ await ensure();
88
+ const id = newAuthUserId();
89
+ const ts = nowUnix();
90
+ const normalizedEmail = String(email || '').trim().toLowerCase();
91
+ await db.prepare(
92
+ `INSERT INTO identity_users (id, email, display_name, password_hash, salt, status, created_at, updated_at)
93
+ VALUES (?, ?, ?, ?, ?, 'active', ?, ?)`,
94
+ ).bind(id, normalizedEmail, displayName || null, passwordHash || null, salt || null, ts, ts).run();
95
+ return this.findUserById(id);
96
+ },
97
+
98
+ async updateUserPassword(userId, passwordHash, salt) {
99
+ await ensure();
100
+ const ts = nowUnix();
101
+ await db.prepare(
102
+ `UPDATE identity_users SET password_hash = ?, salt = ?, updated_at = ? WHERE id = ?`,
103
+ ).bind(passwordHash, salt, ts, userId).run();
104
+ },
105
+
106
+ async findUserByProvider(provider, providerSubject) {
107
+ await ensure();
108
+ const row = await db.prepare(
109
+ `SELECT u.id, u.email, u.display_name, u.password_hash, u.salt, u.status, u.created_at, u.updated_at
110
+ FROM identity_external_accounts ea
111
+ JOIN identity_users u ON u.id = ea.user_id
112
+ WHERE ea.provider = ? AND ea.provider_subject = ?
113
+ LIMIT 1`,
114
+ ).bind(provider, providerSubject).first();
115
+ return row || null;
116
+ },
117
+
118
+ async upsertProviderIdentity({ accountId, provider, providerSubject, email }) {
119
+ await ensure();
120
+ const existing = await db.prepare(
121
+ `SELECT id FROM identity_external_accounts WHERE provider = ? AND provider_subject = ? LIMIT 1`,
122
+ ).bind(provider, providerSubject).first();
123
+ const ts = nowUnix();
124
+ if (existing?.id) {
125
+ await db.prepare(
126
+ `UPDATE identity_external_accounts SET user_id = ?, email = ?, updated_at = ? WHERE id = ?`,
127
+ ).bind(accountId, email || null, ts, existing.id).run();
128
+ return existing.id;
129
+ }
130
+ const id = newAccountIdentityId();
131
+ await db.prepare(
132
+ `INSERT INTO identity_external_accounts (id, user_id, provider, provider_subject, email, created_at, updated_at)
133
+ VALUES (?, ?, ?, ?, ?, ?, ?)`,
134
+ ).bind(id, accountId, provider, providerSubject, email || null, ts, ts).run();
135
+ return id;
136
+ },
137
+
138
+ async createSession({ userId, email, provider, providerSubject, displayName, type }) {
139
+ await ensure();
140
+ const id = newSessionId();
141
+ const ts = nowUnix();
142
+ const desktop = type === SESSION_TYPES.DESKTOP;
143
+ const expiresAt = ts + (desktop ? SESSION_POLICY.desktop.ttlSeconds : sessionTtlSeconds);
144
+ const sessionType = desktop ? SESSION_TYPES.DESKTOP : SESSION_TYPES.BROWSER;
145
+ await db.prepare(
146
+ `INSERT INTO identity_sessions
147
+ (id, user_id, email, provider, provider_subject, display_name, expires_at, created_at, last_active_at, type)
148
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
149
+ ).bind(
150
+ id, userId, email || null, provider || 'email', providerSubject || null,
151
+ displayName || null, expiresAt, ts, ts, sessionType,
152
+ ).run();
153
+ return {
154
+ id,
155
+ type: sessionType,
156
+ user_id: userId,
157
+ email,
158
+ provider: provider || 'email',
159
+ provider_subject: providerSubject || null,
160
+ display_name: displayName || null,
161
+ expires_at: expiresAt,
162
+ created_at: ts,
163
+ };
164
+ },
165
+
166
+ async getSession(sessionId) {
167
+ await ensure();
168
+ const row = await db.prepare(
169
+ `SELECT id, user_id, email, provider, provider_subject, display_name, expires_at, revoked_at, created_at, last_active_at, type
170
+ FROM identity_sessions WHERE id = ? LIMIT 1`,
171
+ ).bind(sessionId).first();
172
+ const now = nowUnix();
173
+ if (!row || row.revoked_at || row.expires_at <= now) return null;
174
+ if (shouldRenewDesktopSession(row, now)) {
175
+ const renewedExpiresAt = now + SESSION_POLICY.desktop.ttlSeconds;
176
+ await db.prepare(
177
+ `UPDATE identity_sessions
178
+ SET expires_at = ?, last_active_at = ?
179
+ WHERE id = ? AND revoked_at IS NULL AND expires_at > ?`,
180
+ ).bind(renewedExpiresAt, now, sessionId, now).run();
181
+ row.expires_at = renewedExpiresAt;
182
+ row.last_active_at = now;
183
+ }
184
+ return row;
185
+ },
186
+
187
+ async revokeSession(sessionId, reason = 'logout') {
188
+ await ensure();
189
+ const ts = nowUnix();
190
+ await db.prepare(
191
+ `UPDATE identity_sessions SET revoked_at = ?, last_active_at = ? WHERE id = ? AND revoked_at IS NULL`,
192
+ ).bind(ts, ts, sessionId).run();
193
+ return { ok: true, reason };
194
+ },
195
+
196
+ async createOAuthTransaction({
197
+ state, provider, codeVerifier, returnTo, redirectTo, appId, ttlSeconds = 600,
198
+ clientType, nativeChallenge, nativeRedirect,
199
+ }) {
200
+ await ensure();
201
+ if (!appId) {
202
+ throw new IdentitySchemaError(
203
+ 'OAUTH_TRANSACTION_APP_ID_REQUIRED',
204
+ 'OAuth transactions must include app_id — refusing to create orphan auth state',
205
+ );
206
+ }
207
+ const ts = nowUnix();
208
+ const columns = ['state', 'provider', 'code_verifier', 'return_to', 'app_id', 'expires_at', 'created_at', 'consumed_at'];
209
+ const values = [state, provider, codeVerifier, returnTo ?? redirectTo ?? null, appId, ts + ttlSeconds, ts, null];
210
+ if (clientType === SESSION_TYPES.DESKTOP) {
211
+ columns.push('client_type', 'native_challenge', 'native_redirect');
212
+ values.push(clientType, nativeChallenge ?? null, nativeRedirect ?? null);
213
+ }
214
+ await db.prepare(
215
+ `INSERT INTO identity_oauth_transactions (${columns.join(', ')})
216
+ VALUES (${columns.map(() => '?').join(', ')})`,
217
+ ).bind(...values).run();
218
+ },
219
+
220
+ /** @deprecated Use createOAuthTransaction */
221
+ async saveOAuthState(input) {
222
+ return this.createOAuthTransaction({
223
+ ...input,
224
+ returnTo: input.redirectTo ?? input.returnTo,
225
+ appId: input.appId,
226
+ });
227
+ },
228
+
229
+ async consumeOAuthTransaction(state) {
230
+ await ensure();
231
+ const row = await db.prepare(
232
+ `SELECT state, provider, code_verifier, return_to, app_id, expires_at, created_at, consumed_at,
233
+ client_type, native_challenge, native_redirect
234
+ FROM identity_oauth_transactions WHERE state = ? LIMIT 1`,
235
+ ).bind(state).first();
236
+ if (!row) return null;
237
+ const ts = nowUnix();
238
+ await db.prepare(
239
+ `UPDATE identity_oauth_transactions SET consumed_at = ? WHERE state = ?`,
240
+ ).bind(ts, state).run();
241
+ await db.prepare(`DELETE FROM identity_oauth_transactions WHERE state = ?`).bind(state).run();
242
+ if (row.consumed_at) return null;
243
+ if (row.expires_at <= ts) return null;
244
+ if (!row.app_id) {
245
+ throw new IdentitySchemaError(
246
+ 'IDENTITY_SCHEMA_MIGRATION_REQUIRED',
247
+ 'OAuth transaction missing app_id — refuse silent pre-app_id semantics',
248
+ );
249
+ }
250
+ return {
251
+ state: row.state,
252
+ provider: row.provider,
253
+ code_verifier: row.code_verifier,
254
+ redirect_to: row.return_to,
255
+ return_to: row.return_to,
256
+ app_id: row.app_id,
257
+ client_type: row.client_type ?? null,
258
+ native_challenge: row.native_challenge ?? null,
259
+ native_redirect: row.native_redirect ?? null,
260
+ expires_at: row.expires_at,
261
+ created_at: row.created_at,
262
+ consumed_at: ts,
263
+ };
264
+ },
265
+
266
+ /** @deprecated Use consumeOAuthTransaction */
267
+ async consumeOAuthState(state) {
268
+ return this.consumeOAuthTransaction(state);
269
+ },
270
+
271
+ async logAuthEvent({
272
+ userId, accountId, eventType, status = 'ok', provider,
273
+ session, capabilities, activity, client, metadata, request,
274
+ }) {
275
+ try {
276
+ await ensure();
277
+ const ip = request?.headers?.get?.('cf-connecting-ip') || request?.headers?.get?.('x-forwarded-for') || '';
278
+ const ua = request?.headers?.get?.('user-agent') || '';
279
+ const [ipHash, uaHash] = await Promise.all([hashValue(ip), hashValue(ua)]);
280
+ const envelope = {
281
+ ...(metadata && typeof metadata === 'object' ? metadata : {}),
282
+ ...(accountId ? { accountId } : {}),
283
+ ...(session ? { session } : {}),
284
+ ...(capabilities ? { capabilities } : {}),
285
+ ...(activity ? { activity } : {}),
286
+ ...(client ? { client } : {}),
287
+ };
288
+ await db.prepare(
289
+ `INSERT INTO identity_auth_events
290
+ (id, user_id, event_type, status, provider, metadata_json, ip_hash, user_agent_hash, created_at)
291
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
292
+ ).bind(
293
+ newAuthEventId(), userId || null, eventType, status, provider || null,
294
+ JSON.stringify(envelope), ipHash, uaHash, nowUnix(),
295
+ ).run();
296
+ } catch {
297
+ /* never break auth on audit failure */
298
+ }
299
+ },
300
+
301
+ async countAuthActivity(userId) {
302
+ await ensure();
303
+ if (!userId) return { loginCount: 0, activeSessionCount: 0, lastLoginAt: null };
304
+ const logins = await db.prepare(
305
+ `SELECT COUNT(*) AS c, MAX(created_at) AS last_at
306
+ FROM identity_auth_events
307
+ WHERE user_id = ? AND event_type = 'login' AND status = 'ok'`,
308
+ ).bind(userId).first();
309
+ const sessions = await db.prepare(
310
+ `SELECT COUNT(*) AS c FROM identity_sessions
311
+ WHERE user_id = ? AND revoked_at IS NULL AND expires_at > ?`,
312
+ ).bind(userId, nowUnix()).first();
313
+ return {
314
+ loginCount: Number(logins?.c || 0),
315
+ activeSessionCount: Number(sessions?.c || 0),
316
+ lastLoginAt: logins?.last_at != null ? Number(logins.last_at) : null,
317
+ };
318
+ },
319
+
320
+ async createNativeHandoff({ handoffHash, sessionId, challenge, ttlSeconds = NATIVE_HANDOFF_TTL_SECONDS }) {
321
+ await ensure();
322
+ const ts = nowUnix();
323
+ await db.prepare(
324
+ `INSERT INTO identity_native_handoffs (handoff_hash, session_id, challenge, expires_at, created_at)
325
+ VALUES (?, ?, ?, ?, ?)`,
326
+ ).bind(handoffHash, sessionId, challenge, ts + ttlSeconds, ts).run();
327
+ },
328
+
329
+ async consumeNativeHandoff(handoffHash) {
330
+ await ensure();
331
+ const ts = nowUnix();
332
+ const row = await db.prepare(
333
+ `UPDATE identity_native_handoffs SET consumed_at = ?
334
+ WHERE handoff_hash = ? AND consumed_at IS NULL AND expires_at > ?
335
+ RETURNING session_id, challenge`,
336
+ ).bind(ts, handoffHash, ts).first();
337
+ return row || null;
338
+ },
339
+
340
+ async getCompanyBySlug(slug = DEFAULT_COMPANY_SLUG) {
341
+ await ensure();
342
+ const row = await db.prepare(
343
+ `SELECT id, slug, name, legal_name, logo_url, favicon_url, primary_color, auth_bg_color,
344
+ support_email, website_url, tagline, meta_json, created_at, updated_at
345
+ FROM identity_companies WHERE slug = ? LIMIT 1`,
346
+ ).bind(slug).first();
347
+ return normalizeCompanyRow(row);
348
+ },
349
+
350
+ async getCompanyByHost(hostname) {
351
+ await ensure();
352
+ const host = String(hostname || '').trim().toLowerCase().split(':')[0];
353
+ if (!host) return null;
354
+ const { results } = await db.prepare(
355
+ `SELECT id, slug, name, legal_name, logo_url, favicon_url, primary_color, auth_bg_color,
356
+ support_email, website_url, tagline, meta_json, created_at, updated_at
357
+ FROM identity_companies`,
358
+ ).all();
359
+ for (const row of results || []) {
360
+ const company = normalizeCompanyRow(row);
361
+ if (!company) continue;
362
+ const hosts = Array.isArray(company.meta?.hosts)
363
+ ? company.meta.hosts.map((h) => String(h || '').trim().toLowerCase()).filter(Boolean)
364
+ : [];
365
+ if (hosts.includes(host)) return company;
366
+ try {
367
+ if (company.websiteUrl) {
368
+ const siteHost = new URL(company.websiteUrl).hostname.toLowerCase();
369
+ if (siteHost && siteHost === host) return company;
370
+ }
371
+ } catch {
372
+ /* ignore invalid website_url */
373
+ }
374
+ }
375
+ return null;
376
+ },
377
+
378
+ async getDefaultCompany() {
379
+ const bySlug = await this.getCompanyBySlug(DEFAULT_COMPANY_SLUG);
380
+ if (bySlug) return bySlug;
381
+ await ensure();
382
+ const row = await db.prepare(
383
+ `SELECT id, slug, name, legal_name, logo_url, favicon_url, primary_color, auth_bg_color,
384
+ support_email, website_url, tagline, meta_json, created_at, updated_at
385
+ FROM identity_companies WHERE id = ? LIMIT 1`,
386
+ ).bind(DEFAULT_COMPANY_ID).first();
387
+ return normalizeCompanyRow(row);
388
+ },
389
+
390
+ async upsertCompany(input) {
391
+ await ensure();
392
+ const ts = nowUnix();
393
+ const id = input.id || DEFAULT_COMPANY_ID;
394
+ const slug = input.slug || DEFAULT_COMPANY_SLUG;
395
+ const existing = await db.prepare(`SELECT id FROM identity_companies WHERE id = ? OR slug = ? LIMIT 1`)
396
+ .bind(id, slug).first();
397
+ const metaJson = input.meta ? JSON.stringify(input.meta) : (input.metaJson ?? null);
398
+ if (existing?.id) {
399
+ await db.prepare(
400
+ `UPDATE identity_companies SET
401
+ slug = ?, name = ?, legal_name = ?, logo_url = ?, favicon_url = ?,
402
+ primary_color = ?, auth_bg_color = ?, support_email = ?, website_url = ?,
403
+ tagline = ?, meta_json = ?, updated_at = ?
404
+ WHERE id = ?`,
405
+ ).bind(
406
+ slug, input.name, input.legalName ?? null, input.logoUrl ?? null, input.faviconUrl ?? null,
407
+ input.primaryColor ?? null, input.authBgColor ?? null, input.supportEmail ?? null,
408
+ input.websiteUrl ?? null, input.tagline ?? null, metaJson, ts, existing.id,
409
+ ).run();
410
+ return this.getCompanyBySlug(slug);
411
+ }
412
+ await db.prepare(
413
+ `INSERT INTO identity_companies
414
+ (id, slug, name, legal_name, logo_url, favicon_url, primary_color, auth_bg_color,
415
+ support_email, website_url, tagline, meta_json, created_at, updated_at)
416
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
417
+ ).bind(
418
+ id, slug, input.name, input.legalName ?? null, input.logoUrl ?? null, input.faviconUrl ?? null,
419
+ input.primaryColor ?? null, input.authBgColor ?? null, input.supportEmail ?? null,
420
+ input.websiteUrl ?? null, input.tagline ?? null, metaJson, ts, ts,
421
+ ).run();
422
+ return this.getCompanyBySlug(slug);
423
+ },
424
+
425
+ async upsertProviderConnection({
426
+ userId, provider, credentialRef, grantedScopes = [], expiresAt = null, refreshable = false,
427
+ }) {
428
+ await ensure();
429
+ if (!credentialRef) {
430
+ throw new IdentitySchemaError(
431
+ 'PROVIDER_CREDENTIAL_REF_REQUIRED',
432
+ 'Provider connections store credential_ref only — never raw secrets',
433
+ );
434
+ }
435
+ const existing = await db.prepare(
436
+ `SELECT id FROM identity_provider_connections WHERE user_id = ? AND provider = ? LIMIT 1`,
437
+ ).bind(userId, provider).first();
438
+ const ts = nowUnix();
439
+ const scopesJson = JSON.stringify(Array.isArray(grantedScopes) ? grantedScopes : []);
440
+ if (existing?.id) {
441
+ await db.prepare(
442
+ `UPDATE identity_provider_connections
443
+ SET granted_scopes_json = ?, credential_ref = ?, expires_at = ?, refreshable = ?, updated_at = ?
444
+ WHERE id = ?`,
445
+ ).bind(scopesJson, credentialRef, expiresAt, refreshable ? 1 : 0, ts, existing.id).run();
446
+ return existing.id;
447
+ }
448
+ const id = `ipc_${crypto.randomUUID().replace(/-/g, '')}`;
449
+ await db.prepare(
450
+ `INSERT INTO identity_provider_connections
451
+ (id, user_id, provider, granted_scopes_json, credential_ref, expires_at, refreshable, created_at, updated_at)
452
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
453
+ ).bind(id, userId, provider, scopesJson, credentialRef, expiresAt, refreshable ? 1 : 0, ts, ts).run();
454
+ return id;
455
+ },
456
+
457
+ async getProviderConnection(userId, provider) {
458
+ await ensure();
459
+ const row = await db.prepare(
460
+ `SELECT id, user_id, provider, granted_scopes_json, credential_ref, expires_at, refreshable, created_at, updated_at
461
+ FROM identity_provider_connections WHERE user_id = ? AND provider = ? LIMIT 1`,
462
+ ).bind(userId, provider).first();
463
+ if (!row) return null;
464
+ let grantedScopes = [];
465
+ try { grantedScopes = JSON.parse(row.granted_scopes_json || '[]'); } catch { grantedScopes = []; }
466
+ return {
467
+ id: row.id,
468
+ user_id: row.user_id,
469
+ provider: row.provider,
470
+ granted_scopes: grantedScopes,
471
+ credential_ref: row.credential_ref,
472
+ expires_at: row.expires_at,
473
+ refreshable: Boolean(row.refreshable),
474
+ created_at: row.created_at,
475
+ updated_at: row.updated_at,
476
+ };
477
+ },
478
+ });
479
+ }