@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,636 @@
|
|
|
1
|
+
import type { OpenAPIHono } from '@hono/zod-openapi';
|
|
2
|
+
import type { Context, MiddlewareHandler } from 'hono';
|
|
3
|
+
import { COMPOSED_HANDLER } from 'hono/utils/constants';
|
|
4
|
+
import { ulid } from 'ulid';
|
|
5
|
+
import {
|
|
6
|
+
actorHookOf,
|
|
7
|
+
declaredContentLength,
|
|
8
|
+
ENTITLEMENT_DENIED,
|
|
9
|
+
ENTITLEMENT_TIERS,
|
|
10
|
+
featureKeyOf,
|
|
11
|
+
PLAN_REQUIRED,
|
|
12
|
+
type ActorRef,
|
|
13
|
+
type EntitlementDeniedDetails,
|
|
14
|
+
type EntitlementRequest,
|
|
15
|
+
type EntitlementService,
|
|
16
|
+
type EntitlementTier,
|
|
17
|
+
type MeterDeclaration,
|
|
18
|
+
type PlanRequiredDetails,
|
|
19
|
+
type Principal,
|
|
20
|
+
routeEntitlementKey,
|
|
21
|
+
type RouteEntitlementDeclarations,
|
|
22
|
+
type RouteEntitlementEntry,
|
|
23
|
+
type ToolEntitlements,
|
|
24
|
+
type UsageEvent,
|
|
25
|
+
type UsageSink
|
|
26
|
+
} from '@antasphere/chassis-contract';
|
|
27
|
+
import type { Logger } from '../logger.js';
|
|
28
|
+
import { pendingBodyRefusal } from '../middleware/body-refusal.js';
|
|
29
|
+
import type { CreditCheck } from './check.js';
|
|
30
|
+
import { defaultProfile, type EntitlementProfiles, type ResolvedProfile } from './profiles.js';
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* The ONE middleware of the billing rail (the spec, §7): installed after the
|
|
34
|
+
* scope gate and before the handler, for every route that declares a meter,
|
|
35
|
+
* a limit or a feature. The dashboard, the CLI and the MCP tools are all
|
|
36
|
+
* clients of `/api/v1`, so this is the one place and the three surfaces need
|
|
37
|
+
* nothing.
|
|
38
|
+
*
|
|
39
|
+
* Per request, once the principal is known:
|
|
40
|
+
*
|
|
41
|
+
* cloud, a hub-projected workspace (an `accountRef`), the METERED path:
|
|
42
|
+
* 1. the actor and the account (the principal's, or the route's `actor`);
|
|
43
|
+
* 2. feature, then limit, against the account's profile (the hub's plan,
|
|
44
|
+
* cached 30 s, the tool's declared values for the tier): a refusal is
|
|
45
|
+
* 403 `plan_required` + `details: { key, plan, requiredPlan, upgradeUrl }`,
|
|
46
|
+
* the link being the hub's upgrade page for the organization with
|
|
47
|
+
* `key` and `requiredPlan` appended (the hub root on an older hub);
|
|
48
|
+
* 3. the deferred body refusal (below);
|
|
49
|
+
* 4. a route that declares a limit refuses a POST, PUT or PATCH that does
|
|
50
|
+
* not declare its size: 411 `length_required` (PRDCT-2652, below);
|
|
51
|
+
* 5. the credit check at the hub (`cloud.credits`, `POST /usage/check`,
|
|
52
|
+
* the price of THIS quantity against the organization's balance):
|
|
53
|
+
* 402 `entitlement_denied` + `details: { credits, balance, topUpUrl }`
|
|
54
|
+
* when the balance is short, 413 `entitlement_denied` with the price
|
|
55
|
+
* and the balance but no top-up link for a quantity the hub cannot
|
|
56
|
+
* price (`unpriceable`: past `Number.MAX_SAFE_INTEGER` credits, beside
|
|
57
|
+
* the instance-limit 413; PRDCT-2677), 403 `account_suspended` for a suspended
|
|
58
|
+
* organization, 403 `hub_unavailable` once an outage has outlasted the
|
|
59
|
+
* fail-open window (the live gate's own codes and wording). The LOCAL
|
|
60
|
+
* `entitlements.check` is NOT asked here: the plan limit is the cap of
|
|
61
|
+
* a metered account (PRDCT-2653: the local service compared every
|
|
62
|
+
* upload against the instance cap, so a pro account above the free
|
|
63
|
+
* value met 413 with the oss message);
|
|
64
|
+
* 6. after a 2xx, one usage event, enriched with the user, the `via` and
|
|
65
|
+
* the resource the handler recorded for its audit row. The event never
|
|
66
|
+
* names the tool: the hub takes the tool from the machine token, the
|
|
67
|
+
* only authority on its name (PRDCT-2629). The hub debits at ingest.
|
|
68
|
+
*
|
|
69
|
+
* oss, and a cloud-LOCAL workspace (no account): no plan exists. The local
|
|
70
|
+
* credit check runs first (today's refusal, byte for byte), then a declared
|
|
71
|
+
* limit is compared against its `oss` value (the operator's env knob; null
|
|
72
|
+
* = unlimited). No 411, no hub call, no event: a self-hosted instance is
|
|
73
|
+
* unmetered by construction (§13).
|
|
74
|
+
*
|
|
75
|
+
* Why 411 on a metered upload with no Content-Length (PRDCT-2652): a limit
|
|
76
|
+
* is judged on the size the request DECLARES before the handler, so a body
|
|
77
|
+
* with no declared size read as 0 bytes and passed the plan limit whatever
|
|
78
|
+
* its size. Refusing before anything is stored is chosen over storing and
|
|
79
|
+
* rolling back: a stored blob is content-addressed and may already be
|
|
80
|
+
* referenced by another deck. Every first-party client declares it (Node's
|
|
81
|
+
* fetch and the browser do for a buffer, a blob or a form; the in-process
|
|
82
|
+
* MCP calls encode the form first).
|
|
83
|
+
*
|
|
84
|
+
* The idempotency middleware sits before this one, so a replayed request is
|
|
85
|
+
* never metered twice; the audit middleware wraps it, so what the handler
|
|
86
|
+
* records for its audit row is what the emit reads.
|
|
87
|
+
*
|
|
88
|
+
* A body-size refusal the size cap deferred (`bodyRefusal`, PRDCT-2632: a
|
|
89
|
+
* declared Content-Length over the tool's cap on a route that declares a
|
|
90
|
+
* limit) fires HERE: after the plan check on a metered account, so the plan
|
|
91
|
+
* refusal with its upgrade link is what an oversize upload meets and the
|
|
92
|
+
* instance cap answers only when the plan allows the size; before anything
|
|
93
|
+
* else when no plan applies (no principal, oss, a cloud-local workspace), so
|
|
94
|
+
* a resolved request no plan applies to answers byte for byte as before.
|
|
95
|
+
* What changed for such a request: the credential resolves and the scope
|
|
96
|
+
* gate answers before the cap does (a key without the scope meets 403
|
|
97
|
+
* insufficient_scope where the cap's 413 came first), and the refusal
|
|
98
|
+
* costs a lookup and a quota token (verifier round 1, accepted).
|
|
99
|
+
*/
|
|
100
|
+
/** The cloud edition's half of the gate: the plan source, the upgrade link, the credit check, the hub-subject resolver. */
|
|
101
|
+
export interface EntitlementCloud {
|
|
102
|
+
profiles: EntitlementProfiles;
|
|
103
|
+
/** The fallback upgrade link (the hub root), when the profile carries no upgrade page of the hub's. */
|
|
104
|
+
upgradeUrl: string;
|
|
105
|
+
/** The hub's credit check (`POST /usage/check`), cached per account per action. */
|
|
106
|
+
credits: CreditCheck;
|
|
107
|
+
hubSubject: (userId: string) => Promise<string | null>;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export interface EntitlementGateDeps {
|
|
111
|
+
declarations: RouteEntitlementDeclarations;
|
|
112
|
+
tool: ToolEntitlements;
|
|
113
|
+
entitlements: EntitlementService;
|
|
114
|
+
usage: UsageSink;
|
|
115
|
+
/**
|
|
116
|
+
* The cloud edition's plan source, upgrade link and hub-subject resolver;
|
|
117
|
+
* undefined on oss. `hubSubject` maps the tool's local user id to the hub
|
|
118
|
+
* user (the SSO `sub` on the account row), which is what the event reports
|
|
119
|
+
* (the hub's `usageEventSchema`); a user with no hub link reports null.
|
|
120
|
+
*/
|
|
121
|
+
cloud: EntitlementCloud | undefined;
|
|
122
|
+
/** The `source` of every event (the instance id is read lazily, cached by the caller). */
|
|
123
|
+
source: () => Promise<UsageEvent['source']>;
|
|
124
|
+
logger: Logger;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const err = (code: string, message: string, details?: unknown) => ({
|
|
128
|
+
error: { code, message, ...(details !== undefined ? { details } : {}) }
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* The wire code of a metered upload that does not declare its size
|
|
133
|
+
* (PRDCT-2652). A literal here, not a contract export: only this gate
|
|
134
|
+
* answers it.
|
|
135
|
+
*/
|
|
136
|
+
const LENGTH_REQUIRED = 'length_required';
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* The live gate's wording for the two hub postures (`identity/hub-live-gate.ts`),
|
|
140
|
+
* reused verbatim so a person meets the same sentence whichever gate refused.
|
|
141
|
+
*/
|
|
142
|
+
const SUSPENDED_MESSAGE = 'This organization is suspended on Antasphere';
|
|
143
|
+
const UNAVAILABLE_MESSAGE =
|
|
144
|
+
'The Antasphere hub has been unreachable for too long; requests are refused until it recovers';
|
|
145
|
+
/** What a viewer on an anonymous surface reads when the owner's account cannot pay (PRDCT-2634): no figure, no link. */
|
|
146
|
+
const ANONYMOUS_DENIED_MESSAGE = 'The owner of this content cannot take this action right now';
|
|
147
|
+
|
|
148
|
+
/** The methods that carry a body the limit judges. */
|
|
149
|
+
const BODY_METHODS = new Set(['POST', 'PUT', 'PATCH']);
|
|
150
|
+
|
|
151
|
+
/** Whether the request declares its body size (a Content-Length of digits, `0` included). */
|
|
152
|
+
function declaresLength(ctx: EntitlementRequest): boolean {
|
|
153
|
+
const raw = ctx.headers.get('content-length');
|
|
154
|
+
return raw !== null && /^\s*\d+\s*$/.test(raw);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/** `{id}` → `:id`: the OpenAPI path as Hono registers it. */
|
|
158
|
+
export function honoPath(path: string): string {
|
|
159
|
+
return path.replaceAll(/\{([^}]+)\}/g, ':$1');
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* The gate handlers of the routes that declare a PLAN LIMIT: the size cap
|
|
164
|
+
* (api/create-api.ts) defers a declared-oversize refusal exactly when one of
|
|
165
|
+
* these is on the request's matched route, so the deferral is keyed on the
|
|
166
|
+
* gate itself, never on a re-reading of the path (PRDCT-2632).
|
|
167
|
+
*/
|
|
168
|
+
const deferringGates = new WeakSet<MiddlewareHandler>();
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Whether a matched handler is a gate that judges a plan limit before the
|
|
172
|
+
* size cap may refuse. Hono's `route()` wraps a sub-app's handlers when that
|
|
173
|
+
* sub-app has its own `onError` (`COMPOSED_HANDLER` points at the original),
|
|
174
|
+
* so the wrapper is unwrapped first: otherwise an `api.onError` added one day
|
|
175
|
+
* would switch every deferral off (verifier round 2).
|
|
176
|
+
*/
|
|
177
|
+
export function isDeferringGate(handler: unknown): boolean {
|
|
178
|
+
const target = (handler as { [COMPOSED_HANDLER]?: unknown } | null)?.[COMPOSED_HANDLER] ?? handler;
|
|
179
|
+
return typeof target === 'function' && deferringGates.has(target as MiddlewareHandler);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/** Register the gate on every declared route, at the caller's place in the chain. */
|
|
183
|
+
export function registerEntitlementGate(api: OpenAPIHono, deps: EntitlementGateDeps): void {
|
|
184
|
+
for (const entry of deps.declarations.values()) {
|
|
185
|
+
const gate = entitlementGate(entry, deps);
|
|
186
|
+
// Only a limit that JUDGES THE SIZE defers the cap's refusal (the plan
|
|
187
|
+
// must see the declared size first, PRDCT-2632); a count limit never
|
|
188
|
+
// judges the size, so the cap answers at once there and no lookup runs
|
|
189
|
+
// on a body the cap has dropped (the code review of PRDCT-2702).
|
|
190
|
+
if (entry.limit?.value === declaredContentLength) deferringGates.add(gate);
|
|
191
|
+
api.on(entry.route.method.toUpperCase(), honoPath(entry.route.path), gate);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function requestOf(c: Context, principal: Principal | null): EntitlementRequest {
|
|
196
|
+
// The body, parsed once and shared with the handler's validator (Hono
|
|
197
|
+
// caches the parse on the request): a count limit or a conditional
|
|
198
|
+
// feature reads what the request asks for (PRDCT-2702). A body that is
|
|
199
|
+
// not JSON, or none at all, reads as undefined; the route's own validator
|
|
200
|
+
// answers 400 for it, the gate never does.
|
|
201
|
+
let body: Promise<unknown> | undefined;
|
|
202
|
+
return {
|
|
203
|
+
method: c.req.method,
|
|
204
|
+
path: c.req.path,
|
|
205
|
+
headers: c.req.raw.headers,
|
|
206
|
+
params: c.req.param() as Record<string, string>,
|
|
207
|
+
principal,
|
|
208
|
+
body: () => (body ??= c.req.json().catch(() => undefined))
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function quantityOf(meter: MeterDeclaration, ctx: EntitlementRequest): number {
|
|
213
|
+
if (!meter.quantity) return 1;
|
|
214
|
+
const q = meter.quantity(ctx);
|
|
215
|
+
return Number.isFinite(q) && q >= 0 ? q : 0;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function principalActor(principal: Principal): ActorRef {
|
|
219
|
+
return {
|
|
220
|
+
userId: principal.userId,
|
|
221
|
+
workspaceId: principal.workspaceId,
|
|
222
|
+
...(principal.accountRef ? { accountRef: principal.accountRef } : {})
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* The paying actor of one request. A route that declares an `actor` hook is
|
|
228
|
+
* a SURFACE ATTRIBUTED TO A RESOURCE'S OWNER (a form response through a share
|
|
229
|
+
* link, PRDCT-2634): the hook, and only the hook, names who pays, whether or
|
|
230
|
+
* not a principal happens to be signed in (a person of any workspace may hold
|
|
231
|
+
* a share link; they are a viewer here, never the payer). A hook that resolves
|
|
232
|
+
* nothing, or throws, leaves the route to its own handling with nothing
|
|
233
|
+
* metered, rather than refusing on a lookup error or billing the viewer. A
|
|
234
|
+
* route without a hook meters its principal, or nothing without one.
|
|
235
|
+
*/
|
|
236
|
+
async function actorOf(
|
|
237
|
+
entry: RouteEntitlementEntry,
|
|
238
|
+
ctx: EntitlementRequest,
|
|
239
|
+
principal: Principal | null,
|
|
240
|
+
logger: Logger
|
|
241
|
+
): Promise<ActorRef | null> {
|
|
242
|
+
const hook = actorHookOf(entry);
|
|
243
|
+
if (hook) {
|
|
244
|
+
try {
|
|
245
|
+
return (await hook(ctx)) ?? null;
|
|
246
|
+
} catch (cause: unknown) {
|
|
247
|
+
// A lookup that fails is the route's to answer, never a refusal; but a
|
|
248
|
+
// cap that stops biting must be visible to an operator.
|
|
249
|
+
logger.warn(
|
|
250
|
+
{ err: cause, route: routeEntitlementKey(entry.route) },
|
|
251
|
+
'entitlements: actor hook threw — nothing judged, nothing metered'
|
|
252
|
+
);
|
|
253
|
+
return null;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
return principal ? principalActor(principal) : null;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Whether the request is a viewer's on an owner-attributed surface: the
|
|
261
|
+
* route declares an actor hook, on its meter or on the entry itself (a
|
|
262
|
+
* public door that checks a limit and meters nothing, PRDCT-2702).
|
|
263
|
+
*/
|
|
264
|
+
function viewerSurface(entry: RouteEntitlementEntry): boolean {
|
|
265
|
+
return actorHookOf(entry) !== null;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* The value a limit observes for this request, or null when the hook says
|
|
270
|
+
* there is nothing to judge (PRDCT-2702) or throws: a lookup error must
|
|
271
|
+
* never become a plan refusal, so the route answers on its own then.
|
|
272
|
+
*/
|
|
273
|
+
async function observedOf(
|
|
274
|
+
entry: RouteEntitlementEntry,
|
|
275
|
+
ctx: EntitlementRequest,
|
|
276
|
+
logger: Logger
|
|
277
|
+
): Promise<number | null> {
|
|
278
|
+
try {
|
|
279
|
+
const observed = await entry.limit!.value(ctx);
|
|
280
|
+
return typeof observed === 'number' && Number.isFinite(observed) ? observed : null;
|
|
281
|
+
} catch (cause: unknown) {
|
|
282
|
+
logger.warn(
|
|
283
|
+
{ err: cause, route: routeEntitlementKey(entry.route), key: entry.limit!.key },
|
|
284
|
+
'entitlements: limit hook threw — the limit was not judged on this request'
|
|
285
|
+
);
|
|
286
|
+
return null;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Whether the route's feature applies to THIS request: a plain key applies
|
|
292
|
+
* always, a conditional one only when its `when` says so (a thing created
|
|
293
|
+
* with the premium option needs the feature, one created without does not).
|
|
294
|
+
* A condition that throws does not apply: the route answers on its own.
|
|
295
|
+
*/
|
|
296
|
+
async function featureApplies(
|
|
297
|
+
entry: RouteEntitlementEntry,
|
|
298
|
+
ctx: EntitlementRequest,
|
|
299
|
+
logger: Logger
|
|
300
|
+
): Promise<boolean> {
|
|
301
|
+
if (!entry.feature) return false;
|
|
302
|
+
if (typeof entry.feature === 'string' || !entry.feature.when) return true;
|
|
303
|
+
try {
|
|
304
|
+
return await entry.feature.when(ctx);
|
|
305
|
+
} catch (cause: unknown) {
|
|
306
|
+
logger.warn(
|
|
307
|
+
{ err: cause, route: routeEntitlementKey(entry.route), key: featureKeyOf(entry) },
|
|
308
|
+
'entitlements: feature condition threw — the feature was not judged on this request'
|
|
309
|
+
);
|
|
310
|
+
return false;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/** The first tier above the account's that allows the value, or null when none does. */
|
|
315
|
+
function requiredPlanFor(
|
|
316
|
+
tool: ToolEntitlements,
|
|
317
|
+
plan: EntitlementTier,
|
|
318
|
+
judge: (tier: EntitlementTier) => boolean
|
|
319
|
+
): EntitlementTier | null {
|
|
320
|
+
const from = ENTITLEMENT_TIERS.indexOf(plan) + 1;
|
|
321
|
+
for (const tier of ENTITLEMENT_TIERS.slice(from)) {
|
|
322
|
+
if (judge(tier)) return tier;
|
|
323
|
+
}
|
|
324
|
+
return null;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
function planRefusal(c: Context, message: string, details: PlanRequiredDetails): Response {
|
|
328
|
+
return c.json(err(PLAN_REQUIRED, message, details), 403);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* The upgrade link of a plan refusal: the hub's upgrade page for the
|
|
333
|
+
* organization (it carries `org`, `tool` and `plan`) with the refused `key`
|
|
334
|
+
* and the `requiredPlan` appended; the hub root when the profile has no page
|
|
335
|
+
* of the hub's (an older hub, or the default plan).
|
|
336
|
+
*/
|
|
337
|
+
function upgradeLink(
|
|
338
|
+
profile: ResolvedProfile,
|
|
339
|
+
key: string,
|
|
340
|
+
requiredPlan: EntitlementTier | null,
|
|
341
|
+
fallback: string
|
|
342
|
+
): string {
|
|
343
|
+
if (!profile.upgradeUrl) return fallback;
|
|
344
|
+
let url: URL;
|
|
345
|
+
try {
|
|
346
|
+
url = new URL(profile.upgradeUrl);
|
|
347
|
+
} catch {
|
|
348
|
+
return fallback;
|
|
349
|
+
}
|
|
350
|
+
url.searchParams.set('key', key);
|
|
351
|
+
if (requiredPlan !== null) url.searchParams.set('requiredPlan', requiredPlan);
|
|
352
|
+
return url.toString();
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* The plan checks (feature, then limit) of one request on the cloud edition.
|
|
357
|
+
* Returns the refusal, or null when the plan allows the action.
|
|
358
|
+
*
|
|
359
|
+
* A limit is judged on what the request DECLARES before the handler (an
|
|
360
|
+
* upload's Content-Length). A metered request that declares none is refused
|
|
361
|
+
* 411 by the gate right after (PRDCT-2652), so a size can no longer pass the
|
|
362
|
+
* plan's value by staying silent; the instance's hard caps (the body limits
|
|
363
|
+
* and the services' mid-stream ceilings) still bound a body that lies.
|
|
364
|
+
*/
|
|
365
|
+
function planCheck(
|
|
366
|
+
c: Context,
|
|
367
|
+
entry: RouteEntitlementEntry,
|
|
368
|
+
profile: ResolvedProfile,
|
|
369
|
+
deps: EntitlementGateDeps,
|
|
370
|
+
/** Whether the route's feature applies to this request (judged before the plan was read). */
|
|
371
|
+
feature: boolean,
|
|
372
|
+
/** The value the route's limit observed for this request (read once, before the plan). */
|
|
373
|
+
observed: number | null,
|
|
374
|
+
/** No principal: a viewer, who must learn nothing of the owner's plan or upgrade page. */
|
|
375
|
+
anonymous = false
|
|
376
|
+
): Response | null {
|
|
377
|
+
const refusal = planCheckDetailed(c, entry, profile, deps, feature, observed);
|
|
378
|
+
if (refusal && anonymous) return c.json(err(PLAN_REQUIRED, ANONYMOUS_DENIED_MESSAGE), 403);
|
|
379
|
+
return refusal;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
function planCheckDetailed(
|
|
383
|
+
c: Context,
|
|
384
|
+
entry: RouteEntitlementEntry,
|
|
385
|
+
profile: ResolvedProfile,
|
|
386
|
+
deps: EntitlementGateDeps,
|
|
387
|
+
feature: boolean,
|
|
388
|
+
observed: number | null
|
|
389
|
+
): Response | null {
|
|
390
|
+
const featureKey = featureKeyOf(entry);
|
|
391
|
+
if (featureKey && feature) {
|
|
392
|
+
const key = featureKey;
|
|
393
|
+
if (!profile.features.has(key)) {
|
|
394
|
+
const requiredPlan = requiredPlanFor(
|
|
395
|
+
deps.tool,
|
|
396
|
+
profile.plan,
|
|
397
|
+
(tier) => deps.tool.features[key]?.[tier] === true
|
|
398
|
+
);
|
|
399
|
+
return planRefusal(
|
|
400
|
+
c,
|
|
401
|
+
requiredPlan
|
|
402
|
+
? `This needs the ${requiredPlan} plan (${key} is not part of the ${profile.plan} plan)`
|
|
403
|
+
: `${key} is not available on any plan`,
|
|
404
|
+
{
|
|
405
|
+
key,
|
|
406
|
+
plan: profile.plan,
|
|
407
|
+
requiredPlan,
|
|
408
|
+
upgradeUrl: upgradeLink(profile, key, requiredPlan, deps.cloud!.upgradeUrl)
|
|
409
|
+
}
|
|
410
|
+
);
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
if (entry.limit) {
|
|
414
|
+
const { key } = entry.limit;
|
|
415
|
+
const max = profile.limits[key];
|
|
416
|
+
if (max !== null && max !== undefined && observed !== null && observed > max) {
|
|
417
|
+
const requiredPlan = requiredPlanFor(deps.tool, profile.plan, (tier) => {
|
|
418
|
+
const value = deps.tool.limits[key]?.[tier];
|
|
419
|
+
return value === null || (typeof value === 'number' && observed <= value);
|
|
420
|
+
});
|
|
421
|
+
return planRefusal(
|
|
422
|
+
c,
|
|
423
|
+
requiredPlan
|
|
424
|
+
? `${key} is limited to ${max} on the ${profile.plan} plan; the ${requiredPlan} plan allows it`
|
|
425
|
+
: `${key} is limited to ${max} on the ${profile.plan} plan`,
|
|
426
|
+
{
|
|
427
|
+
key,
|
|
428
|
+
plan: profile.plan,
|
|
429
|
+
requiredPlan,
|
|
430
|
+
upgradeUrl: upgradeLink(profile, key, requiredPlan, deps.cloud!.upgradeUrl)
|
|
431
|
+
}
|
|
432
|
+
);
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
return null;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
/** The oss half of a limit: the operator's own knob, refused the way the credit check refuses. */
|
|
439
|
+
async function ossLimitCheck(
|
|
440
|
+
c: Context,
|
|
441
|
+
entry: RouteEntitlementEntry,
|
|
442
|
+
ctx: EntitlementRequest,
|
|
443
|
+
tool: ToolEntitlements,
|
|
444
|
+
logger: Logger
|
|
445
|
+
): Promise<Response | null> {
|
|
446
|
+
if (!entry.limit) return null;
|
|
447
|
+
const { key } = entry.limit;
|
|
448
|
+
const max = tool.limits[key]?.oss;
|
|
449
|
+
// The value is read only when an oss ceiling exists: a null ceiling (the
|
|
450
|
+
// operator's unlimited) costs no lookup for a count hook.
|
|
451
|
+
if (max === null || max === undefined) return null;
|
|
452
|
+
const observed = await observedOf(entry, ctx, logger);
|
|
453
|
+
if (observed !== null && observed > max) {
|
|
454
|
+
return c.json(err('entitlement_denied', `${key}: ${observed} exceeds the instance limit (${max})`), 413);
|
|
455
|
+
}
|
|
456
|
+
return null;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
/** The hub's credit check of a metered request; the refusal, or null when the action may run. */
|
|
460
|
+
async function creditRefusal(
|
|
461
|
+
c: Context,
|
|
462
|
+
meter: MeterDeclaration,
|
|
463
|
+
ctx: EntitlementRequest,
|
|
464
|
+
actor: ActorRef,
|
|
465
|
+
cloud: EntitlementCloud,
|
|
466
|
+
/** No principal: a viewer on an anonymous surface, who must learn nothing about the owner's account. */
|
|
467
|
+
anonymous: boolean
|
|
468
|
+
): Promise<Response | null> {
|
|
469
|
+
const verdict = await cloud.credits.check({
|
|
470
|
+
accountRef: actor.accountRef!,
|
|
471
|
+
actionKey: meter.key,
|
|
472
|
+
quantity: quantityOf(meter, ctx),
|
|
473
|
+
unit: meter.unit
|
|
474
|
+
});
|
|
475
|
+
if (verdict.allowed) return null;
|
|
476
|
+
if (anonymous) {
|
|
477
|
+
// The owner's balance, price, top-up page, and even whether the
|
|
478
|
+
// organization is suspended or the hub is down, are the owner's: a
|
|
479
|
+
// viewer gets one code and one neutral sentence, whatever the reason
|
|
480
|
+
// (PRDCT-2634; verifier round 3 on the suspended and unavailable answers).
|
|
481
|
+
return c.json(err(ENTITLEMENT_DENIED, ANONYMOUS_DENIED_MESSAGE), 402);
|
|
482
|
+
}
|
|
483
|
+
if (verdict.reason === 'account_suspended') return c.json(err('account_suspended', SUSPENDED_MESSAGE), 403);
|
|
484
|
+
if (verdict.reason === 'hub_unavailable') return c.json(err('hub_unavailable', UNAVAILABLE_MESSAGE), 403);
|
|
485
|
+
const details: EntitlementDeniedDetails = {
|
|
486
|
+
credits: verdict.check?.credits ?? 0,
|
|
487
|
+
balance: verdict.check?.balance ?? 0,
|
|
488
|
+
topUpUrl: verdict.check?.topUpUrl ?? cloud.upgradeUrl
|
|
489
|
+
};
|
|
490
|
+
// A price past `Number.MAX_SAFE_INTEGER` (the hub clamps `credits` to it):
|
|
491
|
+
// a refusal of the quantity, never an outage (PRDCT-2677). The
|
|
492
|
+
// instance-limit 413's code, with the price and the balance and NO top-up
|
|
493
|
+
// link: no top-up covers it, and the dashboard's and the CLI's readers
|
|
494
|
+
// present a top-up card on any `entitlement_denied` whose details carry
|
|
495
|
+
// `topUpUrl` (verifier round 1).
|
|
496
|
+
if (verdict.reason === 'unpriceable') {
|
|
497
|
+
const figures: Pick<EntitlementDeniedDetails, 'credits' | 'balance'> = {
|
|
498
|
+
credits: details.credits,
|
|
499
|
+
balance: details.balance
|
|
500
|
+
};
|
|
501
|
+
return c.json(
|
|
502
|
+
err(
|
|
503
|
+
ENTITLEMENT_DENIED,
|
|
504
|
+
`This quantity cannot be priced (${figures.credits} credits or more, the organization holds ${figures.balance}); nothing was charged`,
|
|
505
|
+
figures
|
|
506
|
+
),
|
|
507
|
+
413
|
|
508
|
+
);
|
|
509
|
+
}
|
|
510
|
+
return c.json(
|
|
511
|
+
err(
|
|
512
|
+
ENTITLEMENT_DENIED,
|
|
513
|
+
`This needs ${details.credits} credits and the organization holds ${details.balance}; top up at ${details.topUpUrl}`,
|
|
514
|
+
details
|
|
515
|
+
),
|
|
516
|
+
402
|
|
517
|
+
);
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
export function entitlementGate(entry: RouteEntitlementEntry, deps: EntitlementGateDeps): MiddlewareHandler {
|
|
521
|
+
const meterKey = entry.meter ? routeEntitlementKey(entry.route) : null;
|
|
522
|
+
return async (c, next) => {
|
|
523
|
+
const principal: Principal | null = c.get('principal') ?? null;
|
|
524
|
+
const deferredRefusal = pendingBodyRefusal(c);
|
|
525
|
+
const ctx = requestOf(c, principal);
|
|
526
|
+
// The actor: the principal, or, on an ANONYMOUS surface (a form response
|
|
527
|
+
// through a share link, PRDCT-2634), whoever the route's `actor` hook
|
|
528
|
+
// resolves with no principal at all (the share secret → the deck → its
|
|
529
|
+
// owner → the owner's workspace and account: the owner pays and is
|
|
530
|
+
// reported, the viewer is never identified, §8). No principal and no
|
|
531
|
+
// actor: the route's own auth answers (401/404); nothing to meter, and a
|
|
532
|
+
// deferred size refusal still fires first, as the cap did before.
|
|
533
|
+
// A viewer's request (an owner-attributed surface, with or without a
|
|
534
|
+
// signed-in person) learns nothing of the owner's account: every refusal
|
|
535
|
+
// to it is one neutral sentence (verifier round 3, the code review: a
|
|
536
|
+
// signed-in holder of a share link is a viewer too). On oss the hook is
|
|
537
|
+
// not run: no account can exist, the lookups would cost the database.
|
|
538
|
+
const viewer = viewerSurface(entry);
|
|
539
|
+
const actor = viewer && !deps.cloud ? null : await actorOf(entry, ctx, principal, deps.logger);
|
|
540
|
+
if (!actor) return deferredRefusal ? deferredRefusal() : next();
|
|
541
|
+
const metered = Boolean(deps.cloud && actor.accountRef);
|
|
542
|
+
// A viewer's surface whose owner has no account (a cloud-local
|
|
543
|
+
// workspace) has no plan and no credits: byte for byte as it was.
|
|
544
|
+
if (viewer && !metered) return deferredRefusal ? deferredRefusal() : next();
|
|
545
|
+
|
|
546
|
+
// The plan is read only when the route declares a limit, or a feature
|
|
547
|
+
// that APPLIES to this request: a meter-only route has nothing to judge
|
|
548
|
+
// against it (verifier round 3), and a conditional feature whose
|
|
549
|
+
// condition does not hold costs no hub read (the code review of
|
|
550
|
+
// PRDCT-2702: a link renamed must not wait on a cold plan).
|
|
551
|
+
// A count limit's hook that answers null (or throws) has nothing to judge:
|
|
552
|
+
// the caller is one the handler refuses on its own (a guest at a door
|
|
553
|
+
// closed to guests, a resource the caller cannot see), or the lookup
|
|
554
|
+
// failed. The gate's judgement of that request ends there, on the plan,
|
|
555
|
+
// the feature and the price alike, and nothing is metered: the hub is
|
|
556
|
+
// never asked about an act the handler will refuse, so no refusal can
|
|
557
|
+
// show that caller the organization's plan or balance (the tool
|
|
558
|
+
// template's feedback of 29 September 2026). The hook runs once, before
|
|
559
|
+
// the plan is read. A size limit never answers null (the declared length
|
|
560
|
+
// reads 0 when absent, and the 411 below refuses it).
|
|
561
|
+
const observed = metered && entry.limit ? await observedOf(entry, ctx, deps.logger) : null;
|
|
562
|
+
if (metered && entry.limit && observed === null) return deferredRefusal ? deferredRefusal() : next();
|
|
563
|
+
const feature = metered && entry.feature ? await featureApplies(entry, ctx, deps.logger) : false;
|
|
564
|
+
if (metered && (feature || entry.limit)) {
|
|
565
|
+
const profile = await deps.cloud!.profiles.get(actor.accountRef!, deps.tool).catch((cause: unknown) => {
|
|
566
|
+
deps.logger.warn({ err: cause }, 'entitlements: profile read threw — the free tier applies');
|
|
567
|
+
return defaultProfile(deps.tool);
|
|
568
|
+
});
|
|
569
|
+
const refused = planCheck(c, entry, profile, deps, feature, observed, viewer);
|
|
570
|
+
if (refused) return refused;
|
|
571
|
+
}
|
|
572
|
+
// The size cap's deferred refusal: the plan has had its say (or none
|
|
573
|
+
// applies), the instance's hard ceiling answers now.
|
|
574
|
+
if (deferredRefusal) return deferredRefusal();
|
|
575
|
+
if (metered) {
|
|
576
|
+
// A body with no declared size would pass the plan limit as 0 bytes
|
|
577
|
+
// (PRDCT-2652): refused before anything is stored.
|
|
578
|
+
// A route metered in BYTES needs the size too: with no header the check
|
|
579
|
+
// would price 0 bytes and the emit the stored size, so an anonymous
|
|
580
|
+
// upload door could take an owner's balance below zero (verifier round 3).
|
|
581
|
+
// A limit JUDGES the body when its value is the declared length (the
|
|
582
|
+
// contract's own reader), not any limit (a count limit on a JSON route
|
|
583
|
+
// must not refuse a chunked body: the code review).
|
|
584
|
+
const judgesSize = entry.limit?.value === declaredContentLength || entry.meter?.unit === 'bytes';
|
|
585
|
+
if (judgesSize && BODY_METHODS.has(c.req.method.toUpperCase()) && !declaresLength(ctx)) {
|
|
586
|
+
return c.json(err(LENGTH_REQUIRED, 'A metered upload must declare its size (Content-Length)'), 411);
|
|
587
|
+
}
|
|
588
|
+
if (entry.meter) {
|
|
589
|
+
const refused = await creditRefusal(c, entry.meter, ctx, actor, deps.cloud!, viewer);
|
|
590
|
+
if (refused) return refused;
|
|
591
|
+
}
|
|
592
|
+
} else {
|
|
593
|
+
if (entry.meter) {
|
|
594
|
+
// A principal is certain here: a viewer's surface returned above, and a
|
|
595
|
+
// route without a hook resolved no actor without a principal.
|
|
596
|
+
const decision = await deps.entitlements.check(
|
|
597
|
+
principal!,
|
|
598
|
+
{ key: entry.meter.key, quantity: quantityOf(entry.meter, ctx), unit: entry.meter.unit },
|
|
599
|
+
{ route: meterKey }
|
|
600
|
+
);
|
|
601
|
+
if (!decision.allowed) {
|
|
602
|
+
return c.json(err('entitlement_denied', decision.reason), 413);
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
const refused = await ossLimitCheck(c, entry, ctx, deps.tool, deps.logger);
|
|
606
|
+
if (refused) return refused;
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
await next();
|
|
610
|
+
|
|
611
|
+
if (!metered || !entry.meter || c.res.status < 200 || c.res.status >= 300) return;
|
|
612
|
+
const after: EntitlementRequest = { ...ctx, audit: c.get('audit') };
|
|
613
|
+
const audit = after.audit;
|
|
614
|
+
const hubUser = actor.userId ? await deps.cloud!.hubSubject(actor.userId).catch(() => null) : null;
|
|
615
|
+
const event: UsageEvent = {
|
|
616
|
+
id: ulid(),
|
|
617
|
+
meter: entry.meter.key,
|
|
618
|
+
actionKey: entry.meter.key,
|
|
619
|
+
quantity: quantityOf(entry.meter, after),
|
|
620
|
+
unit: entry.meter.unit,
|
|
621
|
+
occurredAt: new Date().toISOString(),
|
|
622
|
+
workspaceId: actor.workspaceId,
|
|
623
|
+
accountRef: actor.accountRef!,
|
|
624
|
+
userId: hubUser,
|
|
625
|
+
// The hub's channel enum has no value for a share link: an anonymous
|
|
626
|
+
// surface reports the owner as if from a browser session (PRDCT-2634;
|
|
627
|
+
// a dedicated value is the hub's contract to add).
|
|
628
|
+
via: viewer ? 'session' : principal!.via,
|
|
629
|
+
...(audit?.resourceType ? { resourceType: audit.resourceType } : {}),
|
|
630
|
+
...(audit?.resourceId ? { resourceId: audit.resourceId } : {}),
|
|
631
|
+
source: await deps.source()
|
|
632
|
+
};
|
|
633
|
+
// Fire and forget: the queue write is durable and the request never waits on billing.
|
|
634
|
+
void deps.usage.emit(event);
|
|
635
|
+
};
|
|
636
|
+
}
|