forgeos 0.1.0-alpha.2 → 0.1.0-alpha.21

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 (406) hide show
  1. package/.npmignore +4 -0
  2. package/AGENTS.md +168 -81
  3. package/CHANGELOG.md +211 -0
  4. package/README.md +88 -14
  5. package/adapters/go/README.md +23 -0
  6. package/adapters/go/go.mod +3 -0
  7. package/adapters/go/http.go +149 -0
  8. package/adapters/go/registry.go +234 -0
  9. package/adapters/go/types.go +136 -0
  10. package/adapters/java/README.md +68 -0
  11. package/adapters/java/pom.xml +34 -0
  12. package/adapters/java/src/main/java/dev/forgeos/adapter/Auth.java +20 -0
  13. package/adapters/java/src/main/java/dev/forgeos/adapter/Diagnostic.java +16 -0
  14. package/adapters/java/src/main/java/dev/forgeos/adapter/Entry.java +38 -0
  15. package/adapters/java/src/main/java/dev/forgeos/adapter/EntryKind.java +16 -0
  16. package/adapters/java/src/main/java/dev/forgeos/adapter/ErrorInfo.java +4 -0
  17. package/adapters/java/src/main/java/dev/forgeos/adapter/Forge.java +94 -0
  18. package/adapters/java/src/main/java/dev/forgeos/adapter/ForgeCall.java +12 -0
  19. package/adapters/java/src/main/java/dev/forgeos/adapter/ForgeContext.java +11 -0
  20. package/adapters/java/src/main/java/dev/forgeos/adapter/ForgeHandler.java +8 -0
  21. package/adapters/java/src/main/java/dev/forgeos/adapter/ForgeHttpHandler.java +179 -0
  22. package/adapters/java/src/main/java/dev/forgeos/adapter/ForgeRegistry.java +121 -0
  23. package/adapters/java/src/main/java/dev/forgeos/adapter/Json.java +14 -0
  24. package/adapters/java/src/main/java/dev/forgeos/adapter/Manifest.java +14 -0
  25. package/adapters/java/src/main/java/dev/forgeos/adapter/RequestEnvelope.java +6 -0
  26. package/adapters/java/src/main/java/dev/forgeos/adapter/ResponseEnvelope.java +25 -0
  27. package/adapters/java/src/main/java/dev/forgeos/adapter/Risk.java +18 -0
  28. package/adapters/java/src/main/java/dev/forgeos/adapter/Schemas.java +36 -0
  29. package/adapters/java/src/main/java/dev/forgeos/adapter/Service.java +65 -0
  30. package/adapters/java/src/main/java/dev/forgeos/adapter/TransactionMode.java +18 -0
  31. package/adapters/java/src/main/java/dev/forgeos/adapter/TypedForgeHandler.java +6 -0
  32. package/adapters/java/target/classes/dev/forgeos/adapter/Auth.class +0 -0
  33. package/adapters/java/target/classes/dev/forgeos/adapter/Diagnostic.class +0 -0
  34. package/adapters/java/target/classes/dev/forgeos/adapter/Entry.class +0 -0
  35. package/adapters/java/target/classes/dev/forgeos/adapter/EntryKind.class +0 -0
  36. package/adapters/java/target/classes/dev/forgeos/adapter/ErrorInfo.class +0 -0
  37. package/adapters/java/target/classes/dev/forgeos/adapter/Forge.class +0 -0
  38. package/adapters/java/target/classes/dev/forgeos/adapter/ForgeCall.class +0 -0
  39. package/adapters/java/target/classes/dev/forgeos/adapter/ForgeContext.class +0 -0
  40. package/adapters/java/target/classes/dev/forgeos/adapter/ForgeHandler.class +0 -0
  41. package/adapters/java/target/classes/dev/forgeos/adapter/ForgeHttpHandler.class +0 -0
  42. package/adapters/java/target/classes/dev/forgeos/adapter/ForgeRegistry$EntryOption.class +0 -0
  43. package/adapters/java/target/classes/dev/forgeos/adapter/ForgeRegistry$RegisteredEntry.class +0 -0
  44. package/adapters/java/target/classes/dev/forgeos/adapter/ForgeRegistry$RegistryOption.class +0 -0
  45. package/adapters/java/target/classes/dev/forgeos/adapter/ForgeRegistry.class +0 -0
  46. package/adapters/java/target/classes/dev/forgeos/adapter/Json.class +0 -0
  47. package/adapters/java/target/classes/dev/forgeos/adapter/Manifest.class +0 -0
  48. package/adapters/java/target/classes/dev/forgeos/adapter/RequestEnvelope.class +0 -0
  49. package/adapters/java/target/classes/dev/forgeos/adapter/ResponseEnvelope.class +0 -0
  50. package/adapters/java/target/classes/dev/forgeos/adapter/Risk.class +0 -0
  51. package/adapters/java/target/classes/dev/forgeos/adapter/Schemas.class +0 -0
  52. package/adapters/java/target/classes/dev/forgeos/adapter/Service.class +0 -0
  53. package/adapters/java/target/classes/dev/forgeos/adapter/TransactionMode.class +0 -0
  54. package/adapters/java/target/classes/dev/forgeos/adapter/TypedForgeHandler.class +0 -0
  55. package/adapters/java/target/forge-java-adapter-0.1.0-alpha.11.jar +0 -0
  56. package/adapters/java/target/maven-archiver/pom.properties +3 -0
  57. package/adapters/java/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst +23 -0
  58. package/adapters/java/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst +20 -0
  59. package/adapters/java-spring-boot-starter/README.md +32 -0
  60. package/adapters/java-spring-boot-starter/pom.xml +36 -0
  61. package/adapters/java-spring-boot-starter/src/main/java/dev/forgeos/adapter/spring/ForgeCommand.java +22 -0
  62. package/adapters/java-spring-boot-starter/src/main/java/dev/forgeos/adapter/spring/ForgeExternalService.java +15 -0
  63. package/adapters/java-spring-boot-starter/src/main/java/dev/forgeos/adapter/spring/ForgeQuery.java +16 -0
  64. package/adapters/java-spring-boot-starter/src/main/java/dev/forgeos/adapter/spring/ForgeServiceBeanCondition.java +18 -0
  65. package/adapters/java-spring-boot-starter/src/main/java/dev/forgeos/adapter/spring/ForgeSpringAutoConfiguration.java +16 -0
  66. package/adapters/java-spring-boot-starter/src/main/java/dev/forgeos/adapter/spring/ForgeSpringRuntime.java +104 -0
  67. package/adapters/java-spring-boot-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +1 -0
  68. package/adapters/java-spring-boot-starter/target/classes/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +1 -0
  69. package/adapters/java-spring-boot-starter/target/classes/dev/forgeos/adapter/spring/ForgeCommand.class +0 -0
  70. package/adapters/java-spring-boot-starter/target/classes/dev/forgeos/adapter/spring/ForgeExternalService.class +0 -0
  71. package/adapters/java-spring-boot-starter/target/classes/dev/forgeos/adapter/spring/ForgeQuery.class +0 -0
  72. package/adapters/java-spring-boot-starter/target/classes/dev/forgeos/adapter/spring/ForgeServiceBeanCondition.class +0 -0
  73. package/adapters/java-spring-boot-starter/target/classes/dev/forgeos/adapter/spring/ForgeSpringAutoConfiguration.class +0 -0
  74. package/adapters/java-spring-boot-starter/target/classes/dev/forgeos/adapter/spring/ForgeSpringRuntime.class +0 -0
  75. package/adapters/java-spring-boot-starter/target/forge-java-spring-boot-starter-0.1.0-alpha.11.jar +0 -0
  76. package/adapters/java-spring-boot-starter/target/maven-archiver/pom.properties +3 -0
  77. package/adapters/java-spring-boot-starter/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst +6 -0
  78. package/adapters/java-spring-boot-starter/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst +6 -0
  79. package/bin/forge.mjs +18 -0
  80. package/docs/changelog.md +242 -0
  81. package/docs/forge-protocol.md +189 -0
  82. package/examples/go-billing/go.mod +7 -0
  83. package/examples/go-billing/main.go +120 -0
  84. package/examples/java-billing/pom.xml +52 -0
  85. package/examples/java-billing/src/main/java/dev/forgeos/examples/billing/CreateInvoiceInput.java +4 -0
  86. package/examples/java-billing/src/main/java/dev/forgeos/examples/billing/Invoice.java +11 -0
  87. package/examples/java-billing/src/main/java/dev/forgeos/examples/billing/Main.java +127 -0
  88. package/examples/java-billing/target/classes/dev/forgeos/examples/billing/CreateInvoiceInput.class +0 -0
  89. package/examples/java-billing/target/classes/dev/forgeos/examples/billing/Invoice.class +0 -0
  90. package/examples/java-billing/target/classes/dev/forgeos/examples/billing/Main$EmptyInput.class +0 -0
  91. package/examples/java-billing/target/classes/dev/forgeos/examples/billing/Main$Options.class +0 -0
  92. package/examples/java-billing/target/classes/dev/forgeos/examples/billing/Main.class +0 -0
  93. package/examples/java-billing/target/java-billing-0.1.0-alpha.11-all.jar +0 -0
  94. package/examples/java-billing/target/java-billing-0.1.0-alpha.11.jar +0 -0
  95. package/examples/java-billing/target/maven-archiver/pom.properties +3 -0
  96. package/examples/java-billing/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst +5 -0
  97. package/examples/java-billing/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst +3 -0
  98. package/package.json +29 -7
  99. package/schemas/forge-manifest.schema.json +57 -0
  100. package/src/forge/_generated/releaseManifest.json +1 -2
  101. package/src/forge/_generated/releaseManifest.ts +3 -3
  102. package/src/forge/agent-adapters/index.ts +1511 -123
  103. package/src/forge/agent-adapters/types.ts +216 -1
  104. package/src/forge/agent-memory/bridge.ts +1245 -0
  105. package/src/forge/agent-memory/context-pack.ts +151 -0
  106. package/src/forge/agent-memory/hook-runner.ts +312 -0
  107. package/src/forge/agent-memory/mcp.ts +224 -0
  108. package/src/forge/agent-memory/normalize.ts +498 -0
  109. package/src/forge/agent-memory/redaction.ts +103 -0
  110. package/src/forge/agent-memory/sources/claude-code.ts +51 -0
  111. package/src/forge/agent-memory/sources/codex-hook-runner.mjs +273 -0
  112. package/src/forge/agent-memory/sources/codex.ts +119 -0
  113. package/src/forge/agent-memory/sources/cursor.ts +35 -0
  114. package/src/forge/agent-memory/types.ts +191 -0
  115. package/src/forge/bench.ts +248 -0
  116. package/src/forge/brownfield-import/index.ts +801 -0
  117. package/src/forge/brownfield-import/types.ts +127 -0
  118. package/src/forge/cair/action-journal.ts +61 -0
  119. package/src/forge/cair/action-parser.ts +314 -0
  120. package/src/forge/cair/action-validator.ts +40 -0
  121. package/src/forge/cair/actions.ts +1818 -0
  122. package/src/forge/cair/format.ts +77 -0
  123. package/src/forge/cair/index.ts +106 -0
  124. package/src/forge/cair/query.ts +478 -0
  125. package/src/forge/cair/snapshot.ts +315 -0
  126. package/src/forge/cair/types.ts +248 -0
  127. package/src/forge/cli/ai.ts +671 -3
  128. package/src/forge/cli/auth.ts +36 -1
  129. package/src/forge/cli/build.ts +20 -4
  130. package/src/forge/cli/changed.ts +300 -0
  131. package/src/forge/cli/codex-app-server.ts +877 -0
  132. package/src/forge/cli/commands.ts +1285 -7
  133. package/src/forge/cli/db.ts +121 -2
  134. package/src/forge/cli/deps.ts +79 -12
  135. package/src/forge/cli/dev.ts +502 -38
  136. package/src/forge/cli/docs.ts +265 -0
  137. package/src/forge/cli/handoff.ts +250 -0
  138. package/src/forge/cli/index.ts +1 -0
  139. package/src/forge/cli/main.ts +49 -3
  140. package/src/forge/cli/new.ts +3 -1
  141. package/src/forge/cli/next-actions.ts +23 -0
  142. package/src/forge/cli/output.ts +290 -1
  143. package/src/forge/cli/parse.ts +770 -36
  144. package/src/forge/cli/query.ts +32 -0
  145. package/src/forge/cli/release.ts +35 -11
  146. package/src/forge/cli/rls.ts +568 -17
  147. package/src/forge/cli/run.ts +41 -0
  148. package/src/forge/cli/secrets.ts +46 -1
  149. package/src/forge/cli/security.ts +381 -0
  150. package/src/forge/cli/self-host.ts +56 -14
  151. package/src/forge/cli/studio.ts +2163 -0
  152. package/src/forge/cli/verify.ts +1422 -32
  153. package/src/forge/compiler/agent-contract/build.ts +725 -41
  154. package/src/forge/compiler/agent-contract/types.ts +85 -0
  155. package/src/forge/compiler/ai-registry/build.ts +62 -1
  156. package/src/forge/compiler/ai-registry/constants.ts +1 -1
  157. package/src/forge/compiler/ai-registry/parse.ts +168 -5
  158. package/src/forge/compiler/api-surface/build.ts +47 -0
  159. package/src/forge/compiler/app-graph/build.ts +68 -8
  160. package/src/forge/compiler/app-graph/extract.ts +107 -0
  161. package/src/forge/compiler/app-graph/forge-apis.ts +1 -0
  162. package/src/forge/compiler/app-graph/module-graph.ts +73 -78
  163. package/src/forge/compiler/app-graph/parser.ts +24 -24
  164. package/src/forge/compiler/app-graph/profile.ts +26 -0
  165. package/src/forge/compiler/app-graph/versions.ts +1 -1
  166. package/src/forge/compiler/classifier/capabilities.ts +3 -2
  167. package/src/forge/compiler/classifier/classify.ts +32 -8
  168. package/src/forge/compiler/classifier/secrets.ts +3 -2
  169. package/src/forge/compiler/classifier/signals.ts +91 -1
  170. package/src/forge/compiler/client-sdk/build-manifest.ts +59 -0
  171. package/src/forge/compiler/client-sdk/render-client.ts +188 -13
  172. package/src/forge/compiler/data-graph/parse.ts +3 -3
  173. package/src/forge/compiler/data-graph/sql/ddl.ts +60 -2
  174. package/src/forge/compiler/data-graph/sql/serialize.ts +4 -0
  175. package/src/forge/compiler/data-graph/sql/types.ts +1 -0
  176. package/src/forge/compiler/dev-manifest/build.ts +3 -0
  177. package/src/forge/compiler/diagnostics/codes.ts +35 -0
  178. package/src/forge/compiler/diagnostics/create.ts +8 -3
  179. package/src/forge/compiler/diagnostics/index.ts +2 -0
  180. package/src/forge/compiler/emitter/barrel.ts +3 -0
  181. package/src/forge/compiler/emitter/render.ts +5 -0
  182. package/src/forge/compiler/external-manifest/registry.ts +205 -0
  183. package/src/forge/compiler/external-manifest/types.ts +91 -0
  184. package/src/forge/compiler/external-manifest/validate.ts +373 -0
  185. package/src/forge/compiler/frontend-graph/build.ts +85 -13
  186. package/src/forge/compiler/integration/add.ts +498 -22
  187. package/src/forge/compiler/integration/snapshot.ts +2 -0
  188. package/src/forge/compiler/make-registry/build.ts +19 -7
  189. package/src/forge/compiler/orchestrator/plan-profile.ts +23 -0
  190. package/src/forge/compiler/orchestrator/plan.ts +78 -7
  191. package/src/forge/compiler/orchestrator/profile.ts +65 -0
  192. package/src/forge/compiler/orchestrator/run.ts +97 -31
  193. package/src/forge/compiler/orchestrator/serialize.ts +101 -8
  194. package/src/forge/compiler/package-graph/compiler.ts +13 -3
  195. package/src/forge/compiler/package-manager/adapter.ts +4 -1
  196. package/src/forge/compiler/package-manager/commands.ts +4 -0
  197. package/src/forge/compiler/package-manager/executor.ts +30 -1
  198. package/src/forge/compiler/policy-registry/build.ts +44 -1
  199. package/src/forge/compiler/test-graph/build.ts +11 -3
  200. package/src/forge/compiler/types/ai-registry.ts +25 -1
  201. package/src/forge/compiler/types/app-graph.ts +9 -2
  202. package/src/forge/compiler/types/cli.ts +76 -1
  203. package/src/forge/compiler/types/dev-manifest.ts +3 -0
  204. package/src/forge/compiler/types/frontend-graph.ts +2 -2
  205. package/src/forge/delta/classifier.ts +52 -0
  206. package/src/forge/delta/explain.ts +126 -0
  207. package/src/forge/delta/git-observer.ts +43 -0
  208. package/src/forge/delta/ids.ts +44 -0
  209. package/src/forge/delta/index.ts +13 -0
  210. package/src/forge/delta/recorder.ts +402 -0
  211. package/src/forge/delta/redaction.ts +50 -0
  212. package/src/forge/delta/schema.ts +240 -0
  213. package/src/forge/delta/session.ts +142 -0
  214. package/src/forge/delta/status.ts +489 -0
  215. package/src/forge/delta/store.ts +2975 -0
  216. package/src/forge/delta/timeline.ts +104 -0
  217. package/src/forge/dev/server.ts +768 -15
  218. package/src/forge/dev/types.ts +15 -1
  219. package/src/forge/dev/watch.ts +17 -7
  220. package/src/forge/dev-console/cycle.ts +233 -21
  221. package/src/forge/dev-console/types.ts +46 -1
  222. package/src/forge/impact/index.ts +46 -8
  223. package/src/forge/impact/types.ts +6 -0
  224. package/src/forge/intent/index.ts +35 -16
  225. package/src/forge/make/index.ts +149 -6
  226. package/src/forge/make/templates.ts +343 -2
  227. package/src/forge/make/types.ts +3 -1
  228. package/src/forge/refactor/index.ts +1 -0
  229. package/src/forge/repair/rules/index.ts +2 -2
  230. package/src/forge/review/index.ts +158 -12
  231. package/src/forge/review/types.ts +15 -0
  232. package/src/forge/runtime/ai/context.ts +210 -5
  233. package/src/forge/runtime/ai/types.ts +70 -0
  234. package/src/forge/runtime/auth/claims.ts +32 -0
  235. package/src/forge/runtime/auth/errors.ts +2 -0
  236. package/src/forge/runtime/context/create-context.ts +30 -6
  237. package/src/forge/runtime/db/generated-client.ts +13 -2
  238. package/src/forge/runtime/db/memory-adapter.ts +2 -2
  239. package/src/forge/runtime/db/pglite-adapter.ts +77 -2
  240. package/src/forge/runtime/db/postgres-adapter.ts +6 -3
  241. package/src/forge/runtime/executor.ts +112 -2
  242. package/src/forge/runtime/external/bridge.ts +649 -0
  243. package/src/forge/runtime/runner/run-entry.ts +16 -7
  244. package/src/forge/runtime/telemetry/scrubber.ts +91 -10
  245. package/src/forge/runtime/webhooks/security.ts +184 -0
  246. package/src/forge/server.ts +100 -2
  247. package/src/forge/version.ts +1 -1
  248. package/src/forge/vue/index.ts +407 -0
  249. package/src/forge/workspace/change-summary.ts +209 -0
  250. package/src/forge/workspace/forge-cli.ts +14 -0
  251. package/src/forge/workspace/git-summary.ts +279 -0
  252. package/templates/agent-workroom/AGENTS.md +29 -0
  253. package/templates/agent-workroom/README.md +34 -0
  254. package/templates/agent-workroom/forge.config.ts +3 -0
  255. package/templates/agent-workroom/package.json +33 -0
  256. package/templates/agent-workroom/src/actions/indexAgentSignal.ts +10 -0
  257. package/templates/agent-workroom/src/commands/openWorkroom.ts +61 -0
  258. package/templates/agent-workroom/src/commands/recordAgentSignal.ts +119 -0
  259. package/templates/agent-workroom/src/commands/recordCheckRun.ts +52 -0
  260. package/templates/agent-workroom/src/forge/schema.ts +54 -0
  261. package/templates/agent-workroom/src/policies.ts +6 -0
  262. package/templates/agent-workroom/src/queries/listWorkrooms.ts +11 -0
  263. package/templates/agent-workroom/src/queries/liveWorkroom.ts +63 -0
  264. package/templates/agent-workroom/tsconfig.json +16 -0
  265. package/templates/agent-workroom/web/index.html +12 -0
  266. package/templates/agent-workroom/web/package.json +21 -0
  267. package/templates/agent-workroom/web/src/App.tsx +345 -0
  268. package/templates/agent-workroom/web/src/lib/forge.ts +13 -0
  269. package/templates/agent-workroom/web/src/main.tsx +13 -0
  270. package/templates/agent-workroom/web/src/styles.css +545 -0
  271. package/templates/agent-workroom/web/tsconfig.json +27 -0
  272. package/templates/b2b-support-web/package.json +2 -0
  273. package/templates/b2b-support-web/tsconfig.json +4 -1
  274. package/templates/b2b-support-web/web/package.json +1 -1
  275. package/templates/minimal-web/package.json +2 -1
  276. package/templates/minimal-web/tsconfig.json +3 -1
  277. package/templates/minimal-web/web/package.json +2 -2
  278. package/src/forge/_generated/actionSubscriptions.json +0 -2
  279. package/src/forge/_generated/actionSubscriptions.ts +0 -10
  280. package/src/forge/_generated/agentAdapterManifest.json +0 -2
  281. package/src/forge/_generated/agentAdapterManifest.ts +0 -73
  282. package/src/forge/_generated/agentContract.json +0 -2
  283. package/src/forge/_generated/agentContract.ts +0 -7696
  284. package/src/forge/_generated/agentQuickstart.md +0 -32
  285. package/src/forge/_generated/aiContext.ts +0 -59
  286. package/src/forge/_generated/aiModels.json +0 -2
  287. package/src/forge/_generated/aiModels.ts +0 -35
  288. package/src/forge/_generated/aiProviders.json +0 -2
  289. package/src/forge/_generated/aiProviders.ts +0 -23
  290. package/src/forge/_generated/aiRegistry.json +0 -2
  291. package/src/forge/_generated/aiRegistry.ts +0 -29
  292. package/src/forge/_generated/api.json +0 -2
  293. package/src/forge/_generated/api.ts +0 -8
  294. package/src/forge/_generated/appGraph.json +0 -2
  295. package/src/forge/_generated/appGraph.ts +0 -14667
  296. package/src/forge/_generated/appMap.md +0 -35
  297. package/src/forge/_generated/artifactManifest.json +0 -2
  298. package/src/forge/_generated/artifactManifest.ts +0 -7
  299. package/src/forge/_generated/authClaims.json +0 -2
  300. package/src/forge/_generated/authClaims.ts +0 -13
  301. package/src/forge/_generated/authConfig.json +0 -2
  302. package/src/forge/_generated/authConfig.ts +0 -17
  303. package/src/forge/_generated/authContext.ts +0 -23
  304. package/src/forge/_generated/authRegistry.json +0 -2
  305. package/src/forge/_generated/authRegistry.ts +0 -25
  306. package/src/forge/_generated/buildInfo.json +0 -2
  307. package/src/forge/_generated/buildInfo.ts +0 -9
  308. package/src/forge/_generated/capabilityMap.json +0 -2
  309. package/src/forge/_generated/capabilityMap.md +0 -15
  310. package/src/forge/_generated/capabilityMap.ts +0 -17
  311. package/src/forge/_generated/client.ts +0 -282
  312. package/src/forge/_generated/clientApi.ts +0 -9
  313. package/src/forge/_generated/clientManifest.json +0 -2
  314. package/src/forge/_generated/clientManifest.ts +0 -39
  315. package/src/forge/_generated/clientTypes.ts +0 -78
  316. package/src/forge/_generated/configRegistry.json +0 -2
  317. package/src/forge/_generated/configRegistry.ts +0 -4
  318. package/src/forge/_generated/dataGraph.json +0 -2
  319. package/src/forge/_generated/dataGraph.ts +0 -8
  320. package/src/forge/_generated/db.json +0 -2
  321. package/src/forge/_generated/db.ts +0 -2
  322. package/src/forge/_generated/dbSecurityManifest.json +0 -2
  323. package/src/forge/_generated/dbSecurityManifest.ts +0 -15
  324. package/src/forge/_generated/dbSessionContext.json +0 -2
  325. package/src/forge/_generated/dbSessionContext.ts +0 -39
  326. package/src/forge/_generated/deployManifest.json +0 -2
  327. package/src/forge/_generated/deployManifest.ts +0 -14
  328. package/src/forge/_generated/devManifest.json +0 -2
  329. package/src/forge/_generated/devManifest.ts +0 -47
  330. package/src/forge/_generated/envSchema.json +0 -2
  331. package/src/forge/_generated/envSchema.ts +0 -59
  332. package/src/forge/_generated/frontendGraph.json +0 -2
  333. package/src/forge/_generated/frontendGraph.ts +0 -27
  334. package/src/forge/_generated/importGuards.json +0 -2
  335. package/src/forge/_generated/importGuards.ts +0 -686
  336. package/src/forge/_generated/index.ts +0 -67
  337. package/src/forge/_generated/liveProductionManifest.json +0 -2
  338. package/src/forge/_generated/liveProductionManifest.ts +0 -23
  339. package/src/forge/_generated/liveProtocol.json +0 -2
  340. package/src/forge/_generated/liveProtocol.ts +0 -21
  341. package/src/forge/_generated/liveQueryRegistry.json +0 -2
  342. package/src/forge/_generated/liveQueryRegistry.ts +0 -9
  343. package/src/forge/_generated/liveTransportConfig.json +0 -2
  344. package/src/forge/_generated/liveTransportConfig.ts +0 -19
  345. package/src/forge/_generated/makeRegistry.json +0 -2
  346. package/src/forge/_generated/makeRegistry.ts +0 -163
  347. package/src/forge/_generated/makeTemplates.json +0 -2
  348. package/src/forge/_generated/makeTemplates.ts +0 -61
  349. package/src/forge/_generated/mockMap.json +0 -2
  350. package/src/forge/_generated/mockMap.ts +0 -7
  351. package/src/forge/_generated/operationPlaybooks.md +0 -147
  352. package/src/forge/_generated/packageGraph.json +0 -2
  353. package/src/forge/_generated/packageGraph.ts +0 -245249
  354. package/src/forge/_generated/packageUpgradeRegistry.json +0 -2
  355. package/src/forge/_generated/packageUpgradeRegistry.ts +0 -15
  356. package/src/forge/_generated/permissionMatrix.json +0 -2
  357. package/src/forge/_generated/permissionMatrix.ts +0 -7
  358. package/src/forge/_generated/policyRegistry.json +0 -2
  359. package/src/forge/_generated/policyRegistry.ts +0 -11
  360. package/src/forge/_generated/queryRegistry.json +0 -2
  361. package/src/forge/_generated/queryRegistry.ts +0 -9
  362. package/src/forge/_generated/react.d.ts +0 -22
  363. package/src/forge/_generated/react.ts +0 -29
  364. package/src/forge/_generated/reactManifest.json +0 -2
  365. package/src/forge/_generated/reactManifest.ts +0 -19
  366. package/src/forge/_generated/rlsPolicies.json +0 -2
  367. package/src/forge/_generated/rlsPolicies.sql +0 -34
  368. package/src/forge/_generated/rlsPolicies.ts +0 -6
  369. package/src/forge/_generated/runtimeGraph.json +0 -2
  370. package/src/forge/_generated/runtimeGraph.ts +0 -8
  371. package/src/forge/_generated/runtimeMatrix.json +0 -2
  372. package/src/forge/_generated/runtimeMatrix.ts +0 -327385
  373. package/src/forge/_generated/runtimeRegistry.ts +0 -2
  374. package/src/forge/_generated/runtimeRules.md +0 -79
  375. package/src/forge/_generated/secretRegistry.json +0 -2
  376. package/src/forge/_generated/secretRegistry.ts +0 -50
  377. package/src/forge/_generated/secretsContext.ts +0 -11
  378. package/src/forge/_generated/serverApi.ts +0 -10
  379. package/src/forge/_generated/sourceMapManifest.json +0 -2
  380. package/src/forge/_generated/sourceMapManifest.ts +0 -7
  381. package/src/forge/_generated/sqlPlan.json +0 -2
  382. package/src/forge/_generated/sqlPlan.ts +0 -88
  383. package/src/forge/_generated/subscriptionManifest.json +0 -2
  384. package/src/forge/_generated/subscriptionManifest.ts +0 -7
  385. package/src/forge/_generated/symbolicationManifest.json +0 -2
  386. package/src/forge/_generated/symbolicationManifest.ts +0 -17
  387. package/src/forge/_generated/telemetryRegistry.json +0 -2
  388. package/src/forge/_generated/telemetryRegistry.ts +0 -9
  389. package/src/forge/_generated/telemetrySinks.json +0 -2
  390. package/src/forge/_generated/telemetrySinks.ts +0 -11
  391. package/src/forge/_generated/tenantScope.json +0 -2
  392. package/src/forge/_generated/tenantScope.ts +0 -8
  393. package/src/forge/_generated/testGraph.json +0 -2
  394. package/src/forge/_generated/testGraph.ts +0 -3108
  395. package/src/forge/_generated/testPlanRegistry.json +0 -2
  396. package/src/forge/_generated/testPlanRegistry.ts +0 -33
  397. package/src/forge/_generated/uiRoutes.json +0 -2
  398. package/src/forge/_generated/uiRoutes.ts +0 -16
  399. package/src/forge/_generated/uiScenarios.json +0 -2
  400. package/src/forge/_generated/uiScenarios.ts +0 -30
  401. package/src/forge/_generated/uiTestManifest.json +0 -2
  402. package/src/forge/_generated/uiTestManifest.ts +0 -27
  403. package/src/forge/_generated/workflowRegistry.json +0 -2
  404. package/src/forge/_generated/workflowRegistry.ts +0 -9
  405. package/src/forge/_generated/workflowSubscriptions.json +0 -2
  406. package/src/forge/_generated/workflowSubscriptions.ts +0 -10
