@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,205 @@
|
|
|
1
|
+
import { USAGE_WRITE_SCOPE } from '@antasphere/chassis-contract';
|
|
2
|
+
import type { Logger } from '../logger.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The tool → hub MACHINE identity (the billing rail spec, §6): a
|
|
6
|
+
* `client_credentials` token minted on the instance's own registry client
|
|
7
|
+
* (`HUB_CLIENT_ID` / `HUB_CLIENT_SECRET`) with the single scope
|
|
8
|
+
* `usage:write`. It carries a client and no subject; the hub refuses every
|
|
9
|
+
* route but `/usage/*` to it, so the only trust it carries is "this tool
|
|
10
|
+
* says this account did this" — it cannot read an organization, a user or a
|
|
11
|
+
* grant, which is what keeps ADR 014's "no service key" stance intact.
|
|
12
|
+
*
|
|
13
|
+
* Cached until shortly before expiry and minted single-flight: concurrent
|
|
14
|
+
* batches share one mint, and a 401 from the hub invalidates the cache so
|
|
15
|
+
* the next call mints again (`invalidate()`).
|
|
16
|
+
*/
|
|
17
|
+
export const HUB_USAGE_SCOPE = USAGE_WRITE_SCOPE;
|
|
18
|
+
|
|
19
|
+
/** How long a TRANSIENT mint failure is held before the hub is asked again: one attempt per instance per window during an outage. */
|
|
20
|
+
export const DEFAULT_FAILURE_HOLD_MS = 5_000;
|
|
21
|
+
/**
|
|
22
|
+
* How long a mint the hub refused as a CONFIGURATION error (`invalid_client`
|
|
23
|
+
* and its kin) is held (PRDCT-2637). It heals only by an operator's change,
|
|
24
|
+
* so asking again in five seconds buys nothing and costs the hub's per-IP
|
|
25
|
+
* token wall, the one bucket people's sign-ins from this instance's
|
|
26
|
+
* address share: a few replicas with a stale secret would start answering
|
|
27
|
+
* 429 to a person signing in. Five minutes bounds a fleet to a handful of
|
|
28
|
+
* mints an hour; `invalidate()` (a 401 from the hub on a live token) still
|
|
29
|
+
* clears the hold, since the hub then changed its mind.
|
|
30
|
+
*/
|
|
31
|
+
export const DEFAULT_INVALID_CLIENT_HOLD_MS = 5 * 60_000;
|
|
32
|
+
|
|
33
|
+
export interface HubMachineTokenOptions {
|
|
34
|
+
issuerUrl: string;
|
|
35
|
+
clientId: string;
|
|
36
|
+
clientSecret: string;
|
|
37
|
+
/** RFC 8707 `resource`: the hub's own API resource (`<hub>/mcp`), the aud the hub mints for its `/usage/*`. */
|
|
38
|
+
resource: string;
|
|
39
|
+
logger: Logger;
|
|
40
|
+
/** Token-endpoint budget per mint. */
|
|
41
|
+
timeoutMs?: number | undefined;
|
|
42
|
+
/** Re-mint this many ms before the hub's `expires_in` runs out. */
|
|
43
|
+
refreshSkewMs?: number | undefined;
|
|
44
|
+
/** How long a transient mint failure is held before the hub is asked again (the negative cache). */
|
|
45
|
+
failureHoldMs?: number | undefined;
|
|
46
|
+
/** How long an `invalid_client`-kind refusal is held (the configuration-error hold, PRDCT-2637). */
|
|
47
|
+
invalidClientHoldMs?: number | undefined;
|
|
48
|
+
fetchImpl?: typeof fetch | undefined;
|
|
49
|
+
now?: (() => number) | undefined;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export class HubMachineTokenError extends Error {
|
|
53
|
+
constructor(
|
|
54
|
+
message: string,
|
|
55
|
+
/** `invalid_client` = the credentials are wrong (never retried into a loop); `transient` = try again later. */
|
|
56
|
+
public readonly kind: 'invalid_client' | 'transient'
|
|
57
|
+
) {
|
|
58
|
+
super(message);
|
|
59
|
+
this.name = 'HubMachineTokenError';
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export class HubMachineToken {
|
|
64
|
+
private readonly tokenUrl: string;
|
|
65
|
+
private readonly fetchImpl: typeof fetch;
|
|
66
|
+
private readonly now: () => number;
|
|
67
|
+
private readonly timeoutMs: number;
|
|
68
|
+
private readonly refreshSkewMs: number;
|
|
69
|
+
private readonly failureHoldMs: number;
|
|
70
|
+
private readonly invalidClientHoldMs: number;
|
|
71
|
+
private cached: { token: string; expiresAtMs: number } | null = null;
|
|
72
|
+
private inflight: Promise<string> | null = null;
|
|
73
|
+
/**
|
|
74
|
+
* The negative cache: after a mint fails, `get()` throws at once until this
|
|
75
|
+
* moment instead of re-asking the hub. During an outage the first metered
|
|
76
|
+
* request of every account per TTL would otherwise pay the token timeout
|
|
77
|
+
* again; with it, one attempt per `failureHoldMs` for the whole instance,
|
|
78
|
+
* and one per `invalidClientHoldMs` when the hub refused the client itself.
|
|
79
|
+
*/
|
|
80
|
+
private failedUntilMs = 0;
|
|
81
|
+
private lastFailure: HubMachineTokenError | null = null;
|
|
82
|
+
/** How many mints reached the hub — a test seam (single-flight, refresh). */
|
|
83
|
+
mints = 0;
|
|
84
|
+
|
|
85
|
+
constructor(private readonly opts: HubMachineTokenOptions) {
|
|
86
|
+
this.tokenUrl = opts.issuerUrl.replace(/\/+$/, '') + '/api/v1/auth/oauth2/token';
|
|
87
|
+
this.fetchImpl = opts.fetchImpl ?? globalThis.fetch.bind(globalThis);
|
|
88
|
+
this.now = opts.now ?? Date.now;
|
|
89
|
+
this.timeoutMs = opts.timeoutMs ?? 10_000;
|
|
90
|
+
this.refreshSkewMs = opts.refreshSkewMs ?? 60_000;
|
|
91
|
+
this.failureHoldMs = opts.failureHoldMs ?? DEFAULT_FAILURE_HOLD_MS;
|
|
92
|
+
this.invalidClientHoldMs = opts.invalidClientHoldMs ?? DEFAULT_INVALID_CLIENT_HOLD_MS;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** The current token, minted or refreshed as needed. Throws `HubMachineTokenError`. */
|
|
96
|
+
async get(): Promise<string> {
|
|
97
|
+
if (this.cached && this.cached.expiresAtMs - this.refreshSkewMs > this.now()) return this.cached.token;
|
|
98
|
+
// Inside the skew window the token is still live at the hub: a refresh
|
|
99
|
+
// that fails serves it rather than the failure, until it really expires.
|
|
100
|
+
const stillLive = this.cached && this.cached.expiresAtMs > this.now() ? this.cached.token : null;
|
|
101
|
+
if (this.lastFailure && this.failedUntilMs > this.now()) {
|
|
102
|
+
if (stillLive) return stillLive;
|
|
103
|
+
throw this.lastFailure;
|
|
104
|
+
}
|
|
105
|
+
if (!this.inflight) {
|
|
106
|
+
this.inflight = this.mint()
|
|
107
|
+
.then((token) => {
|
|
108
|
+
this.lastFailure = null;
|
|
109
|
+
return token;
|
|
110
|
+
})
|
|
111
|
+
.catch((cause: unknown) => {
|
|
112
|
+
const failure =
|
|
113
|
+
cause instanceof HubMachineTokenError
|
|
114
|
+
? cause
|
|
115
|
+
: new HubMachineTokenError(String(cause), 'transient');
|
|
116
|
+
this.lastFailure = failure;
|
|
117
|
+
// A configuration error is held minutes, a transient one seconds
|
|
118
|
+
// (PRDCT-2637): the former never heals by asking again.
|
|
119
|
+
this.failedUntilMs =
|
|
120
|
+
this.now() + (failure.kind === 'invalid_client' ? this.invalidClientHoldMs : this.failureHoldMs);
|
|
121
|
+
throw failure;
|
|
122
|
+
})
|
|
123
|
+
.finally(() => {
|
|
124
|
+
this.inflight = null;
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
if (!stillLive) return this.inflight;
|
|
128
|
+
return this.inflight.catch(() => stillLive);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/** Forget the cached token (the hub answered 401 to it) and the held failure. */
|
|
132
|
+
invalidate(): void {
|
|
133
|
+
this.cached = null;
|
|
134
|
+
this.lastFailure = null;
|
|
135
|
+
this.failedUntilMs = 0;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
private async mint(): Promise<string> {
|
|
139
|
+
this.mints += 1;
|
|
140
|
+
const body = new URLSearchParams({
|
|
141
|
+
grant_type: 'client_credentials',
|
|
142
|
+
scope: HUB_USAGE_SCOPE,
|
|
143
|
+
resource: this.opts.resource
|
|
144
|
+
});
|
|
145
|
+
const basic = Buffer.from(`${this.opts.clientId}:${this.opts.clientSecret}`).toString('base64');
|
|
146
|
+
let res: Response;
|
|
147
|
+
try {
|
|
148
|
+
res = await this.fetchImpl(this.tokenUrl, {
|
|
149
|
+
method: 'POST',
|
|
150
|
+
headers: {
|
|
151
|
+
'content-type': 'application/x-www-form-urlencoded',
|
|
152
|
+
accept: 'application/json',
|
|
153
|
+
authorization: `Basic ${basic}`
|
|
154
|
+
},
|
|
155
|
+
body: body.toString(),
|
|
156
|
+
signal: AbortSignal.timeout(this.timeoutMs)
|
|
157
|
+
});
|
|
158
|
+
} catch (err) {
|
|
159
|
+
this.opts.logger.warn({ err }, 'hub machine token: token endpoint unreachable');
|
|
160
|
+
throw new HubMachineTokenError('hub token endpoint unreachable', 'transient');
|
|
161
|
+
}
|
|
162
|
+
let payload: unknown = null;
|
|
163
|
+
try {
|
|
164
|
+
payload = await res.json();
|
|
165
|
+
} catch {
|
|
166
|
+
// classified by status below
|
|
167
|
+
}
|
|
168
|
+
if (res.ok) {
|
|
169
|
+
const p = (payload ?? {}) as { access_token?: unknown; expires_in?: unknown };
|
|
170
|
+
if (typeof p.access_token !== 'string' || !p.access_token) {
|
|
171
|
+
this.opts.logger.warn('hub machine token: token endpoint 200 without an access_token — malformed');
|
|
172
|
+
throw new HubMachineTokenError('malformed token answer', 'transient');
|
|
173
|
+
}
|
|
174
|
+
const expiresInS =
|
|
175
|
+
typeof p.expires_in === 'number' && Number.isFinite(p.expires_in) && p.expires_in > 0
|
|
176
|
+
? p.expires_in
|
|
177
|
+
: 300;
|
|
178
|
+
this.cached = { token: p.access_token, expiresAtMs: this.now() + expiresInS * 1000 };
|
|
179
|
+
return p.access_token;
|
|
180
|
+
}
|
|
181
|
+
const error = (payload as { error?: unknown } | null)?.error;
|
|
182
|
+
// The hub's config-shaped refusals (PRDCT-2625): a wrong secret, a client
|
|
183
|
+
// outside the registry, a scope that is not exactly usage:write, a
|
|
184
|
+
// resource that is not the hub's. None heals by retrying; every one is
|
|
185
|
+
// an operator's error, logged at error level.
|
|
186
|
+
if (
|
|
187
|
+
(res.status === 400 || res.status === 401) &&
|
|
188
|
+
(error === 'invalid_client' ||
|
|
189
|
+
error === 'unauthorized_client' ||
|
|
190
|
+
error === 'invalid_scope' ||
|
|
191
|
+
error === 'invalid_target')
|
|
192
|
+
) {
|
|
193
|
+
this.opts.logger.error(
|
|
194
|
+
{ error },
|
|
195
|
+
'hub machine token: the hub refuses this client — check HUB_CLIENT_ID/HUB_CLIENT_SECRET, the client\u2019s registry entry and its client_credentials grant'
|
|
196
|
+
);
|
|
197
|
+
throw new HubMachineTokenError(String(error), 'invalid_client');
|
|
198
|
+
}
|
|
199
|
+
this.opts.logger.warn(
|
|
200
|
+
{ status: res.status, error },
|
|
201
|
+
'hub machine token: token endpoint answered non-2xx'
|
|
202
|
+
);
|
|
203
|
+
throw new HubMachineTokenError(`token endpoint answered ${res.status}`, 'transient');
|
|
204
|
+
}
|
|
205
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { and, eq } from 'drizzle-orm';
|
|
2
|
+
import { account, type Db } from '@antasphere/chassis-db';
|
|
3
|
+
import { HUB_SSO_PROVIDER_ID } from '../identity/hub-sso.js';
|
|
4
|
+
|
|
5
|
+
/** How many users the cache may hold before a miss sweeps the expired entries. */
|
|
6
|
+
const SWEEP_ABOVE = 5_000;
|
|
7
|
+
|
|
8
|
+
/** The cache behind `hubSubjectResolver`, exported so its bound can be tested without a database. */
|
|
9
|
+
export interface HubSubjectCache {
|
|
10
|
+
resolve(userId: string): Promise<string | null>;
|
|
11
|
+
readonly size: number;
|
|
12
|
+
clear(): void;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* A per-user TTL cache over a hub-subject lookup. A miss (a user with no hub
|
|
17
|
+
* link) is cached too. The map used to be unbounded: on a long-lived replica
|
|
18
|
+
* serving many API-key principals it held one entry per distinct user that
|
|
19
|
+
* ever performed a metered action, for the life of the process (PRDCT-2654).
|
|
20
|
+
* It is now swept the way `profiles.ts` sweeps its plan cache: whenever it
|
|
21
|
+
* holds more than `sweepAbove` entries at the moment a miss is about to be
|
|
22
|
+
* written, every expired entry (`until <= now`) is deleted first.
|
|
23
|
+
*/
|
|
24
|
+
export function hubSubjectCache(
|
|
25
|
+
lookup: (userId: string) => Promise<string | null>,
|
|
26
|
+
opts: { ttlMs?: number; sweepAbove?: number; now?: () => number } = {}
|
|
27
|
+
): HubSubjectCache {
|
|
28
|
+
const ttlMs = opts.ttlMs ?? 5 * 60_000;
|
|
29
|
+
const sweepAbove = opts.sweepAbove ?? SWEEP_ABOVE;
|
|
30
|
+
const now = opts.now ?? Date.now;
|
|
31
|
+
const cache = new Map<string, { sub: string | null; until: number }>();
|
|
32
|
+
const sweep = (at: number): void => {
|
|
33
|
+
if (cache.size <= sweepAbove) return;
|
|
34
|
+
for (const [key, entry] of cache) {
|
|
35
|
+
if (entry.until <= at) cache.delete(key);
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
return {
|
|
39
|
+
async resolve(userId) {
|
|
40
|
+
const hit = cache.get(userId);
|
|
41
|
+
if (hit && hit.until > now()) return hit.sub;
|
|
42
|
+
const sub = await lookup(userId);
|
|
43
|
+
const at = now();
|
|
44
|
+
sweep(at);
|
|
45
|
+
cache.set(userId, { sub, until: at + ttlMs });
|
|
46
|
+
return sub;
|
|
47
|
+
},
|
|
48
|
+
get size() {
|
|
49
|
+
return cache.size;
|
|
50
|
+
},
|
|
51
|
+
clear() {
|
|
52
|
+
cache.clear();
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* The hub user behind a local user (the billing rail, PRDCT-2625): a usage
|
|
59
|
+
* event reports the hub's `sub`, which the SSO link stores as the account
|
|
60
|
+
* row's `accountId` under the `antasphere` provider. A user with no hub link
|
|
61
|
+
* (the cloud operator, a local invitee) reports null — the hub would refuse
|
|
62
|
+
* a local id as `unknown_user`. Cached per user for `ttlMs` through
|
|
63
|
+
* `hubSubjectCache` (bounded by its sweep); a miss is cached too (the
|
|
64
|
+
* operator uploads as much as anyone).
|
|
65
|
+
*/
|
|
66
|
+
export function hubSubjectResolver(db: Db, ttlMs = 5 * 60_000): (userId: string) => Promise<string | null> {
|
|
67
|
+
const dbLookup = async (userId: string): Promise<string | null> => {
|
|
68
|
+
const [row] = await db
|
|
69
|
+
.select({ sub: account.accountId })
|
|
70
|
+
.from(account)
|
|
71
|
+
.where(and(eq(account.userId, userId), eq(account.providerId, HUB_SSO_PROVIDER_ID)))
|
|
72
|
+
.limit(1);
|
|
73
|
+
return row?.sub ?? null;
|
|
74
|
+
};
|
|
75
|
+
const cache = hubSubjectCache(dbLookup, { ttlMs });
|
|
76
|
+
return (userId) => cache.resolve(userId);
|
|
77
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export { describeHubClockSkew, hubClockSkewMs } from './clock-skew.js';
|
|
2
|
+
export {
|
|
3
|
+
DEFAULT_CREDIT_CHECK_DIALS,
|
|
4
|
+
HubCreditCheck,
|
|
5
|
+
type CreditCheck,
|
|
6
|
+
type CreditCheckDials,
|
|
7
|
+
type CreditCheckRequest,
|
|
8
|
+
type CreditVerdict,
|
|
9
|
+
type HubCreditCheckOptions
|
|
10
|
+
} from './check.js';
|
|
11
|
+
export {
|
|
12
|
+
entitlementGate,
|
|
13
|
+
honoPath,
|
|
14
|
+
isDeferringGate,
|
|
15
|
+
registerEntitlementGate,
|
|
16
|
+
type EntitlementCloud,
|
|
17
|
+
type EntitlementGateDeps
|
|
18
|
+
} from './gate.js';
|
|
19
|
+
export {
|
|
20
|
+
DEFAULT_FAILURE_HOLD_MS,
|
|
21
|
+
DEFAULT_INVALID_CLIENT_HOLD_MS,
|
|
22
|
+
HUB_USAGE_SCOPE,
|
|
23
|
+
HubMachineToken,
|
|
24
|
+
HubMachineTokenError,
|
|
25
|
+
type HubMachineTokenOptions
|
|
26
|
+
} from './hub-machine-token.js';
|
|
27
|
+
export { hubSubjectResolver } from './hub-subject.js';
|
|
28
|
+
export { HubUsagePoster, HubUsagePostError, type HubUsagePosterOptions } from './poster.js';
|
|
29
|
+
export {
|
|
30
|
+
DEFAULT_PROFILE_DIALS,
|
|
31
|
+
EntitlementProfiles,
|
|
32
|
+
defaultProfile,
|
|
33
|
+
type EntitlementProfileDials,
|
|
34
|
+
type EntitlementProfilesOptions,
|
|
35
|
+
type ResolvedProfile
|
|
36
|
+
} from './profiles.js';
|
|
37
|
+
export { assertToolEntitlements, EMPTY_TOOL_ENTITLEMENTS, type ToolEntitlementDeclaration } from './slot.js';
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
import { Counter } from 'prom-client';
|
|
2
|
+
import {
|
|
3
|
+
usageEventOccurrenceIssue,
|
|
4
|
+
usageIngestResultSchema,
|
|
5
|
+
type UsageBatchOutcome,
|
|
6
|
+
type UsageDownstream,
|
|
7
|
+
type UsageEvent
|
|
8
|
+
} from '@antasphere/chassis-contract';
|
|
9
|
+
import type { Logger } from '../logger.js';
|
|
10
|
+
import { HubMachineTokenError, type HubMachineToken } from './hub-machine-token.js';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* The cloud edition's usage downstream (the billing rail spec, §6): the
|
|
14
|
+
* batches the durable queue drains are posted to the hub's
|
|
15
|
+
* `POST /api/v1/usage/events` with the machine token. At-least-once stays
|
|
16
|
+
* the posture — the hub dedupes by ULID and answers per event
|
|
17
|
+
* (`accepted | duplicate | rejected`); anything that is not an answer is a
|
|
18
|
+
* throw, which fails the batch back to the queue for its retry.
|
|
19
|
+
*
|
|
20
|
+
* What is retried and what is not:
|
|
21
|
+
* - unreachable, timeout, 404 (an older hub that has no ingest yet), 408,
|
|
22
|
+
* 429, 5xx: an OUTAGE — thrown, retried by the queue, never data loss;
|
|
23
|
+
* - 401: the token is dead — invalidated and minted again ONCE, then an
|
|
24
|
+
* outage;
|
|
25
|
+
* - 400/422: the batch is malformed — a bug, never healed by a retry:
|
|
26
|
+
* logged at error level and dropped, per the events' ids;
|
|
27
|
+
* - 403 (a client without the scope): logged at error level and treated as
|
|
28
|
+
* an outage, so the events wait for the operator rather than vanish;
|
|
29
|
+
* - a 2xx whose body is not the hub's answer: an OUTAGE too (PRDCT-2629) —
|
|
30
|
+
* a proxy page or a hub regression must never read as delivery;
|
|
31
|
+
* - a `rejected` event in a 2xx answer: logged with the hub's reason and
|
|
32
|
+
* dropped (the hub judged it).
|
|
33
|
+
*
|
|
34
|
+
* The queue's retry budget never ends in a loss: a batch it gives up on is
|
|
35
|
+
* held and re-driven hourly (jobs/pgboss.ts, PRDCT-2635). What the poster
|
|
36
|
+
* saw is counted on /metrics (`usage_poster_events_total` per event
|
|
37
|
+
* outcome, `usage_poster_batches_total` per batch outcome), so a rail that
|
|
38
|
+
* rejects everything is one scrape away, not one log line nobody reads.
|
|
39
|
+
*
|
|
40
|
+
* The date window (PRDCT-2644): the hub refuses an event whose `occurredAt`
|
|
41
|
+
* is more than seven days before, or more than five minutes after, the
|
|
42
|
+
* moment it receives it (`usageEventOccurrenceIssue`, the hub's rule
|
|
43
|
+
* mirrored in the contract), as `rejected(invalid_event)` and never clamped.
|
|
44
|
+
* So the poster runs the same comparison against its own clock BEFORE
|
|
45
|
+
* posting: an event outside the window is never posted, it is HELD, named in
|
|
46
|
+
* the batch outcome (`{ held: [{ id, reason: 'occurred_at_window' }] }`),
|
|
47
|
+
* logged at warn and counted (`outcome="held"`). The usage worker re-sends
|
|
48
|
+
* each held event to the held queue (jobs/pgboss.ts), which re-drives it
|
|
49
|
+
* after the hold, forever: a future-dated event is posted once its time has
|
|
50
|
+
* come, a stale one stays held until an operator acts. An event the hub
|
|
51
|
+
* would refuse on its date is handled where it was made, never counted as a
|
|
52
|
+
* rejection at the hub.
|
|
53
|
+
*/
|
|
54
|
+
export interface HubUsagePosterOptions {
|
|
55
|
+
issuerUrl: string;
|
|
56
|
+
token: HubMachineToken;
|
|
57
|
+
logger: Logger;
|
|
58
|
+
timeoutMs?: number | undefined;
|
|
59
|
+
fetchImpl?: typeof fetch | undefined;
|
|
60
|
+
/** The clock the date window is judged against (a test seam; `Date.now` by default). */
|
|
61
|
+
now?: (() => number) | undefined;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** The reason a held event carries when its `occurredAt` falls outside the hub's window. */
|
|
65
|
+
export const OCCURRED_AT_WINDOW = 'occurred_at_window';
|
|
66
|
+
|
|
67
|
+
export class HubUsagePostError extends Error {
|
|
68
|
+
constructor(message: string) {
|
|
69
|
+
super(message);
|
|
70
|
+
this.name = 'HubUsagePostError';
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
interface EventResult {
|
|
75
|
+
/** null when the element did not parse at all (`invalid_event`). */
|
|
76
|
+
id: string | null;
|
|
77
|
+
status: 'accepted' | 'duplicate' | 'rejected';
|
|
78
|
+
reason?: string | undefined;
|
|
79
|
+
details?: unknown;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** The sentinel `results()` answers for a batch the hub refused as malformed: dropped whole, never retried. */
|
|
83
|
+
const DROPPED: EventResult[] = [];
|
|
84
|
+
|
|
85
|
+
export class HubUsagePoster implements UsageDownstream {
|
|
86
|
+
private readonly url: string;
|
|
87
|
+
private readonly fetchImpl: typeof fetch;
|
|
88
|
+
private readonly timeoutMs: number;
|
|
89
|
+
private readonly now: () => number;
|
|
90
|
+
/** How many POSTs reached the hub — a test seam. */
|
|
91
|
+
posts = 0;
|
|
92
|
+
/** Per-event outcomes as the hub answered them (or as the poster dropped or held them). */
|
|
93
|
+
readonly events: Counter<'outcome'>;
|
|
94
|
+
/** Per-batch outcomes: delivered (a hub answer read), retried (an outage), dropped (malformed). */
|
|
95
|
+
readonly batches: Counter<'outcome'>;
|
|
96
|
+
/** The counters boot registers on the app's /metrics registry (cloud only: oss never builds a poster). */
|
|
97
|
+
readonly promMetrics: Counter<string>[];
|
|
98
|
+
|
|
99
|
+
constructor(private readonly opts: HubUsagePosterOptions) {
|
|
100
|
+
this.url = opts.issuerUrl.replace(/\/+$/, '') + '/api/v1/usage/events';
|
|
101
|
+
this.fetchImpl = opts.fetchImpl ?? globalThis.fetch.bind(globalThis);
|
|
102
|
+
this.timeoutMs = opts.timeoutMs ?? 15_000;
|
|
103
|
+
this.now = opts.now ?? Date.now;
|
|
104
|
+
// registers: [] — boot attaches these to the app registry; a test builds
|
|
105
|
+
// a poster without one.
|
|
106
|
+
this.events = new Counter({
|
|
107
|
+
name: 'usage_poster_events_total',
|
|
108
|
+
help: 'Usage events by outcome: accepted, duplicate, rejected (the hub judged it), dropped (malformed batch), held (outside the hub’s occurredAt window, never posted)',
|
|
109
|
+
labelNames: ['outcome'] as const,
|
|
110
|
+
registers: []
|
|
111
|
+
});
|
|
112
|
+
this.batches = new Counter({
|
|
113
|
+
name: 'usage_poster_batches_total',
|
|
114
|
+
help: 'Usage batches by outcome: delivered, retried (an outage, the queue retries), dropped (malformed)',
|
|
115
|
+
labelNames: ['outcome'] as const,
|
|
116
|
+
registers: []
|
|
117
|
+
});
|
|
118
|
+
this.promMetrics = [this.events, this.batches];
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
async emit(event: UsageEvent): Promise<void> {
|
|
122
|
+
await this.emitBatch([event]);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
async emitBatch(all: readonly UsageEvent[]): Promise<UsageBatchOutcome> {
|
|
126
|
+
const held: Array<{ id: string; reason: string }> = [];
|
|
127
|
+
const events: UsageEvent[] = [];
|
|
128
|
+
const receivedAt = new Date(this.now());
|
|
129
|
+
for (const e of all) {
|
|
130
|
+
const issue = usageEventOccurrenceIssue(new Date(e.occurredAt), receivedAt);
|
|
131
|
+
if (issue === null) {
|
|
132
|
+
events.push(e);
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
held.push({ id: e.id, reason: OCCURRED_AT_WINDOW });
|
|
136
|
+
this.opts.logger.warn(
|
|
137
|
+
{ id: e.id, occurredAt: e.occurredAt, issue: issue.message },
|
|
138
|
+
'usage poster: the event’s occurredAt is outside the hub’s window — held, never posted (counted on usage_poster_events_total{outcome="held"})'
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
// Counted once the batch is settled: a post that throws fails the whole
|
|
142
|
+
// batch (the held events' jobs included) back to the queue, whose retry
|
|
143
|
+
// judges them again, and would count them twice if the count came first.
|
|
144
|
+
const outcome = (): UsageBatchOutcome => {
|
|
145
|
+
if (held.length > 0) this.events.inc({ outcome: 'held' }, held.length);
|
|
146
|
+
return { held };
|
|
147
|
+
};
|
|
148
|
+
if (events.length === 0) return outcome();
|
|
149
|
+
let results: EventResult[];
|
|
150
|
+
try {
|
|
151
|
+
const res = await this.post(events, true);
|
|
152
|
+
results = await this.results(res, events);
|
|
153
|
+
} catch (err) {
|
|
154
|
+
this.batches.inc({ outcome: 'retried' });
|
|
155
|
+
throw err;
|
|
156
|
+
}
|
|
157
|
+
if (results === DROPPED) {
|
|
158
|
+
this.batches.inc({ outcome: 'dropped' });
|
|
159
|
+
this.events.inc({ outcome: 'dropped' }, events.length);
|
|
160
|
+
return outcome();
|
|
161
|
+
}
|
|
162
|
+
this.batches.inc({ outcome: 'delivered' });
|
|
163
|
+
for (const r of results) {
|
|
164
|
+
this.events.inc({ outcome: r.status });
|
|
165
|
+
if (r.status === 'rejected') {
|
|
166
|
+
this.opts.logger.error(
|
|
167
|
+
{ id: r.id, reason: r.reason, details: r.details },
|
|
168
|
+
'usage poster: the hub rejected an event — dropped (the hub judged it; counted on usage_poster_events_total{outcome="rejected"})'
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
return outcome();
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
private async post(events: readonly UsageEvent[], retryOn401: boolean): Promise<Response> {
|
|
176
|
+
let token: string;
|
|
177
|
+
try {
|
|
178
|
+
token = await this.opts.token.get();
|
|
179
|
+
} catch (err) {
|
|
180
|
+
if (err instanceof HubMachineTokenError && err.kind === 'invalid_client') {
|
|
181
|
+
this.opts.logger.error(
|
|
182
|
+
'usage poster: the hub refuses this instance’s client — events wait in the queue'
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
throw new HubUsagePostError('no machine token');
|
|
186
|
+
}
|
|
187
|
+
this.posts += 1;
|
|
188
|
+
let res: Response;
|
|
189
|
+
try {
|
|
190
|
+
res = await this.fetchImpl(this.url, {
|
|
191
|
+
method: 'POST',
|
|
192
|
+
headers: {
|
|
193
|
+
'content-type': 'application/json',
|
|
194
|
+
accept: 'application/json',
|
|
195
|
+
authorization: `Bearer ${token}`
|
|
196
|
+
},
|
|
197
|
+
body: JSON.stringify({ events }),
|
|
198
|
+
signal: AbortSignal.timeout(this.timeoutMs)
|
|
199
|
+
});
|
|
200
|
+
} catch (err) {
|
|
201
|
+
this.opts.logger.warn(
|
|
202
|
+
{ err, count: events.length },
|
|
203
|
+
'usage poster: hub unreachable — the batch is retried'
|
|
204
|
+
);
|
|
205
|
+
throw new HubUsagePostError('hub unreachable');
|
|
206
|
+
}
|
|
207
|
+
if (res.status === 401 && retryOn401) {
|
|
208
|
+
this.opts.token.invalidate();
|
|
209
|
+
return this.post(events, false);
|
|
210
|
+
}
|
|
211
|
+
return res;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
private async results(res: Response, events: readonly UsageEvent[]): Promise<EventResult[]> {
|
|
215
|
+
if (res.ok) {
|
|
216
|
+
const parsed = usageIngestResultSchema.safeParse(await res.json().catch(() => null));
|
|
217
|
+
if (!parsed.success) {
|
|
218
|
+
// Not the hub's answer (a proxy page, a hub regression): an outage,
|
|
219
|
+
// never a delivery — the batch is retried (PRDCT-2629).
|
|
220
|
+
this.opts.logger.warn(
|
|
221
|
+
{ status: res.status },
|
|
222
|
+
'usage poster: the hub answered 2xx without readable results — the batch is retried'
|
|
223
|
+
);
|
|
224
|
+
throw new HubUsagePostError('hub answered 2xx without readable results');
|
|
225
|
+
}
|
|
226
|
+
// One answer per posted element, in the batch's order (the hub's
|
|
227
|
+
// contract): a shorter or longer answer is not the hub's, and a tail
|
|
228
|
+
// it does not name would otherwise be neither counted nor retried.
|
|
229
|
+
if (parsed.data.results.length !== events.length) {
|
|
230
|
+
this.opts.logger.warn(
|
|
231
|
+
{ answered: parsed.data.results.length, posted: events.length },
|
|
232
|
+
'usage poster: the hub answered for a different number of events than posted — the batch is retried'
|
|
233
|
+
);
|
|
234
|
+
throw new HubUsagePostError('hub answered a different number of events than posted');
|
|
235
|
+
}
|
|
236
|
+
return parsed.data.results;
|
|
237
|
+
}
|
|
238
|
+
if (res.status === 400 || res.status === 422) {
|
|
239
|
+
const body = await res.text().catch(() => '');
|
|
240
|
+
this.opts.logger.error(
|
|
241
|
+
{ status: res.status, ids: events.map((e) => e.id), body: body.slice(0, 500) },
|
|
242
|
+
'usage poster: the hub refused the batch as malformed — dropped (a bug, never healed by a retry; counted on usage_poster_events_total{outcome="dropped"})'
|
|
243
|
+
);
|
|
244
|
+
return DROPPED;
|
|
245
|
+
}
|
|
246
|
+
if (res.status === 403) {
|
|
247
|
+
this.opts.logger.error(
|
|
248
|
+
{ status: res.status },
|
|
249
|
+
'usage poster: the hub refuses this client (403) — check the client’s usage:write scope; the batch is retried'
|
|
250
|
+
);
|
|
251
|
+
throw new HubUsagePostError('hub answered 403');
|
|
252
|
+
}
|
|
253
|
+
if (res.status === 404) {
|
|
254
|
+
this.opts.logger.warn(
|
|
255
|
+
'usage poster: the hub has no usage ingest yet (404) — the batch is retried; the hub deploys first'
|
|
256
|
+
);
|
|
257
|
+
throw new HubUsagePostError('hub answered 404');
|
|
258
|
+
}
|
|
259
|
+
this.opts.logger.warn(
|
|
260
|
+
{ status: res.status },
|
|
261
|
+
'usage poster: the hub answered non-2xx — the batch is retried'
|
|
262
|
+
);
|
|
263
|
+
throw new HubUsagePostError(`hub answered ${res.status}`);
|
|
264
|
+
}
|
|
265
|
+
}
|