@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,542 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import type { UsageEvent } from '@antasphere/chassis-contract';
|
|
3
|
+
import { HubMachineToken, HubUsagePoster } from '@antasphere/chassis-server';
|
|
4
|
+
import { DEFAULT_FAILURE_HOLD_MS, DEFAULT_INVALID_CLIENT_HOLD_MS } from '../../src/entitlements/index.js';
|
|
5
|
+
import { DEFAULT_USAGE_RETRY, PgBossUsageSink } from '../../src/jobs/index.js';
|
|
6
|
+
import type { Logger } from '@antasphere/chassis-server/logger';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* The cloud edition's usage downstream against a scripted hub (the billing
|
|
10
|
+
* rail spec, §6): the machine token is minted once and shared, a dead token
|
|
11
|
+
* is minted again exactly once, an outage (404, 5xx, network, 403) fails the
|
|
12
|
+
* batch back to the queue, a malformed batch (400) and a rejected event are
|
|
13
|
+
* dropped with an error log, a 2xx that is not the hub's answer is an outage
|
|
14
|
+
* (PRDCT-2629), every outcome is counted (PRDCT-2635), the hub's per-event
|
|
15
|
+
* answer is read, and a configuration-error mint is held minutes, a
|
|
16
|
+
* transient one seconds (PRDCT-2637).
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
const logs: Array<{ level: string; msg: string; ctx?: unknown }> = [];
|
|
20
|
+
const logger = {
|
|
21
|
+
error: (ctx: unknown, msg?: string) => logs.push({ level: 'error', msg: msg ?? String(ctx), ctx }),
|
|
22
|
+
warn: (ctx: unknown, msg?: string) => logs.push({ level: 'warn', msg: msg ?? String(ctx), ctx }),
|
|
23
|
+
info: () => {},
|
|
24
|
+
debug: () => {}
|
|
25
|
+
} as unknown as Logger;
|
|
26
|
+
|
|
27
|
+
const ISSUER = 'https://hub.test';
|
|
28
|
+
|
|
29
|
+
function event(id: string, occurredAt: Date = new Date()): UsageEvent {
|
|
30
|
+
return {
|
|
31
|
+
id,
|
|
32
|
+
meter: 'things.make',
|
|
33
|
+
actionKey: 'things.make',
|
|
34
|
+
quantity: 1,
|
|
35
|
+
unit: 'call',
|
|
36
|
+
occurredAt: occurredAt.toISOString(),
|
|
37
|
+
workspaceId: 'ws-1',
|
|
38
|
+
accountRef: 'acct-1',
|
|
39
|
+
userId: 'user-1',
|
|
40
|
+
via: 'api_key',
|
|
41
|
+
source: { instanceId: 'inst', edition: 'cloud', version: 'test' }
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
type Script = (url: string, init: RequestInit) => Response | Promise<Response>;
|
|
46
|
+
|
|
47
|
+
/** A hub as a script: the token endpoint always mints; the events endpoint answers per the script. */
|
|
48
|
+
function hub(script: Script, now?: () => number) {
|
|
49
|
+
const calls: Array<{ url: string; init: RequestInit }> = [];
|
|
50
|
+
let mints = 0;
|
|
51
|
+
const fetchImpl = (async (input: string | URL | Request, init?: RequestInit) => {
|
|
52
|
+
const url = String(input);
|
|
53
|
+
calls.push({ url, init: init ?? {} });
|
|
54
|
+
if (url.endsWith('/api/v1/auth/oauth2/token')) {
|
|
55
|
+
mints += 1;
|
|
56
|
+
expect(init?.headers).toMatchObject({ authorization: expect.stringMatching(/^Basic /) });
|
|
57
|
+
expect(String(init?.body)).toContain('grant_type=client_credentials');
|
|
58
|
+
expect(String(init?.body)).toContain('scope=usage%3Awrite');
|
|
59
|
+
expect(String(init?.body)).toContain(`resource=${encodeURIComponent(`${ISSUER}/mcp`)}`);
|
|
60
|
+
return Response.json({ access_token: `mach_${mints}`, token_type: 'Bearer', expires_in: 3600 });
|
|
61
|
+
}
|
|
62
|
+
return script(url, init ?? {});
|
|
63
|
+
}) as typeof fetch;
|
|
64
|
+
const token = new HubMachineToken({
|
|
65
|
+
issuerUrl: ISSUER,
|
|
66
|
+
clientId: 'tool-things',
|
|
67
|
+
clientSecret: 's'.repeat(20),
|
|
68
|
+
resource: `${ISSUER}/mcp`,
|
|
69
|
+
logger,
|
|
70
|
+
fetchImpl
|
|
71
|
+
});
|
|
72
|
+
const poster = new HubUsagePoster({ issuerUrl: ISSUER, token, logger, fetchImpl, now });
|
|
73
|
+
return {
|
|
74
|
+
poster,
|
|
75
|
+
token,
|
|
76
|
+
calls,
|
|
77
|
+
mints: () => mints,
|
|
78
|
+
posts: () => calls.filter((c) => c.url.endsWith('/usage/events'))
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** The hub's answer accepting every id, with the three counts the hub always sends (required since PRDCT-2677). */
|
|
83
|
+
const accepted = (ids: string[]) =>
|
|
84
|
+
Response.json({
|
|
85
|
+
results: ids.map((id) => ({ id, status: 'accepted' })),
|
|
86
|
+
accepted: ids.length,
|
|
87
|
+
duplicate: 0,
|
|
88
|
+
rejected: 0
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
/** A prom-client counter's values by its `outcome` label, zero rows omitted. */
|
|
92
|
+
async function counts(counter: {
|
|
93
|
+
get(): Promise<{ values: Array<{ labels: Record<string, unknown>; value: number }> }>;
|
|
94
|
+
}) {
|
|
95
|
+
const out: Record<string, number> = {};
|
|
96
|
+
for (const v of (await counter.get()).values) if (v.value > 0) out[String(v.labels.outcome)] = v.value;
|
|
97
|
+
return out;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
describe('HubUsagePoster', () => {
|
|
101
|
+
it('posts the batch whole with the machine token, and two batches share one mint', async () => {
|
|
102
|
+
const h = hub((_url, init) => {
|
|
103
|
+
const body = JSON.parse(String(init.body)) as { events: UsageEvent[] };
|
|
104
|
+
return accepted(body.events.map((e) => e.id));
|
|
105
|
+
});
|
|
106
|
+
await Promise.all([h.poster.emitBatch([event('a'), event('b')]), h.poster.emitBatch([event('c')])]);
|
|
107
|
+
expect(h.mints()).toBe(1);
|
|
108
|
+
const posts = h.posts();
|
|
109
|
+
expect(posts).toHaveLength(2);
|
|
110
|
+
expect(posts[0]!.init.headers).toMatchObject({ authorization: 'Bearer mach_1' });
|
|
111
|
+
const bodies = posts.map((p) =>
|
|
112
|
+
(JSON.parse(String(p.init.body)) as { events: UsageEvent[] }).events.map((e) => e.id)
|
|
113
|
+
);
|
|
114
|
+
expect(bodies.flat().sort()).toEqual(['a', 'b', 'c']);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it('a 401 invalidates the token and re-mints exactly once', async () => {
|
|
118
|
+
let first = true;
|
|
119
|
+
const h = hub((_url, init) => {
|
|
120
|
+
if (first) {
|
|
121
|
+
first = false;
|
|
122
|
+
return Response.json({ error: { code: 'invalid_token' } }, { status: 401 });
|
|
123
|
+
}
|
|
124
|
+
const body = JSON.parse(String(init.body)) as { events: UsageEvent[] };
|
|
125
|
+
return accepted(body.events.map((e) => e.id));
|
|
126
|
+
});
|
|
127
|
+
await h.poster.emitBatch([event('a')]);
|
|
128
|
+
expect(h.mints()).toBe(2);
|
|
129
|
+
expect(h.posts()).toHaveLength(2);
|
|
130
|
+
expect(h.posts()[1]!.init.headers).toMatchObject({ authorization: 'Bearer mach_2' });
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it('a second 401 in a row is an outage, not a mint loop', async () => {
|
|
134
|
+
const h = hub(() => Response.json({ error: { code: 'invalid_token' } }, { status: 401 }));
|
|
135
|
+
await expect(h.poster.emitBatch([event('a')])).rejects.toThrow(/401/);
|
|
136
|
+
expect(h.mints()).toBe(2);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it.each([404, 408, 429, 500, 503])('a %s fails the batch back to the queue', async (status) => {
|
|
140
|
+
const h = hub(() => Response.json({ error: { code: 'x' } }, { status }));
|
|
141
|
+
await expect(h.poster.emitBatch([event('a')])).rejects.toThrow(String(status));
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it('an unreachable hub fails the batch back to the queue', async () => {
|
|
145
|
+
const h = hub(() => {
|
|
146
|
+
throw new TypeError('fetch failed');
|
|
147
|
+
});
|
|
148
|
+
await expect(h.poster.emitBatch([event('a')])).rejects.toThrow(/unreachable/);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it('a 403 is an outage the operator must see, never a drop', async () => {
|
|
152
|
+
logs.length = 0;
|
|
153
|
+
const h = hub(() => Response.json({ error: { code: 'insufficient_scope' } }, { status: 403 }));
|
|
154
|
+
await expect(h.poster.emitBatch([event('a')])).rejects.toThrow(/403/);
|
|
155
|
+
expect(logs.some((l) => l.level === 'error' && /usage:write/.test(l.msg))).toBe(true);
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it('a 400 drops the batch with an error naming the ids (a retry could never heal it)', async () => {
|
|
159
|
+
logs.length = 0;
|
|
160
|
+
const h = hub(() => Response.json({ error: { code: 'validation_error' } }, { status: 400 }));
|
|
161
|
+
await expect(h.poster.emitBatch([event('a'), event('b')])).resolves.toEqual({ held: [] });
|
|
162
|
+
const dropped = logs.find((l) => l.level === 'error' && /malformed/.test(l.msg));
|
|
163
|
+
expect(dropped?.ctx).toMatchObject({ ids: ['a', 'b'] });
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
it('reads the per-event answer: duplicates are fine, a rejection is logged and dropped', async () => {
|
|
167
|
+
logs.length = 0;
|
|
168
|
+
const h = hub(() =>
|
|
169
|
+
Response.json({
|
|
170
|
+
results: [
|
|
171
|
+
{ id: 'a', status: 'duplicate' },
|
|
172
|
+
{ id: 'b', status: 'rejected', reason: 'unknown_account' },
|
|
173
|
+
{ id: null, status: 'rejected', reason: 'invalid_event', details: [{ path: 'occurredAt' }] }
|
|
174
|
+
],
|
|
175
|
+
accepted: 0,
|
|
176
|
+
duplicate: 1,
|
|
177
|
+
rejected: 2
|
|
178
|
+
})
|
|
179
|
+
);
|
|
180
|
+
await expect(h.poster.emitBatch([event('a'), event('b'), event('c')])).resolves.toEqual({ held: [] });
|
|
181
|
+
const rejected = logs.filter((l) => l.level === 'error' && /rejected/.test(l.msg));
|
|
182
|
+
expect(rejected.map((l) => l.ctx)).toEqual([
|
|
183
|
+
expect.objectContaining({ id: 'b', reason: 'unknown_account' }),
|
|
184
|
+
expect.objectContaining({ id: null, reason: 'invalid_event' })
|
|
185
|
+
]);
|
|
186
|
+
// Every outcome is counted where an operator can see it (PRDCT-2635).
|
|
187
|
+
expect(await counts(h.poster.events)).toEqual({ duplicate: 1, rejected: 2 });
|
|
188
|
+
expect(await counts(h.poster.batches)).toEqual({ delivered: 1 });
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
it('a 2xx that is not the hub’s answer is an outage the queue retries, never a delivery (PRDCT-2629)', async () => {
|
|
192
|
+
logs.length = 0;
|
|
193
|
+
const h = hub(() => new Response('<html>a proxy page</html>', { status: 200 }));
|
|
194
|
+
await expect(h.poster.emitBatch([event('a')])).rejects.toThrow(/readable results/);
|
|
195
|
+
expect(logs.some((l) => l.level === 'warn' && /retried/.test(l.msg))).toBe(true);
|
|
196
|
+
expect(await counts(h.poster.batches)).toEqual({ retried: 1 });
|
|
197
|
+
expect(await counts(h.poster.events)).toEqual({});
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
it('a hub answer with fewer results than events posted is an outage too, never a partial delivery', async () => {
|
|
201
|
+
const h = hub(() => accepted(['a']));
|
|
202
|
+
await expect(h.poster.emitBatch([event('a'), event('b')])).rejects.toThrow(/different number/);
|
|
203
|
+
expect(await counts(h.poster.batches)).toEqual({ retried: 1 });
|
|
204
|
+
expect(await counts(h.poster.events)).toEqual({});
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
it('counts a dropped batch per event and a retried outage per batch', async () => {
|
|
208
|
+
const dropped = hub(() => Response.json({ error: { code: 'validation_error' } }, { status: 400 }));
|
|
209
|
+
await dropped.poster.emitBatch([event('a'), event('b'), event('c')]);
|
|
210
|
+
expect(await counts(dropped.poster.events)).toEqual({ dropped: 3 });
|
|
211
|
+
expect(await counts(dropped.poster.batches)).toEqual({ dropped: 1 });
|
|
212
|
+
const outage = hub(() => Response.json({ error: { code: 'internal' } }, { status: 503 }));
|
|
213
|
+
await expect(outage.poster.emitBatch([event('a')])).rejects.toThrow(/503/);
|
|
214
|
+
expect(await counts(outage.poster.batches)).toEqual({ retried: 1 });
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
it.each(['invalid_client', 'unauthorized_client', 'invalid_scope', 'invalid_target'])(
|
|
218
|
+
'a mint the hub refuses as %s is an outage with an error log, never a loop',
|
|
219
|
+
async (error) => {
|
|
220
|
+
logs.length = 0;
|
|
221
|
+
const fetchImpl = (async () => Response.json({ error }, { status: 400 })) as unknown as typeof fetch;
|
|
222
|
+
const token = new HubMachineToken({
|
|
223
|
+
issuerUrl: ISSUER,
|
|
224
|
+
clientId: 'tool-things',
|
|
225
|
+
clientSecret: 's'.repeat(20),
|
|
226
|
+
resource: `${ISSUER}/mcp`,
|
|
227
|
+
logger,
|
|
228
|
+
fetchImpl
|
|
229
|
+
});
|
|
230
|
+
const poster = new HubUsagePoster({ issuerUrl: ISSUER, token, logger, fetchImpl });
|
|
231
|
+
await expect(poster.emitBatch([event('a')])).rejects.toThrow(/no machine token/);
|
|
232
|
+
expect(logs.some((l) => l.level === 'error' && /HUB_CLIENT_ID/.test(l.msg))).toBe(true);
|
|
233
|
+
}
|
|
234
|
+
);
|
|
235
|
+
|
|
236
|
+
it('an empty batch touches nothing', async () => {
|
|
237
|
+
const h = hub(() => {
|
|
238
|
+
throw new Error('never');
|
|
239
|
+
});
|
|
240
|
+
await h.poster.emitBatch([]);
|
|
241
|
+
expect(h.calls).toHaveLength(0);
|
|
242
|
+
});
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
describe('HubUsagePoster, the hub’s occurredAt window (PRDCT-2644)', () => {
|
|
246
|
+
const NOW = Date.parse('2026-09-23T12:00:00.000Z');
|
|
247
|
+
const MIN = 60_000;
|
|
248
|
+
const DAY = 24 * 60 * MIN;
|
|
249
|
+
const at = (offsetMs: number) => new Date(NOW + offsetMs);
|
|
250
|
+
const echo: Script = (_url, init) => {
|
|
251
|
+
const body = JSON.parse(String(init.body)) as { events: UsageEvent[] };
|
|
252
|
+
return accepted(body.events.map((e) => e.id));
|
|
253
|
+
};
|
|
254
|
+
const postedIds = (h: ReturnType<typeof hub>) =>
|
|
255
|
+
h
|
|
256
|
+
.posts()
|
|
257
|
+
.flatMap((p) => (JSON.parse(String(p.init.body)) as { events: UsageEvent[] }).events.map((e) => e.id));
|
|
258
|
+
|
|
259
|
+
it('an event eight days old is held, never posted: no fetch at all, named in the outcome, counted as held', async () => {
|
|
260
|
+
logs.length = 0;
|
|
261
|
+
const h = hub(echo, () => NOW);
|
|
262
|
+
const outcome = await h.poster.emitBatch([event('stale', at(-8 * DAY))]);
|
|
263
|
+
expect(outcome).toEqual({ held: [{ id: 'stale', reason: 'occurred_at_window' }] });
|
|
264
|
+
expect(h.calls).toHaveLength(0); // neither a mint nor a post
|
|
265
|
+
expect(await counts(h.poster.events)).toEqual({ held: 1 });
|
|
266
|
+
expect(await counts(h.poster.batches)).toEqual({});
|
|
267
|
+
const warn = logs.find((l) => l.level === 'warn' && /window/.test(l.msg));
|
|
268
|
+
expect(warn?.ctx).toMatchObject({
|
|
269
|
+
id: 'stale',
|
|
270
|
+
occurredAt: at(-8 * DAY).toISOString(),
|
|
271
|
+
issue: 'more than 7 days before the hub received the event'
|
|
272
|
+
});
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
it('an event four minutes ahead is posted; one six minutes ahead is held', async () => {
|
|
276
|
+
const h = hub(echo, () => NOW);
|
|
277
|
+
expect(await h.poster.emitBatch([event('soon', at(4 * MIN))])).toEqual({ held: [] });
|
|
278
|
+
expect(postedIds(h)).toEqual(['soon']);
|
|
279
|
+
logs.length = 0;
|
|
280
|
+
expect(await h.poster.emitBatch([event('future', at(6 * MIN))])).toEqual({
|
|
281
|
+
held: [{ id: 'future', reason: 'occurred_at_window' }]
|
|
282
|
+
});
|
|
283
|
+
expect(postedIds(h)).toEqual(['soon']);
|
|
284
|
+
expect(logs.find((l) => l.level === 'warn' && /window/.test(l.msg))?.ctx).toMatchObject({
|
|
285
|
+
issue: 'more than 5 minutes after the hub received the event'
|
|
286
|
+
});
|
|
287
|
+
expect(await counts(h.poster.events)).toEqual({ accepted: 1, held: 1 });
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
it('both bounds are inclusive: exactly seven days before and exactly five minutes after are posted', async () => {
|
|
291
|
+
const h = hub(echo, () => NOW);
|
|
292
|
+
await h.poster.emitBatch([event('edge-past', at(-7 * DAY)), event('edge-future', at(5 * MIN))]);
|
|
293
|
+
expect(postedIds(h)).toEqual(['edge-past', 'edge-future']);
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
it('a mixed batch posts only the in-window events, in order, and holds the rest', async () => {
|
|
297
|
+
const h = hub(echo, () => NOW);
|
|
298
|
+
const outcome = await h.poster.emitBatch([
|
|
299
|
+
event('a', at(-1 * DAY)),
|
|
300
|
+
event('old', at(-8 * DAY)),
|
|
301
|
+
event('b', at(0)),
|
|
302
|
+
event('ahead', at(10 * MIN))
|
|
303
|
+
]);
|
|
304
|
+
expect(postedIds(h)).toEqual(['a', 'b']);
|
|
305
|
+
expect(outcome).toEqual({
|
|
306
|
+
held: [
|
|
307
|
+
{ id: 'old', reason: 'occurred_at_window' },
|
|
308
|
+
{ id: 'ahead', reason: 'occurred_at_window' }
|
|
309
|
+
]
|
|
310
|
+
});
|
|
311
|
+
expect(await counts(h.poster.events)).toEqual({ accepted: 2, held: 2 });
|
|
312
|
+
expect(await counts(h.poster.batches)).toEqual({ delivered: 1 });
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
it('a batch of only stale events makes no POST and answers the outcome', async () => {
|
|
316
|
+
const h = hub(
|
|
317
|
+
() => {
|
|
318
|
+
throw new Error('never posted');
|
|
319
|
+
},
|
|
320
|
+
() => NOW
|
|
321
|
+
);
|
|
322
|
+
const outcome = await h.poster.emitBatch([event('x', at(-30 * DAY)), event('y', at(-8 * DAY))]);
|
|
323
|
+
expect(outcome.held.map((e) => e.id)).toEqual(['x', 'y']);
|
|
324
|
+
expect(h.posts()).toHaveLength(0);
|
|
325
|
+
expect(h.poster.posts).toBe(0);
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
it('a held event is not counted when the rest of its batch meets an outage (the queue’s retry judges it again)', async () => {
|
|
329
|
+
const h = hub(
|
|
330
|
+
() => Response.json({ error: { code: 'internal' } }, { status: 503 }),
|
|
331
|
+
() => NOW
|
|
332
|
+
);
|
|
333
|
+
await expect(h.poster.emitBatch([event('ok', at(0)), event('old', at(-8 * DAY))])).rejects.toThrow(/503/);
|
|
334
|
+
expect(await counts(h.poster.events)).toEqual({});
|
|
335
|
+
expect(postedIds(h)).toEqual(['ok']);
|
|
336
|
+
});
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
describe('HubMachineToken', () => {
|
|
340
|
+
it('refreshes before expiry, single-flight', async () => {
|
|
341
|
+
let now = 1_000_000;
|
|
342
|
+
let mints = 0;
|
|
343
|
+
const fetchImpl = (async () => {
|
|
344
|
+
mints += 1;
|
|
345
|
+
return Response.json({ access_token: `mach_${mints}`, expires_in: 100 });
|
|
346
|
+
}) as unknown as typeof fetch;
|
|
347
|
+
const token = new HubMachineToken({
|
|
348
|
+
issuerUrl: ISSUER,
|
|
349
|
+
clientId: 'tool-things',
|
|
350
|
+
clientSecret: 's'.repeat(20),
|
|
351
|
+
resource: `${ISSUER}/mcp`,
|
|
352
|
+
logger,
|
|
353
|
+
fetchImpl,
|
|
354
|
+
now: () => now,
|
|
355
|
+
refreshSkewMs: 20_000
|
|
356
|
+
});
|
|
357
|
+
expect(await Promise.all([token.get(), token.get(), token.get()])).toEqual([
|
|
358
|
+
'mach_1',
|
|
359
|
+
'mach_1',
|
|
360
|
+
'mach_1'
|
|
361
|
+
]);
|
|
362
|
+
expect(mints).toBe(1);
|
|
363
|
+
now += 85_000; // 15 s left: inside the skew, so the next get mints
|
|
364
|
+
expect(await token.get()).toBe('mach_2');
|
|
365
|
+
expect(mints).toBe(2);
|
|
366
|
+
token.invalidate();
|
|
367
|
+
expect(await token.get()).toBe('mach_3');
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
it('holds a failed mint for the negative-cache window, then asks again; invalidate clears the hold', async () => {
|
|
371
|
+
let now = 1_000_000;
|
|
372
|
+
let attempts = 0;
|
|
373
|
+
let mode: 'down' | 'ok' = 'down';
|
|
374
|
+
const fetchImpl = (async () => {
|
|
375
|
+
attempts += 1;
|
|
376
|
+
if (mode === 'down') throw new TypeError('fetch failed');
|
|
377
|
+
return Response.json({ access_token: `mach_${attempts}`, expires_in: 900 });
|
|
378
|
+
}) as unknown as typeof fetch;
|
|
379
|
+
const token = new HubMachineToken({
|
|
380
|
+
issuerUrl: ISSUER,
|
|
381
|
+
clientId: 'tool-things',
|
|
382
|
+
clientSecret: 's'.repeat(20),
|
|
383
|
+
resource: `${ISSUER}/mcp`,
|
|
384
|
+
logger,
|
|
385
|
+
fetchImpl,
|
|
386
|
+
now: () => now,
|
|
387
|
+
failureHoldMs: 5_000
|
|
388
|
+
});
|
|
389
|
+
await expect(token.get()).rejects.toThrow(/unreachable/);
|
|
390
|
+
await expect(token.get()).rejects.toThrow(/unreachable/);
|
|
391
|
+
expect(attempts).toBe(1); // the second get did not ask the hub
|
|
392
|
+
now += 5_001;
|
|
393
|
+
await expect(token.get()).rejects.toThrow(/unreachable/);
|
|
394
|
+
expect(attempts).toBe(2);
|
|
395
|
+
token.invalidate();
|
|
396
|
+
mode = 'ok';
|
|
397
|
+
expect(await token.get()).toBe('mach_3');
|
|
398
|
+
});
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
describe('HubMachineToken, the two holds (PRDCT-2637)', () => {
|
|
402
|
+
it('a mint the hub refuses as a configuration error is held five minutes, a transient failure five seconds', async () => {
|
|
403
|
+
let now = 1_000_000;
|
|
404
|
+
let attempts = 0;
|
|
405
|
+
let mode: 'invalid_client' | 'down' | 'ok' = 'invalid_client';
|
|
406
|
+
const fetchImpl = (async () => {
|
|
407
|
+
attempts += 1;
|
|
408
|
+
if (mode === 'down') throw new TypeError('fetch failed');
|
|
409
|
+
if (mode === 'invalid_client') return Response.json({ error: 'invalid_client' }, { status: 401 });
|
|
410
|
+
return Response.json({ access_token: `mach_${attempts}`, expires_in: 900 });
|
|
411
|
+
}) as unknown as typeof fetch;
|
|
412
|
+
const token = new HubMachineToken({
|
|
413
|
+
issuerUrl: ISSUER,
|
|
414
|
+
clientId: 'tool-things',
|
|
415
|
+
clientSecret: 's'.repeat(20),
|
|
416
|
+
resource: `${ISSUER}/mcp`,
|
|
417
|
+
logger,
|
|
418
|
+
fetchImpl,
|
|
419
|
+
now: () => now
|
|
420
|
+
});
|
|
421
|
+
expect(DEFAULT_INVALID_CLIENT_HOLD_MS).toBe(5 * 60_000);
|
|
422
|
+
await expect(token.get()).rejects.toThrow(/invalid_client/);
|
|
423
|
+
now += DEFAULT_FAILURE_HOLD_MS + 1; // past the transient hold: still held
|
|
424
|
+
await expect(token.get()).rejects.toThrow(/invalid_client/);
|
|
425
|
+
expect(attempts).toBe(1);
|
|
426
|
+
now += DEFAULT_INVALID_CLIENT_HOLD_MS; // past the configuration hold: asked again
|
|
427
|
+
await expect(token.get()).rejects.toThrow(/invalid_client/);
|
|
428
|
+
expect(attempts).toBe(2);
|
|
429
|
+
// A transient failure keeps its short hold.
|
|
430
|
+
mode = 'down';
|
|
431
|
+
now += DEFAULT_INVALID_CLIENT_HOLD_MS + 1;
|
|
432
|
+
await expect(token.get()).rejects.toThrow(/unreachable/);
|
|
433
|
+
expect(attempts).toBe(3);
|
|
434
|
+
now += DEFAULT_FAILURE_HOLD_MS + 1;
|
|
435
|
+
await expect(token.get()).rejects.toThrow(/unreachable/);
|
|
436
|
+
expect(attempts).toBe(4);
|
|
437
|
+
// invalidate() (a 401 from the hub on a live token) clears either hold.
|
|
438
|
+
mode = 'invalid_client';
|
|
439
|
+
now += DEFAULT_FAILURE_HOLD_MS + 1;
|
|
440
|
+
await expect(token.get()).rejects.toThrow(/invalid_client/);
|
|
441
|
+
token.invalidate();
|
|
442
|
+
mode = 'ok';
|
|
443
|
+
const minted = attempts + 1;
|
|
444
|
+
expect(await token.get()).toBe(`mach_${minted}`);
|
|
445
|
+
});
|
|
446
|
+
});
|
|
447
|
+
|
|
448
|
+
describe('HubMachineToken, the default hold and the skew window', () => {
|
|
449
|
+
it('a token built with no dials holds a failed mint five seconds (the outage posture rests on the default)', async () => {
|
|
450
|
+
let now = 1_000_000;
|
|
451
|
+
let attempts = 0;
|
|
452
|
+
const fetchImpl = (async () => {
|
|
453
|
+
attempts += 1;
|
|
454
|
+
throw new TypeError('fetch failed');
|
|
455
|
+
}) as unknown as typeof fetch;
|
|
456
|
+
const token = new HubMachineToken({
|
|
457
|
+
issuerUrl: ISSUER,
|
|
458
|
+
clientId: 'tool-things',
|
|
459
|
+
clientSecret: 's'.repeat(20),
|
|
460
|
+
resource: `${ISSUER}/mcp`,
|
|
461
|
+
logger,
|
|
462
|
+
fetchImpl,
|
|
463
|
+
now: () => now
|
|
464
|
+
});
|
|
465
|
+
expect(DEFAULT_FAILURE_HOLD_MS).toBe(5_000);
|
|
466
|
+
await expect(token.get()).rejects.toThrow();
|
|
467
|
+
now += 4_999;
|
|
468
|
+
await expect(token.get()).rejects.toThrow();
|
|
469
|
+
expect(attempts).toBe(1);
|
|
470
|
+
now += 2;
|
|
471
|
+
await expect(token.get()).rejects.toThrow();
|
|
472
|
+
expect(attempts).toBe(2);
|
|
473
|
+
});
|
|
474
|
+
|
|
475
|
+
it('a refresh that fails inside the skew window serves the cached token while it is still live at the hub', async () => {
|
|
476
|
+
let now = 1_000_000;
|
|
477
|
+
let mode: 'ok' | 'down' = 'ok';
|
|
478
|
+
let attempts = 0;
|
|
479
|
+
const fetchImpl = (async () => {
|
|
480
|
+
attempts += 1;
|
|
481
|
+
if (mode === 'down') throw new TypeError('fetch failed');
|
|
482
|
+
return Response.json({ access_token: `mach_${attempts}`, expires_in: 900 });
|
|
483
|
+
}) as unknown as typeof fetch;
|
|
484
|
+
const token = new HubMachineToken({
|
|
485
|
+
issuerUrl: ISSUER,
|
|
486
|
+
clientId: 'tool-things',
|
|
487
|
+
clientSecret: 's'.repeat(20),
|
|
488
|
+
resource: `${ISSUER}/mcp`,
|
|
489
|
+
logger,
|
|
490
|
+
fetchImpl,
|
|
491
|
+
now: () => now
|
|
492
|
+
});
|
|
493
|
+
expect(await token.get()).toBe('mach_1');
|
|
494
|
+
mode = 'down';
|
|
495
|
+
now += 850_000; // 50 s of validity left, inside the 60 s skew: a refresh is tried and fails
|
|
496
|
+
expect(await token.get()).toBe('mach_1');
|
|
497
|
+
expect(attempts).toBe(2);
|
|
498
|
+
now += 1_000; // inside the hold: no attempt, the live token is served
|
|
499
|
+
expect(await token.get()).toBe('mach_1');
|
|
500
|
+
expect(attempts).toBe(2);
|
|
501
|
+
now += 60_000; // expired: the failure is what there is
|
|
502
|
+
await expect(token.get()).rejects.toThrow(/unreachable/);
|
|
503
|
+
mode = 'ok';
|
|
504
|
+
now += 5_001;
|
|
505
|
+
const next = attempts + 1;
|
|
506
|
+
expect(await token.get()).toBe(`mach_${next}`);
|
|
507
|
+
});
|
|
508
|
+
});
|
|
509
|
+
|
|
510
|
+
describe('PgBossUsageSink', () => {
|
|
511
|
+
it('sends with the ULID as singleton key and the default retry budget of ten retries from thirty seconds, backing off', async () => {
|
|
512
|
+
const sends: Array<{ queue: string; data: unknown; options: Record<string, unknown> }> = [];
|
|
513
|
+
const boss = {
|
|
514
|
+
send: async (queue: string, data: unknown, options: Record<string, unknown>) => {
|
|
515
|
+
sends.push({ queue, data, options });
|
|
516
|
+
return 'job-1';
|
|
517
|
+
}
|
|
518
|
+
};
|
|
519
|
+
const sink = new PgBossUsageSink(boss as never, logger);
|
|
520
|
+
const e = event('01JZZZZZZZZZZZZZZZZZZZZZZ9');
|
|
521
|
+
await sink.emit(e);
|
|
522
|
+
expect(DEFAULT_USAGE_RETRY).toEqual({ limit: 10, delaySeconds: 30, heldDelaySeconds: 3600 });
|
|
523
|
+
expect(sends).toEqual([
|
|
524
|
+
{
|
|
525
|
+
queue: 'usage-events',
|
|
526
|
+
data: e,
|
|
527
|
+
// The budget's end is a hold (the QUEUE's dead letter, set at install,
|
|
528
|
+
// never named on a send), never a drop (PRDCT-2635).
|
|
529
|
+
options: { singletonKey: e.id, retryLimit: 10, retryDelay: 30, retryBackoff: true }
|
|
530
|
+
}
|
|
531
|
+
]);
|
|
532
|
+
// A shrunk budget (a test seam) is passed through as given.
|
|
533
|
+
const fast = new PgBossUsageSink(boss as never, logger, {
|
|
534
|
+
limit: 2,
|
|
535
|
+
delaySeconds: 1,
|
|
536
|
+
heldDelaySeconds: 1
|
|
537
|
+
});
|
|
538
|
+
await fast.emit(e);
|
|
539
|
+
expect(sends[1]!.options).toMatchObject({ retryLimit: 2, retryDelay: 1 });
|
|
540
|
+
expect(sends[1]!.options).not.toHaveProperty('deadLetter');
|
|
541
|
+
});
|
|
542
|
+
});
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import {
|
|
3
|
+
USAGE_EVENT_MAX_FUTURE_MS,
|
|
4
|
+
USAGE_EVENT_MAX_PAST_MS,
|
|
5
|
+
usageEventOccurrenceIssue
|
|
6
|
+
} from '@antasphere/chassis-contract';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* The pin of the hub's occurredAt window as the chassis copies it
|
|
10
|
+
* (PRDCT-2644): the two bounds, their inclusive edges and the two messages
|
|
11
|
+
* byte for byte. The agreement with the hub's own definitions is the wire
|
|
12
|
+
* check's (PRDCT-2677): `pnpm --filter @antasphere/chassis-contract
|
|
13
|
+
* wire:check`, the `hub-wire` CI job, whose snapshot carries the bounds and
|
|
14
|
+
* the occurrence table on the hub's offsets.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
const DAY = 24 * 60 * 60 * 1000;
|
|
18
|
+
const MIN = 60 * 1000;
|
|
19
|
+
const RECEIVED = new Date('2026-09-23T12:00:00.000Z');
|
|
20
|
+
const at = (offsetMs: number) => new Date(RECEIVED.getTime() + offsetMs);
|
|
21
|
+
|
|
22
|
+
const PAST_MESSAGE = 'more than 7 days before the hub received the event';
|
|
23
|
+
const FUTURE_MESSAGE = 'more than 5 minutes after the hub received the event';
|
|
24
|
+
|
|
25
|
+
describe('the hub’s occurredAt window, mirrored (PRDCT-2644)', () => {
|
|
26
|
+
it('seven days back, five minutes forward', () => {
|
|
27
|
+
expect(USAGE_EVENT_MAX_PAST_MS).toBe(7 * 24 * 60 * 60 * 1000);
|
|
28
|
+
expect(USAGE_EVENT_MAX_FUTURE_MS).toBe(5 * 60 * 1000);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('answers null inside the window, both bounds inclusive', () => {
|
|
32
|
+
expect(usageEventOccurrenceIssue(at(0), RECEIVED)).toBeNull();
|
|
33
|
+
expect(usageEventOccurrenceIssue(at(-7 * DAY), RECEIVED)).toBeNull();
|
|
34
|
+
expect(usageEventOccurrenceIssue(at(5 * MIN), RECEIVED)).toBeNull();
|
|
35
|
+
expect(usageEventOccurrenceIssue(at(-3 * DAY), RECEIVED)).toBeNull();
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('names occurredAt with the two messages, byte for byte, one millisecond past each bound', () => {
|
|
39
|
+
expect(usageEventOccurrenceIssue(at(-7 * DAY - 1), RECEIVED)).toEqual({
|
|
40
|
+
path: 'occurredAt',
|
|
41
|
+
message: PAST_MESSAGE
|
|
42
|
+
});
|
|
43
|
+
expect(usageEventOccurrenceIssue(at(5 * MIN + 1), RECEIVED)).toEqual({
|
|
44
|
+
path: 'occurredAt',
|
|
45
|
+
message: FUTURE_MESSAGE
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
});
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { Hono } from 'hono';
|
|
3
|
+
import { auditMiddleware, type AuditService, type AuditWrite } from '@antasphere/chassis-server/audit';
|
|
4
|
+
import type { Principal } from '@antasphere/chassis-contract';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* A workspace never learns what its members do elsewhere (PRDCT-2444): the
|
|
8
|
+
* generic audit middleware attributes every mutation to the workspace the
|
|
9
|
+
* caller is CURRENTLY in — for POST /workspaces that is the wrong trail, so
|
|
10
|
+
* the path is exempt and the handler writes its one genesis row into the NEW
|
|
11
|
+
* workspace itself. This pins the exemption, and that it is exact.
|
|
12
|
+
*/
|
|
13
|
+
function appWithAudit() {
|
|
14
|
+
const writes: AuditWrite[] = [];
|
|
15
|
+
const audit = {
|
|
16
|
+
write: async (entry: AuditWrite) => {
|
|
17
|
+
writes.push(entry);
|
|
18
|
+
}
|
|
19
|
+
} as unknown as AuditService;
|
|
20
|
+
const app = new Hono();
|
|
21
|
+
app.use('*', async (c, next) => {
|
|
22
|
+
c.set('principal', { userId: 'user-1', workspaceId: 'ws-current', via: 'session' } as Principal);
|
|
23
|
+
await next();
|
|
24
|
+
});
|
|
25
|
+
app.use(
|
|
26
|
+
'*',
|
|
27
|
+
auditMiddleware(audit, () => '127.0.0.1')
|
|
28
|
+
);
|
|
29
|
+
app.post('/api/v1/workspaces', (c) => c.json({ workspace: { id: 'ws-new', name: 'New' } }, 201));
|
|
30
|
+
app.post('/api/v1/workspaces-other', (c) => c.json({ ok: true }, 201));
|
|
31
|
+
return { app, writes };
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
describe('audit middleware and workspace creation', () => {
|
|
35
|
+
it('lands NO generic row in the caller’s current workspace for POST /workspaces', async () => {
|
|
36
|
+
const { app, writes } = appWithAudit();
|
|
37
|
+
const res = await app.request('/api/v1/workspaces', { method: 'POST' });
|
|
38
|
+
expect(res.status).toBe(201);
|
|
39
|
+
expect(writes).toEqual([]);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('the exemption is exact — a neighbouring path is still audited', async () => {
|
|
43
|
+
const { app, writes } = appWithAudit();
|
|
44
|
+
await app.request('/api/v1/workspaces-other', { method: 'POST' });
|
|
45
|
+
expect(writes).toHaveLength(1);
|
|
46
|
+
expect(writes[0]?.workspaceId).toBe('ws-current');
|
|
47
|
+
});
|
|
48
|
+
});
|