@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
|
@@ -0,0 +1,466 @@
|
|
|
1
|
+
import { afterAll, beforeAll, describe, expect, it } from 'vitest';
|
|
2
|
+
import type { PoolClient } from 'pg';
|
|
3
|
+
import type { StartedPostgreSqlContainer } from '@testcontainers/postgresql';
|
|
4
|
+
import { workspaceMembers } from '@antasphere/chassis-db';
|
|
5
|
+
import {
|
|
6
|
+
createDatabase,
|
|
7
|
+
createTestApp,
|
|
8
|
+
extractCookie,
|
|
9
|
+
readJson,
|
|
10
|
+
startPostgres,
|
|
11
|
+
type TestApp
|
|
12
|
+
} from './helpers.js';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* A removal against a concurrent add (the end-to-end verification, finding 5).
|
|
16
|
+
*
|
|
17
|
+
* An add (a project grant, a team seat, a demo pass) read the membership
|
|
18
|
+
* active outside any transaction, then waited on another lock; a removal
|
|
19
|
+
* committed meanwhile; the add then wrote its right on the now inactive row,
|
|
20
|
+
* and the right came back with the person at their re-invitation. Two changes
|
|
21
|
+
* close it: every add re-reads the membership inside its transaction FOR
|
|
22
|
+
* SHARE (`holdLiveMembership`), and the removal runs `deleteMembershipGrants`
|
|
23
|
+
* a second time after the row is off.
|
|
24
|
+
*
|
|
25
|
+
* Each arm is made deterministic by a dedicated pg client holding a lock, and
|
|
26
|
+
* by waiting until the expected number of backends is blocked on a lock
|
|
27
|
+
* (`waitForWaiters`) rather than on a timer. Whatever the add answers, the end
|
|
28
|
+
* state is the same: nothing of what it added survives the removal, and the
|
|
29
|
+
* re-invitation brings the person back with nothing.
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
const PASSWORD = 'a-long-demo-pass-password-1';
|
|
33
|
+
const ORIGIN = 'http://localhost:3000';
|
|
34
|
+
const OWNER = { email: 'owner@example.com', name: 'Olive Owner', password: PASSWORD };
|
|
35
|
+
|
|
36
|
+
let container: StartedPostgreSqlContainer;
|
|
37
|
+
let app: TestApp;
|
|
38
|
+
let workspaceId = '';
|
|
39
|
+
let ownerCookie = '';
|
|
40
|
+
let adminCookie = '';
|
|
41
|
+
|
|
42
|
+
let ipCounter = 0;
|
|
43
|
+
const nextIp = () => `10.89.${Math.floor(ipCounter / 250)}.${(ipCounter++ % 250) + 1}`;
|
|
44
|
+
|
|
45
|
+
const send = (method: string, path: string, who: { cookie?: string } = {}, body?: unknown) =>
|
|
46
|
+
app.app.request(`/api/v1${path}`, {
|
|
47
|
+
method,
|
|
48
|
+
headers: {
|
|
49
|
+
'x-forwarded-for': nextIp(),
|
|
50
|
+
origin: ORIGIN,
|
|
51
|
+
...(who.cookie ? { cookie: who.cookie } : {}),
|
|
52
|
+
...(body !== undefined ? { 'content-type': 'application/json' } : {})
|
|
53
|
+
},
|
|
54
|
+
...(body !== undefined ? { body: JSON.stringify(body) } : {})
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
const signIn = async (email: string) => {
|
|
58
|
+
const res = await send('POST', '/auth/sign-in/email', {}, { email, password: PASSWORD });
|
|
59
|
+
expect(res.status).toBe(200);
|
|
60
|
+
return extractCookie(res);
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
async function addMember(
|
|
64
|
+
email: string,
|
|
65
|
+
role: 'admin' | 'member'
|
|
66
|
+
): Promise<{ userId: string; memberId: string; cookie: string }> {
|
|
67
|
+
const created = await app.auth.api.signUpEmail({ body: { email, password: PASSWORD, name: email } });
|
|
68
|
+
const [m] = await app.db.db
|
|
69
|
+
.insert(workspaceMembers)
|
|
70
|
+
.values({ workspaceId, userId: created.user.id, role, origin: 'local' })
|
|
71
|
+
.returning({ id: workspaceMembers.id });
|
|
72
|
+
return { userId: created.user.id, memberId: m!.id, cookie: await signIn(email) };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** Resolves once at least `n` backends of this database wait on a lock (10 s at most). */
|
|
76
|
+
async function waitForWaiters(n: number): Promise<void> {
|
|
77
|
+
const deadline = Date.now() + 10_000;
|
|
78
|
+
for (;;) {
|
|
79
|
+
const { rows } = await app.db.pool.query<{ n: number }>(
|
|
80
|
+
`SELECT count(*)::int AS n FROM pg_stat_activity
|
|
81
|
+
WHERE datname = current_database() AND wait_event_type = 'Lock'`
|
|
82
|
+
);
|
|
83
|
+
if (rows[0]!.n >= n) return;
|
|
84
|
+
if (Date.now() > deadline) throw new Error(`expected ${n} backends waiting on a lock, saw ${rows[0]!.n}`);
|
|
85
|
+
await new Promise((r) => setTimeout(r, 25));
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Resolves once a backend of this database waits on a lock while running a
|
|
91
|
+
* statement that starts with `prefix` (10 s at most): a wait on one named
|
|
92
|
+
* statement, not on whichever lock happens to be queued.
|
|
93
|
+
*/
|
|
94
|
+
async function waitForStatementWaiting(prefix: string): Promise<void> {
|
|
95
|
+
const deadline = Date.now() + 10_000;
|
|
96
|
+
for (;;) {
|
|
97
|
+
const { rows } = await app.db.pool.query<{ n: number }>(
|
|
98
|
+
`SELECT count(*)::int AS n FROM pg_stat_activity
|
|
99
|
+
WHERE datname = current_database() AND wait_event_type = 'Lock' AND query LIKE $1`,
|
|
100
|
+
[`${prefix}%`]
|
|
101
|
+
);
|
|
102
|
+
if (rows[0]!.n >= 1) return;
|
|
103
|
+
if (Date.now() > deadline) throw new Error(`expected a backend waiting on a lock in "${prefix}…"`);
|
|
104
|
+
await new Promise((r) => setTimeout(r, 25));
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** The database's deadlock counter, read after the statistics had time to flush. */
|
|
109
|
+
async function deadlockCount(): Promise<number> {
|
|
110
|
+
const { rows } = await app.db.pool.query<{ deadlocks: string }>(
|
|
111
|
+
`SELECT deadlocks FROM pg_stat_database WHERE datname = current_database()`
|
|
112
|
+
);
|
|
113
|
+
return Number(rows[0]!.deadlocks);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** Resolves with the promise's value once settled, or with `pending` after `ms`. */
|
|
117
|
+
const settledWithin = <T>(p: Promise<T>, ms: number): Promise<T | 'pending'> =>
|
|
118
|
+
Promise.race([p, new Promise<'pending'>((r) => setTimeout(() => r('pending'), ms))]);
|
|
119
|
+
|
|
120
|
+
async function withLockClient<T>(run: (client: PoolClient) => Promise<T>): Promise<T> {
|
|
121
|
+
const client = await app.db.pool.connect();
|
|
122
|
+
try {
|
|
123
|
+
return await run(client);
|
|
124
|
+
} finally {
|
|
125
|
+
await client.query('ROLLBACK').catch(() => undefined);
|
|
126
|
+
client.release();
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** The owner re-invites the person, who accepts: the way back a removal leaves open. */
|
|
131
|
+
async function reinvite(email: string, cookie: string): Promise<void> {
|
|
132
|
+
const invited = await send('POST', '/invitations', { cookie: ownerCookie }, { email, role: 'member' });
|
|
133
|
+
expect(invited.status).toBe(201);
|
|
134
|
+
const token = ((await readJson(invited)).acceptUrl as string).split('/invite/')[1]!;
|
|
135
|
+
const accept = await send('POST', '/invitations/accept', { cookie }, { token });
|
|
136
|
+
expect(accept.status).toBe(200);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const grantRows = async (memberId: string, projectId: string) =>
|
|
140
|
+
(
|
|
141
|
+
await app.db.pool.query(`SELECT role FROM project_members WHERE member_id = $1 AND project_id = $2`, [
|
|
142
|
+
memberId,
|
|
143
|
+
projectId
|
|
144
|
+
])
|
|
145
|
+
).rows;
|
|
146
|
+
|
|
147
|
+
const seatRows = async (memberId: string, teamId: string) =>
|
|
148
|
+
(
|
|
149
|
+
await app.db.pool.query(`SELECT 1 FROM workspace_team_members WHERE member_id = $1 AND team_id = $2`, [
|
|
150
|
+
memberId,
|
|
151
|
+
teamId
|
|
152
|
+
])
|
|
153
|
+
).rows;
|
|
154
|
+
|
|
155
|
+
async function newProject(name: string): Promise<string> {
|
|
156
|
+
const res = await send('POST', '/projects', { cookie: ownerCookie }, { name });
|
|
157
|
+
expect(res.status).toBe(201);
|
|
158
|
+
return (await readJson(res)).id as string;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
async function expectAddOutcome(res: Response, code: string): Promise<number> {
|
|
162
|
+
if (res.status !== 201) {
|
|
163
|
+
const body = await readJson(res);
|
|
164
|
+
expect({ status: res.status, code: body?.error?.code }).toEqual({ status: 404, code });
|
|
165
|
+
}
|
|
166
|
+
return res.status;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
beforeAll(async () => {
|
|
170
|
+
container = await startPostgres();
|
|
171
|
+
app = await createTestApp(await createDatabase(container, 'member_removal_race'), { DEMO_SIGN_IN: 'true' });
|
|
172
|
+
const setup = await send(
|
|
173
|
+
'POST',
|
|
174
|
+
'/setup',
|
|
175
|
+
{},
|
|
176
|
+
{ setupToken: 'integration-test-setup-token', instanceName: 'Demo', owner: OWNER }
|
|
177
|
+
);
|
|
178
|
+
expect(setup.status).toBe(201);
|
|
179
|
+
workspaceId = (await readJson(setup)).workspaceId;
|
|
180
|
+
ownerCookie = await signIn(OWNER.email);
|
|
181
|
+
adminCookie = (await addMember('admin@example.com', 'admin')).cookie;
|
|
182
|
+
}, 240_000);
|
|
183
|
+
|
|
184
|
+
afterAll(async () => {
|
|
185
|
+
await app?.stop();
|
|
186
|
+
await container?.stop();
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
describe('a removal against a concurrent add', () => {
|
|
190
|
+
it('a project grant added while the project row is held: the add sees the removal, no grant survives the re-invitation', async () => {
|
|
191
|
+
const x = await addMember('grant-late@example.com', 'member');
|
|
192
|
+
const projectId = await newProject('Race A');
|
|
193
|
+
|
|
194
|
+
const addRes = await withLockClient(async (lock) => {
|
|
195
|
+
await lock.query('BEGIN');
|
|
196
|
+
await lock.query('SELECT 1 FROM projects WHERE id = $1 FOR UPDATE', [projectId]);
|
|
197
|
+
// The add passes its first read and waits on the project row (`whileLive`).
|
|
198
|
+
const add = send(
|
|
199
|
+
'POST',
|
|
200
|
+
`/projects/${projectId}/members`,
|
|
201
|
+
{ cookie: adminCookie },
|
|
202
|
+
{
|
|
203
|
+
userId: x.userId,
|
|
204
|
+
role: 'editor'
|
|
205
|
+
}
|
|
206
|
+
);
|
|
207
|
+
await waitForWaiters(1);
|
|
208
|
+
// The removal does not need the project row: it commits meanwhile.
|
|
209
|
+
const removed = await send('POST', `/members/${x.memberId}/remove`, { cookie: ownerCookie });
|
|
210
|
+
expect(removed.status).toBe(200);
|
|
211
|
+
await lock.query('COMMIT');
|
|
212
|
+
return add;
|
|
213
|
+
});
|
|
214
|
+
const status = await expectAddOutcome(addRes, 'member_not_found');
|
|
215
|
+
console.log('arm A (project row held): add answered', status);
|
|
216
|
+
expect(await grantRows(x.memberId, projectId)).toEqual([]);
|
|
217
|
+
|
|
218
|
+
await reinvite('grant-late@example.com', x.cookie);
|
|
219
|
+
expect(await grantRows(x.memberId, projectId)).toEqual([]);
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
it('a project grant that holds the membership before the removal switches it off: the second pass takes it', async () => {
|
|
223
|
+
const x = await addMember('grant-early@example.com', 'member');
|
|
224
|
+
const projectId = await newProject('Race B');
|
|
225
|
+
|
|
226
|
+
const [addRes, removed] = await withLockClient(async (lock) => {
|
|
227
|
+
// An uncommitted grant for (X, P) holds the unique slot: the add takes
|
|
228
|
+
// the project and membership rows FOR SHARE, then waits on the slot.
|
|
229
|
+
await lock.query('BEGIN');
|
|
230
|
+
await lock.query(
|
|
231
|
+
`INSERT INTO project_members (project_id, member_id, role, added_by) VALUES ($1, $2, 'viewer', NULL)`,
|
|
232
|
+
[projectId, x.memberId]
|
|
233
|
+
);
|
|
234
|
+
const add = send(
|
|
235
|
+
'POST',
|
|
236
|
+
`/projects/${projectId}/members`,
|
|
237
|
+
{ cookie: adminCookie },
|
|
238
|
+
{
|
|
239
|
+
userId: x.userId,
|
|
240
|
+
role: 'editor'
|
|
241
|
+
}
|
|
242
|
+
);
|
|
243
|
+
await waitForWaiters(1);
|
|
244
|
+
// The removal's first pass sees no grant; its update then waits for the
|
|
245
|
+
// add's share lock on the membership row.
|
|
246
|
+
const remove = Promise.resolve(send('POST', `/members/${x.memberId}/remove`, { cookie: ownerCookie }));
|
|
247
|
+
// Settled by then only when nothing made the removal wait (the red run).
|
|
248
|
+
await settledWithin(remove, 1500);
|
|
249
|
+
await lock.query('ROLLBACK');
|
|
250
|
+
return Promise.all([add, remove]);
|
|
251
|
+
});
|
|
252
|
+
expect(removed.status).toBe(200);
|
|
253
|
+
const status = await expectAddOutcome(addRes, 'member_not_found');
|
|
254
|
+
console.log('arm B (membership held by the add): add answered', status);
|
|
255
|
+
expect(await grantRows(x.memberId, projectId)).toEqual([]);
|
|
256
|
+
|
|
257
|
+
await reinvite('grant-early@example.com', x.cookie);
|
|
258
|
+
expect(await grantRows(x.memberId, projectId)).toEqual([]);
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
it('a team seat added while the team row is held: the add sees the removal, no seat survives the re-invitation', async () => {
|
|
262
|
+
const x = await addMember('seat@example.com', 'member');
|
|
263
|
+
const team = await send('POST', '/teams', { cookie: ownerCookie }, { name: 'Race team' });
|
|
264
|
+
expect(team.status).toBe(201);
|
|
265
|
+
const teamId = (await readJson(team)).id as string;
|
|
266
|
+
|
|
267
|
+
const addRes = await withLockClient(async (lock) => {
|
|
268
|
+
await lock.query('BEGIN');
|
|
269
|
+
await lock.query('SELECT 1 FROM workspace_teams WHERE id = $1 FOR UPDATE', [teamId]);
|
|
270
|
+
const add = send('POST', `/teams/${teamId}/members`, { cookie: adminCookie }, { userId: x.userId });
|
|
271
|
+
await waitForWaiters(1);
|
|
272
|
+
const removed = await send('POST', `/members/${x.memberId}/remove`, { cookie: ownerCookie });
|
|
273
|
+
expect(removed.status).toBe(200);
|
|
274
|
+
await lock.query('COMMIT');
|
|
275
|
+
return add;
|
|
276
|
+
});
|
|
277
|
+
const status = await expectAddOutcome(addRes, 'member_not_found');
|
|
278
|
+
console.log('arm C (team row held): add answered', status);
|
|
279
|
+
expect(await seatRows(x.memberId, teamId)).toEqual([]);
|
|
280
|
+
|
|
281
|
+
await reinvite('seat@example.com', x.cookie);
|
|
282
|
+
expect(await seatRows(x.memberId, teamId)).toEqual([]);
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
it('a demo pass minted while the membership row is held: no live pass survives the removal or the re-invitation', async () => {
|
|
286
|
+
const x = await addMember('pass@example.com', 'member');
|
|
287
|
+
const livePasses = async () =>
|
|
288
|
+
(
|
|
289
|
+
await app.db.pool.query(
|
|
290
|
+
`SELECT id FROM demo_passes WHERE user_id = $1 AND workspace_id = $2 AND revoked_at IS NULL`,
|
|
291
|
+
[x.userId, workspaceId]
|
|
292
|
+
)
|
|
293
|
+
).rows;
|
|
294
|
+
|
|
295
|
+
const [mintRes, removed] = await withLockClient(async (lock) => {
|
|
296
|
+
await lock.query('BEGIN');
|
|
297
|
+
await lock.query('SELECT 1 FROM workspace_members WHERE id = $1 FOR UPDATE', [x.memberId]);
|
|
298
|
+
// The mint queues first on the membership row (its FOR SHARE), the
|
|
299
|
+
// removal's update second; the lock goes to them in that order.
|
|
300
|
+
const mint = send('POST', '/demo/passes', { cookie: ownerCookie }, { email: 'pass@example.com' });
|
|
301
|
+
await waitForWaiters(1);
|
|
302
|
+
const remove = send('POST', `/members/${x.memberId}/remove`, { cookie: ownerCookie });
|
|
303
|
+
await waitForWaiters(2);
|
|
304
|
+
await lock.query('COMMIT');
|
|
305
|
+
return Promise.all([mint, remove]);
|
|
306
|
+
});
|
|
307
|
+
expect(removed.status).toBe(200);
|
|
308
|
+
const status = await expectAddOutcome(mintRes, 'no_such_member');
|
|
309
|
+
console.log('arm D (membership row held): mint answered', status);
|
|
310
|
+
expect(await livePasses()).toEqual([]);
|
|
311
|
+
|
|
312
|
+
await reinvite('pass@example.com', x.cookie);
|
|
313
|
+
expect(await livePasses()).toEqual([]);
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
it('E. a duplicate grant add fired with the removal, 20 rounds: the removal answers 200, the add 409 or 404, never a 500, no deadlock', async () => {
|
|
317
|
+
const x = await addMember('grant-dup@example.com', 'member');
|
|
318
|
+
const projectId = await newProject('Race E');
|
|
319
|
+
const addGrant = () =>
|
|
320
|
+
send(
|
|
321
|
+
'POST',
|
|
322
|
+
`/projects/${projectId}/members`,
|
|
323
|
+
{ cookie: adminCookie },
|
|
324
|
+
{ userId: x.userId, role: 'editor' }
|
|
325
|
+
);
|
|
326
|
+
// Other backends' pending statistics flush within the idle interval (10 s).
|
|
327
|
+
await new Promise((r) => setTimeout(r, 11_000));
|
|
328
|
+
const deadlocksBefore = await deadlockCount();
|
|
329
|
+
|
|
330
|
+
const ROUNDS = 20;
|
|
331
|
+
const adds: number[] = [];
|
|
332
|
+
const removals: number[] = [];
|
|
333
|
+
let grantsLeft = 0;
|
|
334
|
+
for (let round = 0; round < ROUNDS; round++) {
|
|
335
|
+
// The grant exists and the person is active before each round (a 409:
|
|
336
|
+
// a failed removal of the round before left it in place).
|
|
337
|
+
expect([201, 409]).toContain((await addGrant()).status);
|
|
338
|
+
const [add, removal] = await Promise.all([
|
|
339
|
+
addGrant(),
|
|
340
|
+
send('POST', `/members/${x.memberId}/remove`, { cookie: ownerCookie })
|
|
341
|
+
]);
|
|
342
|
+
adds.push(add.status);
|
|
343
|
+
removals.push(removal.status);
|
|
344
|
+
grantsLeft += (await grantRows(x.memberId, projectId)).length;
|
|
345
|
+
// An admin's Reactivate, the act of the workspace that brings the person back.
|
|
346
|
+
const back = await send('PATCH', `/members/${x.memberId}`, { cookie: ownerCookie }, { isActive: true });
|
|
347
|
+
expect(back.status).toBe(200);
|
|
348
|
+
}
|
|
349
|
+
await new Promise((r) => setTimeout(r, 11_000));
|
|
350
|
+
const deadlocksAfter = await deadlockCount();
|
|
351
|
+
const tally = (list: number[]) =>
|
|
352
|
+
Object.entries(
|
|
353
|
+
list.reduce<Record<number, number>>((acc, s) => ({ ...acc, [s]: (acc[s] ?? 0) + 1 }), {})
|
|
354
|
+
)
|
|
355
|
+
.map(([status, n]) => `${status}=${n}`)
|
|
356
|
+
.join(' ');
|
|
357
|
+
console.log(
|
|
358
|
+
`arm E (${ROUNDS} rounds): add ${tally(adds)}; removal ${tally(removals)}; grants left ${grantsLeft}; deadlocks ${deadlocksBefore} -> ${deadlocksAfter}`
|
|
359
|
+
);
|
|
360
|
+
expect(removals.every((status) => status === 200)).toBe(true);
|
|
361
|
+
expect(adds.every((status) => status === 409 || status === 404)).toBe(true);
|
|
362
|
+
expect(grantsLeft).toBe(0);
|
|
363
|
+
expect(deadlocksAfter).toBe(deadlocksBefore);
|
|
364
|
+
}, 180_000);
|
|
365
|
+
|
|
366
|
+
it('G. a duplicate seat add fired with the removal, 20 rounds: the removal answers 200, the add 409 or 404, never a 500, no deadlock', async () => {
|
|
367
|
+
// Arm E's twin on the team seat (verifier round 2, G4): the seat add
|
|
368
|
+
// reads the existing row before it inserts, so a repeat never waits on
|
|
369
|
+
// the removal's uncommitted delete of that seat.
|
|
370
|
+
const x = await addMember('seat-dup@example.com', 'member');
|
|
371
|
+
const team = await send('POST', '/teams', { cookie: ownerCookie }, { name: 'Race G team' });
|
|
372
|
+
expect(team.status).toBe(201);
|
|
373
|
+
const teamId = (await readJson(team)).id as string;
|
|
374
|
+
const addSeat = () =>
|
|
375
|
+
send('POST', `/teams/${teamId}/members`, { cookie: adminCookie }, { userId: x.userId });
|
|
376
|
+
await new Promise((r) => setTimeout(r, 11_000));
|
|
377
|
+
const deadlocksBefore = await deadlockCount();
|
|
378
|
+
|
|
379
|
+
const ROUNDS = 20;
|
|
380
|
+
const adds: number[] = [];
|
|
381
|
+
const removals: number[] = [];
|
|
382
|
+
let seatsLeft = 0;
|
|
383
|
+
for (let round = 0; round < ROUNDS; round++) {
|
|
384
|
+
expect([201, 409]).toContain((await addSeat()).status);
|
|
385
|
+
const [add, removal] = await Promise.all([
|
|
386
|
+
addSeat(),
|
|
387
|
+
send('POST', `/members/${x.memberId}/remove`, { cookie: ownerCookie })
|
|
388
|
+
]);
|
|
389
|
+
adds.push(add.status);
|
|
390
|
+
removals.push(removal.status);
|
|
391
|
+
seatsLeft += (await seatRows(x.memberId, teamId)).length;
|
|
392
|
+
const back = await send('PATCH', `/members/${x.memberId}`, { cookie: ownerCookie }, { isActive: true });
|
|
393
|
+
expect(back.status).toBe(200);
|
|
394
|
+
}
|
|
395
|
+
await new Promise((r) => setTimeout(r, 11_000));
|
|
396
|
+
const deadlocksAfter = await deadlockCount();
|
|
397
|
+
const tally = (list: number[]) =>
|
|
398
|
+
Object.entries(
|
|
399
|
+
list.reduce<Record<number, number>>((acc, s) => ({ ...acc, [s]: (acc[s] ?? 0) + 1 }), {})
|
|
400
|
+
)
|
|
401
|
+
.map(([status, n]) => `${status}=${n}`)
|
|
402
|
+
.join(' ');
|
|
403
|
+
console.log(
|
|
404
|
+
`arm G (${ROUNDS} rounds): add ${tally(adds)}; removal ${tally(removals)}; seats left ${seatsLeft}; deadlocks ${deadlocksBefore} -> ${deadlocksAfter}`
|
|
405
|
+
);
|
|
406
|
+
expect(removals.every((status) => status === 200)).toBe(true);
|
|
407
|
+
expect(adds.every((status) => status === 409 || status === 404)).toBe(true);
|
|
408
|
+
expect(seatsLeft).toBe(0);
|
|
409
|
+
expect(deadlocksAfter).toBe(deadlocksBefore);
|
|
410
|
+
}, 180_000);
|
|
411
|
+
|
|
412
|
+
it('F. a project the person creates while the removal sits between its update and its commit: no grant of theirs survives', async () => {
|
|
413
|
+
const x = await addMember('creator@example.com', 'member');
|
|
414
|
+
const outcome = await withLockClient(async (holdRow) => {
|
|
415
|
+
const holdInvitation = await app.db.pool.connect();
|
|
416
|
+
try {
|
|
417
|
+
// The removal's first pass runs; its update then waits on the held row.
|
|
418
|
+
await holdRow.query('BEGIN');
|
|
419
|
+
await holdRow.query('SELECT 1 FROM workspace_members WHERE id = $1 FOR SHARE', [x.memberId]);
|
|
420
|
+
const remove = send('POST', `/members/${x.memberId}/remove`, { cookie: ownerCookie });
|
|
421
|
+
await waitForStatementWaiting('update "workspace_members"');
|
|
422
|
+
// An invitation the person issued, made after the first pass and held,
|
|
423
|
+
// parks the removal's SECOND pass after its grant delete, before its commit.
|
|
424
|
+
const invited = await send(
|
|
425
|
+
'POST',
|
|
426
|
+
'/invitations',
|
|
427
|
+
{ cookie: adminCookie },
|
|
428
|
+
{ email: 'someone-creator-asked@example.com', role: 'member' }
|
|
429
|
+
);
|
|
430
|
+
expect(invited.status).toBe(201);
|
|
431
|
+
const invitationId = (await readJson(invited)).invitation.id as string;
|
|
432
|
+
await app.db.pool.query(`UPDATE invitations SET invited_by = $1 WHERE id = $2`, [
|
|
433
|
+
x.userId,
|
|
434
|
+
invitationId
|
|
435
|
+
]);
|
|
436
|
+
await holdInvitation.query('BEGIN');
|
|
437
|
+
await holdInvitation.query('SELECT 1 FROM invitations WHERE id = $1 FOR UPDATE', [invitationId]);
|
|
438
|
+
await holdRow.query('COMMIT');
|
|
439
|
+
await waitForStatementWaiting('update "invitations"');
|
|
440
|
+
// The person creates a project now: the creator's read of their own
|
|
441
|
+
// membership waits on the removal's uncommitted update.
|
|
442
|
+
const create = Promise.resolve(
|
|
443
|
+
send('POST', '/projects', { cookie: x.cookie }, { name: 'Made during the removal' })
|
|
444
|
+
);
|
|
445
|
+
const early = await settledWithin(create, 1500);
|
|
446
|
+
console.log(
|
|
447
|
+
'arm F: create settled before the removal committed?',
|
|
448
|
+
early === 'pending' ? 'no (waiting)' : `yes, ${early.status}`
|
|
449
|
+
);
|
|
450
|
+
await holdInvitation.query('ROLLBACK');
|
|
451
|
+
const [createRes, removeRes] = await Promise.all([create, remove]);
|
|
452
|
+
return { create: createRes.status, remove: removeRes.status };
|
|
453
|
+
} finally {
|
|
454
|
+
await holdInvitation.query('ROLLBACK').catch(() => undefined);
|
|
455
|
+
holdInvitation.release();
|
|
456
|
+
}
|
|
457
|
+
});
|
|
458
|
+
console.log('arm F outcome', JSON.stringify(outcome));
|
|
459
|
+
expect(outcome.remove).toBe(200);
|
|
460
|
+
expect([401, 404, 201]).toContain(outcome.create);
|
|
461
|
+
const { rows } = await app.db.pool.query(`SELECT project_id FROM project_members WHERE member_id = $1`, [
|
|
462
|
+
x.memberId
|
|
463
|
+
]);
|
|
464
|
+
expect(rows).toEqual([]);
|
|
465
|
+
});
|
|
466
|
+
});
|