forgeos 0.1.0-alpha.4 → 0.1.0-alpha.41
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.
- package/.npmignore +10 -0
- package/AGENTS.md +142 -90
- package/CHANGELOG.md +433 -0
- package/LICENSE +21 -0
- package/README.md +67 -8
- package/adapters/go/README.md +23 -0
- package/adapters/go/go.mod +3 -0
- package/adapters/go/http.go +149 -0
- package/adapters/go/registry.go +234 -0
- package/adapters/go/types.go +136 -0
- package/adapters/java/README.md +68 -0
- package/adapters/java/pom.xml +34 -0
- package/adapters/java/src/main/java/dev/forgeos/adapter/Auth.java +20 -0
- package/adapters/java/src/main/java/dev/forgeos/adapter/Diagnostic.java +16 -0
- package/adapters/java/src/main/java/dev/forgeos/adapter/Entry.java +38 -0
- package/adapters/java/src/main/java/dev/forgeos/adapter/EntryKind.java +16 -0
- package/adapters/java/src/main/java/dev/forgeos/adapter/ErrorInfo.java +4 -0
- package/adapters/java/src/main/java/dev/forgeos/adapter/Forge.java +94 -0
- package/adapters/java/src/main/java/dev/forgeos/adapter/ForgeCall.java +12 -0
- package/adapters/java/src/main/java/dev/forgeos/adapter/ForgeContext.java +11 -0
- package/adapters/java/src/main/java/dev/forgeos/adapter/ForgeHandler.java +8 -0
- package/adapters/java/src/main/java/dev/forgeos/adapter/ForgeHttpHandler.java +179 -0
- package/adapters/java/src/main/java/dev/forgeos/adapter/ForgeRegistry.java +121 -0
- package/adapters/java/src/main/java/dev/forgeos/adapter/Json.java +14 -0
- package/adapters/java/src/main/java/dev/forgeos/adapter/Manifest.java +14 -0
- package/adapters/java/src/main/java/dev/forgeos/adapter/RequestEnvelope.java +6 -0
- package/adapters/java/src/main/java/dev/forgeos/adapter/ResponseEnvelope.java +25 -0
- package/adapters/java/src/main/java/dev/forgeos/adapter/Risk.java +18 -0
- package/adapters/java/src/main/java/dev/forgeos/adapter/Schemas.java +36 -0
- package/adapters/java/src/main/java/dev/forgeos/adapter/Service.java +65 -0
- package/adapters/java/src/main/java/dev/forgeos/adapter/TransactionMode.java +18 -0
- package/adapters/java/src/main/java/dev/forgeos/adapter/TypedForgeHandler.java +6 -0
- package/adapters/java-spring-boot-starter/README.md +32 -0
- package/adapters/java-spring-boot-starter/pom.xml +36 -0
- package/adapters/java-spring-boot-starter/src/main/java/dev/forgeos/adapter/spring/ForgeCommand.java +22 -0
- package/adapters/java-spring-boot-starter/src/main/java/dev/forgeos/adapter/spring/ForgeExternalService.java +15 -0
- package/adapters/java-spring-boot-starter/src/main/java/dev/forgeos/adapter/spring/ForgeQuery.java +16 -0
- package/adapters/java-spring-boot-starter/src/main/java/dev/forgeos/adapter/spring/ForgeServiceBeanCondition.java +18 -0
- package/adapters/java-spring-boot-starter/src/main/java/dev/forgeos/adapter/spring/ForgeSpringAutoConfiguration.java +16 -0
- package/adapters/java-spring-boot-starter/src/main/java/dev/forgeos/adapter/spring/ForgeSpringRuntime.java +104 -0
- package/adapters/java-spring-boot-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +1 -0
- package/bin/forge.mjs +18 -0
- package/docs/cair-protocol.md +103 -0
- package/docs/changelog.md +548 -0
- package/docs/forge-protocol.md +189 -0
- package/examples/go-billing/go.mod +7 -0
- package/examples/go-billing/main.go +120 -0
- package/examples/java-billing/pom.xml +52 -0
- package/examples/java-billing/src/main/java/dev/forgeos/examples/billing/CreateInvoiceInput.java +4 -0
- package/examples/java-billing/src/main/java/dev/forgeos/examples/billing/Invoice.java +11 -0
- package/examples/java-billing/src/main/java/dev/forgeos/examples/billing/Main.java +127 -0
- package/package.json +31 -3
- package/schemas/forge-manifest.schema.json +57 -0
- package/src/forge/_generated/releaseManifest.json +1 -2
- package/src/forge/_generated/releaseManifest.ts +3 -3
- package/src/forge/agent-adapters/index.ts +1584 -125
- package/src/forge/agent-adapters/types.ts +224 -1
- package/src/forge/agent-memory/bridge.ts +1333 -0
- package/src/forge/agent-memory/context-pack.ts +277 -0
- package/src/forge/agent-memory/hook-runner.ts +312 -0
- package/src/forge/agent-memory/mcp.ts +224 -0
- package/src/forge/agent-memory/normalize.ts +498 -0
- package/src/forge/agent-memory/redaction.ts +103 -0
- package/src/forge/agent-memory/sources/claude-code.ts +51 -0
- package/src/forge/agent-memory/sources/codex-hook-runner.mjs +273 -0
- package/src/forge/agent-memory/sources/codex.ts +119 -0
- package/src/forge/agent-memory/sources/cursor.ts +35 -0
- package/src/forge/agent-memory/types.ts +204 -0
- package/src/forge/bench.ts +248 -0
- package/src/forge/brownfield-import/index.ts +801 -0
- package/src/forge/brownfield-import/types.ts +127 -0
- package/src/forge/cair/action-journal.ts +61 -0
- package/src/forge/cair/action-parser.ts +314 -0
- package/src/forge/cair/action-validator.ts +40 -0
- package/src/forge/cair/actions.ts +1818 -0
- package/src/forge/cair/format.ts +77 -0
- package/src/forge/cair/index.ts +106 -0
- package/src/forge/cair/query.ts +478 -0
- package/src/forge/cair/snapshot.ts +315 -0
- package/src/forge/cair/types.ts +248 -0
- package/src/forge/cli/ai.ts +339 -21
- package/src/forge/cli/auth.ts +348 -10
- package/src/forge/cli/authmd.ts +441 -0
- package/src/forge/cli/baseline.ts +112 -0
- package/src/forge/cli/build.ts +20 -4
- package/src/forge/cli/changed.ts +437 -0
- package/src/forge/cli/codex-app-server.ts +877 -0
- package/src/forge/cli/commands.ts +1620 -20
- package/src/forge/cli/db.ts +209 -2
- package/src/forge/cli/deploy.ts +887 -0
- package/src/forge/cli/deps.ts +82 -13
- package/src/forge/cli/dev.ts +961 -46
- package/src/forge/cli/docs.ts +265 -0
- package/src/forge/cli/doctor.ts +297 -0
- package/src/forge/cli/field-test.ts +329 -0
- package/src/forge/cli/handoff.ts +336 -0
- package/src/forge/cli/index.ts +1 -0
- package/src/forge/cli/last-run.ts +84 -0
- package/src/forge/cli/main.ts +107 -3
- package/src/forge/cli/new.ts +152 -13
- package/src/forge/cli/next-actions.ts +23 -0
- package/src/forge/cli/output.ts +389 -6
- package/src/forge/cli/parse.ts +1050 -34
- package/src/forge/cli/progress.ts +51 -0
- package/src/forge/cli/query.ts +32 -0
- package/src/forge/cli/release.ts +35 -11
- package/src/forge/cli/rls.ts +43 -4
- package/src/forge/cli/run.ts +41 -0
- package/src/forge/cli/security.ts +112 -0
- package/src/forge/cli/self-host.ts +56 -14
- package/src/forge/cli/studio.ts +2218 -0
- package/src/forge/cli/verify.ts +1455 -32
- package/src/forge/cli/windows.ts +23 -0
- package/src/forge/cli/workos.ts +1020 -0
- package/src/forge/compiler/agent-contract/build.ts +670 -64
- package/src/forge/compiler/agent-contract/types.ts +45 -2
- package/src/forge/compiler/ai-registry/parse.ts +71 -2
- package/src/forge/compiler/api-surface/build.ts +47 -0
- package/src/forge/compiler/app-graph/build.ts +112 -9
- package/src/forge/compiler/app-graph/extract.ts +107 -0
- package/src/forge/compiler/app-graph/module-graph.ts +73 -78
- package/src/forge/compiler/app-graph/parser.ts +24 -24
- package/src/forge/compiler/app-graph/profile.ts +26 -0
- package/src/forge/compiler/app-graph/versions.ts +1 -1
- package/src/forge/compiler/classifier/capabilities.ts +3 -2
- package/src/forge/compiler/classifier/classify.ts +32 -8
- package/src/forge/compiler/classifier/secrets.ts +3 -2
- package/src/forge/compiler/classifier/signals.ts +91 -1
- package/src/forge/compiler/client-sdk/build-manifest.ts +59 -0
- package/src/forge/compiler/client-sdk/render-client.ts +295 -13
- package/src/forge/compiler/data-graph/parse.ts +20 -4
- package/src/forge/compiler/data-graph/sql/ddl.ts +144 -31
- package/src/forge/compiler/data-graph/sql/serialize.ts +4 -0
- package/src/forge/compiler/data-graph/sql/types.ts +1 -0
- package/src/forge/compiler/diagnostics/codes.ts +30 -0
- package/src/forge/compiler/diagnostics/create.ts +21 -2
- package/src/forge/compiler/diagnostics/index.ts +2 -0
- package/src/forge/compiler/emitter/barrel.ts +3 -0
- package/src/forge/compiler/emitter/render.ts +9 -0
- package/src/forge/compiler/external-manifest/registry.ts +205 -0
- package/src/forge/compiler/external-manifest/types.ts +91 -0
- package/src/forge/compiler/external-manifest/validate.ts +373 -0
- package/src/forge/compiler/frontend-graph/build.ts +142 -14
- package/src/forge/compiler/integration/add.ts +618 -24
- package/src/forge/compiler/integration/plan.ts +43 -12
- package/src/forge/compiler/integration/render.ts +29 -0
- package/src/forge/compiler/integration/snapshot.ts +2 -0
- package/src/forge/compiler/integration/templates/convex.ts +70 -0
- package/src/forge/compiler/integration/templates/index.ts +1 -0
- package/src/forge/compiler/integration/templates/render.ts +38 -0
- package/src/forge/compiler/integration/templates/types.ts +3 -0
- package/src/forge/compiler/integration/templates/workos.ts +1219 -0
- package/src/forge/compiler/make-registry/build.ts +8 -9
- package/src/forge/compiler/orchestrator/fast-check.ts +4 -0
- package/src/forge/compiler/orchestrator/generate-lock.ts +14 -3
- package/src/forge/compiler/orchestrator/manifest.ts +1 -1
- package/src/forge/compiler/orchestrator/plan-profile.ts +23 -0
- package/src/forge/compiler/orchestrator/plan.ts +247 -10
- package/src/forge/compiler/orchestrator/profile.ts +65 -0
- package/src/forge/compiler/orchestrator/run.ts +97 -31
- package/src/forge/compiler/orchestrator/serialize.ts +33 -8
- package/src/forge/compiler/orchestrator/types.ts +1 -1
- package/src/forge/compiler/package-graph/compiler.ts +3 -1
- package/src/forge/compiler/package-graph/constants.ts +1 -1
- package/src/forge/compiler/package-manager/adapter.ts +4 -1
- package/src/forge/compiler/package-manager/commands.ts +4 -0
- package/src/forge/compiler/package-manager/executor.ts +30 -1
- package/src/forge/compiler/package-manager/version.ts +36 -12
- package/src/forge/compiler/package-upgrades/planner.ts +155 -18
- package/src/forge/compiler/package-upgrades/types.ts +2 -0
- package/src/forge/compiler/policy-registry/build.ts +47 -2
- package/src/forge/compiler/policy-registry/parse.ts +32 -2
- package/src/forge/compiler/recipes/definitions.ts +63 -0
- package/src/forge/compiler/recipes/index.ts +2 -0
- package/src/forge/compiler/recipes/registry.ts +6 -0
- package/src/forge/compiler/test-graph/build.ts +11 -3
- package/src/forge/compiler/types/app-graph.ts +8 -2
- package/src/forge/compiler/types/cli.ts +75 -1
- package/src/forge/compiler/types/dev-manifest.ts +4 -0
- package/src/forge/compiler/types/emit.ts +2 -0
- package/src/forge/compiler/types/frontend-graph.ts +2 -2
- package/src/forge/compiler/types/integration.ts +1 -0
- package/src/forge/compiler/types/package-graph.ts +6 -0
- package/src/forge/compiler/types/policy-registry.ts +3 -1
- package/src/forge/delta/classifier.ts +52 -0
- package/src/forge/delta/explain.ts +238 -0
- package/src/forge/delta/git-observer.ts +43 -0
- package/src/forge/delta/ids.ts +44 -0
- package/src/forge/delta/index.ts +25 -0
- package/src/forge/delta/recorder.ts +540 -0
- package/src/forge/delta/redaction.ts +50 -0
- package/src/forge/delta/schema.ts +240 -0
- package/src/forge/delta/session.ts +142 -0
- package/src/forge/delta/status.ts +1202 -0
- package/src/forge/delta/store.ts +3284 -0
- package/src/forge/delta/timeline.ts +178 -0
- package/src/forge/dev/server.ts +808 -17
- package/src/forge/dev/types.ts +15 -1
- package/src/forge/dev/watch.ts +17 -7
- package/src/forge/dev-console/cycle.ts +280 -33
- package/src/forge/dev-console/types.ts +47 -1
- package/src/forge/impact/index.ts +204 -9
- package/src/forge/impact/types.ts +33 -1
- package/src/forge/intent/index.ts +35 -16
- package/src/forge/make/fields.ts +26 -0
- package/src/forge/make/index.ts +40 -6
- package/src/forge/make/templates.ts +155 -2
- package/src/forge/make/types.ts +2 -1
- package/src/forge/policy.ts +1 -1
- package/src/forge/react/index.ts +160 -2
- package/src/forge/refactor/index.ts +1 -0
- package/src/forge/repair/rules/index.ts +2 -2
- package/src/forge/review/index.ts +158 -12
- package/src/forge/review/types.ts +15 -0
- package/src/forge/runtime/auth/config.ts +17 -0
- package/src/forge/runtime/auth/evaluate.ts +15 -2
- package/src/forge/runtime/auth/resolve.ts +61 -6
- package/src/forge/runtime/auth/types.ts +1 -0
- package/src/forge/runtime/db/factory.ts +1 -3
- package/src/forge/runtime/db/generated-client.ts +13 -2
- package/src/forge/runtime/db/memory-adapter.ts +139 -32
- package/src/forge/runtime/db/pglite-adapter.ts +263 -2
- package/src/forge/runtime/db/postgres-adapter.ts +6 -3
- package/src/forge/runtime/executor.ts +129 -16
- package/src/forge/runtime/external/bridge.ts +649 -0
- package/src/forge/runtime/runner/run-entry.ts +16 -7
- package/src/forge/runtime/telemetry/buffer.ts +4 -1
- package/src/forge/runtime/telemetry/scrubber.ts +35 -5
- package/src/forge/runtime/webhooks/security.ts +12 -7
- package/src/forge/server.ts +17 -3
- package/src/forge/ui/index.ts +209 -0
- package/src/forge/ui/types.ts +1 -0
- package/src/forge/version.ts +1 -1
- package/src/forge/vue/index.ts +422 -0
- package/src/forge/workspace/baseline.ts +112 -0
- package/src/forge/workspace/change-summary.ts +250 -0
- package/src/forge/workspace/forge-cli.ts +42 -0
- package/src/forge/workspace/git-summary.ts +391 -0
- package/templates/agent-workroom/AGENTS.md +29 -0
- package/templates/agent-workroom/README.md +34 -0
- package/templates/agent-workroom/forge.config.ts +3 -0
- package/templates/agent-workroom/package.json +33 -0
- package/templates/agent-workroom/src/actions/indexAgentSignal.ts +10 -0
- package/templates/agent-workroom/src/commands/openWorkroom.ts +61 -0
- package/templates/agent-workroom/src/commands/recordAgentSignal.ts +119 -0
- package/templates/agent-workroom/src/commands/recordCheckRun.ts +52 -0
- package/templates/agent-workroom/src/forge/schema.ts +54 -0
- package/templates/agent-workroom/src/policies.ts +6 -0
- package/templates/agent-workroom/src/queries/listWorkrooms.ts +11 -0
- package/templates/agent-workroom/src/queries/liveWorkroom.ts +63 -0
- package/templates/agent-workroom/tsconfig.json +16 -0
- package/templates/agent-workroom/web/index.html +12 -0
- package/templates/agent-workroom/web/package.json +21 -0
- package/templates/agent-workroom/web/src/App.tsx +345 -0
- package/templates/agent-workroom/web/src/lib/forge.ts +13 -0
- package/templates/agent-workroom/web/src/main.tsx +13 -0
- package/templates/agent-workroom/web/src/styles.css +545 -0
- package/templates/agent-workroom/web/tsconfig.json +27 -0
- package/templates/b2b-support-web/package.json +1 -0
- package/templates/b2b-support-web/web/package.json +1 -1
- package/templates/minimal-web/package.json +1 -1
- package/templates/minimal-web/web/package.json +2 -2
- package/templates/nuxt-web/.vscode/settings.json +14 -0
- package/templates/nuxt-web/README.md +30 -0
- package/templates/nuxt-web/forge.config.ts +3 -0
- package/templates/nuxt-web/package.json +33 -0
- package/templates/nuxt-web/src/actions/logNoteCreated.ts +11 -0
- package/templates/nuxt-web/src/commands/createNote.ts +26 -0
- package/templates/nuxt-web/src/forge/schema.ts +12 -0
- package/templates/nuxt-web/src/policies.ts +6 -0
- package/templates/nuxt-web/src/queries/listNotes.ts +8 -0
- package/templates/nuxt-web/src/queries/liveNotes.ts +8 -0
- package/templates/nuxt-web/tsconfig.json +17 -0
- package/templates/nuxt-web/web/app.vue +67 -0
- package/templates/nuxt-web/web/components/LiveNotes.vue +89 -0
- package/templates/nuxt-web/web/components/NoteComposer.vue +100 -0
- package/templates/nuxt-web/web/composables/forge.ts +13 -0
- package/templates/nuxt-web/web/composables/useNotes.ts +24 -0
- package/templates/nuxt-web/web/nuxt.config.ts +11 -0
- package/templates/nuxt-web/web/package.json +18 -0
- package/templates/nuxt-web/web/plugins/forge.client.ts +10 -0
- package/templates/nuxt-web/web/plugins/forge.server.ts +10 -0
- package/templates/nuxt-web/web/server/api/forge-health.get.ts +7 -0
- package/templates/nuxt-web/web/tsconfig.json +6 -0
- package/src/forge/_generated/actionSubscriptions.json +0 -2
- package/src/forge/_generated/actionSubscriptions.ts +0 -10
- package/src/forge/_generated/agentAdapterManifest.json +0 -2
- package/src/forge/_generated/agentAdapterManifest.ts +0 -73
- package/src/forge/_generated/agentContract.json +0 -2
- package/src/forge/_generated/agentContract.ts +0 -7829
- package/src/forge/_generated/agentQuickstart.md +0 -34
- package/src/forge/_generated/agentTools.json +0 -2
- package/src/forge/_generated/agentTools.md +0 -16
- package/src/forge/_generated/agentTools.ts +0 -12
- package/src/forge/_generated/aiContext.ts +0 -125
- package/src/forge/_generated/aiModels.json +0 -2
- package/src/forge/_generated/aiModels.ts +0 -51
- package/src/forge/_generated/aiProviders.json +0 -2
- package/src/forge/_generated/aiProviders.ts +0 -23
- package/src/forge/_generated/aiRegistry.json +0 -2
- package/src/forge/_generated/aiRegistry.ts +0 -31
- package/src/forge/_generated/api.json +0 -2
- package/src/forge/_generated/api.ts +0 -8
- package/src/forge/_generated/appGraph.json +0 -2
- package/src/forge/_generated/appGraph.ts +0 -14919
- package/src/forge/_generated/appMap.md +0 -55
- package/src/forge/_generated/artifactManifest.json +0 -2
- package/src/forge/_generated/artifactManifest.ts +0 -7
- package/src/forge/_generated/authClaims.json +0 -2
- package/src/forge/_generated/authClaims.ts +0 -13
- package/src/forge/_generated/authConfig.json +0 -2
- package/src/forge/_generated/authConfig.ts +0 -17
- package/src/forge/_generated/authContext.ts +0 -23
- package/src/forge/_generated/authRegistry.json +0 -2
- package/src/forge/_generated/authRegistry.ts +0 -25
- package/src/forge/_generated/buildInfo.json +0 -2
- package/src/forge/_generated/buildInfo.ts +0 -9
- package/src/forge/_generated/capabilityMap.json +0 -2
- package/src/forge/_generated/capabilityMap.md +0 -15
- package/src/forge/_generated/capabilityMap.ts +0 -17
- package/src/forge/_generated/client.ts +0 -282
- package/src/forge/_generated/clientApi.ts +0 -9
- package/src/forge/_generated/clientManifest.json +0 -2
- package/src/forge/_generated/clientManifest.ts +0 -39
- package/src/forge/_generated/clientTypes.ts +0 -78
- package/src/forge/_generated/configRegistry.json +0 -2
- package/src/forge/_generated/configRegistry.ts +0 -4
- package/src/forge/_generated/dataGraph.json +0 -2
- package/src/forge/_generated/dataGraph.ts +0 -8
- package/src/forge/_generated/db.json +0 -2
- package/src/forge/_generated/db.ts +0 -2
- package/src/forge/_generated/dbSecurityManifest.json +0 -2
- package/src/forge/_generated/dbSecurityManifest.ts +0 -15
- package/src/forge/_generated/dbSessionContext.json +0 -2
- package/src/forge/_generated/dbSessionContext.ts +0 -39
- package/src/forge/_generated/deployManifest.json +0 -2
- package/src/forge/_generated/deployManifest.ts +0 -14
- package/src/forge/_generated/devManifest.json +0 -2
- package/src/forge/_generated/devManifest.ts +0 -62
- package/src/forge/_generated/envSchema.json +0 -2
- package/src/forge/_generated/envSchema.ts +0 -59
- package/src/forge/_generated/frontendGraph.json +0 -2
- package/src/forge/_generated/frontendGraph.ts +0 -27
- package/src/forge/_generated/importGuards.json +0 -2
- package/src/forge/_generated/importGuards.ts +0 -686
- package/src/forge/_generated/index.ts +0 -68
- package/src/forge/_generated/liveProductionManifest.json +0 -2
- package/src/forge/_generated/liveProductionManifest.ts +0 -23
- package/src/forge/_generated/liveProtocol.json +0 -2
- package/src/forge/_generated/liveProtocol.ts +0 -21
- package/src/forge/_generated/liveQueryRegistry.json +0 -2
- package/src/forge/_generated/liveQueryRegistry.ts +0 -9
- package/src/forge/_generated/liveTransportConfig.json +0 -2
- package/src/forge/_generated/liveTransportConfig.ts +0 -19
- package/src/forge/_generated/makeRegistry.json +0 -2
- package/src/forge/_generated/makeRegistry.ts +0 -177
- package/src/forge/_generated/makeTemplates.json +0 -2
- package/src/forge/_generated/makeTemplates.ts +0 -66
- package/src/forge/_generated/mockMap.json +0 -2
- package/src/forge/_generated/mockMap.ts +0 -7
- package/src/forge/_generated/operationPlaybooks.md +0 -167
- package/src/forge/_generated/packageGraph.json +0 -2
- package/src/forge/_generated/packageGraph.ts +0 -249334
- package/src/forge/_generated/packageUpgradeRegistry.json +0 -2
- package/src/forge/_generated/packageUpgradeRegistry.ts +0 -15
- package/src/forge/_generated/permissionMatrix.json +0 -2
- package/src/forge/_generated/permissionMatrix.ts +0 -7
- package/src/forge/_generated/policyRegistry.json +0 -2
- package/src/forge/_generated/policyRegistry.ts +0 -11
- package/src/forge/_generated/queryRegistry.json +0 -2
- package/src/forge/_generated/queryRegistry.ts +0 -9
- package/src/forge/_generated/react.d.ts +0 -22
- package/src/forge/_generated/react.ts +0 -29
- package/src/forge/_generated/reactManifest.json +0 -2
- package/src/forge/_generated/reactManifest.ts +0 -19
- package/src/forge/_generated/rlsPolicies.json +0 -2
- package/src/forge/_generated/rlsPolicies.sql +0 -34
- package/src/forge/_generated/rlsPolicies.ts +0 -6
- package/src/forge/_generated/runtimeGraph.json +0 -2
- package/src/forge/_generated/runtimeGraph.ts +0 -8
- package/src/forge/_generated/runtimeMatrix.json +0 -2
- package/src/forge/_generated/runtimeMatrix.ts +0 -334130
- package/src/forge/_generated/runtimeRegistry.ts +0 -2
- package/src/forge/_generated/runtimeRules.md +0 -91
- package/src/forge/_generated/secretRegistry.json +0 -2
- package/src/forge/_generated/secretRegistry.ts +0 -50
- package/src/forge/_generated/secretsContext.ts +0 -11
- package/src/forge/_generated/serverApi.ts +0 -10
- package/src/forge/_generated/sourceMapManifest.json +0 -2
- package/src/forge/_generated/sourceMapManifest.ts +0 -7
- package/src/forge/_generated/sqlPlan.json +0 -2
- package/src/forge/_generated/sqlPlan.ts +0 -88
- package/src/forge/_generated/subscriptionManifest.json +0 -2
- package/src/forge/_generated/subscriptionManifest.ts +0 -7
- package/src/forge/_generated/symbolicationManifest.json +0 -2
- package/src/forge/_generated/symbolicationManifest.ts +0 -17
- package/src/forge/_generated/telemetryRegistry.json +0 -2
- package/src/forge/_generated/telemetryRegistry.ts +0 -9
- package/src/forge/_generated/telemetrySinks.json +0 -2
- package/src/forge/_generated/telemetrySinks.ts +0 -11
- package/src/forge/_generated/tenantScope.json +0 -2
- package/src/forge/_generated/tenantScope.ts +0 -8
- package/src/forge/_generated/testGraph.json +0 -2
- package/src/forge/_generated/testGraph.ts +0 -3430
- package/src/forge/_generated/testPlanRegistry.json +0 -2
- package/src/forge/_generated/testPlanRegistry.ts +0 -33
- package/src/forge/_generated/uiRoutes.json +0 -2
- package/src/forge/_generated/uiRoutes.ts +0 -16
- package/src/forge/_generated/uiScenarios.json +0 -2
- package/src/forge/_generated/uiScenarios.ts +0 -30
- package/src/forge/_generated/uiTestManifest.json +0 -2
- package/src/forge/_generated/uiTestManifest.ts +0 -27
- package/src/forge/_generated/workflowRegistry.json +0 -2
- package/src/forge/_generated/workflowRegistry.ts +0 -9
- package/src/forge/_generated/workflowSubscriptions.json +0 -2
- package/src/forge/_generated/workflowSubscriptions.ts +0 -10
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
export interface CommandHeartbeat {
|
|
2
|
+
setPhase: (phase: string) => void;
|
|
3
|
+
stop: () => void;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export function startCommandHeartbeat(input: {
|
|
7
|
+
label: string;
|
|
8
|
+
enabled?: boolean;
|
|
9
|
+
initialPhase?: string;
|
|
10
|
+
intervalMs?: number;
|
|
11
|
+
stallMs?: number;
|
|
12
|
+
}): CommandHeartbeat {
|
|
13
|
+
if (input.enabled === false || process.env.FORGE_PROGRESS === "0") {
|
|
14
|
+
return {
|
|
15
|
+
setPhase: () => {},
|
|
16
|
+
stop: () => {},
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const startedAt = Date.now();
|
|
21
|
+
const intervalMs = input.intervalMs ?? 10_000;
|
|
22
|
+
const stallMs = input.stallMs ?? 30_000;
|
|
23
|
+
let phase = input.initialPhase ?? "running";
|
|
24
|
+
let ticks = 0;
|
|
25
|
+
|
|
26
|
+
const timer = setInterval(() => {
|
|
27
|
+
ticks += 1;
|
|
28
|
+
const elapsedMs = Date.now() - startedAt;
|
|
29
|
+
const elapsedSeconds = Math.round(elapsedMs / 1000);
|
|
30
|
+
const stalled = elapsedMs >= stallMs;
|
|
31
|
+
const prefix = stalled ? "still waiting" : "working";
|
|
32
|
+
process.stderr.write(
|
|
33
|
+
`[forge] ${input.label}: ${prefix}; phase=${phase}; elapsed=${elapsedSeconds}s\n`,
|
|
34
|
+
);
|
|
35
|
+
if (stalled && ticks === Math.ceil(stallMs / intervalMs)) {
|
|
36
|
+
process.stderr.write(
|
|
37
|
+
`[forge] ${input.label}: run with --json for structured status; for dev startup failures, use forge last --json.\n`,
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
}, intervalMs);
|
|
41
|
+
timer.unref?.();
|
|
42
|
+
|
|
43
|
+
return {
|
|
44
|
+
setPhase: (nextPhase: string) => {
|
|
45
|
+
phase = nextPhase;
|
|
46
|
+
},
|
|
47
|
+
stop: () => {
|
|
48
|
+
clearInterval(timer);
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
}
|
package/src/forge/cli/query.ts
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import { listQueries, runQuery, type ListQueriesResult, type RunQueryResult } from "../runtime/query/run-query.ts";
|
|
2
2
|
import { resolveAuthFromCli } from "../runtime/auth/resolve.ts";
|
|
3
3
|
import { createDiagnostic } from "../compiler/diagnostics/create.ts";
|
|
4
|
+
import {
|
|
5
|
+
resolveExternalQualifiedName,
|
|
6
|
+
runExternalEntry,
|
|
7
|
+
} from "../runtime/external/bridge.ts";
|
|
4
8
|
|
|
5
9
|
export type QuerySubcommand = "list" | "run";
|
|
6
10
|
|
|
@@ -52,6 +56,34 @@ export async function runQueryCommand(
|
|
|
52
56
|
role: options.role,
|
|
53
57
|
});
|
|
54
58
|
|
|
59
|
+
const external = resolveExternalQualifiedName(options.workspaceRoot, options.name, "query");
|
|
60
|
+
if (external) {
|
|
61
|
+
const run = await runExternalEntry(options.workspaceRoot, {
|
|
62
|
+
kind: "query",
|
|
63
|
+
serviceName: external.serviceName,
|
|
64
|
+
entryName: external.entryName,
|
|
65
|
+
args: options.args,
|
|
66
|
+
auth,
|
|
67
|
+
});
|
|
68
|
+
return {
|
|
69
|
+
run: {
|
|
70
|
+
ok: run.ok,
|
|
71
|
+
result: run.result,
|
|
72
|
+
query: {
|
|
73
|
+
name: `${external.serviceName}.${external.entryName}`,
|
|
74
|
+
qualifiedName: `${external.serviceName}.${external.entryName}`,
|
|
75
|
+
file: `external:${external.serviceName}`,
|
|
76
|
+
symbolId: `external:${external.serviceName}:query:${external.entryName}`,
|
|
77
|
+
moduleId: `external:${external.serviceName}`,
|
|
78
|
+
},
|
|
79
|
+
diagnostics: run.diagnostics,
|
|
80
|
+
exitCode: run.exitCode,
|
|
81
|
+
traceId: run.traceId,
|
|
82
|
+
},
|
|
83
|
+
exitCode: run.exitCode,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
55
87
|
const run = await runQuery(options.workspaceRoot, options.name, {
|
|
56
88
|
args: options.args,
|
|
57
89
|
auth,
|
package/src/forge/cli/release.ts
CHANGED
|
@@ -18,12 +18,14 @@ import {
|
|
|
18
18
|
providerConfigDiagnostics,
|
|
19
19
|
} from "../runtime/release/runtime.ts";
|
|
20
20
|
import { symbolicateStacktrace } from "../runtime/release/symbolicate.ts";
|
|
21
|
+
import { releasePrepareNextActions, releaseReadyNextActions } from "./next-actions.ts";
|
|
21
22
|
|
|
22
23
|
export type ReleaseArea = "release" | "artifacts" | "sourcemaps";
|
|
23
24
|
export type ReleaseAction =
|
|
24
25
|
| "prepare"
|
|
25
26
|
| "inspect"
|
|
26
27
|
| "check"
|
|
28
|
+
| "doctor"
|
|
27
29
|
| "finalize"
|
|
28
30
|
| "collect"
|
|
29
31
|
| "list"
|
|
@@ -44,12 +46,15 @@ export interface ReleaseCommandOptions {
|
|
|
44
46
|
target?: ReleaseExportProvider;
|
|
45
47
|
allowDirty: boolean;
|
|
46
48
|
allowPublicSourcemaps: boolean;
|
|
49
|
+
allowMissingLocalRelease?: boolean;
|
|
47
50
|
}
|
|
48
51
|
|
|
49
52
|
export interface ReleaseCommandResult {
|
|
50
53
|
ok: boolean;
|
|
51
54
|
data?: unknown;
|
|
52
55
|
diagnostics: Diagnostic[];
|
|
56
|
+
nextActions?: string[];
|
|
57
|
+
failureKind?: string;
|
|
53
58
|
exitCode: 0 | 1;
|
|
54
59
|
}
|
|
55
60
|
|
|
@@ -125,19 +130,34 @@ export async function runReleaseCommand(
|
|
|
125
130
|
const dir = latestReleaseDir(options.workspaceRoot);
|
|
126
131
|
const diagnostics: Diagnostic[] = [];
|
|
127
132
|
if (!dir || !nodeFileSystem.exists(join(dir, "release.json"))) {
|
|
128
|
-
|
|
129
|
-
createDiagnostic({
|
|
130
|
-
severity: "
|
|
133
|
+
if (options.allowMissingLocalRelease) {
|
|
134
|
+
diagnostics.push(createDiagnostic({
|
|
135
|
+
severity: "warning",
|
|
131
136
|
code: FORGE_RELEASE_ID_MISSING,
|
|
132
|
-
message: "no prepared local release found",
|
|
133
|
-
|
|
134
|
-
|
|
137
|
+
message: "no prepared local release found; treating this as not-prepared because --allow-missing-local-release was provided",
|
|
138
|
+
fixHint: "Run forge release prepare when you want to validate a concrete release bundle.",
|
|
139
|
+
suggestedCommands: releasePrepareNextActions(),
|
|
140
|
+
}));
|
|
141
|
+
} else {
|
|
142
|
+
diagnostics.push(
|
|
143
|
+
createDiagnostic({
|
|
144
|
+
severity: "error",
|
|
145
|
+
code: FORGE_RELEASE_ID_MISSING,
|
|
146
|
+
message: "no prepared local release found; run forge release prepare before release check/finalize",
|
|
147
|
+
fixHint: "Prepare a local release first, then re-run the release gate.",
|
|
148
|
+
suggestedCommands: releasePrepareNextActions(),
|
|
149
|
+
}),
|
|
150
|
+
);
|
|
151
|
+
}
|
|
135
152
|
}
|
|
153
|
+
const errors = diagnostics.filter((diagnostic) => diagnostic.severity === "error");
|
|
136
154
|
return {
|
|
137
|
-
ok:
|
|
138
|
-
data: { releaseDir: dir },
|
|
155
|
+
ok: errors.length === 0,
|
|
156
|
+
data: { releaseDir: dir, state: diagnostics.length === 0 ? "prepared" : "missing-prepared-release" },
|
|
139
157
|
diagnostics,
|
|
140
|
-
|
|
158
|
+
nextActions: diagnostics.length === 0 ? releaseReadyNextActions() : releasePrepareNextActions(),
|
|
159
|
+
failureKind: errors.length === 0 ? undefined : "missing-prepared-release",
|
|
160
|
+
exitCode: errors.length === 0 ? 0 : 1,
|
|
141
161
|
};
|
|
142
162
|
}
|
|
143
163
|
|
|
@@ -256,7 +276,7 @@ export async function runReleaseCommand(
|
|
|
256
276
|
};
|
|
257
277
|
}
|
|
258
278
|
|
|
259
|
-
return { ok: false, diagnostics: [], exitCode: 1 };
|
|
279
|
+
return { ok: false, diagnostics: [], failureKind: "unsupported-release-command", exitCode: 1 };
|
|
260
280
|
}
|
|
261
281
|
|
|
262
282
|
function missingRelease(): ReleaseCommandResult {
|
|
@@ -266,9 +286,13 @@ function missingRelease(): ReleaseCommandResult {
|
|
|
266
286
|
createDiagnostic({
|
|
267
287
|
severity: "error",
|
|
268
288
|
code: FORGE_RELEASE_ID_MISSING,
|
|
269
|
-
message: "release not found",
|
|
289
|
+
message: "release not found; pass --release <id> or run forge release prepare first",
|
|
290
|
+
fixHint: "Inspect the prepared releases directory or create a new local release.",
|
|
291
|
+
suggestedCommands: ["forge release prepare --env production", "forge release inspect --json"],
|
|
270
292
|
}),
|
|
271
293
|
],
|
|
294
|
+
nextActions: ["forge release prepare --env production", "forge release inspect --json"],
|
|
295
|
+
failureKind: "missing-prepared-release",
|
|
272
296
|
exitCode: 1,
|
|
273
297
|
};
|
|
274
298
|
}
|
package/src/forge/cli/rls.ts
CHANGED
|
@@ -19,7 +19,7 @@ import type { DbAdapter, DbAdapterKind, DbTransaction } from "../runtime/db/adap
|
|
|
19
19
|
import { createDbAdapter } from "../runtime/db/factory.ts";
|
|
20
20
|
import { databaseUrlUsesPostgresSuperuser } from "../runtime/db/session-context.ts";
|
|
21
21
|
import type { RlsTableSecurity } from "../compiler/data-graph/rls/types.ts";
|
|
22
|
-
import { createHash } from "node:crypto";
|
|
22
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
23
23
|
|
|
24
24
|
export type RlsSubcommand = "generate" | "check" | "apply" | "test" | "mutate-test";
|
|
25
25
|
|
|
@@ -201,34 +201,61 @@ async function setTenant(tx: DbTransaction, tenantId: string): Promise<void> {
|
|
|
201
201
|
async function setProbeRole(adapter: DbAdapter, tableNames: string[]): Promise<string | null> {
|
|
202
202
|
const roleName = "forge_rls_probe";
|
|
203
203
|
try {
|
|
204
|
+
const schema = await adapter.query("SELECT current_schema() AS schema");
|
|
205
|
+
const currentSchema = String(schema.rows[0]?.schema ?? "public");
|
|
204
206
|
await adapter.query(
|
|
205
207
|
`DO $$ BEGIN CREATE ROLE ${quoteIdent(roleName)} NOLOGIN NOBYPASSRLS; EXCEPTION WHEN duplicate_object THEN NULL; END $$`,
|
|
206
208
|
);
|
|
207
209
|
await adapter.query(`GRANT USAGE ON SCHEMA public TO ${quoteIdent(roleName)}`);
|
|
208
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
|
+
}
|
|
209
214
|
for (const tableName of tableNames) {
|
|
210
215
|
await adapter.query(
|
|
211
216
|
`GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE ${quoteIdent(tableName)} TO ${quoteIdent(roleName)}`,
|
|
212
217
|
);
|
|
213
218
|
}
|
|
214
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
|
+
}
|
|
215
225
|
return roleName;
|
|
216
226
|
} catch {
|
|
217
227
|
return null;
|
|
218
228
|
}
|
|
219
229
|
}
|
|
220
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
|
+
|
|
221
242
|
async function seedReferencedRows(
|
|
222
243
|
tx: DbTransaction,
|
|
223
244
|
plan: SqlPlan,
|
|
224
245
|
columns: ColumnDef[],
|
|
225
246
|
row: Record<string, unknown>,
|
|
226
247
|
seed: string,
|
|
248
|
+
seen: Set<string>,
|
|
227
249
|
): Promise<void> {
|
|
228
250
|
for (const column of columns) {
|
|
229
251
|
if (!column.references) {
|
|
230
252
|
continue;
|
|
231
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);
|
|
232
259
|
const referenced = tablePlan(plan, column.references.table);
|
|
233
260
|
if (!referenced?.columns) {
|
|
234
261
|
continue;
|
|
@@ -297,9 +324,10 @@ async function runTableProbe(
|
|
|
297
324
|
|
|
298
325
|
const tx = await adapter.begin();
|
|
299
326
|
try {
|
|
300
|
-
|
|
301
|
-
await seedReferencedRows(tx, plan, columns,
|
|
302
|
-
await seedReferencedRows(tx, plan, columns,
|
|
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);
|
|
303
331
|
if (role) {
|
|
304
332
|
await tx.query(`SET ROLE ${quoteIdent(role)}`);
|
|
305
333
|
}
|
|
@@ -419,7 +447,13 @@ async function runRlsIsolationTests(options: RlsCommandOptions): Promise<RlsComm
|
|
|
419
447
|
};
|
|
420
448
|
}
|
|
421
449
|
|
|
450
|
+
let probeSchema: string | null = null;
|
|
451
|
+
|
|
422
452
|
try {
|
|
453
|
+
if (options.db === "postgres") {
|
|
454
|
+
probeSchema = await isolateRlsProbeSchema(adapter);
|
|
455
|
+
}
|
|
456
|
+
|
|
423
457
|
const migrationDiagnostics = await applyMigrations(adapter, plan);
|
|
424
458
|
const migrationErrors = migrationDiagnostics.filter((diagnostic) => diagnostic.severity === "error");
|
|
425
459
|
if (migrationErrors.length > 0) {
|
|
@@ -490,6 +524,11 @@ async function runRlsIsolationTests(options: RlsCommandOptions): Promise<RlsComm
|
|
|
490
524
|
exitCode: 1,
|
|
491
525
|
};
|
|
492
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
|
+
}
|
|
493
532
|
await adapter.close();
|
|
494
533
|
}
|
|
495
534
|
}
|
package/src/forge/cli/run.ts
CHANGED
|
@@ -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,
|
|
@@ -9,6 +9,9 @@ import type { AiCommandResult } from "./ai.ts";
|
|
|
9
9
|
import { runAiCommand } from "./ai.ts";
|
|
10
10
|
import { runCheckCommand } from "./commands.ts";
|
|
11
11
|
import type { GenerateResult } from "../compiler/types/cli.ts";
|
|
12
|
+
import { spawnSync } from "node:child_process";
|
|
13
|
+
import { existsSync } from "node:fs";
|
|
14
|
+
import { join } from "node:path";
|
|
12
15
|
|
|
13
16
|
export type SecuritySubcommand = "prove";
|
|
14
17
|
|
|
@@ -27,6 +30,17 @@ export interface SecurityCommandOptions {
|
|
|
27
30
|
json: boolean;
|
|
28
31
|
db: DbAdapterKind;
|
|
29
32
|
databaseUrl?: string;
|
|
33
|
+
runTests: boolean;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface SecurityTestRunResult {
|
|
37
|
+
enabled: boolean;
|
|
38
|
+
ok: boolean;
|
|
39
|
+
command: string[];
|
|
40
|
+
tests: string[];
|
|
41
|
+
exitCode: number | null;
|
|
42
|
+
stdout: string;
|
|
43
|
+
stderr: string;
|
|
30
44
|
}
|
|
31
45
|
|
|
32
46
|
export interface SecurityProofResult {
|
|
@@ -41,6 +55,7 @@ export interface SecurityProofResult {
|
|
|
41
55
|
rls: RlsCommandResult;
|
|
42
56
|
rlsMutation: RlsCommandResult;
|
|
43
57
|
agentRedteam: AiCommandResult;
|
|
58
|
+
securityTests: SecurityTestRunResult;
|
|
44
59
|
};
|
|
45
60
|
evidence: {
|
|
46
61
|
invariants: SecurityInvariantEvidence[];
|
|
@@ -155,6 +170,88 @@ function passed(name: string, ok: boolean, summary: SecurityProofResult["summary
|
|
|
155
170
|
}
|
|
156
171
|
}
|
|
157
172
|
|
|
173
|
+
function securityTestFiles(options: SecurityCommandOptions): string[] {
|
|
174
|
+
const tests = new Set<string>();
|
|
175
|
+
for (const invariant of invariantEvidence()) {
|
|
176
|
+
for (const test of invariant.tests) {
|
|
177
|
+
if (!test.startsWith("tests/security/")) {
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
if (options.db !== "postgres" && test.includes("rls-postgres-adversarial.test.ts")) {
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
tests.add(test);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
return [...tests].sort();
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function runSecurityTests(options: SecurityCommandOptions): SecurityTestRunResult {
|
|
190
|
+
const tests = securityTestFiles(options);
|
|
191
|
+
const command = [
|
|
192
|
+
"./bin/forge-bun.mjs",
|
|
193
|
+
"test",
|
|
194
|
+
...tests,
|
|
195
|
+
"--timeout",
|
|
196
|
+
"120000",
|
|
197
|
+
];
|
|
198
|
+
|
|
199
|
+
if (!options.runTests) {
|
|
200
|
+
return {
|
|
201
|
+
enabled: false,
|
|
202
|
+
ok: true,
|
|
203
|
+
command: ["node", ...command],
|
|
204
|
+
tests,
|
|
205
|
+
exitCode: null,
|
|
206
|
+
stdout: "",
|
|
207
|
+
stderr: "",
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const missingTests = tests.filter((test) => !existsSync(join(options.workspaceRoot, test)));
|
|
212
|
+
const runnerPath = join(options.workspaceRoot, "bin", "forge-bun.mjs");
|
|
213
|
+
if (missingTests.length > 0 || !existsSync(runnerPath)) {
|
|
214
|
+
return {
|
|
215
|
+
enabled: false,
|
|
216
|
+
ok: true,
|
|
217
|
+
command: ["node", ...command],
|
|
218
|
+
tests,
|
|
219
|
+
exitCode: null,
|
|
220
|
+
stdout: "",
|
|
221
|
+
stderr:
|
|
222
|
+
"security invariant test fixtures are not available in this workspace; structural proofs still ran. Run this command from the ForgeOS source checkout to execute the full framework test fixtures.",
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const result = spawnSync(process.execPath, command, {
|
|
227
|
+
cwd: options.workspaceRoot,
|
|
228
|
+
encoding: "utf8",
|
|
229
|
+
env: {
|
|
230
|
+
...process.env,
|
|
231
|
+
...(options.databaseUrl ? { DATABASE_URL: options.databaseUrl } : {}),
|
|
232
|
+
},
|
|
233
|
+
windowsHide: true,
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
return {
|
|
237
|
+
enabled: true,
|
|
238
|
+
ok: result.status === 0,
|
|
239
|
+
command: ["node", ...command],
|
|
240
|
+
tests,
|
|
241
|
+
exitCode: result.status,
|
|
242
|
+
stdout: limitOutput(result.stdout ?? ""),
|
|
243
|
+
stderr: limitOutput(result.stderr ?? ""),
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function limitOutput(output: string): string {
|
|
248
|
+
const maxLength = 20_000;
|
|
249
|
+
if (output.length <= maxLength) {
|
|
250
|
+
return output;
|
|
251
|
+
}
|
|
252
|
+
return `${output.slice(0, 4_000)}\n\n[forge output truncated]\n\n${output.slice(-16_000)}`;
|
|
253
|
+
}
|
|
254
|
+
|
|
158
255
|
export async function runSecurityCommand(
|
|
159
256
|
options: SecurityCommandOptions,
|
|
160
257
|
): Promise<SecurityProofResult> {
|
|
@@ -189,6 +286,7 @@ export async function runSecurityCommand(
|
|
|
189
286
|
workspaceRoot: options.workspaceRoot,
|
|
190
287
|
json: true,
|
|
191
288
|
});
|
|
289
|
+
const securityTests = runSecurityTests(options);
|
|
192
290
|
|
|
193
291
|
const summary: SecurityProofResult["summary"] = {
|
|
194
292
|
passed: [],
|
|
@@ -201,6 +299,9 @@ export async function runSecurityCommand(
|
|
|
201
299
|
passed("rls-proof", rls.exitCode === 0, summary);
|
|
202
300
|
passed("rls-mutation-proof", rlsMutation.exitCode === 0, summary);
|
|
203
301
|
passed("agent-redteam", agentRedteam.exitCode === 0, summary);
|
|
302
|
+
if (securityTests.enabled) {
|
|
303
|
+
passed("security-tests", securityTests.ok, summary);
|
|
304
|
+
}
|
|
204
305
|
|
|
205
306
|
if (auth.mode === "dev-headers") {
|
|
206
307
|
summary.warnings.push("auth-proof uses local-only dev-headers mode");
|
|
@@ -220,6 +321,15 @@ export async function runSecurityCommand(
|
|
|
220
321
|
summary.warnings.push(`${diagnostic.code}: ${diagnostic.message}`);
|
|
221
322
|
}
|
|
222
323
|
}
|
|
324
|
+
if (!securityTests.enabled && !options.runTests) {
|
|
325
|
+
summary.warnings.push("security-tests not executed; pass --full or --run-tests to run invariant security tests");
|
|
326
|
+
}
|
|
327
|
+
if (!securityTests.enabled && options.runTests) {
|
|
328
|
+
summary.warnings.push(securityTests.stderr);
|
|
329
|
+
}
|
|
330
|
+
if (options.runTests && options.db !== "postgres") {
|
|
331
|
+
summary.warnings.push("postgres RLS adversarial test skipped because --db postgres was not selected");
|
|
332
|
+
}
|
|
223
333
|
|
|
224
334
|
const ok = summary.failed.length === 0;
|
|
225
335
|
const assurance =
|
|
@@ -240,6 +350,7 @@ export async function runSecurityCommand(
|
|
|
240
350
|
rls,
|
|
241
351
|
rlsMutation,
|
|
242
352
|
agentRedteam,
|
|
353
|
+
securityTests,
|
|
243
354
|
},
|
|
244
355
|
evidence: {
|
|
245
356
|
invariants: invariantEvidence(),
|
|
@@ -261,6 +372,7 @@ export function formatSecurityHuman(result: SecurityProofResult): string {
|
|
|
261
372
|
`Assurance: ${result.assurance}`,
|
|
262
373
|
`Passed: ${result.summary.passed.join(", ") || "none"}`,
|
|
263
374
|
`Failed: ${result.summary.failed.join(", ") || "none"}`,
|
|
375
|
+
`Security tests: ${result.proofs.securityTests.enabled ? (result.proofs.securityTests.ok ? "passed" : "failed") : "not run"}`,
|
|
264
376
|
];
|
|
265
377
|
if (result.summary.warnings.length > 0) {
|
|
266
378
|
lines.push("", "Warnings:", ...result.summary.warnings.map((warning) => `- ${warning}`));
|
|
@@ -4,6 +4,7 @@ import { GENERATED_DIR } from "../compiler/emitter/constants.ts";
|
|
|
4
4
|
import { stripDeterministicHeader } from "../compiler/primitives/header.ts";
|
|
5
5
|
import { runGenerateCommand } from "./commands.ts";
|
|
6
6
|
import { runVerifyCommand } from "./verify.ts";
|
|
7
|
+
import { selfHostPrepareNextActions, selfHostReadyNextActions } from "./next-actions.ts";
|
|
7
8
|
|
|
8
9
|
export type SelfHostSubcommand = "compose" | "env" | "check" | "clean";
|
|
9
10
|
|
|
@@ -15,6 +16,7 @@ export interface SelfHostCommandOptions {
|
|
|
15
16
|
postgresVersion: string;
|
|
16
17
|
runtimePort: number;
|
|
17
18
|
webPort: number;
|
|
19
|
+
preparedOnly?: boolean;
|
|
18
20
|
}
|
|
19
21
|
|
|
20
22
|
export interface SelfHostCheck {
|
|
@@ -26,8 +28,10 @@ export interface SelfHostCheck {
|
|
|
26
28
|
export interface SelfHostCommandResult {
|
|
27
29
|
ok: boolean;
|
|
28
30
|
exitCode: 0 | 1;
|
|
31
|
+
state?: "ready" | "not-prepared" | "failed";
|
|
29
32
|
files?: string[];
|
|
30
33
|
checks?: SelfHostCheck[];
|
|
34
|
+
nextActions?: string[];
|
|
31
35
|
}
|
|
32
36
|
|
|
33
37
|
function deployDir(workspaceRoot: string): string {
|
|
@@ -392,6 +396,28 @@ export async function runSelfHostCommand(
|
|
|
392
396
|
}
|
|
393
397
|
|
|
394
398
|
const checks: SelfHostCheck[] = [];
|
|
399
|
+
const requiredDeployFiles = [
|
|
400
|
+
"docker-compose.yml",
|
|
401
|
+
"Dockerfile.runtime",
|
|
402
|
+
"Dockerfile.web",
|
|
403
|
+
".dockerignore",
|
|
404
|
+
".env.example",
|
|
405
|
+
"README.md",
|
|
406
|
+
];
|
|
407
|
+
const missingDeployFiles = requiredDeployFiles.filter((file) => !nodeFileSystem.exists(join(dir, file)));
|
|
408
|
+
if (options.preparedOnly && missingDeployFiles.length > 0) {
|
|
409
|
+
return {
|
|
410
|
+
ok: true,
|
|
411
|
+
state: "not-prepared",
|
|
412
|
+
exitCode: 0,
|
|
413
|
+
checks: missingDeployFiles.map((file) => ({
|
|
414
|
+
name: `deploy/${file}`,
|
|
415
|
+
ok: true,
|
|
416
|
+
details: { state: "not-prepared", missing: true, command: "forge self-host compose" },
|
|
417
|
+
})),
|
|
418
|
+
nextActions: selfHostPrepareNextActions(),
|
|
419
|
+
};
|
|
420
|
+
}
|
|
395
421
|
const generated = await runGenerateCommand({
|
|
396
422
|
workspaceRoot: options.workspaceRoot,
|
|
397
423
|
check: true,
|
|
@@ -399,7 +425,11 @@ export async function runSelfHostCommand(
|
|
|
399
425
|
json: false,
|
|
400
426
|
concurrency: 4,
|
|
401
427
|
});
|
|
402
|
-
checks.push({
|
|
428
|
+
checks.push({
|
|
429
|
+
name: "generated",
|
|
430
|
+
ok: generated.exitCode === 0,
|
|
431
|
+
details: generated.exitCode === 0 ? undefined : { command: "forge generate" },
|
|
432
|
+
});
|
|
403
433
|
|
|
404
434
|
const verify = await runVerifyCommand({
|
|
405
435
|
workspaceRoot: options.workspaceRoot,
|
|
@@ -409,17 +439,19 @@ export async function runSelfHostCommand(
|
|
|
409
439
|
skipEslint: true,
|
|
410
440
|
strict: true,
|
|
411
441
|
});
|
|
412
|
-
checks.push({
|
|
442
|
+
checks.push({
|
|
443
|
+
name: "verify-strict",
|
|
444
|
+
ok: verify.exitCode === 0,
|
|
445
|
+
details: verify.exitCode === 0 ? undefined : { command: "forge verify --strict" },
|
|
446
|
+
});
|
|
413
447
|
|
|
414
|
-
for (const file of
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
]) {
|
|
422
|
-
checks.push({ name: `deploy/${file}`, ok: nodeFileSystem.exists(join(dir, file)) });
|
|
448
|
+
for (const file of requiredDeployFiles) {
|
|
449
|
+
const exists = nodeFileSystem.exists(join(dir, file));
|
|
450
|
+
checks.push({
|
|
451
|
+
name: `deploy/${file}`,
|
|
452
|
+
ok: exists,
|
|
453
|
+
details: exists ? undefined : { missing: true, command: "forge self-host compose" },
|
|
454
|
+
});
|
|
423
455
|
}
|
|
424
456
|
|
|
425
457
|
const envExample = nodeFileSystem.exists(join(dir, ".env.example"))
|
|
@@ -452,7 +484,13 @@ export async function runSelfHostCommand(
|
|
|
452
484
|
});
|
|
453
485
|
|
|
454
486
|
const ok = checks.every((check) => check.ok);
|
|
455
|
-
return {
|
|
487
|
+
return {
|
|
488
|
+
ok,
|
|
489
|
+
state: ok ? "ready" : "failed",
|
|
490
|
+
exitCode: ok ? 0 : 1,
|
|
491
|
+
checks,
|
|
492
|
+
nextActions: ok ? selfHostReadyNextActions() : selfHostPrepareNextActions(),
|
|
493
|
+
};
|
|
456
494
|
}
|
|
457
495
|
|
|
458
496
|
export function formatSelfHostHuman(result: SelfHostCommandResult): string {
|
|
@@ -460,9 +498,13 @@ export function formatSelfHostHuman(result: SelfHostCommandResult): string {
|
|
|
460
498
|
return `wrote self-host files:\n${result.files.map((file) => ` ${file}`).join("\n")}\n`;
|
|
461
499
|
}
|
|
462
500
|
if (result.checks) {
|
|
463
|
-
|
|
501
|
+
const lines = result.checks
|
|
464
502
|
.map((check) => `${check.ok ? "ok" : "fail"} ${check.name}`)
|
|
465
|
-
.join("\n")
|
|
503
|
+
.join("\n");
|
|
504
|
+
const next = result.nextActions && result.nextActions.length > 0
|
|
505
|
+
? `\nNext:\n${result.nextActions.map((action) => ` ${action}`).join("\n")}\n`
|
|
506
|
+
: "\n";
|
|
507
|
+
return `${lines}${next}`;
|
|
466
508
|
}
|
|
467
509
|
return result.ok ? "self-host clean complete\n" : "self-host command failed\n";
|
|
468
510
|
}
|