@antasphere/chassis-server 1.0.0
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/LICENSE +82 -0
- package/dist/accounts/deletion.d.ts +163 -0
- package/dist/accounts/deletion.d.ts.map +1 -0
- package/dist/accounts/deletion.js +360 -0
- package/dist/accounts/deletion.js.map +1 -0
- package/dist/accounts/erasure-log.d.ts +66 -0
- package/dist/accounts/erasure-log.d.ts.map +1 -0
- package/dist/accounts/erasure-log.js +117 -0
- package/dist/accounts/erasure-log.js.map +1 -0
- package/dist/accounts/index.d.ts +6 -0
- package/dist/accounts/index.d.ts.map +1 -0
- package/dist/accounts/index.js +6 -0
- package/dist/accounts/index.js.map +1 -0
- package/dist/accounts/mint-refusal.d.ts +47 -0
- package/dist/accounts/mint-refusal.d.ts.map +1 -0
- package/dist/accounts/mint-refusal.js +59 -0
- package/dist/accounts/mint-refusal.js.map +1 -0
- package/dist/accounts/signup-duplicate.d.ts +14 -0
- package/dist/accounts/signup-duplicate.d.ts.map +1 -0
- package/dist/accounts/signup-duplicate.js +25 -0
- package/dist/accounts/signup-duplicate.js.map +1 -0
- package/dist/accounts/superadmin.d.ts +27 -0
- package/dist/accounts/superadmin.d.ts.map +1 -0
- package/dist/accounts/superadmin.js +46 -0
- package/dist/accounts/superadmin.js.map +1 -0
- package/dist/api/apikeys.d.ts +14 -0
- package/dist/api/apikeys.d.ts.map +1 -0
- package/dist/api/apikeys.js +124 -0
- package/dist/api/apikeys.js.map +1 -0
- package/dist/api/audit.d.ts +4 -0
- package/dist/api/audit.d.ts.map +1 -0
- package/dist/api/audit.js +75 -0
- package/dist/api/audit.js.map +1 -0
- package/dist/api/break-glass.d.ts +48 -0
- package/dist/api/break-glass.d.ts.map +1 -0
- package/dist/api/break-glass.js +217 -0
- package/dist/api/break-glass.js.map +1 -0
- package/dist/api/cli-auth.d.ts +84 -0
- package/dist/api/cli-auth.d.ts.map +1 -0
- package/dist/api/cli-auth.js +177 -0
- package/dist/api/cli-auth.js.map +1 -0
- package/dist/api/create-api.d.ts +123 -0
- package/dist/api/create-api.d.ts.map +1 -0
- package/dist/api/create-api.js +1165 -0
- package/dist/api/create-api.js.map +1 -0
- package/dist/api/demo-passes.d.ts +45 -0
- package/dist/api/demo-passes.d.ts.map +1 -0
- package/dist/api/demo-passes.js +143 -0
- package/dist/api/demo-passes.js.map +1 -0
- package/dist/api/errors.d.ts +5 -0
- package/dist/api/errors.d.ts.map +1 -0
- package/dist/api/errors.js +5 -0
- package/dist/api/errors.js.map +1 -0
- package/dist/api/export.d.ts +63 -0
- package/dist/api/export.d.ts.map +1 -0
- package/dist/api/export.js +393 -0
- package/dist/api/export.js.map +1 -0
- package/dist/api/files.d.ts +40 -0
- package/dist/api/files.d.ts.map +1 -0
- package/dist/api/files.js +150 -0
- package/dist/api/files.js.map +1 -0
- package/dist/api/index.d.ts +17 -0
- package/dist/api/index.d.ts.map +1 -0
- package/dist/api/index.js +17 -0
- package/dist/api/index.js.map +1 -0
- package/dist/api/invitations.d.ts +50 -0
- package/dist/api/invitations.d.ts.map +1 -0
- package/dist/api/invitations.js +313 -0
- package/dist/api/invitations.js.map +1 -0
- package/dist/api/members.d.ts +25 -0
- package/dist/api/members.d.ts.map +1 -0
- package/dist/api/members.js +522 -0
- package/dist/api/members.js.map +1 -0
- package/dist/api/onboarding.d.ts +26 -0
- package/dist/api/onboarding.d.ts.map +1 -0
- package/dist/api/onboarding.js +23 -0
- package/dist/api/onboarding.js.map +1 -0
- package/dist/api/openapi-doc.d.ts +21 -0
- package/dist/api/openapi-doc.d.ts.map +1 -0
- package/dist/api/openapi-doc.js +25 -0
- package/dist/api/openapi-doc.js.map +1 -0
- package/dist/api/projects.d.ts +24 -0
- package/dist/api/projects.d.ts.map +1 -0
- package/dist/api/projects.js +624 -0
- package/dist/api/projects.js.map +1 -0
- package/dist/api/scope-routes.d.ts +12 -0
- package/dist/api/scope-routes.d.ts.map +1 -0
- package/dist/api/scope-routes.js +2 -0
- package/dist/api/scope-routes.js.map +1 -0
- package/dist/api/sso-connect.d.ts +70 -0
- package/dist/api/sso-connect.d.ts.map +1 -0
- package/dist/api/sso-connect.js +164 -0
- package/dist/api/sso-connect.js.map +1 -0
- package/dist/api/sso-logout.d.ts +41 -0
- package/dist/api/sso-logout.d.ts.map +1 -0
- package/dist/api/sso-logout.js +34 -0
- package/dist/api/sso-logout.js.map +1 -0
- package/dist/api/teams.d.ts +43 -0
- package/dist/api/teams.d.ts.map +1 -0
- package/dist/api/teams.js +447 -0
- package/dist/api/teams.js.map +1 -0
- package/dist/api/workspaces.d.ts +114 -0
- package/dist/api/workspaces.d.ts.map +1 -0
- package/dist/api/workspaces.js +462 -0
- package/dist/api/workspaces.js.map +1 -0
- package/dist/apikeys/index.d.ts +3 -0
- package/dist/apikeys/index.d.ts.map +1 -0
- package/dist/apikeys/index.js +3 -0
- package/dist/apikeys/index.js.map +1 -0
- package/dist/apikeys/peppers.d.ts +53 -0
- package/dist/apikeys/peppers.d.ts.map +1 -0
- package/dist/apikeys/peppers.js +97 -0
- package/dist/apikeys/peppers.js.map +1 -0
- package/dist/apikeys/service.d.ts +56 -0
- package/dist/apikeys/service.d.ts.map +1 -0
- package/dist/apikeys/service.js +196 -0
- package/dist/apikeys/service.js.map +1 -0
- package/dist/app.d.ts +65 -0
- package/dist/app.d.ts.map +1 -0
- package/dist/app.js +142 -0
- package/dist/app.js.map +1 -0
- package/dist/audit/filters.d.ts +38 -0
- package/dist/audit/filters.d.ts.map +1 -0
- package/dist/audit/filters.js +97 -0
- package/dist/audit/filters.js.map +1 -0
- package/dist/audit/index.d.ts +3 -0
- package/dist/audit/index.d.ts.map +1 -0
- package/dist/audit/index.js +3 -0
- package/dist/audit/index.js.map +1 -0
- package/dist/audit/service.d.ts +65 -0
- package/dist/audit/service.d.ts.map +1 -0
- package/dist/audit/service.js +103 -0
- package/dist/audit/service.js.map +1 -0
- package/dist/boot.d.ts +8 -0
- package/dist/boot.d.ts.map +1 -0
- package/dist/boot.js +822 -0
- package/dist/boot.js.map +1 -0
- package/dist/constant-time.d.ts +3 -0
- package/dist/constant-time.d.ts.map +1 -0
- package/dist/constant-time.js +10 -0
- package/dist/constant-time.js.map +1 -0
- package/dist/email/driver.d.ts +16 -0
- package/dist/email/driver.d.ts.map +1 -0
- package/dist/email/driver.js +136 -0
- package/dist/email/driver.js.map +1 -0
- package/dist/email/index.d.ts +4 -0
- package/dist/email/index.d.ts.map +1 -0
- package/dist/email/index.js +4 -0
- package/dist/email/index.js.map +1 -0
- package/dist/email/shell.d.ts +72 -0
- package/dist/email/shell.d.ts.map +1 -0
- package/dist/email/shell.js +146 -0
- package/dist/email/shell.js.map +1 -0
- package/dist/email/templates.d.ts +83 -0
- package/dist/email/templates.d.ts.map +1 -0
- package/dist/email/templates.js +96 -0
- package/dist/email/templates.js.map +1 -0
- package/dist/entitlements/check.d.ts +140 -0
- package/dist/entitlements/check.d.ts.map +1 -0
- package/dist/entitlements/check.js +276 -0
- package/dist/entitlements/check.js.map +1 -0
- package/dist/entitlements/clock-skew.d.ts +27 -0
- package/dist/entitlements/clock-skew.d.ts.map +1 -0
- package/dist/entitlements/clock-skew.js +58 -0
- package/dist/entitlements/clock-skew.js.map +1 -0
- package/dist/entitlements/gate.d.ts +113 -0
- package/dist/entitlements/gate.d.ts.map +1 -0
- package/dist/entitlements/gate.js +430 -0
- package/dist/entitlements/gate.js.map +1 -0
- package/dist/entitlements/hub-machine-token.d.ts +83 -0
- package/dist/entitlements/hub-machine-token.d.ts.map +1 -0
- package/dist/entitlements/hub-machine-token.js +175 -0
- package/dist/entitlements/hub-machine-token.js.map +1 -0
- package/dist/entitlements/hub-subject.d.ts +32 -0
- package/dist/entitlements/hub-subject.d.ts.map +1 -0
- package/dist/entitlements/hub-subject.js +68 -0
- package/dist/entitlements/hub-subject.js.map +1 -0
- package/dist/entitlements/index.d.ts +9 -0
- package/dist/entitlements/index.d.ts.map +1 -0
- package/dist/entitlements/index.js +9 -0
- package/dist/entitlements/index.js.map +1 -0
- package/dist/entitlements/poster.d.ts +81 -0
- package/dist/entitlements/poster.d.ts.map +1 -0
- package/dist/entitlements/poster.js +169 -0
- package/dist/entitlements/poster.js.map +1 -0
- package/dist/entitlements/profiles.d.ts +121 -0
- package/dist/entitlements/profiles.d.ts.map +1 -0
- package/dist/entitlements/profiles.js +212 -0
- package/dist/entitlements/profiles.js.map +1 -0
- package/dist/entitlements/slot.d.ts +33 -0
- package/dist/entitlements/slot.d.ts.map +1 -0
- package/dist/entitlements/slot.js +75 -0
- package/dist/entitlements/slot.js.map +1 -0
- package/dist/env.d.ts +293 -0
- package/dist/env.d.ts.map +1 -0
- package/dist/env.js +383 -0
- package/dist/env.js.map +1 -0
- package/dist/files/http.d.ts +23 -0
- package/dist/files/http.d.ts.map +1 -0
- package/dist/files/http.js +66 -0
- package/dist/files/http.js.map +1 -0
- package/dist/files/index.d.ts +5 -0
- package/dist/files/index.d.ts.map +1 -0
- package/dist/files/index.js +5 -0
- package/dist/files/index.js.map +1 -0
- package/dist/files/serve.d.ts +43 -0
- package/dist/files/serve.d.ts.map +1 -0
- package/dist/files/serve.js +71 -0
- package/dist/files/serve.js.map +1 -0
- package/dist/files/service.d.ts +69 -0
- package/dist/files/service.d.ts.map +1 -0
- package/dist/files/service.js +188 -0
- package/dist/files/service.js.map +1 -0
- package/dist/files/spool.d.ts +21 -0
- package/dist/files/spool.d.ts.map +1 -0
- package/dist/files/spool.js +47 -0
- package/dist/files/spool.js.map +1 -0
- package/dist/identity/better-auth.d.ts +3927 -0
- package/dist/identity/better-auth.d.ts.map +1 -0
- package/dist/identity/better-auth.js +896 -0
- package/dist/identity/better-auth.js.map +1 -0
- package/dist/identity/demo-pass-plugin.d.ts +29 -0
- package/dist/identity/demo-pass-plugin.d.ts.map +1 -0
- package/dist/identity/demo-pass-plugin.js +88 -0
- package/dist/identity/demo-pass-plugin.js.map +1 -0
- package/dist/identity/demo-pass-rules.d.ts +84 -0
- package/dist/identity/demo-pass-rules.d.ts.map +1 -0
- package/dist/identity/demo-pass-rules.js +223 -0
- package/dist/identity/demo-pass-rules.js.map +1 -0
- package/dist/identity/demo-pass.d.ts +159 -0
- package/dist/identity/demo-pass.d.ts.map +1 -0
- package/dist/identity/demo-pass.js +319 -0
- package/dist/identity/demo-pass.js.map +1 -0
- package/dist/identity/hub-grant.d.ts +182 -0
- package/dist/identity/hub-grant.d.ts.map +1 -0
- package/dist/identity/hub-grant.js +594 -0
- package/dist/identity/hub-grant.js.map +1 -0
- package/dist/identity/hub-jwt.d.ts +22 -0
- package/dist/identity/hub-jwt.d.ts.map +1 -0
- package/dist/identity/hub-jwt.js +77 -0
- package/dist/identity/hub-jwt.js.map +1 -0
- package/dist/identity/hub-live-gate.d.ts +17 -0
- package/dist/identity/hub-live-gate.d.ts.map +1 -0
- package/dist/identity/hub-live-gate.js +146 -0
- package/dist/identity/hub-live-gate.js.map +1 -0
- package/dist/identity/hub-logout.d.ts +88 -0
- package/dist/identity/hub-logout.d.ts.map +1 -0
- package/dist/identity/hub-logout.js +158 -0
- package/dist/identity/hub-logout.js.map +1 -0
- package/dist/identity/hub-projection.d.ts +103 -0
- package/dist/identity/hub-projection.d.ts.map +1 -0
- package/dist/identity/hub-projection.js +130 -0
- package/dist/identity/hub-projection.js.map +1 -0
- package/dist/identity/hub-reconcile.d.ts +151 -0
- package/dist/identity/hub-reconcile.d.ts.map +1 -0
- package/dist/identity/hub-reconcile.js +400 -0
- package/dist/identity/hub-reconcile.js.map +1 -0
- package/dist/identity/hub-sso.d.ts +363 -0
- package/dist/identity/hub-sso.d.ts.map +1 -0
- package/dist/identity/hub-sso.js +536 -0
- package/dist/identity/hub-sso.js.map +1 -0
- package/dist/identity/hub-user-client.d.ts +222 -0
- package/dist/identity/hub-user-client.d.ts.map +1 -0
- package/dist/identity/hub-user-client.js +406 -0
- package/dist/identity/hub-user-client.js.map +1 -0
- package/dist/identity/index.d.ts +13 -0
- package/dist/identity/index.d.ts.map +1 -0
- package/dist/identity/index.js +13 -0
- package/dist/identity/index.js.map +1 -0
- package/dist/identity/oauth-jwt.d.ts +32 -0
- package/dist/identity/oauth-jwt.d.ts.map +1 -0
- package/dist/identity/oauth-jwt.js +122 -0
- package/dist/identity/oauth-jwt.js.map +1 -0
- package/dist/identity/resolve-membership.d.ts +28 -0
- package/dist/identity/resolve-membership.d.ts.map +1 -0
- package/dist/identity/resolve-membership.js +69 -0
- package/dist/identity/resolve-membership.js.map +1 -0
- package/dist/identity/signing-key.d.ts +49 -0
- package/dist/identity/signing-key.d.ts.map +1 -0
- package/dist/identity/signing-key.js +144 -0
- package/dist/identity/signing-key.js.map +1 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +11 -0
- package/dist/index.js.map +1 -0
- package/dist/invitations/index.d.ts +2 -0
- package/dist/invitations/index.d.ts.map +1 -0
- package/dist/invitations/index.js +2 -0
- package/dist/invitations/index.js.map +1 -0
- package/dist/invitations/service.d.ts +36 -0
- package/dist/invitations/service.d.ts.map +1 -0
- package/dist/invitations/service.js +122 -0
- package/dist/invitations/service.js.map +1 -0
- package/dist/jobs/index.d.ts +3 -0
- package/dist/jobs/index.d.ts.map +1 -0
- package/dist/jobs/index.js +3 -0
- package/dist/jobs/index.js.map +1 -0
- package/dist/jobs/pgboss.d.ts +119 -0
- package/dist/jobs/pgboss.d.ts.map +1 -0
- package/dist/jobs/pgboss.js +521 -0
- package/dist/jobs/pgboss.js.map +1 -0
- package/dist/jobs/timers.d.ts +127 -0
- package/dist/jobs/timers.d.ts.map +1 -0
- package/dist/jobs/timers.js +228 -0
- package/dist/jobs/timers.js.map +1 -0
- package/dist/logger.d.ts +17 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +64 -0
- package/dist/logger.js.map +1 -0
- package/dist/mcp/errors.d.ts +37 -0
- package/dist/mcp/errors.d.ts.map +1 -0
- package/dist/mcp/errors.js +77 -0
- package/dist/mcp/errors.js.map +1 -0
- package/dist/mcp/http.d.ts +35 -0
- package/dist/mcp/http.d.ts.map +1 -0
- package/dist/mcp/http.js +80 -0
- package/dist/mcp/http.js.map +1 -0
- package/dist/mcp/index.d.ts +8 -0
- package/dist/mcp/index.d.ts.map +1 -0
- package/dist/mcp/index.js +8 -0
- package/dist/mcp/index.js.map +1 -0
- package/dist/mcp/inputs.d.ts +19 -0
- package/dist/mcp/inputs.d.ts.map +1 -0
- package/dist/mcp/inputs.js +28 -0
- package/dist/mcp/inputs.js.map +1 -0
- package/dist/mcp/projects.d.ts +31 -0
- package/dist/mcp/projects.d.ts.map +1 -0
- package/dist/mcp/projects.js +315 -0
- package/dist/mcp/projects.js.map +1 -0
- package/dist/mcp/server.d.ts +52 -0
- package/dist/mcp/server.d.ts.map +1 -0
- package/dist/mcp/server.js +93 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/mcp/teams.d.ts +7 -0
- package/dist/mcp/teams.d.ts.map +1 -0
- package/dist/mcp/teams.js +62 -0
- package/dist/mcp/teams.js.map +1 -0
- package/dist/mcp/tool-kit.d.ts +67 -0
- package/dist/mcp/tool-kit.d.ts.map +1 -0
- package/dist/mcp/tool-kit.js +74 -0
- package/dist/mcp/tool-kit.js.map +1 -0
- package/dist/members/removal.d.ts +95 -0
- package/dist/members/removal.d.ts.map +1 -0
- package/dist/members/removal.js +143 -0
- package/dist/members/removal.js.map +1 -0
- package/dist/middleware/auth-body.d.ts +25 -0
- package/dist/middleware/auth-body.d.ts.map +1 -0
- package/dist/middleware/auth-body.js +55 -0
- package/dist/middleware/auth-body.js.map +1 -0
- package/dist/middleware/auth-context.d.ts +170 -0
- package/dist/middleware/auth-context.d.ts.map +1 -0
- package/dist/middleware/auth-context.js +251 -0
- package/dist/middleware/auth-context.js.map +1 -0
- package/dist/middleware/body-cap.d.ts +17 -0
- package/dist/middleware/body-cap.d.ts.map +1 -0
- package/dist/middleware/body-cap.js +31 -0
- package/dist/middleware/body-cap.js.map +1 -0
- package/dist/middleware/body-refusal.d.ts +20 -0
- package/dist/middleware/body-refusal.d.ts.map +1 -0
- package/dist/middleware/body-refusal.js +5 -0
- package/dist/middleware/body-refusal.js.map +1 -0
- package/dist/middleware/cross-site.d.ts +61 -0
- package/dist/middleware/cross-site.d.ts.map +1 -0
- package/dist/middleware/cross-site.js +59 -0
- package/dist/middleware/cross-site.js.map +1 -0
- package/dist/middleware/hub-managed.d.ts +45 -0
- package/dist/middleware/hub-managed.d.ts.map +1 -0
- package/dist/middleware/hub-managed.js +56 -0
- package/dist/middleware/hub-managed.js.map +1 -0
- package/dist/middleware/idempotency.d.ts +14 -0
- package/dist/middleware/idempotency.d.ts.map +1 -0
- package/dist/middleware/idempotency.js +222 -0
- package/dist/middleware/idempotency.js.map +1 -0
- package/dist/middleware/index.d.ts +12 -0
- package/dist/middleware/index.d.ts.map +1 -0
- package/dist/middleware/index.js +12 -0
- package/dist/middleware/index.js.map +1 -0
- package/dist/middleware/json-depth.d.ts +32 -0
- package/dist/middleware/json-depth.d.ts.map +1 -0
- package/dist/middleware/json-depth.js +80 -0
- package/dist/middleware/json-depth.js.map +1 -0
- package/dist/middleware/no-store.d.ts +21 -0
- package/dist/middleware/no-store.d.ts.map +1 -0
- package/dist/middleware/no-store.js +32 -0
- package/dist/middleware/no-store.js.map +1 -0
- package/dist/middleware/oauth-public.d.ts +16 -0
- package/dist/middleware/oauth-public.d.ts.map +1 -0
- package/dist/middleware/oauth-public.js +50 -0
- package/dist/middleware/oauth-public.js.map +1 -0
- package/dist/middleware/rate-limit.d.ts +182 -0
- package/dist/middleware/rate-limit.d.ts.map +1 -0
- package/dist/middleware/rate-limit.js +277 -0
- package/dist/middleware/rate-limit.js.map +1 -0
- package/dist/middleware/request-id.d.ts +10 -0
- package/dist/middleware/request-id.d.ts.map +1 -0
- package/dist/middleware/request-id.js +42 -0
- package/dist/middleware/request-id.js.map +1 -0
- package/dist/middleware/scopes.d.ts +33 -0
- package/dist/middleware/scopes.d.ts.map +1 -0
- package/dist/middleware/scopes.js +139 -0
- package/dist/middleware/scopes.js.map +1 -0
- package/dist/middleware/security-headers.d.ts +53 -0
- package/dist/middleware/security-headers.d.ts.map +1 -0
- package/dist/middleware/security-headers.js +96 -0
- package/dist/middleware/security-headers.js.map +1 -0
- package/dist/observability/index.d.ts +3 -0
- package/dist/observability/index.d.ts.map +1 -0
- package/dist/observability/index.js +3 -0
- package/dist/observability/index.js.map +1 -0
- package/dist/observability/metrics.d.ts +17 -0
- package/dist/observability/metrics.d.ts.map +1 -0
- package/dist/observability/metrics.js +82 -0
- package/dist/observability/metrics.js.map +1 -0
- package/dist/observability/otel.d.ts +20 -0
- package/dist/observability/otel.d.ts.map +1 -0
- package/dist/observability/otel.js +54 -0
- package/dist/observability/otel.js.map +1 -0
- package/dist/pagination.d.ts +57 -0
- package/dist/pagination.d.ts.map +1 -0
- package/dist/pagination.js +90 -0
- package/dist/pagination.js.map +1 -0
- package/dist/platform/edition.d.ts +57 -0
- package/dist/platform/edition.d.ts.map +1 -0
- package/dist/platform/edition.js +90 -0
- package/dist/platform/edition.js.map +1 -0
- package/dist/platform/entitlements.d.ts +26 -0
- package/dist/platform/entitlements.d.ts.map +1 -0
- package/dist/platform/entitlements.js +28 -0
- package/dist/platform/entitlements.js.map +1 -0
- package/dist/platform/events.d.ts +87 -0
- package/dist/platform/events.d.ts.map +1 -0
- package/dist/platform/events.js +32 -0
- package/dist/platform/events.js.map +1 -0
- package/dist/platform/index.d.ts +8 -0
- package/dist/platform/index.d.ts.map +1 -0
- package/dist/platform/index.js +8 -0
- package/dist/platform/index.js.map +1 -0
- package/dist/platform/local-identity.d.ts +39 -0
- package/dist/platform/local-identity.d.ts.map +1 -0
- package/dist/platform/local-identity.js +87 -0
- package/dist/platform/local-identity.js.map +1 -0
- package/dist/platform/registry.d.ts +23 -0
- package/dist/platform/registry.d.ts.map +1 -0
- package/dist/platform/registry.js +5 -0
- package/dist/platform/registry.js.map +1 -0
- package/dist/platform/usage.d.ts +11 -0
- package/dist/platform/usage.d.ts.map +1 -0
- package/dist/platform/usage.js +12 -0
- package/dist/platform/usage.js.map +1 -0
- package/dist/platform/workspaces.d.ts +25 -0
- package/dist/platform/workspaces.d.ts.map +1 -0
- package/dist/platform/workspaces.js +44 -0
- package/dist/platform/workspaces.js.map +1 -0
- package/dist/projects/access.d.ts +40 -0
- package/dist/projects/access.d.ts.map +1 -0
- package/dist/projects/access.js +133 -0
- package/dist/projects/access.js.map +1 -0
- package/dist/projects/index.d.ts +2 -0
- package/dist/projects/index.d.ts.map +1 -0
- package/dist/projects/index.js +2 -0
- package/dist/projects/index.js.map +1 -0
- package/dist/route-label.d.ts +23 -0
- package/dist/route-label.d.ts.map +1 -0
- package/dist/route-label.js +29 -0
- package/dist/route-label.js.map +1 -0
- package/dist/routes/health.d.ts +30 -0
- package/dist/routes/health.d.ts.map +1 -0
- package/dist/routes/health.js +54 -0
- package/dist/routes/health.js.map +1 -0
- package/dist/routes/index.d.ts +3 -0
- package/dist/routes/index.d.ts.map +1 -0
- package/dist/routes/index.js +3 -0
- package/dist/routes/index.js.map +1 -0
- package/dist/routes/wellknown.d.ts +11 -0
- package/dist/routes/wellknown.d.ts.map +1 -0
- package/dist/routes/wellknown.js +45 -0
- package/dist/routes/wellknown.js.map +1 -0
- package/dist/secret.d.ts +30 -0
- package/dist/secret.d.ts.map +1 -0
- package/dist/secret.js +97 -0
- package/dist/secret.js.map +1 -0
- package/dist/setup-transport.d.ts +23 -0
- package/dist/setup-transport.d.ts.map +1 -0
- package/dist/setup-transport.js +52 -0
- package/dist/setup-transport.js.map +1 -0
- package/dist/state.d.ts +21 -0
- package/dist/state.d.ts.map +1 -0
- package/dist/state.js +4 -0
- package/dist/state.js.map +1 -0
- package/dist/storage/driver.d.ts +38 -0
- package/dist/storage/driver.d.ts.map +1 -0
- package/dist/storage/driver.js +14 -0
- package/dist/storage/driver.js.map +1 -0
- package/dist/storage/factory.d.ts +4 -0
- package/dist/storage/factory.d.ts.map +1 -0
- package/dist/storage/factory.js +20 -0
- package/dist/storage/factory.js.map +1 -0
- package/dist/storage/index.d.ts +4 -0
- package/dist/storage/index.d.ts.map +1 -0
- package/dist/storage/index.js +4 -0
- package/dist/storage/index.js.map +1 -0
- package/dist/storage/local.d.ts +21 -0
- package/dist/storage/local.d.ts.map +1 -0
- package/dist/storage/local.js +88 -0
- package/dist/storage/local.js.map +1 -0
- package/dist/storage/s3.d.ts +29 -0
- package/dist/storage/s3.d.ts.map +1 -0
- package/dist/storage/s3.js +82 -0
- package/dist/storage/s3.js.map +1 -0
- package/dist/testing/fake-hub.d.ts +425 -0
- package/dist/testing/fake-hub.d.ts.map +1 -0
- package/dist/testing/fake-hub.js +1074 -0
- package/dist/testing/fake-hub.js.map +1 -0
- package/dist/testing/helpers.d.ts +124 -0
- package/dist/testing/helpers.d.ts.map +1 -0
- package/dist/testing/helpers.js +174 -0
- package/dist/testing/helpers.js.map +1 -0
- package/dist/testing/index.d.ts +12 -0
- package/dist/testing/index.d.ts.map +1 -0
- package/dist/testing/index.js +12 -0
- package/dist/testing/index.js.map +1 -0
- package/dist/testing/setup-origins.d.ts +21 -0
- package/dist/testing/setup-origins.d.ts.map +1 -0
- package/dist/testing/setup-origins.js +57 -0
- package/dist/testing/setup-origins.js.map +1 -0
- package/dist/testing/sso-helpers.d.ts +43 -0
- package/dist/testing/sso-helpers.d.ts.map +1 -0
- package/dist/testing/sso-helpers.js +159 -0
- package/dist/testing/sso-helpers.js.map +1 -0
- package/dist/tool-definition.d.ts +382 -0
- package/dist/tool-definition.d.ts.map +1 -0
- package/dist/tool-definition.js +46 -0
- package/dist/tool-definition.js.map +1 -0
- package/dist/util/index.d.ts +8 -0
- package/dist/util/index.d.ts.map +1 -0
- package/dist/util/index.js +8 -0
- package/dist/util/index.js.map +1 -0
- package/package.json +161 -0
- package/src/accounts/deletion.ts +389 -0
- package/src/accounts/erasure-log.ts +140 -0
- package/src/accounts/index.ts +5 -0
- package/src/accounts/mint-refusal.ts +67 -0
- package/src/accounts/signup-duplicate.ts +21 -0
- package/src/accounts/superadmin.ts +46 -0
- package/src/api/apikeys.ts +147 -0
- package/src/api/audit.ts +83 -0
- package/src/api/break-glass.ts +294 -0
- package/src/api/cli-auth.ts +308 -0
- package/src/api/create-api.ts +1403 -0
- package/src/api/demo-passes.ts +207 -0
- package/src/api/errors.ts +13 -0
- package/src/api/export.ts +482 -0
- package/src/api/files.ts +200 -0
- package/src/api/index.ts +29 -0
- package/src/api/invitations.ts +396 -0
- package/src/api/members.ts +617 -0
- package/src/api/onboarding.ts +50 -0
- package/src/api/openapi-doc.ts +34 -0
- package/src/api/projects.ts +788 -0
- package/src/api/scope-routes.ts +12 -0
- package/src/api/sso-connect.ts +246 -0
- package/src/api/sso-logout.ts +76 -0
- package/src/api/teams.ts +575 -0
- package/src/api/workspaces.ts +649 -0
- package/src/apikeys/index.ts +2 -0
- package/src/apikeys/peppers.ts +118 -0
- package/src/apikeys/service.ts +223 -0
- package/src/app.ts +228 -0
- package/src/audit/filters.ts +101 -0
- package/src/audit/index.ts +9 -0
- package/src/audit/service.ts +154 -0
- package/src/boot.ts +953 -0
- package/src/constant-time.ts +9 -0
- package/src/email/driver.ts +139 -0
- package/src/email/index.ts +26 -0
- package/src/email/shell.ts +201 -0
- package/src/email/templates.ts +184 -0
- package/src/entitlements/check.ts +435 -0
- package/src/entitlements/clock-skew.ts +58 -0
- package/src/entitlements/gate.ts +636 -0
- package/src/entitlements/hub-machine-token.ts +205 -0
- package/src/entitlements/hub-subject.ts +77 -0
- package/src/entitlements/index.ts +37 -0
- package/src/entitlements/poster.ts +265 -0
- package/src/entitlements/profiles.ts +303 -0
- package/src/entitlements/slot.ts +105 -0
- package/src/env.ts +456 -0
- package/src/files/http.ts +77 -0
- package/src/files/index.ts +4 -0
- package/src/files/serve.ts +115 -0
- package/src/files/service.ts +209 -0
- package/src/files/spool.ts +58 -0
- package/src/identity/better-auth.ts +1103 -0
- package/src/identity/demo-pass-plugin.ts +121 -0
- package/src/identity/demo-pass-rules.ts +229 -0
- package/src/identity/demo-pass.ts +390 -0
- package/src/identity/hub-grant.ts +704 -0
- package/src/identity/hub-jwt.ts +78 -0
- package/src/identity/hub-live-gate.ts +167 -0
- package/src/identity/hub-logout.ts +174 -0
- package/src/identity/hub-projection.ts +251 -0
- package/src/identity/hub-reconcile.ts +572 -0
- package/src/identity/hub-sso.ts +696 -0
- package/src/identity/hub-user-client.ts +554 -0
- package/src/identity/index.ts +56 -0
- package/src/identity/oauth-jwt.ts +128 -0
- package/src/identity/resolve-membership.ts +104 -0
- package/src/identity/signing-key.ts +164 -0
- package/src/index.ts +65 -0
- package/src/invitations/index.ts +1 -0
- package/src/invitations/service.ts +171 -0
- package/src/jobs/index.ts +25 -0
- package/src/jobs/pgboss.ts +594 -0
- package/src/jobs/timers.ts +287 -0
- package/src/logger.ts +67 -0
- package/src/mcp/errors.ts +111 -0
- package/src/mcp/http.ts +129 -0
- package/src/mcp/index.ts +28 -0
- package/src/mcp/inputs.ts +33 -0
- package/src/mcp/projects.ts +480 -0
- package/src/mcp/server.ts +174 -0
- package/src/mcp/teams.ts +97 -0
- package/src/mcp/tool-kit.ts +148 -0
- package/src/members/removal.ts +216 -0
- package/src/middleware/auth-body.ts +58 -0
- package/src/middleware/auth-context.ts +384 -0
- package/src/middleware/body-cap.ts +33 -0
- package/src/middleware/body-refusal.ts +23 -0
- package/src/middleware/cross-site.ts +122 -0
- package/src/middleware/hub-managed.ts +61 -0
- package/src/middleware/idempotency.ts +264 -0
- package/src/middleware/index.ts +33 -0
- package/src/middleware/json-depth.ts +74 -0
- package/src/middleware/no-store.ts +30 -0
- package/src/middleware/oauth-public.ts +54 -0
- package/src/middleware/rate-limit.ts +432 -0
- package/src/middleware/request-id.ts +54 -0
- package/src/middleware/scopes.ts +150 -0
- package/src/middleware/security-headers.ts +129 -0
- package/src/observability/index.ts +2 -0
- package/src/observability/metrics.ts +102 -0
- package/src/observability/otel.ts +79 -0
- package/src/pagination.ts +118 -0
- package/src/platform/edition.ts +140 -0
- package/src/platform/entitlements.ts +47 -0
- package/src/platform/events.ts +71 -0
- package/src/platform/index.ts +7 -0
- package/src/platform/local-identity.ts +90 -0
- package/src/platform/registry.ts +28 -0
- package/src/platform/usage.ts +13 -0
- package/src/platform/workspaces.ts +41 -0
- package/src/projects/access.ts +160 -0
- package/src/projects/index.ts +7 -0
- package/src/route-label.ts +29 -0
- package/src/routes/health.ts +79 -0
- package/src/routes/index.ts +2 -0
- package/src/routes/wellknown.ts +67 -0
- package/src/secret.ts +115 -0
- package/src/setup-transport.ts +46 -0
- package/src/state.ts +23 -0
- package/src/storage/driver.ts +48 -0
- package/src/storage/factory.ts +34 -0
- package/src/storage/index.ts +3 -0
- package/src/storage/local.ts +93 -0
- package/src/storage/s3.ts +109 -0
- package/src/testing/fake-hub.ts +1316 -0
- package/src/testing/helpers.ts +254 -0
- package/src/testing/index.ts +11 -0
- package/src/testing/setup-origins.ts +58 -0
- package/src/testing/sso-helpers.ts +178 -0
- package/src/tool-definition.ts +463 -0
- package/src/util/index.ts +17 -0
- package/test/fixtures/onboarding-backfill.sql +13 -0
- package/test/fixtures/operator-user-id-backfill.sql +18 -0
- package/test/host/dr-lib.sh +253 -0
- package/test/host/drizzle/0000_chassis_baseline.sql +438 -0
- package/test/host/drizzle/0001_last_owner_guard.sql +42 -0
- package/test/host/drizzle/meta/0000_snapshot.json +3566 -0
- package/test/host/drizzle/meta/_journal.json +20 -0
- package/test/host/drizzle.config.ts +17 -0
- package/test/host/identity.ts +41 -0
- package/test/host/minimal-host.ts +35 -0
- package/test/host/minimal-tool.ts +123 -0
- package/test/integration/account-oracle-uniformity.test.ts +284 -0
- package/test/integration/account-recovery.test.ts +236 -0
- package/test/integration/account.test.ts +120 -0
- package/test/integration/api-quota.test.ts +208 -0
- package/test/integration/apikey-expiry.test.ts +178 -0
- package/test/integration/apikey-pepper-rotation.test.ts +190 -0
- package/test/integration/boot-posture.test.ts +169 -0
- package/test/integration/break-glass.test.ts +484 -0
- package/test/integration/cloud-lifecycle.test.ts +233 -0
- package/test/integration/credential-boundaries.test.ts +195 -0
- package/test/integration/default-workspace.test.ts +414 -0
- package/test/integration/demo-pass-rules.test.ts +332 -0
- package/test/integration/demo-pass.test.ts +1222 -0
- package/test/integration/dr-verify-real-dump.test.ts +154 -0
- package/test/integration/edition.test.ts +868 -0
- package/test/integration/email-change.test.ts +394 -0
- package/test/integration/empty-tool.test.ts +267 -0
- package/test/integration/entitlements-null-hook.test.ts +187 -0
- package/test/integration/entitlements.test.ts +693 -0
- package/test/integration/export-entries.test.ts +321 -0
- package/test/integration/files.test.ts +320 -0
- package/test/integration/first-boot-claim.test.ts +131 -0
- package/test/integration/gdpr.test.ts +532 -0
- package/test/integration/helpers.ts +29 -0
- package/test/integration/hub-grant.test.ts +505 -0
- package/test/integration/hub-live-gate.test.ts +612 -0
- package/test/integration/hub-managed-membership.test.ts +559 -0
- package/test/integration/hub-org-selector.test.ts +281 -0
- package/test/integration/hub-reconcile.test.ts +646 -0
- package/test/integration/hub-sso.test.ts +704 -0
- package/test/integration/hub-teams.test.ts +771 -0
- package/test/integration/idempotency.test.ts +261 -0
- package/test/integration/invite-verification.test.ts +210 -0
- package/test/integration/last-owner-race.test.ts +263 -0
- package/test/integration/m9-fixes.test.ts +290 -0
- package/test/integration/mcp-projects.test.ts +463 -0
- package/test/integration/member-removal-race.test.ts +466 -0
- package/test/integration/member-removal.test.ts +794 -0
- package/test/integration/oauth-mcp.test.ts +477 -0
- package/test/integration/oauth-workspace.test.ts +371 -0
- package/test/integration/observability.test.ts +183 -0
- package/test/integration/onboarding.test.ts +268 -0
- package/test/integration/orphan-purge.test.ts +451 -0
- package/test/integration/pagination.test.ts +344 -0
- package/test/integration/platform-core.test.ts +305 -0
- package/test/integration/platform.test.ts +246 -0
- package/test/integration/pool-survives-restart.test.ts +80 -0
- package/test/integration/projects-cloud.test.ts +193 -0
- package/test/integration/projects.test.ts +1310 -0
- package/test/integration/security-fixes.test.ts +190 -0
- package/test/integration/setup-transport.test.ts +118 -0
- package/test/integration/signin-wall.test.ts +103 -0
- package/test/integration/signing-key-rotation.test.ts +349 -0
- package/test/integration/sso-helpers.ts +15 -0
- package/test/integration/sso-logout.test.ts +283 -0
- package/test/integration/teams-cloud.test.ts +194 -0
- package/test/integration/teams.test.ts +551 -0
- package/test/integration/timers.test.ts +377 -0
- package/test/integration/two-factor.test.ts +301 -0
- package/test/integration/usage-window.test.ts +161 -0
- package/test/integration/workspace-create-cloud.test.ts +523 -0
- package/test/integration/workspace-create.test.ts +510 -0
- package/test/integration/workspace-scoping.test.ts +530 -0
- package/test/integration/zz-adv-replay-owner.test.ts +205 -0
- package/test/unit/apikey-peppers.test.ts +110 -0
- package/test/unit/apikey-prefix.test.ts +34 -0
- package/test/unit/audit-filters.test.ts +135 -0
- package/test/unit/auth-origins.test.ts +92 -0
- package/test/unit/body-cap.test.ts +84 -0
- package/test/unit/clock-skew.test.ts +68 -0
- package/test/unit/cross-site.test.ts +166 -0
- package/test/unit/declared-credits.test.ts +13 -0
- package/test/unit/demo-pass-rules.test.ts +192 -0
- package/test/unit/email-driver.test.ts +127 -0
- package/test/unit/end-pool.test.ts +181 -0
- package/test/unit/entitlement-check.test.ts +492 -0
- package/test/unit/entitlement-gate.test.ts +1333 -0
- package/test/unit/entitlement-profiles.test.ts +289 -0
- package/test/unit/erasure-log.test.ts +72 -0
- package/test/unit/error-mapping.test.ts +89 -0
- package/test/unit/export-entries.test.ts +85 -0
- package/test/unit/export-sanitize.test.ts +27 -0
- package/test/unit/files-head-route.test.ts +155 -0
- package/test/unit/files-http.test.ts +97 -0
- package/test/unit/head-rewrite.test.ts +72 -0
- package/test/unit/hub-create-org.test.ts +181 -0
- package/test/unit/hub-jwt.test.ts +93 -0
- package/test/unit/hub-sso-params.test.ts +39 -0
- package/test/unit/hub-subject.test.ts +83 -0
- package/test/unit/idempotency-crypto.test.ts +45 -0
- package/test/unit/json-depth.test.ts +88 -0
- package/test/unit/logger-redaction.test.ts +66 -0
- package/test/unit/mcp-errors.test.ts +110 -0
- package/test/unit/mcp-inputs.test.ts +106 -0
- package/test/unit/mcp-project-hints.test.ts +26 -0
- package/test/unit/no-store.test.ts +49 -0
- package/test/unit/no-tool-migration-filename.test.ts +70 -0
- package/test/unit/observability.test.ts +64 -0
- package/test/unit/openapi-doc.test.ts +93 -0
- package/test/unit/rate-limit-consume-on.test.ts +76 -0
- package/test/unit/rate-limit.test.ts +39 -0
- package/test/unit/readyz-storage.test.ts +157 -0
- package/test/unit/request-quota.test.ts +146 -0
- package/test/unit/scopes.test.ts +66 -0
- package/test/unit/security-headers.test.ts +131 -0
- package/test/unit/serve-content-type.test.ts +74 -0
- package/test/unit/setup-transport.test.ts +29 -0
- package/test/unit/storage-local.test.ts +53 -0
- package/test/unit/superadmin.test.ts +32 -0
- package/test/unit/timers.test.ts +287 -0
- package/test/unit/tool-definition-guards.test.ts +166 -0
- package/test/unit/tool-entitlements-guard.test.ts +168 -0
- package/test/unit/usage-poster.test.ts +542 -0
- package/test/unit/usage-window.test.ts +48 -0
- package/test/unit/workspace-create-audit.test.ts +48 -0
package/src/boot.ts
ADDED
|
@@ -0,0 +1,953 @@
|
|
|
1
|
+
import { migrationStatus, runMigrations } from '@antasphere/chassis-db/migrate';
|
|
2
|
+
import { and, count, eq, isNull } from 'drizzle-orm';
|
|
3
|
+
import type { Hono } from 'hono';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
import type { z } from 'zod';
|
|
6
|
+
import { APIError } from 'better-auth/api';
|
|
7
|
+
import { AccountDeletionService, LastOwnerError } from './accounts/index.js';
|
|
8
|
+
import { ErasureLog } from './accounts/index.js';
|
|
9
|
+
import { createApiApp } from './api/create-api.js';
|
|
10
|
+
import { buildPepperRegistry } from './apikeys/index.js';
|
|
11
|
+
import { ApiKeyService } from './apikeys/index.js';
|
|
12
|
+
import { createApp } from './app.js';
|
|
13
|
+
import { hubSubjectResolver } from './entitlements/hub-subject.js';
|
|
14
|
+
import { AuditService } from './audit/index.js';
|
|
15
|
+
import { createEmailDriver, emailAssetsAt, setEmailAssets } from './email/index.js';
|
|
16
|
+
import {
|
|
17
|
+
buildChangeEmailConfirmEmail,
|
|
18
|
+
buildOtpEmail,
|
|
19
|
+
buildPasswordResetEmail,
|
|
20
|
+
buildVerifyEmailEmail
|
|
21
|
+
} from './email/index.js';
|
|
22
|
+
import { hubConfig, parseEnv } from './env.js';
|
|
23
|
+
import { createAuth, mcpResourceUrl, type AccountEvent } from './identity/index.js';
|
|
24
|
+
import { HubSsoService } from './identity/index.js';
|
|
25
|
+
import { HubGrantService } from './identity/index.js';
|
|
26
|
+
import { HubLogoutService } from './identity/index.js';
|
|
27
|
+
import { hubApiResource, HubUserClient } from './identity/index.js';
|
|
28
|
+
import { DEFAULT_FEDERATION_DIALS, HubOrgReconciler, type HubFederationDials } from './identity/index.js';
|
|
29
|
+
import { OauthJwtVerifier } from './identity/index.js';
|
|
30
|
+
import { preflightSigningKey } from './identity/index.js';
|
|
31
|
+
import { mcpRoutes } from './mcp/index.js';
|
|
32
|
+
import { wellKnownRoutes } from './routes/index.js';
|
|
33
|
+
import { instanceSettings, user as userTable, workspaceMembers, workspaces } from '@antasphere/chassis-db';
|
|
34
|
+
import { FileService } from './files/index.js';
|
|
35
|
+
import { createJobs, DEFAULT_USAGE_RETRY, PgBossUsageSink, type UsageRetry } from './jobs/index.js';
|
|
36
|
+
import {
|
|
37
|
+
assertToolEntitlements,
|
|
38
|
+
describeHubClockSkew,
|
|
39
|
+
EMPTY_TOOL_ENTITLEMENTS,
|
|
40
|
+
EntitlementProfiles,
|
|
41
|
+
hubClockSkewMs,
|
|
42
|
+
HubCreditCheck,
|
|
43
|
+
HubMachineToken,
|
|
44
|
+
HubUsagePoster
|
|
45
|
+
} from './entitlements/index.js';
|
|
46
|
+
import { createLogger } from './logger.js';
|
|
47
|
+
import { createStorageDriver } from './storage/index.js';
|
|
48
|
+
import { createRateLimiters, makeClientIp, rateLimit } from './middleware/index.js';
|
|
49
|
+
import { hstsValue } from './middleware/index.js';
|
|
50
|
+
import { createMetrics } from './observability/index.js';
|
|
51
|
+
import { createOtel } from './observability/index.js';
|
|
52
|
+
import { bindEditionSeams } from './platform/index.js';
|
|
53
|
+
import { AllowAllEntitlements } from './platform/index.js';
|
|
54
|
+
import { EventBus } from './platform/index.js';
|
|
55
|
+
import { LocalIdentityProvider } from './platform/index.js';
|
|
56
|
+
import { createRegistry } from './platform/index.js';
|
|
57
|
+
import { NoopUsageSink } from './platform/index.js';
|
|
58
|
+
import { WorkspaceService } from './platform/index.js';
|
|
59
|
+
import { clearGeneratedSetupToken, resolveAuthSecret, resolveSetupToken } from './util/index.js';
|
|
60
|
+
import { createRuntimeState } from './util/index.js';
|
|
61
|
+
import {
|
|
62
|
+
assertToolCopy,
|
|
63
|
+
assertToolIdentity,
|
|
64
|
+
type BootOverrides,
|
|
65
|
+
type BootResult,
|
|
66
|
+
type PlatformCore,
|
|
67
|
+
type ServiceCore,
|
|
68
|
+
type ToolDefinition
|
|
69
|
+
} from './tool-definition.js';
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* The boot sequence, in its locked order: env → logger → db probe → secret →
|
|
73
|
+
* migrations under advisory lock → app assembly → ready.
|
|
74
|
+
*/
|
|
75
|
+
export async function bootPlatform<
|
|
76
|
+
TEnvShape extends z.ZodRawShape,
|
|
77
|
+
TDomain,
|
|
78
|
+
TBuckets extends string,
|
|
79
|
+
TEvents,
|
|
80
|
+
TToolOverrides
|
|
81
|
+
>(
|
|
82
|
+
tool: ToolDefinition<TEnvShape, TDomain, TBuckets, TEvents, TToolOverrides>,
|
|
83
|
+
source: NodeJS.ProcessEnv = process.env,
|
|
84
|
+
overrides: BootOverrides<TToolOverrides> = {}
|
|
85
|
+
): Promise<BootResult<TEnvShape, TDomain, TEvents>> {
|
|
86
|
+
assertToolIdentity(tool.identity);
|
|
87
|
+
assertToolCopy(tool.copy);
|
|
88
|
+
const env = parseEnv<TEnvShape>(source, {
|
|
89
|
+
version: tool.runtime.version,
|
|
90
|
+
...(tool.env ? { extension: tool.env } : {})
|
|
91
|
+
});
|
|
92
|
+
const logger = createLogger(env);
|
|
93
|
+
const state = createRuntimeState();
|
|
94
|
+
|
|
95
|
+
logger.info({ serviceRole: env.SERVICE_ROLE, nodeEnv: env.NODE_ENV }, 'booting');
|
|
96
|
+
|
|
97
|
+
state.reason = 'connecting to database';
|
|
98
|
+
const db = tool.db(env.DATABASE_URL);
|
|
99
|
+
// createDb already survives an idle connection ended by the server (a
|
|
100
|
+
// Postgres restart); this listener is only what makes it visible in the log.
|
|
101
|
+
db.pool.on('error', (err) =>
|
|
102
|
+
logger.warn(
|
|
103
|
+
{ err },
|
|
104
|
+
'postgres ended an idle pooled connection; the pool opens a new one on the next query'
|
|
105
|
+
)
|
|
106
|
+
);
|
|
107
|
+
await db.pool.query('SELECT 1');
|
|
108
|
+
logger.info('database reachable');
|
|
109
|
+
|
|
110
|
+
const authSecret = await resolveAuthSecret(env.AUTH_SECRET, env.DATA_DIR, logger);
|
|
111
|
+
|
|
112
|
+
const migrationsFolder = tool.runtime.findMigrationsDir();
|
|
113
|
+
let migrationsPending = false;
|
|
114
|
+
/** The migration-stage reason /readyz must keep reporting (later stages overwrite state.reason). */
|
|
115
|
+
let readinessRefusal: string | null = null;
|
|
116
|
+
// Hash-based status FIRST, on every boot (OPS-6, PRDCT-1357): a database
|
|
117
|
+
// carrying migrations this image does not ship was migrated by a NEWER
|
|
118
|
+
// image — a rollback / `:next` behind `:latest`. drizzle's migrator would
|
|
119
|
+
// happily consider it up to date (it compares timestamps), and a
|
|
120
|
+
// count-based status called it "current". Refuse readiness instead: the
|
|
121
|
+
// schema is ahead of the code, and the newer version's share links and
|
|
122
|
+
// rows are exactly what the older code would corrupt.
|
|
123
|
+
const preStatus = await migrationStatus({ connectionString: env.DATABASE_URL, migrationsFolder });
|
|
124
|
+
if (preStatus.downgrade) {
|
|
125
|
+
migrationsPending = true;
|
|
126
|
+
state.reason =
|
|
127
|
+
`database is AHEAD of this image: ${preStatus.unknownApplied} applied migration(s) this ` +
|
|
128
|
+
`version does not ship (a downgrade, or edited migration files) — deploy the version that applied them`;
|
|
129
|
+
readinessRefusal = state.reason;
|
|
130
|
+
logger.error({ status: preStatus }, 'refusing readiness: ' + state.reason);
|
|
131
|
+
} else if (env.AUTO_MIGRATE) {
|
|
132
|
+
state.reason = 'applying migrations';
|
|
133
|
+
await runMigrations({
|
|
134
|
+
connectionString: env.DATABASE_URL,
|
|
135
|
+
migrationsFolder,
|
|
136
|
+
log: (msg) => logger.info({ scope: 'migrate' }, msg)
|
|
137
|
+
});
|
|
138
|
+
} else {
|
|
139
|
+
const status = preStatus;
|
|
140
|
+
migrationsPending = status.pending;
|
|
141
|
+
if (status.pending) {
|
|
142
|
+
state.reason = `migrations pending (${status.applied}/${status.onDisk} applied) and AUTO_MIGRATE=false`;
|
|
143
|
+
readinessRefusal = state.reason;
|
|
144
|
+
logger.error({ status }, 'refusing readiness: run migrations manually or set AUTO_MIGRATE=true');
|
|
145
|
+
// The process stays up (healthz green) but /readyz keeps failing.
|
|
146
|
+
} else {
|
|
147
|
+
logger.info({ status }, 'migrations current (AUTO_MIGRATE=false)');
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// R7 edition-flip guard (internal/federation.md): EDITION selects the identity
|
|
152
|
+
// binding, so flipping it under an already-set-up instance silently changes
|
|
153
|
+
// who can log in and where workspace ownership is asserted. Setup stamps
|
|
154
|
+
// the instance's edition; a boot whose env EDITION differs refuses to start
|
|
155
|
+
// unless the operator explicitly acknowledges with EDITION_CHANGE_ALLOWED=true
|
|
156
|
+
// (which re-stamps and proceeds). A fresh database (no instance row yet)
|
|
157
|
+
// boots under any edition — cloud instances start from a fresh DB. The
|
|
158
|
+
// stamp is compared whenever it is READABLE — including while migrations
|
|
159
|
+
// are pending (readiness is red then, but the API still serves, so a
|
|
160
|
+
// skipped guard would be one whole mis-bound boot). Only a schema that
|
|
161
|
+
// predates the stamp column (migration 0020, AUTO_MIGRATE=false) has no
|
|
162
|
+
// stamp to compare; the first migrated boot enforces the guard.
|
|
163
|
+
let stampedEdition: string | undefined;
|
|
164
|
+
try {
|
|
165
|
+
const [instanceRow] = await db.db
|
|
166
|
+
.select({ edition: instanceSettings.edition })
|
|
167
|
+
.from(instanceSettings)
|
|
168
|
+
.limit(1);
|
|
169
|
+
stampedEdition = instanceRow?.edition;
|
|
170
|
+
} catch (cause) {
|
|
171
|
+
if (!migrationsPending) throw cause;
|
|
172
|
+
// Pending migrations + unreadable stamp: the edition column (or the
|
|
173
|
+
// table) is not migrated in yet on this database — nothing to compare.
|
|
174
|
+
}
|
|
175
|
+
if (stampedEdition !== undefined && stampedEdition !== env.EDITION) {
|
|
176
|
+
if (!env.EDITION_CHANGE_ALLOWED) {
|
|
177
|
+
const message =
|
|
178
|
+
`refusing to boot: EDITION=${env.EDITION} but this instance was set up as ` +
|
|
179
|
+
`'${stampedEdition}'. An edition flip on a populated instance changes identity ` +
|
|
180
|
+
`semantics for existing users and workspaces. If this is intentional, set ` +
|
|
181
|
+
`EDITION_CHANGE_ALLOWED=true for ONE boot to re-stamp it (internal/federation.md).`;
|
|
182
|
+
logger.error({ stamped: stampedEdition, env: env.EDITION }, message);
|
|
183
|
+
await db.pool.end(); // clean refusal — no leaked pool for the caller
|
|
184
|
+
throw new Error(message);
|
|
185
|
+
}
|
|
186
|
+
// Pre-flight (EDIT-1/3/4, PRDCT-1356): the acknowledgement alone is not
|
|
187
|
+
// enough — a flip that migrates nothing leaves the cloud enforcement
|
|
188
|
+
// model not applying to pre-flip data, so the flip is REFUSED unless the
|
|
189
|
+
// instance is already in the shape the target edition requires.
|
|
190
|
+
const refuseFlip = async (reason: string) => {
|
|
191
|
+
const message =
|
|
192
|
+
`refusing the edition flip '${stampedEdition}' → '${env.EDITION}' (EDITION_CHANGE_ALLOWED=true): ` +
|
|
193
|
+
reason;
|
|
194
|
+
logger.error({ stamped: stampedEdition, env: env.EDITION }, message);
|
|
195
|
+
await db.pool.end();
|
|
196
|
+
throw new Error(message);
|
|
197
|
+
};
|
|
198
|
+
if (stampedEdition === 'cloud' && env.EDITION === 'oss') {
|
|
199
|
+
// The reverse flip would leave every hub-minted session, OAuth grant
|
|
200
|
+
// and hub-origin membership behind as unrevocable LOCAL credentials
|
|
201
|
+
// (EDIT-4). Rebuild from a fresh database instead.
|
|
202
|
+
await refuseFlip(
|
|
203
|
+
'a cloud instance cannot be flipped back to oss — hub-minted credentials would become ' +
|
|
204
|
+
'unrevocable local ones (internal/federation.md). Set up a fresh oss instance and restore data into it.'
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
if (env.EDITION === 'cloud') {
|
|
208
|
+
const [legacy] = await db.db
|
|
209
|
+
.select({ n: count() })
|
|
210
|
+
.from(workspaces)
|
|
211
|
+
.where(isNull(workspaces.centralAccountId));
|
|
212
|
+
if ((legacy?.n ?? 0) > 0) {
|
|
213
|
+
// EDIT-1: a workspace without a hub projection is outside every
|
|
214
|
+
// hub gate (HubLiveGate returns ok before any hub read) — the whole
|
|
215
|
+
// cloud enforcement model would never apply to it.
|
|
216
|
+
await refuseFlip(
|
|
217
|
+
`${legacy?.n} workspace(s) carry no hub projection (central_account_id IS NULL). ` +
|
|
218
|
+
'Project each one at the hub (or delete it) before flipping — cloud enforcement cannot cover an unprojected workspace.'
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
const [unverified] = await db.db
|
|
222
|
+
.select({ n: count() })
|
|
223
|
+
.from(userTable)
|
|
224
|
+
.where(eq(userTable.emailVerified, false));
|
|
225
|
+
if ((unverified?.n ?? 0) > 0) {
|
|
226
|
+
// EDIT-3: hub-only login links the trusted provider onto a VERIFIED
|
|
227
|
+
// local email only; every unverified user would be locked out.
|
|
228
|
+
await refuseFlip(
|
|
229
|
+
`${unverified?.n} user(s) have an unverified email and would be locked out under hub-only login. ` +
|
|
230
|
+
'Have them verify (or remove them) before flipping.'
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
await db.db.update(instanceSettings).set({ edition: env.EDITION });
|
|
235
|
+
logger.warn(
|
|
236
|
+
{ from: stampedEdition, to: env.EDITION },
|
|
237
|
+
'edition re-stamped (EDITION_CHANGE_ALLOWED=true) — unset the flag again after this boot'
|
|
238
|
+
);
|
|
239
|
+
} else if (env.EDITION_CHANGE_ALLOWED) {
|
|
240
|
+
// A leftover acknowledgement must never rot silently: while the flag
|
|
241
|
+
// stays set, the guard above is disarmed — any future EDITION change
|
|
242
|
+
// would re-stamp without refusing. Say so on every boot.
|
|
243
|
+
logger.warn(
|
|
244
|
+
'EDITION_CHANGE_ALLOWED=true but no edition change is pending — unset it: while it stays ' +
|
|
245
|
+
'set, the R7 edition-flip guard is disarmed and a future EDITION change re-stamps ' +
|
|
246
|
+
'without refusal (internal/federation.md)'
|
|
247
|
+
);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// The first-boot claim credential (PRDCT-1347): SETUP_TOKEN, or a token
|
|
251
|
+
// generated into the data volume while the instance is unclaimed. A boot
|
|
252
|
+
// that cannot read the stamp (pending migrations, pre-stamp schema) is
|
|
253
|
+
// treated as unclaimed — generating a token an already-set-up instance
|
|
254
|
+
// never needs is harmless; skipping it on an unclaimed one is the hole.
|
|
255
|
+
const setupToken = await resolveSetupToken(
|
|
256
|
+
env.SETUP_TOKEN,
|
|
257
|
+
env.DATA_DIR,
|
|
258
|
+
stampedEdition !== undefined,
|
|
259
|
+
logger
|
|
260
|
+
);
|
|
261
|
+
|
|
262
|
+
// OAuth signing-key preflight (ADR 023): one decrypt of the key the jwt
|
|
263
|
+
// plugin would sign with. A rotated AUTH_SECRET leaves the stored JWKS
|
|
264
|
+
// private key undecryptable and — with no rotationInterval — never
|
|
265
|
+
// replaced, so every token mint would 500 while authorize still 302s.
|
|
266
|
+
// Refuse to boot instead, with the remedies in the message. Skipped only
|
|
267
|
+
// while migrations are pending (the jwks table may not exist yet, and the
|
|
268
|
+
// instance already refuses readiness in that state).
|
|
269
|
+
if (!migrationsPending) {
|
|
270
|
+
state.reason = 'verifying OAuth signing key';
|
|
271
|
+
const preflight = await preflightSigningKey(db.db, authSecret);
|
|
272
|
+
if (!preflight.ok) {
|
|
273
|
+
logger.fatal({ kid: preflight.kid }, preflight.reason);
|
|
274
|
+
// Fail-fast means clean-fast: release the pool this boot opened so the
|
|
275
|
+
// refusing process (and the integration test that pins it) never
|
|
276
|
+
// strands an open connection.
|
|
277
|
+
await db.pool.end().catch(() => {});
|
|
278
|
+
throw new Error('OAuth signing-key preflight failed — see the preceding log line');
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// Email first: whether it delivers decides whether email-OTP login,
|
|
283
|
+
// self-serve password reset, and self-serve email change exist.
|
|
284
|
+
const email = overrides.email ?? createEmailDriver(env, logger);
|
|
285
|
+
// the mails' band and mark are served from the dashboard's static folder
|
|
286
|
+
setEmailAssets(emailAssetsAt(env.PUBLIC_BASE_URL));
|
|
287
|
+
|
|
288
|
+
// Audit is built before auth so credential events on Better-Auth-native
|
|
289
|
+
// routes (password reset/change) can be recorded through it.
|
|
290
|
+
const audit = new AuditService(db.db, logger);
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Best-effort audit for a Better-Auth credential event. Credential events
|
|
294
|
+
* are USER-level, so the row lands in every workspace the user is an
|
|
295
|
+
* ACTIVE member of (ADR 014) — each workspace's trail records its own
|
|
296
|
+
* members' password/2FA changes, and a workspace the user was deactivated
|
|
297
|
+
* from records nothing; a single-workspace instance gets exactly one row,
|
|
298
|
+
* as before.
|
|
299
|
+
*/
|
|
300
|
+
const auditAccountEvent = async (event: AccountEvent, userId: string) => {
|
|
301
|
+
const rows = await db.db
|
|
302
|
+
.select({ workspaceId: workspaceMembers.workspaceId })
|
|
303
|
+
.from(workspaceMembers)
|
|
304
|
+
.where(and(eq(workspaceMembers.userId, userId), eq(workspaceMembers.isActive, true)));
|
|
305
|
+
for (const row of rows) {
|
|
306
|
+
await audit.write({
|
|
307
|
+
workspaceId: row.workspaceId,
|
|
308
|
+
principal: { userId, via: 'session' },
|
|
309
|
+
action: `user.${event}`,
|
|
310
|
+
resourceType: 'user',
|
|
311
|
+
resourceId: userId
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
};
|
|
315
|
+
|
|
316
|
+
// Account deletion: shared by the self-service /delete-user hooks below
|
|
317
|
+
// and the admin DELETE /members/{id} route. No storage dep — files are
|
|
318
|
+
// workspace data and survive the delete (ADR 006). The connection string
|
|
319
|
+
// feeds the dedicated advisory-lock clients that serialize last-owner
|
|
320
|
+
// removals (the migration-lock pattern).
|
|
321
|
+
const erasureLog = new ErasureLog(env.DATA_DIR, logger, authSecret);
|
|
322
|
+
const accountDeletion = new AccountDeletionService(db.db, audit, logger, env.DATABASE_URL, erasureLog);
|
|
323
|
+
|
|
324
|
+
// The event bus exists before auth so identity-layer hooks can publish:
|
|
325
|
+
// `user.created` fires from Better Auth's databaseHooks.user.create.after,
|
|
326
|
+
// covering EVERY account entrance by construction (setup, invitation
|
|
327
|
+
// accept, collaborator claim, future SSO JIT) — never from individual
|
|
328
|
+
// call sites.
|
|
329
|
+
const events = new EventBus<TEvents>(logger);
|
|
330
|
+
|
|
331
|
+
// The cloud edition's hub SSO binding (internal/federation.md): constructed
|
|
332
|
+
// iff EDITION=cloud — hubConfig() is the single switch, so an oss boot
|
|
333
|
+
// provably instantiates no SSO surface. Everything hub-SSO hangs off this
|
|
334
|
+
// one object: the genericOAuth provider inside createAuth, the per-login
|
|
335
|
+
// projection after-hook, and the login-scope wrap around the auth mount.
|
|
336
|
+
const hub = hubConfig(env);
|
|
337
|
+
// THE seam constant: the RFC 8707 `resource` for BOTH the SSO code
|
|
338
|
+
// exchange and every refresh — `<hub>/mcp`, so the tokens the tool holds
|
|
339
|
+
// are HUB-audienced and callable at hub /api/v1 as the user. Flipping it
|
|
340
|
+
// to null (if the hub ever accepts opaque tokens) is this one line.
|
|
341
|
+
const hubTokenResource = hub ? hubApiResource(hub.issuerUrl) : null;
|
|
342
|
+
const hubSso = hub
|
|
343
|
+
? new HubSsoService({
|
|
344
|
+
db: db.db,
|
|
345
|
+
logger,
|
|
346
|
+
issuerUrl: hub.issuerUrl,
|
|
347
|
+
clientId: hub.clientId,
|
|
348
|
+
clientSecret: hub.clientSecret,
|
|
349
|
+
// Own resource URL — always derived from PUBLIC_BASE_URL, never
|
|
350
|
+
// configured. Pins H3 connect tokens only; login tokens are
|
|
351
|
+
// hub-audienced via tokenResource.
|
|
352
|
+
resourceUrl: mcpResourceUrl(env.PUBLIC_BASE_URL),
|
|
353
|
+
tokenResource: hubTokenResource,
|
|
354
|
+
publicBaseUrl: env.PUBLIC_BASE_URL
|
|
355
|
+
})
|
|
356
|
+
: undefined;
|
|
357
|
+
|
|
358
|
+
// ONE source for the never-trusted origins: Better Auth here, the cross-site
|
|
359
|
+
// guard of the API app below.
|
|
360
|
+
const untrustedOrigins = tool.api.untrustedOrigins?.(env) ?? [];
|
|
361
|
+
|
|
362
|
+
// Identity + seams: local defaults, swappable at this one point.
|
|
363
|
+
// The product's name and footer line in the four mails Better Auth sends (the fifth, the invitation, is api/invitations.ts).
|
|
364
|
+
const brand = { name: tool.identity.displayName, tagline: tool.copy.mail.tagline };
|
|
365
|
+
const auth = createAuth({
|
|
366
|
+
db: db.db,
|
|
367
|
+
env,
|
|
368
|
+
authSecret,
|
|
369
|
+
// The tool's values for the two generic-by-parameter points of the
|
|
370
|
+
// identity module: the published OAuth scope list (order included) and
|
|
371
|
+
// the origins that are never trusted (e.g. a tool's viewer origin,
|
|
372
|
+
// where author-controlled script runs, PRDCT-1352). None = nothing installed.
|
|
373
|
+
oauthScopes: tool.scopes.oauth,
|
|
374
|
+
untrustedOrigins,
|
|
375
|
+
hubSso,
|
|
376
|
+
// Demo sign-in (the demo pass spec): the redeem endpoint's deps, only while the switch is on.
|
|
377
|
+
...(env.DEMO_SIGN_IN
|
|
378
|
+
? { demoPass: { db: db.db, emailDomains: env.DEMO_SIGN_IN_EMAIL_DOMAINS, audit } }
|
|
379
|
+
: {}),
|
|
380
|
+
onAccountEvent: auditAccountEvent,
|
|
381
|
+
onUserCreated: (user) =>
|
|
382
|
+
events.chassisView().emit('user.created', { userId: user.id, email: user.email }),
|
|
383
|
+
beforeUserDelete: async (userId) => {
|
|
384
|
+
try {
|
|
385
|
+
await accountDeletion.beforeUserDelete(userId);
|
|
386
|
+
} catch (cause) {
|
|
387
|
+
// Better Auth turns APIError into the route's 400 response.
|
|
388
|
+
if (cause instanceof LastOwnerError) {
|
|
389
|
+
throw new APIError('BAD_REQUEST', { message: cause.message });
|
|
390
|
+
}
|
|
391
|
+
throw cause;
|
|
392
|
+
}
|
|
393
|
+
},
|
|
394
|
+
afterUserDelete: (user) => accountDeletion.afterUserDelete(user),
|
|
395
|
+
...(email.delivers
|
|
396
|
+
? {
|
|
397
|
+
sendOtp: async ({ email: to, otp, type }: { email: string; otp: string; type: string }) => {
|
|
398
|
+
const msg = buildOtpEmail({ brand, otp, type });
|
|
399
|
+
await email.send({ to, ...msg });
|
|
400
|
+
},
|
|
401
|
+
sendResetPassword: async ({ email: to, url }: { email: string; url: string; token: string }) => {
|
|
402
|
+
// Better Auth builds `url` as the API callback that 302s to the
|
|
403
|
+
// dashboard reset page with ?token — mail it verbatim.
|
|
404
|
+
const msg = buildPasswordResetEmail({
|
|
405
|
+
brand,
|
|
406
|
+
resetUrl: url,
|
|
407
|
+
expiresAt: new Date(Date.now() + 3600_000)
|
|
408
|
+
});
|
|
409
|
+
await email.send({ to, ...msg });
|
|
410
|
+
},
|
|
411
|
+
// Two-leg email change, leg 1 (verified users only): confirmation
|
|
412
|
+
// to the OLD address. Mail Better Auth's `url` verbatim.
|
|
413
|
+
sendChangeEmailConfirmation: async ({
|
|
414
|
+
email: to,
|
|
415
|
+
newEmail,
|
|
416
|
+
url
|
|
417
|
+
}: {
|
|
418
|
+
email: string;
|
|
419
|
+
newEmail: string;
|
|
420
|
+
url: string;
|
|
421
|
+
token: string;
|
|
422
|
+
}) => {
|
|
423
|
+
const msg = buildChangeEmailConfirmEmail({
|
|
424
|
+
brand,
|
|
425
|
+
newEmail,
|
|
426
|
+
confirmUrl: url,
|
|
427
|
+
expiresAt: new Date(Date.now() + 3600_000)
|
|
428
|
+
});
|
|
429
|
+
await email.send({ to, ...msg });
|
|
430
|
+
},
|
|
431
|
+
// Final leg of every email change: verification to the NEW address.
|
|
432
|
+
sendVerificationEmail: async ({
|
|
433
|
+
email: to,
|
|
434
|
+
url
|
|
435
|
+
}: {
|
|
436
|
+
email: string;
|
|
437
|
+
url: string;
|
|
438
|
+
token: string;
|
|
439
|
+
}) => {
|
|
440
|
+
const msg = buildVerifyEmailEmail({
|
|
441
|
+
brand,
|
|
442
|
+
verifyUrl: url,
|
|
443
|
+
expiresAt: new Date(Date.now() + 3600_000)
|
|
444
|
+
});
|
|
445
|
+
await email.send({ to, ...msg });
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
: {})
|
|
449
|
+
});
|
|
450
|
+
|
|
451
|
+
// OPS-3 (PRDCT-1357): replay the erasure tombstone before the app serves.
|
|
452
|
+
// A restore from an older dump brings erased users back; the tombstone
|
|
453
|
+
// (kept outside the dump, carried forward by restore.sh) says who must
|
|
454
|
+
// stay gone. Runs through the same Better Auth cascade every GDPR surface
|
|
455
|
+
// uses (memberships, sessions, accounts go with the user) and lands an
|
|
456
|
+
// instance-level audit row per replay. Skipped while migrations are
|
|
457
|
+
// pending (the tables may not exist; readiness is red anyway).
|
|
458
|
+
//
|
|
459
|
+
// PRDCT-1809: the replay runs under EVERY last-owner guard the subject is
|
|
460
|
+
// subject to (the same locks + re-check the HTTP surfaces use; the 0009
|
|
461
|
+
// trigger backstops), so a sole-owner subject is refused BEFORE any row is
|
|
462
|
+
// touched — Better Auth's cascade drops the account rows before the user
|
|
463
|
+
// row, and an unguarded refusal left a half-erased owner. A refused
|
|
464
|
+
// tombstone is a FAIL-CLOSED verdict: the subject asked to be forgotten
|
|
465
|
+
// and the product cannot decide who inherits their workspace, so the boot
|
|
466
|
+
// audits the refusal, keeps /readyz red with the reason, and CLOSES the
|
|
467
|
+
// service surface (state.closed → 503 everywhere but the probes) until an
|
|
468
|
+
// operator promotes another owner and restarts (docs/operations/
|
|
469
|
+
// backup-restore.md "Erasures hold across a restore"). Readiness alone is
|
|
470
|
+
// not a closure: the API keeps serving under a red /readyz.
|
|
471
|
+
if (!migrationsPending) {
|
|
472
|
+
state.reason = 'replaying erasure tombstones';
|
|
473
|
+
const authCtx = await auth.$context;
|
|
474
|
+
const { replayed, refused } = await erasureLog.replay(
|
|
475
|
+
async (userId) => {
|
|
476
|
+
const [row] = await db.db
|
|
477
|
+
.select({ id: userTable.id })
|
|
478
|
+
.from(userTable)
|
|
479
|
+
.where(eq(userTable.id, userId))
|
|
480
|
+
.limit(1);
|
|
481
|
+
return row !== undefined;
|
|
482
|
+
},
|
|
483
|
+
(userId) =>
|
|
484
|
+
accountDeletion.withAllLastOwnerGuards(userId, async () => {
|
|
485
|
+
await authCtx.internalAdapter.deleteUser(userId);
|
|
486
|
+
await authCtx.internalAdapter.deleteUserSessions(userId);
|
|
487
|
+
})
|
|
488
|
+
);
|
|
489
|
+
for (const userId of replayed) {
|
|
490
|
+
await audit.write({
|
|
491
|
+
workspaceId: null,
|
|
492
|
+
principal: null, // → actorVia 'system'
|
|
493
|
+
action: 'user.erasure_replayed',
|
|
494
|
+
resourceType: 'user',
|
|
495
|
+
resourceId: userId,
|
|
496
|
+
metadata: { reason: 'tombstoned user present after boot (restore from an older backup)' }
|
|
497
|
+
});
|
|
498
|
+
}
|
|
499
|
+
for (const r of refused) {
|
|
500
|
+
const lastOwner = r.cause instanceof LastOwnerError;
|
|
501
|
+
await audit.write({
|
|
502
|
+
workspaceId: null,
|
|
503
|
+
principal: null, // → actorVia 'system'
|
|
504
|
+
action: 'user.erasure_replay_refused',
|
|
505
|
+
resourceType: 'user',
|
|
506
|
+
resourceId: r.userId,
|
|
507
|
+
metadata: {
|
|
508
|
+
reason: lastOwner
|
|
509
|
+
? 'tombstoned user is the last active owner of a workspace; the instance refuses to serve until another owner is promoted'
|
|
510
|
+
: 'tombstoned user could not be re-erased; the instance refuses to serve',
|
|
511
|
+
erasedAt: r.erasedAt
|
|
512
|
+
}
|
|
513
|
+
});
|
|
514
|
+
}
|
|
515
|
+
if (refused.length > 0) {
|
|
516
|
+
// The public reason stays COARSE: /readyz is unauthenticated and the
|
|
517
|
+
// subject asked to be forgotten, so their id lives in the log line and
|
|
518
|
+
// the audit row only. The remedy names the cause the operator faces.
|
|
519
|
+
const lastOwner = refused.filter((r) => r.cause instanceof LastOwnerError).length;
|
|
520
|
+
const other = refused.length - lastOwner;
|
|
521
|
+
const remedy = [
|
|
522
|
+
lastOwner > 0
|
|
523
|
+
? `${lastOwner} subject(s) present again after a restore as the last active owner of a workspace — promote another member to owner, then restart`
|
|
524
|
+
: null,
|
|
525
|
+
other > 0
|
|
526
|
+
? `${other} re-erasure(s) failed — see the container log, fix the cause, then restart`
|
|
527
|
+
: null
|
|
528
|
+
]
|
|
529
|
+
.filter((s): s is string => s !== null)
|
|
530
|
+
.join('; ');
|
|
531
|
+
state.reason =
|
|
532
|
+
`refusing to serve: ${refused.length} erasure tombstone(s) could not be replayed: ${remedy} ` +
|
|
533
|
+
'(docs/operations/backup-restore.md, audit action user.erasure_replay_refused)';
|
|
534
|
+
readinessRefusal = state.reason;
|
|
535
|
+
state.closed = state.reason;
|
|
536
|
+
logger.error(
|
|
537
|
+
{ refused: refused.map((r) => ({ userId: r.userId, lastOwner: r.cause instanceof LastOwnerError })) },
|
|
538
|
+
state.reason
|
|
539
|
+
);
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
// The live user-scoped federation stack (cloud only, internal/federation.md):
|
|
544
|
+
// each user's OWN hub grant (offline_access refresh token on the account
|
|
545
|
+
// row) → as-the-user `GET /orgs` reads → the org reconciler that the
|
|
546
|
+
// login pass, the live gate, and the unknown-workspace miss hook all
|
|
547
|
+
// share. Constructed AFTER createAuth because the grant encrypts tokens
|
|
548
|
+
// with Better Auth's own key material ((await auth.$context).secretConfig
|
|
549
|
+
// — equal to authSecret under our string-secret config; the fallback
|
|
550
|
+
// keeps the read total if a future Better Auth reshapes the context).
|
|
551
|
+
const hubDials: HubFederationDials = { ...DEFAULT_FEDERATION_DIALS, ...overrides.hubDials };
|
|
552
|
+
const hubGrant = hub
|
|
553
|
+
? new HubGrantService({
|
|
554
|
+
db: db.db,
|
|
555
|
+
issuerUrl: hub.issuerUrl,
|
|
556
|
+
clientId: hub.clientId,
|
|
557
|
+
clientSecret: hub.clientSecret,
|
|
558
|
+
tokenResource: hubTokenResource,
|
|
559
|
+
key: async () =>
|
|
560
|
+
((await auth.$context) as unknown as { secretConfig?: string }).secretConfig ?? authSecret,
|
|
561
|
+
connectionString: env.DATABASE_URL,
|
|
562
|
+
logger,
|
|
563
|
+
dials: {
|
|
564
|
+
accessSkewMs: hubDials.accessSkewMs,
|
|
565
|
+
tokenTimeoutMs: hubDials.tokenTimeoutMs,
|
|
566
|
+
// The RFC 7662 probe rides the token-endpoint budget: same hub,
|
|
567
|
+
// same hop, and it only runs after an unanswered presentation.
|
|
568
|
+
introspectTimeoutMs: hubDials.tokenTimeoutMs,
|
|
569
|
+
lockWatchdogMs: hubDials.lockWatchdogMs
|
|
570
|
+
}
|
|
571
|
+
})
|
|
572
|
+
: undefined;
|
|
573
|
+
// RP-initiated logout against the hub (SL-2, cloud only): builds the
|
|
574
|
+
// end-session URL /sso/logout returns. Same key seam as the grant store
|
|
575
|
+
// (it only decrypts if a future Better Auth starts encrypting id_tokens);
|
|
576
|
+
// discovery fetches ride the tight orgs timeout dial.
|
|
577
|
+
const hubLogout = hub
|
|
578
|
+
? new HubLogoutService({
|
|
579
|
+
db: db.db,
|
|
580
|
+
issuerUrl: hub.issuerUrl,
|
|
581
|
+
publicBaseUrl: env.PUBLIC_BASE_URL,
|
|
582
|
+
key: async () =>
|
|
583
|
+
((await auth.$context) as unknown as { secretConfig?: string }).secretConfig ?? authSecret,
|
|
584
|
+
logger,
|
|
585
|
+
discoveryTimeoutMs: hubDials.orgsTimeoutMs
|
|
586
|
+
})
|
|
587
|
+
: undefined;
|
|
588
|
+
// The ONE as-the-user hub client: the reconciler's GET /orgs reads and
|
|
589
|
+
// POST /workspaces' organization creation both present the user's own
|
|
590
|
+
// grant through it (never a second, hand-rolled token path).
|
|
591
|
+
const hubUserClient =
|
|
592
|
+
hub && hubGrant
|
|
593
|
+
? new HubUserClient({
|
|
594
|
+
grant: hubGrant,
|
|
595
|
+
issuerUrl: hub.issuerUrl,
|
|
596
|
+
logger,
|
|
597
|
+
timeoutMs: hubDials.orgsTimeoutMs,
|
|
598
|
+
// A creation is one deliberate human act, not an identity-path
|
|
599
|
+
// read: it rides the token-endpoint budget, not the tight one.
|
|
600
|
+
createTimeoutMs: hubDials.tokenTimeoutMs
|
|
601
|
+
})
|
|
602
|
+
: undefined;
|
|
603
|
+
const hubReconciler =
|
|
604
|
+
hub && hubGrant && hubUserClient
|
|
605
|
+
? new HubOrgReconciler({
|
|
606
|
+
db: db.db,
|
|
607
|
+
client: hubUserClient,
|
|
608
|
+
audit,
|
|
609
|
+
logger,
|
|
610
|
+
dials: hubDials,
|
|
611
|
+
onMembershipRemoval: tool.membershipRemoval,
|
|
612
|
+
...(overrides.hubNow ? { now: overrides.hubNow } : {})
|
|
613
|
+
})
|
|
614
|
+
: undefined;
|
|
615
|
+
// The SSO login's fail-closed step 3 (assertLogin) runs THIS reconciler.
|
|
616
|
+
if (hubSso && hubReconciler) hubSso.bindReconciler(hubReconciler);
|
|
617
|
+
// The reconciler's CACHED pass (its TTL + retry throttle, so no caller can
|
|
618
|
+
// hammer the hub), run by two callers: the unknown-workspace retry of all
|
|
619
|
+
// three credential kinds (one pass + one re-lookup; the requested id is
|
|
620
|
+
// ignored), and the zero-membership /me (the live gate never reaches a
|
|
621
|
+
// principal-less session). undefined on oss.
|
|
622
|
+
const cachedReconcile = hubReconciler
|
|
623
|
+
? async (userId: string) => {
|
|
624
|
+
await hubReconciler.reconcile(userId);
|
|
625
|
+
}
|
|
626
|
+
: undefined;
|
|
627
|
+
|
|
628
|
+
// Jobs: pg-boss (durable queue). The registry's UsageSink emits into it;
|
|
629
|
+
// the worker side hands batches to the downstream sink (no-op locally).
|
|
630
|
+
// A tool job may need a service built further down (the storage driver is
|
|
631
|
+
// probed later); it reads the domain through this holder at RUN time (nightly).
|
|
632
|
+
const domainRef: { current: TDomain | null } = { current: null };
|
|
633
|
+
// The billing rail's tool → hub machine channel (the spec, §6), cloud
|
|
634
|
+
// only: one client_credentials token on the instance's own hub client
|
|
635
|
+
// (scope usage:write), shared by the usage poster (the queue's downstream)
|
|
636
|
+
// and the entitlement profile reads. An oss boot constructs none of it.
|
|
637
|
+
const hubMachineToken =
|
|
638
|
+
hub && hubTokenResource
|
|
639
|
+
? new HubMachineToken({
|
|
640
|
+
issuerUrl: hub.issuerUrl,
|
|
641
|
+
clientId: hub.clientId,
|
|
642
|
+
clientSecret: hub.clientSecret,
|
|
643
|
+
resource: hubTokenResource,
|
|
644
|
+
logger,
|
|
645
|
+
timeoutMs: hubDials.tokenTimeoutMs
|
|
646
|
+
})
|
|
647
|
+
: undefined;
|
|
648
|
+
const hubUsagePoster = hubMachineToken
|
|
649
|
+
? new HubUsagePoster({ issuerUrl: hub!.issuerUrl, token: hubMachineToken, logger })
|
|
650
|
+
: undefined;
|
|
651
|
+
// The usage queue's retry budget and its hold (PRDCT-2635): stated once,
|
|
652
|
+
// shared by the sink that sends and the held queue that re-drives.
|
|
653
|
+
const usageRetry: UsageRetry = { ...DEFAULT_USAGE_RETRY, ...overrides.usageRetry };
|
|
654
|
+
const jobs = await createJobs(
|
|
655
|
+
env,
|
|
656
|
+
db.db,
|
|
657
|
+
db.pool,
|
|
658
|
+
logger,
|
|
659
|
+
overrides.usageDownstream ?? hubUsagePoster ?? new NoopUsageSink(),
|
|
660
|
+
auth,
|
|
661
|
+
audit,
|
|
662
|
+
tool.jobs?.({ env, db: db.db, logger, getTool: () => domainRef.current }) ?? [],
|
|
663
|
+
usageRetry
|
|
664
|
+
);
|
|
665
|
+
// The tool's billing-rail declarations (slot 22), asserted once here: a
|
|
666
|
+
// route that meters an action the price book does not know, or a limit no
|
|
667
|
+
// tier values, stops the boot naming the route.
|
|
668
|
+
// The slot runs before `services`; an actor hook of an anonymous surface
|
|
669
|
+
// reads the domain lazily through `getTool()` at request time (PRDCT-2634).
|
|
670
|
+
const entitlements =
|
|
671
|
+
tool.entitlements?.(env, { db: db.db, getTool: () => domainRef.current }) ?? EMPTY_TOOL_ENTITLEMENTS;
|
|
672
|
+
assertToolEntitlements(entitlements);
|
|
673
|
+
// The credit check at the hub (§7 steps 3 and 4), cloud only: the price
|
|
674
|
+
// of a metered request against the organization's balance, before the
|
|
675
|
+
// handler runs. Its metrics join /metrics beside the poster's.
|
|
676
|
+
const hubCreditCheck =
|
|
677
|
+
hub && hubMachineToken
|
|
678
|
+
? new HubCreditCheck({
|
|
679
|
+
issuerUrl: hub.issuerUrl,
|
|
680
|
+
token: hubMachineToken,
|
|
681
|
+
logger,
|
|
682
|
+
dials: overrides.entitlementCheckDials
|
|
683
|
+
})
|
|
684
|
+
: undefined;
|
|
685
|
+
const entitlementCloud =
|
|
686
|
+
hub && hubMachineToken && hubCreditCheck
|
|
687
|
+
? {
|
|
688
|
+
profiles: new EntitlementProfiles({
|
|
689
|
+
issuerUrl: hub.issuerUrl,
|
|
690
|
+
token: hubMachineToken,
|
|
691
|
+
logger,
|
|
692
|
+
dials: overrides.entitlementDials
|
|
693
|
+
}),
|
|
694
|
+
// The upgrade link of a plan refusal when the hub's answer carries
|
|
695
|
+
// no upgrade page of its own (an older hub): the hub root.
|
|
696
|
+
upgradeUrl: hub.issuerUrl,
|
|
697
|
+
credits: hubCreditCheck,
|
|
698
|
+
// The reported user of an event is the HUB user (the SSO `sub` on
|
|
699
|
+
// the account row), never the tool's local id; a user with no hub
|
|
700
|
+
// link (the operator) reports null. Cached per user for five
|
|
701
|
+
// minutes: an upload burst is one read, not one per event.
|
|
702
|
+
hubSubject: hubSubjectResolver(db.db)
|
|
703
|
+
}
|
|
704
|
+
: undefined;
|
|
705
|
+
|
|
706
|
+
// The clock check (PRDCT-2644), cloud only and off the boot's critical
|
|
707
|
+
// path: the hub judges every usage event's date against ITS clock and the
|
|
708
|
+
// poster against this instance's, so a clock more than five minutes ahead
|
|
709
|
+
// of the hub's stamps events the hub would refuse and the poster cannot
|
|
710
|
+
// see it. One read of the hub's Date header, a warning when they disagree
|
|
711
|
+
// beyond the forward bound, nothing refused.
|
|
712
|
+
if (hub) {
|
|
713
|
+
void hubClockSkewMs(hub.issuerUrl).then((skewMs) => {
|
|
714
|
+
if (skewMs === null) return;
|
|
715
|
+
const sentence = describeHubClockSkew(skewMs);
|
|
716
|
+
if (sentence) logger.warn({ skewMs }, `hub clock: ${sentence}`);
|
|
717
|
+
});
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
// The edition split (internal/federation.md): the local defaults below are the
|
|
721
|
+
// oss binding, passed through bindEditionSeams — the ONE place EDITION
|
|
722
|
+
// decides what the registry gets. oss returns them untouched; cloud
|
|
723
|
+
// rebinds the identity descriptor and wires the live principal gate off
|
|
724
|
+
// the reconciler built above. Entitlements stay LOCAL on both editions.
|
|
725
|
+
const seams = bindEditionSeams(
|
|
726
|
+
hub,
|
|
727
|
+
{
|
|
728
|
+
identity: new LocalIdentityProvider(
|
|
729
|
+
auth,
|
|
730
|
+
db.db,
|
|
731
|
+
Boolean(env.GOOGLE_CLIENT_ID),
|
|
732
|
+
email.delivers,
|
|
733
|
+
email.delivers, // self-serve password reset needs a delivering email driver
|
|
734
|
+
email.delivers, // self-serve email change needs one too
|
|
735
|
+
cachedReconcile
|
|
736
|
+
),
|
|
737
|
+
entitlements: new AllowAllEntitlements({
|
|
738
|
+
maxFileSizeMb: env.MAX_FILE_SIZE_MB,
|
|
739
|
+
apiRequestsPerMinute: env.API_RATE_LIMIT_PER_MINUTE,
|
|
740
|
+
apiRequestsBurstPerSecond: env.API_RATE_LIMIT_BURST
|
|
741
|
+
}),
|
|
742
|
+
usage: new PgBossUsageSink(jobs.boss, logger, usageRetry)
|
|
743
|
+
},
|
|
744
|
+
logger,
|
|
745
|
+
{ db: db.db, reconciler: hubReconciler }
|
|
746
|
+
);
|
|
747
|
+
const registry = createRegistry({
|
|
748
|
+
identity: seams.identity,
|
|
749
|
+
entitlements: seams.entitlements,
|
|
750
|
+
usage: seams.usage,
|
|
751
|
+
events,
|
|
752
|
+
workspaces: new WorkspaceService(db.db)
|
|
753
|
+
});
|
|
754
|
+
|
|
755
|
+
// API-key pepper registry (ADR 008): version 1 is the AUTH_SECRET-derived
|
|
756
|
+
// pepper; API_KEY_PEPPERS adds/pins versions so keys survive rotations.
|
|
757
|
+
const pepperRegistry = buildPepperRegistry(authSecret, env.API_KEY_PEPPERS);
|
|
758
|
+
if (env.API_KEY_PEPPERS !== undefined && !pepperRegistry.v1Pinned) {
|
|
759
|
+
logger.warn(
|
|
760
|
+
'API_KEY_PEPPERS is set but does not pin version 1 — version-1 API keys still ' +
|
|
761
|
+
'depend on the live AUTH_SECRET; pin `1:<historical secret>` before rotating it ' +
|
|
762
|
+
'(internal/security-runbooks.md).'
|
|
763
|
+
);
|
|
764
|
+
}
|
|
765
|
+
const apiKeys = new ApiKeyService(db.db, pepperRegistry, tool.identity.apiKeyPrefix, cachedReconcile);
|
|
766
|
+
// Storage: probed at boot — /readyz stays red on an unwritable volume.
|
|
767
|
+
state.reason = 'probing storage';
|
|
768
|
+
const storage = createStorageDriver(env);
|
|
769
|
+
await storage.healthcheck();
|
|
770
|
+
logger.info({ driver: storage.name }, 'storage writable');
|
|
771
|
+
const fileService = new FileService(db.db, storage, join(env.DATA_DIR, 'tmp'), logger);
|
|
772
|
+
|
|
773
|
+
// The tool's domain services, built ONCE, here: after the storage probe
|
|
774
|
+
// and BEFORE the rate limiters, which is where the first service a job
|
|
775
|
+
// reads lazily was always built (a nightly purge firing during boot finds
|
|
776
|
+
// it from this line on). A tool subscribes to the event bus inside this
|
|
777
|
+
// call (no request can arrive before boot returns, so a subscription made
|
|
778
|
+
// here precedes every emission).
|
|
779
|
+
//
|
|
780
|
+
// ONE step swapped sides in the move, and this is it. At base
|
|
781
|
+
// (`apps/server/src/boot.ts@461db68`) the tool's services STRADDLED
|
|
782
|
+
// `createRateLimiters`: the form and form-upload services were built before
|
|
783
|
+
// it (and the purge cell filled), the collaborator service after it. Here
|
|
784
|
+
// `createRateLimiters` runs after the whole `tool.services` slot. The one
|
|
785
|
+
// consequence: a Redis connect failure from the limiters now surfaces after
|
|
786
|
+
// the domain services exist rather than between them; what moved ahead of
|
|
787
|
+
// the connect is the collaborator service's constructor, pure field
|
|
788
|
+
// assignment with no I/O. The limiters are not put back first because the
|
|
789
|
+
// job pollers are live from `createJobs` above, well before this line (at
|
|
790
|
+
// base too): with the limiters first, the form-upload purge cell would be
|
|
791
|
+
// empty during the limiter connect, where at base it was already filled,
|
|
792
|
+
// and a purge job picked up in that window would run as a no-op and
|
|
793
|
+
// complete. One slot cannot sit on both sides of the limiters; this side is
|
|
794
|
+
// the unobservable one.
|
|
795
|
+
const serviceCore: ServiceCore<TEnvShape, TEvents, TToolOverrides> = {
|
|
796
|
+
db: db.db,
|
|
797
|
+
env,
|
|
798
|
+
logger,
|
|
799
|
+
storage,
|
|
800
|
+
fileService,
|
|
801
|
+
pepperRegistry,
|
|
802
|
+
email,
|
|
803
|
+
events,
|
|
804
|
+
authSecret,
|
|
805
|
+
timers: jobs.timers,
|
|
806
|
+
overrides: overrides.tool
|
|
807
|
+
};
|
|
808
|
+
const domain = tool.services(serviceCore);
|
|
809
|
+
domainRef.current = domain;
|
|
810
|
+
|
|
811
|
+
const limiters = await createRateLimiters<TBuckets>(env, logger, tool.rateLimiters);
|
|
812
|
+
const core: PlatformCore<TEnvShape, TBuckets, TEvents, TToolOverrides> = { ...serviceCore, limiters };
|
|
813
|
+
|
|
814
|
+
// OAuth-bearer verification: local JWKS (we minted the token) + live
|
|
815
|
+
// membership re-check. One instance, shared by the API and /mcp gates.
|
|
816
|
+
const oauthJwt = new OauthJwtVerifier(auth, db.db, env.PUBLIC_BASE_URL, cachedReconcile);
|
|
817
|
+
|
|
818
|
+
const api = createApiApp({
|
|
819
|
+
db: db.db,
|
|
820
|
+
env,
|
|
821
|
+
auth,
|
|
822
|
+
registry,
|
|
823
|
+
logger,
|
|
824
|
+
apiKeys,
|
|
825
|
+
audit,
|
|
826
|
+
email,
|
|
827
|
+
limiters,
|
|
828
|
+
storage,
|
|
829
|
+
fileService,
|
|
830
|
+
oauthJwt,
|
|
831
|
+
authSecret,
|
|
832
|
+
setupToken,
|
|
833
|
+
clearGeneratedSetupToken: () => clearGeneratedSetupToken(env.DATA_DIR),
|
|
834
|
+
accountDeletion,
|
|
835
|
+
tool,
|
|
836
|
+
domain,
|
|
837
|
+
untrustedOrigins,
|
|
838
|
+
hubSso,
|
|
839
|
+
// Cloud only: /sso/cli-connect stores the H3 offline grant through it.
|
|
840
|
+
hubGrant,
|
|
841
|
+
// Cloud only: /sso/logout builds the hub end-session leg through it.
|
|
842
|
+
hubLogout,
|
|
843
|
+
// Cloud only (internal/federation.md): the post-resolution LIVE hub gate —
|
|
844
|
+
// reconcile-as-the-user + suspension/revocation/grant-death verdicts —
|
|
845
|
+
// run by authContext on every authenticated request. undefined on oss.
|
|
846
|
+
principalGate: seams.principalGate,
|
|
847
|
+
entitlements,
|
|
848
|
+
entitlementCloud,
|
|
849
|
+
// Cloud only (PRDCT-2443): POST /workspaces creates the organization at
|
|
850
|
+
// the hub AS THE CALLER, then forces their reconcile. undefined on oss —
|
|
851
|
+
// the route creates locally there.
|
|
852
|
+
workspaceCloud:
|
|
853
|
+
hub && hubGrant && hubUserClient && hubReconciler
|
|
854
|
+
? {
|
|
855
|
+
createOrg: overrides.hubCreateOrg ?? ((userId, name) => hubUserClient.createOrg(userId, name)),
|
|
856
|
+
forceReconcile: (userId) => hubReconciler.forceReconcile(userId),
|
|
857
|
+
hasHubLink: (userId) => hubGrant.hasStoredGrant(userId),
|
|
858
|
+
manageUrl: hub.issuerUrl
|
|
859
|
+
}
|
|
860
|
+
: undefined,
|
|
861
|
+
// Cloud only: the refusal page's hint (/me.hubDenied), read from the
|
|
862
|
+
// reconciler's memory of the last definitive hub list. undefined on oss.
|
|
863
|
+
hubDeniedOrgs: hubReconciler ? (userId) => hubReconciler.deniedOrgs(userId) : undefined,
|
|
864
|
+
// Cloud only: a person re-seated at the hub is re-admitted on a bare
|
|
865
|
+
// zero-state read. undefined on oss.
|
|
866
|
+
reconcileForZeroState: cachedReconcile
|
|
867
|
+
});
|
|
868
|
+
|
|
869
|
+
// The tool's public routes (e.g. a share-link viewer, Phase 4,
|
|
870
|
+
// ADR 012): anonymous, mounted in app.ts's public-route slot.
|
|
871
|
+
const publicRoutes = tool.app?.publicRoutes?.({ ...core, overrides: undefined }, domain);
|
|
872
|
+
|
|
873
|
+
// Observability: tracing (exporterless = zero phone-home) + Prometheus.
|
|
874
|
+
const otel = await createOtel(env, logger, { serviceName: tool.identity.otelServiceName });
|
|
875
|
+
const metrics = createMetrics(db.db, jobs.boss);
|
|
876
|
+
// The counters of the seams that talk to the hub join the app registry so
|
|
877
|
+
// a degraded hub, dying grants, a rail that rejects everything or usage
|
|
878
|
+
// held past its retry budget is visible on /metrics (the poster's are
|
|
879
|
+
// cloud only: oss never builds one).
|
|
880
|
+
for (const metric of [
|
|
881
|
+
...(hubReconciler?.promMetrics ?? []),
|
|
882
|
+
...(hubGrant?.promMetrics ?? []),
|
|
883
|
+
...(hubUsagePoster?.promMetrics ?? []),
|
|
884
|
+
...(hubCreditCheck?.promMetrics ?? []),
|
|
885
|
+
...jobs.promMetrics
|
|
886
|
+
]) {
|
|
887
|
+
metrics.registry.registerMetric(metric);
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
// MCP tools call the instance's own API in-process, forwarding the caller's
|
|
891
|
+
// bearer — MCP is just another API client. The root app does not exist yet
|
|
892
|
+
// at this point, so the fetcher closes over a holder filled right after
|
|
893
|
+
// createApp (before any request can arrive).
|
|
894
|
+
const rootApp: { current: Hono | null } = { current: null };
|
|
895
|
+
let cachedName: string | null = null;
|
|
896
|
+
const mcp = mcpRoutes({
|
|
897
|
+
publicBaseUrl: env.PUBLIC_BASE_URL,
|
|
898
|
+
version: env.APP_VERSION,
|
|
899
|
+
fetchApi: async (path, init) => {
|
|
900
|
+
if (!rootApp.current) throw new Error('mcp fetchApi called before app assembly');
|
|
901
|
+
return rootApp.current.request(path, init);
|
|
902
|
+
},
|
|
903
|
+
// The /mcp transport gate resolves the credential with NO workspace
|
|
904
|
+
// selector (default-org perspective): the per-tool-call `workspace`
|
|
905
|
+
// argument is mapped to the header at the in-process /api/v1 re-entry,
|
|
906
|
+
// where the real per-request selection happens.
|
|
907
|
+
resolveOauthJwt: (token) => oauthJwt.resolve(token, null),
|
|
908
|
+
resolveApiKey: (token) => apiKeys.resolve(token, null),
|
|
909
|
+
isApiKeyToken: apiKeys.isToken,
|
|
910
|
+
limiter: rateLimit(limiters.mcp, makeClientIp(env.TRUST_PROXY)),
|
|
911
|
+
tool: tool.mcp,
|
|
912
|
+
identity: tool.identity.mcp,
|
|
913
|
+
instanceName: async () => {
|
|
914
|
+
if (cachedName) return cachedName;
|
|
915
|
+
const [row] = await db.db.select({ name: instanceSettings.name }).from(instanceSettings).limit(1);
|
|
916
|
+
cachedName = row?.name ?? tool.identity.displayName;
|
|
917
|
+
return cachedName;
|
|
918
|
+
}
|
|
919
|
+
});
|
|
920
|
+
|
|
921
|
+
const app = await createApp({
|
|
922
|
+
logger,
|
|
923
|
+
state,
|
|
924
|
+
publicDir: tool.runtime.publicDir,
|
|
925
|
+
displayName: tool.identity.displayName,
|
|
926
|
+
api,
|
|
927
|
+
mcp,
|
|
928
|
+
wellKnown: wellKnownRoutes({ auth, publicBaseUrl: env.PUBLIC_BASE_URL, oauthScopes: tool.scopes.oauth }),
|
|
929
|
+
publicRoutes,
|
|
930
|
+
metricsMiddleware: metrics.middleware,
|
|
931
|
+
metricsRoutes: metrics.routes(env.METRICS_TOKEN),
|
|
932
|
+
otelMiddleware: otel.middleware,
|
|
933
|
+
// The boot probe above runs once. /readyz re-runs this one so a store
|
|
934
|
+
// that goes away later (unmounted volume, wiped /data after a failed
|
|
935
|
+
// restore, revoked S3 credentials) stops reporting ready.
|
|
936
|
+
probeStorage: () => storage.healthcheck(),
|
|
937
|
+
hsts: hstsValue(env),
|
|
938
|
+
rootMiddleware: tool.app?.rootMiddleware?.(env),
|
|
939
|
+
cspFrameSrc: tool.app?.cspFrameSrc?.(env) ?? [],
|
|
940
|
+
cspImgSrc: tool.app?.cspImgSrc?.(env) ?? []
|
|
941
|
+
});
|
|
942
|
+
rootApp.current = app;
|
|
943
|
+
|
|
944
|
+
if (!migrationsPending && !readinessRefusal) {
|
|
945
|
+
state.ready = true;
|
|
946
|
+
state.reason = '';
|
|
947
|
+
} else if (readinessRefusal) {
|
|
948
|
+
// Keep the migration-stage verdict on /readyz, not the last probe's label.
|
|
949
|
+
state.reason = readinessRefusal;
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
return { app, env, logger, state, db, auth, registry, jobs, email, otel, authSecret, tool: domain };
|
|
953
|
+
}
|