@@ -5,6 +5,7 @@ import { createDiagnostic } from "../compiler/diagnostics/create.ts";
5
5
  import {
6
6
  FORGE_DB_SUPERUSER_RUNTIME,
7
7
  FORGE_RLS_APPLY_FAILED,
8
+ FORGE_RLS_MUTATION_FAILED,
8
9
  FORGE_RLS_PGLITE_NOT_AUTHORITATIVE,
9
10
  FORGE_RLS_POLICY_MISSING,
10
11
  FORGE_RLS_TEST_FAILED,
@@ -12,11 +13,15 @@ import {
12
13
  import { GENERATED_DIR } from "../compiler/emitter/constants.ts";
13
14
  import { stripDeterministicHeader } from "../compiler/primitives/header.ts";
14
15
  import type { Diagnostic } from "../compiler/types/diagnostic.ts";
15
- import type { DbAdapterKind } from "../runtime/db/adapter.ts";
16
+ import type { ColumnDef, SqlPlan } from "../compiler/data-graph/sql/types.ts";
17
+ import { applyMigrations } from "../runtime/db/migrate.ts";
18
+ import type { DbAdapter, DbAdapterKind, DbTransaction } from "../runtime/db/adapter.ts";
16
19
  import { createDbAdapter } from "../runtime/db/factory.ts";
17
20
  import { databaseUrlUsesPostgresSuperuser } from "../runtime/db/session-context.ts";
21
+ import type { RlsTableSecurity } from "../compiler/data-graph/rls/types.ts";
22
+ import { createHash, randomUUID } from "node:crypto";
18
23
 
19
- export type RlsSubcommand = "generate" | "check" | "apply" | "test";
24
+ export type RlsSubcommand = "generate" | "check" | "apply" | "test" | "mutate-test";
20
25
 
21
26
  export interface RlsCommandOptions {
22
27
  subcommand: RlsSubcommand;
@@ -88,6 +93,446 @@ function splitSqlStatements(sql: string): string[] {
88
93
  return statements;
89
94
  }
90
95
 
96
+ function quoteIdent(identifier: string): string {
97
+ return `"${identifier.replace(/"/g, '""')}"`;
98
+ }
99
+
100
+ function stableUuid(seed: string): string {
101
+ const hex = createHash("sha256").update(seed).digest("hex");
102
+ return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-4${hex.slice(13, 16)}-8${hex.slice(17, 20)}-${hex.slice(20, 32)}`;
103
+ }
104
+
105
+ function enumValue(column: ColumnDef): string | null {
106
+ const values = column.checkConstraint?.match(/'([^']+)'/g);
107
+ return values?.[0]?.slice(1, -1) ?? null;
108
+ }
109
+
110
+ function valueForColumn(column: ColumnDef, seed: string): unknown {
111
+ const sqlType = column.sqlType.toLowerCase();
112
+ if (sqlType === "uuid") {
113
+ return stableUuid(`${seed}:${column.name}`);
114
+ }
115
+ if (sqlType === "boolean") {
116
+ return true;
117
+ }
118
+ if (sqlType === "integer" || sqlType === "bigint" || sqlType === "smallint") {
119
+ return Math.abs(Number.parseInt(createHash("sha1").update(seed).digest("hex").slice(0, 6), 16)) % 100000;
120
+ }
121
+ if (sqlType === "double precision" || sqlType === "numeric" || sqlType === "real") {
122
+ return 42.25;
123
+ }
124
+ if (sqlType === "jsonb" || sqlType === "json") {
125
+ return JSON.stringify({ forgeRlsProbe: seed });
126
+ }
127
+ if (sqlType === "timestamptz" || sqlType === "timestamp" || sqlType === "date") {
128
+ return "2026-01-01T00:00:00.000Z";
129
+ }
130
+ return enumValue(column) ?? `forge-rls-probe-${seed}-${column.name}`;
131
+ }
132
+
133
+ function placeholderForColumn(column: ColumnDef, index: number): string {
134
+ const sqlType = column.sqlType.toLowerCase();
135
+ const placeholder = `$${index}`;
136
+ if (sqlType === "uuid") {
137
+ return `${placeholder}::uuid`;
138
+ }
139
+ if (sqlType === "jsonb") {
140
+ return `${placeholder}::jsonb`;
141
+ }
142
+ if (sqlType === "json") {
143
+ return `${placeholder}::json`;
144
+ }
145
+ if (sqlType === "timestamptz") {
146
+ return `${placeholder}::timestamptz`;
147
+ }
148
+ if (sqlType === "timestamp") {
149
+ return `${placeholder}::timestamp`;
150
+ }
151
+ if (sqlType === "date") {
152
+ return `${placeholder}::date`;
153
+ }
154
+ return placeholder;
155
+ }
156
+
157
+ function tablePlan(plan: SqlPlan, tableName: string) {
158
+ return plan.tables.find((table) => table.table === tableName && table.columns);
159
+ }
160
+
161
+ function primaryColumn(columns: ColumnDef[]): ColumnDef {
162
+ return columns.find((column) => column.primaryKey) ?? columns[0]!;
163
+ }
164
+
165
+ function buildRow(
166
+ table: { table?: string; columns?: ColumnDef[] },
167
+ seed: string,
168
+ overrides: Record<string, unknown> = {},
169
+ ): Record<string, unknown> {
170
+ const row: Record<string, unknown> = {};
171
+ for (const column of table.columns ?? []) {
172
+ row[column.name] = Object.hasOwn(overrides, column.name)
173
+ ? overrides[column.name]
174
+ : valueForColumn(column, seed);
175
+ }
176
+ return row;
177
+ }
178
+
179
+ async function insertRow(
180
+ tx: DbTransaction,
181
+ tableName: string,
182
+ columns: ColumnDef[],
183
+ row: Record<string, unknown>,
184
+ ): Promise<void> {
185
+ const insertColumns = columns.filter((column) => Object.hasOwn(row, column.name));
186
+ const names = insertColumns.map((column) => quoteIdent(column.name)).join(", ");
187
+ const placeholders = insertColumns
188
+ .map((column, index) => placeholderForColumn(column, index + 1))
189
+ .join(", ");
190
+ const values = insertColumns.map((column) => row[column.name]);
191
+ await tx.query(
192
+ `INSERT INTO ${quoteIdent(tableName)} (${names}) VALUES (${placeholders})`,
193
+ values,
194
+ );
195
+ }
196
+
197
+ async function setTenant(tx: DbTransaction, tenantId: string): Promise<void> {
198
+ await tx.query("SELECT set_config($1, $2, true)", ["forge.tenant_id", tenantId]);
199
+ }
200
+
201
+ async function setProbeRole(adapter: DbAdapter, tableNames: string[]): Promise<string | null> {
202
+ const roleName = "forge_rls_probe";
203
+ try {
204
+ const schema = await adapter.query("SELECT current_schema() AS schema");
205
+ const currentSchema = String(schema.rows[0]?.schema ?? "public");
206
+ await adapter.query(
207
+ `DO $$ BEGIN CREATE ROLE ${quoteIdent(roleName)} NOLOGIN NOBYPASSRLS; EXCEPTION WHEN duplicate_object THEN NULL; END $$`,
208
+ );
209
+ await adapter.query(`GRANT USAGE ON SCHEMA public TO ${quoteIdent(roleName)}`);
210
+ await adapter.query(`GRANT USAGE ON SCHEMA forge TO ${quoteIdent(roleName)}`);
211
+ if (currentSchema !== "public" && currentSchema !== "forge") {
212
+ await adapter.query(`GRANT USAGE ON SCHEMA ${quoteIdent(currentSchema)} TO ${quoteIdent(roleName)}`);
213
+ }
214
+ for (const tableName of tableNames) {
215
+ await adapter.query(
216
+ `GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE ${quoteIdent(tableName)} TO ${quoteIdent(roleName)}`,
217
+ );
218
+ }
219
+ await adapter.query(`GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO ${quoteIdent(roleName)}`);
220
+ if (currentSchema !== "public" && currentSchema !== "forge") {
221
+ await adapter.query(
222
+ `GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA ${quoteIdent(currentSchema)} TO ${quoteIdent(roleName)}`,
223
+ );
224
+ }
225
+ return roleName;
226
+ } catch {
227
+ return null;
228
+ }
229
+ }
230
+
231
+ async function isolateRlsProbeSchema(adapter: DbAdapter): Promise<string | null> {
232
+ const schemaName = `forge_rls_probe_${randomUUID().replaceAll("-", "").slice(0, 16)}`;
233
+ try {
234
+ await adapter.query(`CREATE SCHEMA ${quoteIdent(schemaName)}`);
235
+ await adapter.query(`SET search_path TO ${quoteIdent(schemaName)}, forge, public`);
236
+ return schemaName;
237
+ } catch {
238
+ return null;
239
+ }
240
+ }
241
+
242
+ async function seedReferencedRows(
243
+ tx: DbTransaction,
244
+ plan: SqlPlan,
245
+ columns: ColumnDef[],
246
+ row: Record<string, unknown>,
247
+ seed: string,
248
+ seen: Set<string>,
249
+ ): Promise<void> {
250
+ for (const column of columns) {
251
+ if (!column.references) {
252
+ continue;
253
+ }
254
+ const key = `${column.references.table}.${column.references.column}:${String(row[column.name])}`;
255
+ if (seen.has(key)) {
256
+ continue;
257
+ }
258
+ seen.add(key);
259
+ const referenced = tablePlan(plan, column.references.table);
260
+ if (!referenced?.columns) {
261
+ continue;
262
+ }
263
+ const referencedRow = buildRow(referenced, `${seed}:${column.references.table}`, {
264
+ [column.references.column]: row[column.name],
265
+ });
266
+ await insertRow(tx, column.references.table, referenced.columns, referencedRow);
267
+ }
268
+ }
269
+
270
+ async function applyRlsSql(adapter: DbAdapter, workspaceRoot: string): Promise<number> {
271
+ const sql = readGeneratedText(workspaceRoot, `${GENERATED_DIR}/rlsPolicies.sql`);
272
+ if (!sql) {
273
+ return 0;
274
+ }
275
+ const statements = splitSqlStatements(sql);
276
+ for (const statement of statements) {
277
+ await adapter.query(statement);
278
+ }
279
+ return statements.length;
280
+ }
281
+
282
+ interface RlsProbeResult {
283
+ table: string;
284
+ role: string | null;
285
+ tenantAVisible: number;
286
+ tenantBVisible: number;
287
+ unscopedVisible: number;
288
+ crossTenantUpdateBlocked: boolean;
289
+ crossTenantDeleteBlocked: boolean;
290
+ mismatchedInsertBlocked: boolean;
291
+ }
292
+
293
+ async function runTableProbe(
294
+ adapter: DbAdapter,
295
+ plan: SqlPlan,
296
+ table: RlsTableSecurity,
297
+ role: string | null,
298
+ ): Promise<RlsProbeResult> {
299
+ const change = tablePlan(plan, table.table);
300
+ const columns = change?.columns ?? [];
301
+ const primary = primaryColumn(columns);
302
+ const tenantA = table.tenantType === "uuid" ? "11111111-1111-4111-8111-111111111111" : "tenant-a";
303
+ const tenantB = table.tenantType === "uuid" ? "22222222-2222-4222-8222-222222222222" : "tenant-b";
304
+ const tenantColumn = columns.find((column) => column.name === table.tenantColumn);
305
+ const mutableColumn = columns.find(
306
+ (column) => !column.primaryKey && column.name !== table.tenantColumn,
307
+ );
308
+ if (!tenantColumn) {
309
+ throw new Error(`missing tenant column ${table.table}.${table.tenantColumn}`);
310
+ }
311
+
312
+ const rowA = buildRow(change!, `${table.table}:a`, {
313
+ [table.tenantColumn]: tenantA,
314
+ [primary.name]: valueForColumn(primary, `${table.table}:a:${primary.name}`),
315
+ });
316
+ const rowB = buildRow(change!, `${table.table}:b`, {
317
+ [table.tenantColumn]: tenantB,
318
+ [primary.name]: valueForColumn(primary, `${table.table}:b:${primary.name}`),
319
+ });
320
+ const mismatchRow = buildRow(change!, `${table.table}:mismatch`, {
321
+ [table.tenantColumn]: tenantB,
322
+ [primary.name]: valueForColumn(primary, `${table.table}:mismatch:${primary.name}`),
323
+ });
324
+
325
+ const tx = await adapter.begin();
326
+ try {
327
+ const seededReferences = new Set<string>();
328
+ await seedReferencedRows(tx, plan, columns, rowA, `${table.table}:a`, seededReferences);
329
+ await seedReferencedRows(tx, plan, columns, rowB, `${table.table}:b`, seededReferences);
330
+ await seedReferencedRows(tx, plan, columns, mismatchRow, `${table.table}:mismatch`, seededReferences);
331
+ if (role) {
332
+ await tx.query(`SET ROLE ${quoteIdent(role)}`);
333
+ }
334
+
335
+ await setTenant(tx, tenantA);
336
+ await insertRow(tx, table.table, columns, rowA);
337
+ await setTenant(tx, tenantB);
338
+ await insertRow(tx, table.table, columns, rowB);
339
+
340
+ await setTenant(tx, tenantA);
341
+ const tenantAVisible = await tx.query(
342
+ `SELECT ${quoteIdent(primary.name)} FROM ${quoteIdent(table.table)} WHERE ${quoteIdent(primary.name)} IN (${placeholderForColumn(primary, 1)}, ${placeholderForColumn(primary, 2)})`,
343
+ [rowA[primary.name], rowB[primary.name]],
344
+ );
345
+
346
+ await setTenant(tx, tenantB);
347
+ const tenantBVisible = await tx.query(
348
+ `SELECT ${quoteIdent(primary.name)} FROM ${quoteIdent(table.table)} WHERE ${quoteIdent(primary.name)} IN (${placeholderForColumn(primary, 1)}, ${placeholderForColumn(primary, 2)})`,
349
+ [rowA[primary.name], rowB[primary.name]],
350
+ );
351
+
352
+ await setTenant(tx, "");
353
+ const unscopedVisible = await tx.query(
354
+ `SELECT ${quoteIdent(primary.name)} FROM ${quoteIdent(table.table)} WHERE ${quoteIdent(primary.name)} IN (${placeholderForColumn(primary, 1)}, ${placeholderForColumn(primary, 2)})`,
355
+ [rowA[primary.name], rowB[primary.name]],
356
+ );
357
+
358
+ await setTenant(tx, tenantA);
359
+ let crossTenantUpdateBlocked = true;
360
+ if (mutableColumn) {
361
+ const updated = await tx.query(
362
+ `UPDATE ${quoteIdent(table.table)} SET ${quoteIdent(mutableColumn.name)} = ${placeholderForColumn(mutableColumn, 1)} WHERE ${quoteIdent(primary.name)} = ${placeholderForColumn(primary, 2)} RETURNING ${quoteIdent(primary.name)}`,
363
+ [valueForColumn(mutableColumn, `${table.table}:updated`), rowB[primary.name]],
364
+ );
365
+ crossTenantUpdateBlocked = updated.rows.length === 0;
366
+ }
367
+
368
+ const deleted = await tx.query(
369
+ `DELETE FROM ${quoteIdent(table.table)} WHERE ${quoteIdent(primary.name)} = ${placeholderForColumn(primary, 1)} RETURNING ${quoteIdent(primary.name)}`,
370
+ [rowB[primary.name]],
371
+ );
372
+ const crossTenantDeleteBlocked = deleted.rows.length === 0;
373
+
374
+ let mismatchedInsertBlocked = false;
375
+ try {
376
+ await insertRow(tx, table.table, columns, mismatchRow);
377
+ } catch {
378
+ mismatchedInsertBlocked = true;
379
+ }
380
+
381
+ return {
382
+ table: table.table,
383
+ role,
384
+ tenantAVisible: tenantAVisible.rows.length,
385
+ tenantBVisible: tenantBVisible.rows.length,
386
+ unscopedVisible: unscopedVisible.rows.length,
387
+ crossTenantUpdateBlocked,
388
+ crossTenantDeleteBlocked,
389
+ mismatchedInsertBlocked,
390
+ };
391
+ } finally {
392
+ await tx.rollback();
393
+ if (role) {
394
+ await adapter.query("RESET ROLE").catch(() => undefined);
395
+ }
396
+ }
397
+ }
398
+
399
+ async function runRlsIsolationTests(options: RlsCommandOptions): Promise<RlsCommandResult> {
400
+ const checked = checkGeneratedArtifacts(options);
401
+ if (!checked.ok) {
402
+ return {
403
+ ...checked,
404
+ diagnostics: [
405
+ ...checked.diagnostics,
406
+ createDiagnostic({
407
+ severity: "error",
408
+ code: FORGE_RLS_TEST_FAILED,
409
+ message: "RLS structural check failed before database isolation test",
410
+ }),
411
+ ],
412
+ exitCode: 1,
413
+ };
414
+ }
415
+
416
+ const plan = readGeneratedJson<SqlPlan>(options.workspaceRoot, `${GENERATED_DIR}/sqlPlan.json`);
417
+ const manifest = readGeneratedJson<{
418
+ tables?: RlsTableSecurity[];
419
+ }>(options.workspaceRoot, `${GENERATED_DIR}/dbSecurityManifest.json`);
420
+ const scopedTables = manifest?.tables ?? [];
421
+
422
+ if (!plan) {
423
+ return {
424
+ ok: false,
425
+ diagnostics: [
426
+ ...checked.diagnostics,
427
+ createDiagnostic({
428
+ severity: "error",
429
+ code: FORGE_RLS_TEST_FAILED,
430
+ message: `missing ${GENERATED_DIR}/sqlPlan.json; run forge generate first`,
431
+ }),
432
+ ],
433
+ exitCode: 1,
434
+ };
435
+ }
436
+
437
+ const { adapter, diagnostics } = await createDbAdapter({
438
+ kind: options.db,
439
+ workspaceRoot: options.workspaceRoot,
440
+ databaseUrl: options.databaseUrl,
441
+ });
442
+ if (!adapter) {
443
+ return {
444
+ ok: false,
445
+ diagnostics: [...checked.diagnostics, ...diagnostics],
446
+ exitCode: 1,
447
+ };
448
+ }
449
+
450
+ let probeSchema: string | null = null;
451
+
452
+ try {
453
+ if (options.db === "postgres") {
454
+ probeSchema = await isolateRlsProbeSchema(adapter);
455
+ }
456
+
457
+ const migrationDiagnostics = await applyMigrations(adapter, plan);
458
+ const migrationErrors = migrationDiagnostics.filter((diagnostic) => diagnostic.severity === "error");
459
+ if (migrationErrors.length > 0) {
460
+ return {
461
+ ok: false,
462
+ diagnostics: [...checked.diagnostics, ...diagnostics, ...migrationDiagnostics],
463
+ exitCode: 1,
464
+ };
465
+ }
466
+
467
+ const appliedStatements = await applyRlsSql(adapter, options.workspaceRoot);
468
+ const role = await setProbeRole(
469
+ adapter,
470
+ [...new Set(plan.tables.map((table) => table.table).filter((table): table is string => Boolean(table)))],
471
+ );
472
+ const probes: RlsProbeResult[] = [];
473
+ const failures: Diagnostic[] = [];
474
+
475
+ for (const table of scopedTables) {
476
+ const probe = await runTableProbe(adapter, plan, table, role);
477
+ probes.push(probe);
478
+ if (
479
+ probe.tenantAVisible !== 1 ||
480
+ probe.tenantBVisible !== 1 ||
481
+ probe.unscopedVisible !== 0 ||
482
+ !probe.crossTenantUpdateBlocked ||
483
+ !probe.crossTenantDeleteBlocked ||
484
+ !probe.mismatchedInsertBlocked
485
+ ) {
486
+ failures.push(
487
+ createDiagnostic({
488
+ severity: "error",
489
+ code: FORGE_RLS_TEST_FAILED,
490
+ message: `RLS adversarial probe failed for table '${table.table}'`,
491
+ }),
492
+ );
493
+ }
494
+ }
495
+
496
+ return {
497
+ ok: failures.length === 0,
498
+ data: {
499
+ structural: checked.ok,
500
+ appliedStatements,
501
+ role,
502
+ probes,
503
+ },
504
+ diagnostics: [
505
+ ...checked.diagnostics,
506
+ ...diagnostics,
507
+ ...migrationDiagnostics,
508
+ ...failures,
509
+ ],
510
+ exitCode: failures.length === 0 ? 0 : 1,
511
+ };
512
+ } catch (error) {
513
+ return {
514
+ ok: false,
515
+ diagnostics: [
516
+ ...checked.diagnostics,
517
+ ...diagnostics,
518
+ createDiagnostic({
519
+ severity: "error",
520
+ code: FORGE_RLS_TEST_FAILED,
521
+ message: error instanceof Error ? error.message : "RLS adversarial probe failed",
522
+ }),
523
+ ],
524
+ exitCode: 1,
525
+ };
526
+ } finally {
527
+ if (probeSchema) {
528
+ await adapter.query("RESET ROLE").catch(() => undefined);
529
+ await adapter.query("RESET search_path").catch(() => undefined);
530
+ await adapter.query(`DROP SCHEMA IF EXISTS ${quoteIdent(probeSchema)} CASCADE`).catch(() => undefined);
531
+ }
532
+ await adapter.close();
533
+ }
534
+ }
535
+
91
536
  function dbWarnings(options: RlsCommandOptions): Diagnostic[] {
92
537
  const diagnostics: Diagnostic[] = [];
93
538
 
@@ -158,6 +603,119 @@ function checkGeneratedArtifacts(options: RlsCommandOptions): RlsCommandResult {
158
603
  };
159
604
  }
160
605
 
606
+ function manifestCoverageErrors(
607
+ manifest: { tables?: Array<{ table: string; forceRowLevelSecurity?: boolean; policies?: unknown[] }> } | null,
608
+ ): string[] {
609
+ const errors: string[] = [];
610
+ for (const table of manifest?.tables ?? []) {
611
+ if (!table.forceRowLevelSecurity || (table.policies?.length ?? 0) < 4) {
612
+ errors.push(`table '${table.table}' is missing complete FORCE RLS policy coverage`);
613
+ }
614
+ }
615
+ return errors;
616
+ }
617
+
618
+ function sqlHasUnsafeRlsPredicate(sql: string): boolean {
619
+ return /\bUSING\s*\(\s*true\s*\)/i.test(sql) || /\bWITH\s+CHECK\s*\(\s*true\s*\)/i.test(sql);
620
+ }
621
+
622
+ function sqlUsesBypassRls(sql: string): boolean {
623
+ return /\bBYPASSRLS\b/i.test(sql);
624
+ }
625
+
626
+ function mutation(id: string, description: string, killed: boolean) {
627
+ return {
628
+ id,
629
+ description,
630
+ killed,
631
+ };
632
+ }
633
+
634
+ function runRlsMutationTests(options: RlsCommandOptions): RlsCommandResult {
635
+ const checked = checkGeneratedArtifacts(options);
636
+ if (!checked.ok) {
637
+ return checked;
638
+ }
639
+
640
+ const manifest = readGeneratedJson<{
641
+ tables?: Array<{ table: string; forceRowLevelSecurity?: boolean; policies?: unknown[] }>;
642
+ }>(options.workspaceRoot, `${GENERATED_DIR}/dbSecurityManifest.json`);
643
+ const sql = readGeneratedText(options.workspaceRoot, `${GENERATED_DIR}/rlsPolicies.sql`) ?? "";
644
+ const firstTable = manifest?.tables?.[0];
645
+ const mutations: Array<ReturnType<typeof mutation>> = [];
646
+
647
+ if (firstTable) {
648
+ mutations.push(
649
+ mutation(
650
+ "force-rls-removed",
651
+ "disable FORCE ROW LEVEL SECURITY on a tenant-scoped table",
652
+ manifestCoverageErrors({
653
+ tables: [
654
+ ...((manifest?.tables ?? []).slice(0, 0)),
655
+ { ...firstTable, forceRowLevelSecurity: false },
656
+ ...((manifest?.tables ?? []).slice(1)),
657
+ ],
658
+ }).length > 0,
659
+ ),
660
+ );
661
+
662
+ mutations.push(
663
+ mutation(
664
+ "policy-removed",
665
+ "remove one generated RLS policy from a tenant-scoped table",
666
+ manifestCoverageErrors({
667
+ tables: [
668
+ { ...firstTable, policies: (firstTable.policies ?? []).slice(1) },
669
+ ...((manifest?.tables ?? []).slice(1)),
670
+ ],
671
+ }).length > 0,
672
+ ),
673
+ );
674
+ }
675
+
676
+ mutations.push(
677
+ mutation(
678
+ "unsafe-using-true",
679
+ "replace an RLS USING predicate with an unconditional predicate",
680
+ sqlHasUnsafeRlsPredicate(`${sql}\nCREATE POLICY forge_mutant ON tickets USING (true);`),
681
+ ),
682
+ );
683
+ mutations.push(
684
+ mutation(
685
+ "unsafe-with-check-true",
686
+ "replace an RLS WITH CHECK predicate with an unconditional predicate",
687
+ sqlHasUnsafeRlsPredicate(`${sql}\nCREATE POLICY forge_mutant ON tickets WITH CHECK (true);`),
688
+ ),
689
+ );
690
+ mutations.push(
691
+ mutation(
692
+ "bypassrls-role",
693
+ "grant a runtime role BYPASSRLS",
694
+ sqlUsesBypassRls(`${sql}\nALTER ROLE forge_runtime BYPASSRLS;`),
695
+ ),
696
+ );
697
+
698
+ const survivors = mutations.filter((item) => !item.killed);
699
+ const failures = survivors.map((item) =>
700
+ createDiagnostic({
701
+ severity: "error",
702
+ code: FORGE_RLS_MUTATION_FAILED,
703
+ message: `RLS mutation survived: ${item.id}`,
704
+ }),
705
+ );
706
+
707
+ return {
708
+ ok: failures.length === 0,
709
+ data: {
710
+ kind: "rls-mutation-proof",
711
+ structural: true,
712
+ mutations,
713
+ },
714
+ diagnostics: [...checked.diagnostics, ...failures],
715
+ exitCode: failures.length === 0 ? 0 : 1,
716
+ };
717
+ }
718
+
161
719
  async function applyRls(options: RlsCommandOptions): Promise<RlsCommandResult> {
162
720
  const checked = checkGeneratedArtifacts(options);
163
721
  if (!checked.ok) {
@@ -265,6 +823,10 @@ export async function runRlsCommand(options: RlsCommandOptions): Promise<RlsComm
265
823
  return applyRls(options);
266
824
  }
267
825
 
826
+ if (options.subcommand === "mutate-test") {
827
+ return runRlsMutationTests(options);
828
+ }
829
+
268
830
  const checked = checkGeneratedArtifacts(options);
269
831
  if (options.db !== "postgres") {
270
832
  return {
@@ -275,21 +837,7 @@ export async function runRlsCommand(options: RlsCommandOptions): Promise<RlsComm
275
837
  };
276
838
  }
277
839
 
278
- return {
279
- ok: checked.ok,
280
- data: { structural: checked.ok },
281
- diagnostics: checked.ok
282
- ? checked.diagnostics
283
- : [
284
- ...checked.diagnostics,
285
- createDiagnostic({
286
- severity: "error",
287
- code: FORGE_RLS_TEST_FAILED,
288
- message: "RLS structural check failed before database isolation test",
289
- }),
290
- ],
291
- exitCode: checked.ok ? 0 : 1,
292
- };
840
+ return runRlsIsolationTests(options);
293
841
  }
294
842
 
295
843
  export function formatRlsJson(result: RlsCommandResult): string {
@@ -318,5 +866,8 @@ export function formatRlsHuman(subcommand: RlsSubcommand, result: RlsCommandResu
318
866
  if (subcommand === "test") {
319
867
  return `rls checks passed${suffix}`;
320
868
  }
869
+ if (subcommand === "mutate-test") {
870
+ return `rls mutation checks passed${suffix}`;
871
+ }
321
872
  return `rls contract is up to date${suffix}`;
322
873
  }
@@ -1,6 +1,11 @@
1
1
  import { listEntries, runEntry } from "../runtime/executor.ts";
2
2
  import { createDiagnostic } from "../compiler/diagnostics/create.ts";
3
3
  import type { RunEntryResult, ListEntriesResult } from "../runtime/executor.ts";
4
+ import {
5
+ resolveExternalQualifiedName,
6
+ runExternalEntry,
7
+ } from "../runtime/external/bridge.ts";
8
+ import { resolveAuthFromCli } from "../runtime/auth/resolve.ts";
4
9
 
5
10
  export interface RunCommandOptions {
6
11
  name?: string;
@@ -10,6 +15,7 @@ export interface RunCommandOptions {
10
15
  userId?: string;
11
16
  tenantId?: string;
12
17
  role?: string;
18
+ args?: unknown;
13
19
  workspaceRoot: string;
14
20
  }
15
21
 
@@ -29,6 +35,41 @@ export async function runRunCommand(
29
35
  return { list, exitCode: list.exitCode };
30
36
  }
31
37
 
38
+ const external = resolveExternalQualifiedName(options.workspaceRoot, options.name!, "command");
39
+ if (external) {
40
+ const run = await runExternalEntry(options.workspaceRoot, {
41
+ kind: "command",
42
+ serviceName: external.serviceName,
43
+ entryName: external.entryName,
44
+ args: options.args,
45
+ auth: resolveAuthFromCli({
46
+ userId: options.userId,
47
+ tenantId: options.tenantId,
48
+ role: options.role,
49
+ }),
50
+ });
51
+ return {
52
+ run: {
53
+ ok: run.ok,
54
+ result: run.result,
55
+ diagnostics: run.diagnostics,
56
+ exitCode: run.exitCode,
57
+ traceId: run.traceId,
58
+ entry: {
59
+ id: `external:${external.serviceName}:command:${external.entryName}`,
60
+ kind: "command",
61
+ name: `${external.serviceName}.${external.entryName}`,
62
+ qualifiedName: `${external.serviceName}.${external.entryName}`,
63
+ file: `external:${external.serviceName}`,
64
+ moduleId: `external:${external.serviceName}`,
65
+ runtimeContext: "command",
66
+ dependencies: [],
67
+ },
68
+ },
69
+ exitCode: run.exitCode,
70
+ };
71
+ }
72
+
32
73
  const run = await runEntry(options.workspaceRoot, options.name!, {
33
74
  json: options.json,
34
75
  mock: options.mock,