@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,287 @@
|
|
|
1
|
+
import type PgBoss from 'pg-boss';
|
|
2
|
+
import type pg from 'pg';
|
|
3
|
+
import type { Logger } from '../logger.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* One-off timers (PRDCT-3254): a tool runs a function ONCE at an instant or
|
|
7
|
+
* after a delay, with a JSON payload, on a queue its `jobs` slot declared.
|
|
8
|
+
* The runtime is pg-boss, the same durable queue the crons ride: a timer is a
|
|
9
|
+
* job row with `start_after` in the future, so it lives in the tool's own
|
|
10
|
+
* Postgres, survives a restart and a redeploy, and is taken by exactly one
|
|
11
|
+
* worker replica (pg-boss fetches with SKIP LOCKED). Edition-neutral, and an
|
|
12
|
+
* api-role replica schedules and cancels like any other: neither needs DDL.
|
|
13
|
+
*
|
|
14
|
+
* A KEY makes a timer replaceable: at most one PENDING timer per (queue, key).
|
|
15
|
+
* `schedule` with a key withdraws the pending timer of that key and inserts
|
|
16
|
+
* the new one in ONE transaction under a per-key advisory lock, so two
|
|
17
|
+
* replicas re-arming the same thing at once never leave two timers, and
|
|
18
|
+
* `cancel({ key })` withdraws whatever is pending under it. A timer that is
|
|
19
|
+
* already RUNNING is neither replaced nor cancelled (its handler is on it;
|
|
20
|
+
* the handler's own claim decides what it does), so a consumer keeps its
|
|
21
|
+
* at-most-once guard in the data it writes (meet's claim-before-send stamps),
|
|
22
|
+
* never in the timer alone. What a replace or a cancel DOES do to a running
|
|
23
|
+
* timer is take its retries away (`retry_limit = retry_count`): pg-boss's
|
|
24
|
+
* fail path re-inserts a failed run as a pending `retry` row with the same
|
|
25
|
+
* key, which would put the withdrawn payload back beside the new one
|
|
26
|
+
* (verifier round 1, F1). A withdrawn run that throws therefore ends
|
|
27
|
+
* `failed`, and "one pending per key" holds across the run's failure too.
|
|
28
|
+
* `cancel` itself runs without the per-key lock: a cancel concurrent with a
|
|
29
|
+
* replace may answer 0 and leave the replace's fresh timer pending (it ran
|
|
30
|
+
* first); a consumer that wants "nothing pending" cancels after its last
|
|
31
|
+
* schedule, as every path does.
|
|
32
|
+
*
|
|
33
|
+
* The payload is a plain JSON object (not an array, not a Date, nothing a
|
|
34
|
+
* `JSON.stringify` refuses): node-postgres serialises anything else into a
|
|
35
|
+
* value the `jsonb` column refuses with a raw database error, so the check is
|
|
36
|
+
* made here and answers `invalid_argument` before any statement runs.
|
|
37
|
+
*
|
|
38
|
+
* What a timer costs in pg-boss's terms: `keep_until` is `start_after` plus
|
|
39
|
+
* the queue's retention (fourteen days by default), so a timer months ahead is
|
|
40
|
+
* not archived before its time; the handler's run is bounded by the queue's
|
|
41
|
+
* `expireIn` (fifteen minutes by default); a handler that throws fails the
|
|
42
|
+
* job, retried per `retry` or the queue's own settings (pg-boss's queue
|
|
43
|
+
* default is two retries with no delay, so a handler that always throws runs
|
|
44
|
+
* three times back to back), then left `failed`.
|
|
45
|
+
*
|
|
46
|
+
* The pending-state SQL reads pg-boss's own `job` table (its schema is
|
|
47
|
+
* `PGBOSS_SCHEMA`, the column names pg-boss 10's), because pg-boss exposes no
|
|
48
|
+
* lookup by key; `timers.test.ts` pins the statements against the installed
|
|
49
|
+
* version. A pg-boss bump re-verifies them.
|
|
50
|
+
*/
|
|
51
|
+
|
|
52
|
+
/** The pg-boss schema the chassis installs (`createJobs`). */
|
|
53
|
+
export const PGBOSS_SCHEMA = 'pgboss';
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Advisory-lock class serializing the replace of a keyed timer, beside the
|
|
57
|
+
* migration lock (7432001), the last-owner locks (7432002 xact / 7432003
|
|
58
|
+
* session) and the pg-boss install lock (7432004). Transaction-scoped, two-int
|
|
59
|
+
* form: the class and `hashtext(queue + US + key)`.
|
|
60
|
+
*/
|
|
61
|
+
export const TIMER_KEY_LOCK_CLASS = 7_432_005;
|
|
62
|
+
|
|
63
|
+
/** What a handler learns about the run beside its payload. */
|
|
64
|
+
export interface TimerJob {
|
|
65
|
+
/** pg-boss's job id, the one `schedule` answered. */
|
|
66
|
+
id: string;
|
|
67
|
+
queue: string;
|
|
68
|
+
/** The dedupe key the timer was scheduled with; null without one, and on a cron's run. */
|
|
69
|
+
key: string | null;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** When the timer fires: an instant, or a delay from now in seconds. Exactly one. */
|
|
73
|
+
export type TimerWhen = { at: Date; inSeconds?: never } | { inSeconds: number; at?: never };
|
|
74
|
+
|
|
75
|
+
/** How a failing handler is retried: `limit` more attempts, `delaySeconds` apart (doubled each time with `backoff`). */
|
|
76
|
+
export interface TimerRetry {
|
|
77
|
+
limit: number;
|
|
78
|
+
delaySeconds?: number;
|
|
79
|
+
backoff?: boolean;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export type TimerOptions = TimerWhen & {
|
|
83
|
+
/** The dedupe key: at most one pending timer per (queue, key); a new schedule under it replaces the pending one. */
|
|
84
|
+
key?: string;
|
|
85
|
+
/** Absent: the queue's own retry settings. */
|
|
86
|
+
retry?: TimerRetry;
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
export type TimerRef = { id: string; key?: never } | { key: string; id?: never };
|
|
90
|
+
|
|
91
|
+
export interface Timers {
|
|
92
|
+
/**
|
|
93
|
+
* Run the queue's handler once with `data`, at `at` or `inSeconds` from
|
|
94
|
+
* now. Answers the job id. Throws `TimerError` (`unknown_queue`) when the
|
|
95
|
+
* queue is not one the tool's `jobs` slot declared, `invalid_argument` on a
|
|
96
|
+
* malformed call.
|
|
97
|
+
*/
|
|
98
|
+
schedule<TData extends object>(queue: string, data: TData, options: TimerOptions): Promise<string>;
|
|
99
|
+
/**
|
|
100
|
+
* Withdraw a timer that has not run yet, by its id or by its key. Answers
|
|
101
|
+
* how many pending timers were withdrawn (0 when none was pending: already
|
|
102
|
+
* run, already withdrawn, running right now, or never scheduled).
|
|
103
|
+
*/
|
|
104
|
+
cancel(queue: string, ref: TimerRef): Promise<number>;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export class TimerError extends Error {
|
|
108
|
+
constructor(
|
|
109
|
+
readonly code: 'unknown_queue' | 'invalid_argument',
|
|
110
|
+
message: string
|
|
111
|
+
) {
|
|
112
|
+
super(message);
|
|
113
|
+
this.name = 'TimerError';
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
118
|
+
/** pg-boss's own rule for a queue name (`Attorney.assertQueueName`), applied before any statement runs. */
|
|
119
|
+
const QUEUE_NAME = /^[\w-]+$/;
|
|
120
|
+
/** Unit separator: joins the queue and the key under one lock hash, and can appear in neither. */
|
|
121
|
+
const US = '\u001f';
|
|
122
|
+
|
|
123
|
+
const invalid = (message: string) => new TimerError('invalid_argument', `timers: ${message}`);
|
|
124
|
+
/** The longest delay a timer takes, in seconds: a hundred years. Postgres refuses an interval written in exponent form. */
|
|
125
|
+
export const MAX_IN_SECONDS = 100 * 365.25 * 24 * 3600;
|
|
126
|
+
|
|
127
|
+
function assertQueue(queue: unknown): asserts queue is string {
|
|
128
|
+
if (typeof queue !== 'string' || !QUEUE_NAME.test(queue)) {
|
|
129
|
+
throw invalid('the queue must be a name of letters, digits, underscores and hyphens');
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/** The pg-boss `startAfter` for `options`: the Date of `at`, or the delay in seconds. */
|
|
134
|
+
export function startAfterOf(options: TimerWhen): Date | number {
|
|
135
|
+
const hasAt = options.at !== undefined;
|
|
136
|
+
const hasIn = options.inSeconds !== undefined;
|
|
137
|
+
if (hasAt === hasIn) throw invalid('exactly one of `at` and `inSeconds` is required');
|
|
138
|
+
if (hasAt) {
|
|
139
|
+
const at = options.at as Date;
|
|
140
|
+
if (!(at instanceof Date) || !Number.isFinite(at.getTime())) throw invalid('`at` must be a valid Date');
|
|
141
|
+
return at;
|
|
142
|
+
}
|
|
143
|
+
const seconds = options.inSeconds as number;
|
|
144
|
+
if (!Number.isFinite(seconds) || seconds < 0 || seconds > MAX_IN_SECONDS)
|
|
145
|
+
throw invalid('`inSeconds` must be a finite number, between 0 and a hundred years');
|
|
146
|
+
return seconds;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function retryOptionsOf(retry: TimerRetry | undefined): PgBoss.RetryOptions {
|
|
150
|
+
if (retry === undefined) return {};
|
|
151
|
+
if (!Number.isInteger(retry.limit) || retry.limit < 0)
|
|
152
|
+
throw invalid('`retry.limit` must be an integer, 0 or more');
|
|
153
|
+
const delay = retry.delaySeconds ?? 0;
|
|
154
|
+
if (!Number.isInteger(delay) || delay < 0)
|
|
155
|
+
throw invalid('`retry.delaySeconds` must be an integer, 0 or more');
|
|
156
|
+
return { retryLimit: retry.limit, retryDelay: delay, retryBackoff: retry.backoff ?? false };
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function assertData(data: unknown): void {
|
|
160
|
+
const proto = typeof data === 'object' && data !== null ? Object.getPrototypeOf(data) : undefined;
|
|
161
|
+
if (proto !== Object.prototype && proto !== null) {
|
|
162
|
+
throw invalid('the payload must be a plain object');
|
|
163
|
+
}
|
|
164
|
+
try {
|
|
165
|
+
JSON.stringify(data);
|
|
166
|
+
} catch {
|
|
167
|
+
throw invalid('the payload must be JSON (no bigint, no circular reference)');
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function assertKey(key: unknown): asserts key is string {
|
|
172
|
+
if (typeof key !== 'string' || key.length === 0 || key.includes(US)) {
|
|
173
|
+
throw invalid('the key must be a non-empty string');
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/** The one statement that withdraws pending timers: created or waiting for a retry, never running. */
|
|
178
|
+
const CANCEL_PENDING = (where: string) => `
|
|
179
|
+
UPDATE ${PGBOSS_SCHEMA}.job
|
|
180
|
+
SET state = 'cancelled', completed_on = now()
|
|
181
|
+
WHERE name = $1 AND ${where} AND state IN ('created', 'retry')
|
|
182
|
+
`;
|
|
183
|
+
/**
|
|
184
|
+
* The statement that takes a RUNNING timer's retries away, so that if the
|
|
185
|
+
* run fails it ends `failed` and never comes back as a pending `retry` row
|
|
186
|
+
* under the withdrawn key or id (pg-boss retries while
|
|
187
|
+
* `retry_count < retry_limit`).
|
|
188
|
+
*/
|
|
189
|
+
const DISARM_ACTIVE = (where: string) => `
|
|
190
|
+
UPDATE ${PGBOSS_SCHEMA}.job
|
|
191
|
+
SET retry_limit = retry_count
|
|
192
|
+
WHERE name = $1 AND ${where} AND state = 'active'
|
|
193
|
+
`;
|
|
194
|
+
|
|
195
|
+
interface Queryable {
|
|
196
|
+
query(text: string, values: unknown[]): Promise<{ rowCount: number | null }>;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/** Withdraw the pending timers `ref` names and disarm a running one; answers the pending count withdrawn. */
|
|
200
|
+
async function cancelPending(client: Queryable, queue: string, ref: TimerRef): Promise<number> {
|
|
201
|
+
let where: string;
|
|
202
|
+
let value: string;
|
|
203
|
+
if (ref.id !== undefined) {
|
|
204
|
+
if (!UUID.test(ref.id)) throw invalid('the id must be a job id');
|
|
205
|
+
where = 'id = $2::uuid';
|
|
206
|
+
value = ref.id;
|
|
207
|
+
} else {
|
|
208
|
+
assertKey(ref.key);
|
|
209
|
+
where = 'singleton_key = $2';
|
|
210
|
+
value = ref.key;
|
|
211
|
+
}
|
|
212
|
+
// The disarm FIRST, then the withdraw (verifier round 2, F7): pg-boss's fail
|
|
213
|
+
// path deletes the active row and re-inserts it as `retry` under the same
|
|
214
|
+
// key. Disarm first either takes the row lock before that fail (which then
|
|
215
|
+
// waits for the commit and computes `failed` on the disarmed row) or runs
|
|
216
|
+
// after it, and the withdraw, a new statement with a new snapshot, then
|
|
217
|
+
// sees the re-inserted `retry` row and cancels it. In the other order both
|
|
218
|
+
// statements could miss the row, one round trip wide. A replace whose
|
|
219
|
+
// insert then fails rolls the disarm back with it: the old run keeps its
|
|
220
|
+
// retries, a failed replace changes nothing.
|
|
221
|
+
await client.query(DISARM_ACTIVE(where), [queue, value]);
|
|
222
|
+
const res = await client.query(CANCEL_PENDING(where), [queue, value]);
|
|
223
|
+
return res.rowCount ?? 0;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
export class PgBossTimers implements Timers {
|
|
227
|
+
constructor(
|
|
228
|
+
private readonly boss: PgBoss,
|
|
229
|
+
private readonly pool: pg.Pool,
|
|
230
|
+
private readonly logger: Logger
|
|
231
|
+
) {}
|
|
232
|
+
|
|
233
|
+
async schedule<TData extends object>(queue: string, data: TData, options: TimerOptions): Promise<string> {
|
|
234
|
+
assertQueue(queue);
|
|
235
|
+
assertData(data);
|
|
236
|
+
const send: PgBoss.SendOptions = { startAfter: startAfterOf(options), ...retryOptionsOf(options.retry) };
|
|
237
|
+
if (options.key === undefined) {
|
|
238
|
+
return this.insert(queue, data, send);
|
|
239
|
+
}
|
|
240
|
+
assertKey(options.key);
|
|
241
|
+
const key = options.key;
|
|
242
|
+
// Replace = withdraw + insert in one transaction, serialized per key
|
|
243
|
+
// across replicas by a transaction-scoped advisory lock on a client of
|
|
244
|
+
// our own: pg-boss runs the insert through the `db` executor we hand it.
|
|
245
|
+
const client = await this.pool.connect();
|
|
246
|
+
try {
|
|
247
|
+
await client.query('BEGIN');
|
|
248
|
+
await client.query('SELECT pg_advisory_xact_lock($1, hashtext($2))', [
|
|
249
|
+
TIMER_KEY_LOCK_CLASS,
|
|
250
|
+
queue + US + key
|
|
251
|
+
]);
|
|
252
|
+
const replaced = await cancelPending(client, queue, { key });
|
|
253
|
+
const id = await this.insert(queue, data, {
|
|
254
|
+
...send,
|
|
255
|
+
singletonKey: key,
|
|
256
|
+
db: { executeSql: (text, values) => client.query(text, values) }
|
|
257
|
+
});
|
|
258
|
+
await client.query('COMMIT');
|
|
259
|
+
if (replaced > 0) this.logger.debug({ queue, key, replaced }, 'timers: a pending timer was replaced');
|
|
260
|
+
return id;
|
|
261
|
+
} catch (err) {
|
|
262
|
+
await client.query('ROLLBACK').catch(() => {});
|
|
263
|
+
throw err;
|
|
264
|
+
} finally {
|
|
265
|
+
client.release();
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
async cancel(queue: string, ref: TimerRef): Promise<number> {
|
|
270
|
+
assertQueue(queue);
|
|
271
|
+
if ((ref.id === undefined) === (ref.key === undefined))
|
|
272
|
+
throw invalid('exactly one of `id` and `key` is required');
|
|
273
|
+
return cancelPending(this.pool, queue, ref);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/** pg-boss answers null for a queue that does not exist (its insert joins the queue row): that is an unknown queue, named. */
|
|
277
|
+
private async insert(queue: string, data: object, send: PgBoss.SendOptions): Promise<string> {
|
|
278
|
+
const id = await this.boss.send(queue, data, send);
|
|
279
|
+
if (id === null) {
|
|
280
|
+
throw new TimerError(
|
|
281
|
+
'unknown_queue',
|
|
282
|
+
`timers: the queue "${queue}" is not declared by the tool's jobs slot (a timer rides a declared queue)`
|
|
283
|
+
);
|
|
284
|
+
}
|
|
285
|
+
return id;
|
|
286
|
+
}
|
|
287
|
+
}
|
package/src/logger.ts
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { pino, type Logger } from 'pino';
|
|
2
|
+
import type { Env } from './env.js';
|
|
3
|
+
|
|
4
|
+
export type { Logger };
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The single redaction list — secrets never reach the log stream.
|
|
8
|
+
*
|
|
9
|
+
* Two rules when adding to it. (1) pino's `*.x` glob matches the key `x` at
|
|
10
|
+
* depth 1 ONLY, and it is an EXACT key match: `*.secret` does NOT cover
|
|
11
|
+
* `clientSecret`, `client_secret`, or `apiSecret`, so every camel/snake spelling
|
|
12
|
+
* a payload can carry needs its own entry. (2) Redaction keys off object
|
|
13
|
+
* PROPERTIES — it can do nothing about a secret interpolated into a message
|
|
14
|
+
* string. Never build a log message out of a config payload; log the shape
|
|
15
|
+
* (`{ length, path }`), never the bytes.
|
|
16
|
+
*/
|
|
17
|
+
export const REDACT_PATHS = [
|
|
18
|
+
'req.headers.authorization',
|
|
19
|
+
'req.headers.cookie',
|
|
20
|
+
'*.password',
|
|
21
|
+
'*.secret',
|
|
22
|
+
'*.token',
|
|
23
|
+
// Exact-key spellings the `*.secret` glob misses — OAuth/hub-federation
|
|
24
|
+
// payloads carry these verbatim.
|
|
25
|
+
'*.clientSecret',
|
|
26
|
+
'*.client_secret',
|
|
27
|
+
'*.accessToken',
|
|
28
|
+
'*.access_token',
|
|
29
|
+
'*.refreshToken',
|
|
30
|
+
'*.refresh_token',
|
|
31
|
+
'*.apiKey',
|
|
32
|
+
'*.api_key',
|
|
33
|
+
'DATABASE_URL',
|
|
34
|
+
'AUTH_SECRET',
|
|
35
|
+
'API_KEY_PEPPERS',
|
|
36
|
+
'SETUP_TOKEN',
|
|
37
|
+
'METRICS_TOKEN',
|
|
38
|
+
'SMTP_URL',
|
|
39
|
+
'RESEND_API_KEY',
|
|
40
|
+
'BREVO_API_KEY',
|
|
41
|
+
'S3_SECRET_ACCESS_KEY',
|
|
42
|
+
'S3_ACCESS_KEY_ID',
|
|
43
|
+
// Optional social credential — read in env.ts, so any "here is the parsed
|
|
44
|
+
// env" line would otherwise carry it.
|
|
45
|
+
'GOOGLE_CLIENT_SECRET',
|
|
46
|
+
// Cloud edition: the hub-federation client credential (ADR 015 / user-scoped
|
|
47
|
+
// federation). It is THIS tool's identity at the Antasphere hub — leaking it
|
|
48
|
+
// lets anyone impersonate the tool at the authorization server.
|
|
49
|
+
'HUB_CLIENT_SECRET'
|
|
50
|
+
];
|
|
51
|
+
|
|
52
|
+
export function createLogger(env: Pick<Env, 'LOG_LEVEL' | 'NODE_ENV'>): Logger {
|
|
53
|
+
const options = {
|
|
54
|
+
level: env.LOG_LEVEL,
|
|
55
|
+
redact: { paths: REDACT_PATHS, censor: '[redacted]' }
|
|
56
|
+
};
|
|
57
|
+
if (env.NODE_ENV === 'development') {
|
|
58
|
+
// pino-pretty is a devDependency; if it is absent (e.g. NODE_ENV flipped
|
|
59
|
+
// on a pruned production image) fall back to JSON instead of crashing.
|
|
60
|
+
try {
|
|
61
|
+
return pino({ ...options, transport: { target: 'pino-pretty', options: { colorize: true } } });
|
|
62
|
+
} catch {
|
|
63
|
+
// fall through to JSON output
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return pino(options);
|
|
67
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* API error → model-readable hint mapping (the predecessor MCP template's
|
|
3
|
+
* DOMAIN_HINTS pattern). The API returns `{ error: { code, message } }` on
|
|
4
|
+
* failure; each domain code can carry an actionable hint so the model
|
|
5
|
+
* self-corrects instead of retrying blindly. The table below holds the hints of
|
|
6
|
+
* the chassis' own codes; a tool adds its domain's through
|
|
7
|
+
* {@link mergeErrorHints} (the `errorHints` of its MCP definition).
|
|
8
|
+
*/
|
|
9
|
+
export type ErrorHints = Readonly<Record<string, string>>;
|
|
10
|
+
|
|
11
|
+
const DOMAIN_HINTS: Record<string, string> = {
|
|
12
|
+
insufficient_scope:
|
|
13
|
+
'Reconnect the MCP server and approve the permission on the consent screen ' +
|
|
14
|
+
'(or mint an API key that includes the scope).',
|
|
15
|
+
endpoint_not_allowed:
|
|
16
|
+
'This endpoint is not opened to machine credentials — a human must do this in the dashboard.',
|
|
17
|
+
invalid_token: 'The OAuth token expired or was revoked. Reconnect the MCP server.',
|
|
18
|
+
unauthenticated: 'No valid credential reached the API. Reconnect the MCP server.',
|
|
19
|
+
rate_limited: 'Rate limited — wait before retrying.',
|
|
20
|
+
plan_required:
|
|
21
|
+
'The workspace\u2019s plan does not allow this — the upgrade link in this message is where a human raises it.',
|
|
22
|
+
// The same code on both editions: the cloud's credit refusal (402, with the
|
|
23
|
+
// top-up link) and a self-hosted instance's cap (413, no details).
|
|
24
|
+
entitlement_denied:
|
|
25
|
+
'The instance refused this action: on the cloud the organization lacks the credits and the top-up link in this message is where a human adds them, or the quantity is beyond what can be priced and no top-up covers it (no link then); on a self-hosted instance the request exceeds the operator’s cap.'
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
/** The chassis hints plus a tool's own: ONE table, the one every lookup reads. */
|
|
29
|
+
export function mergeErrorHints(toolHints: ErrorHints): ErrorHints {
|
|
30
|
+
return { ...DOMAIN_HINTS, ...toolHints };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Thrown when the in-process API call returns a non-2xx wire-shape response. */
|
|
34
|
+
export class ApiToolError extends Error {
|
|
35
|
+
readonly status: number;
|
|
36
|
+
readonly code: string | null;
|
|
37
|
+
|
|
38
|
+
/** The wire's `error.details`, when the API sent any (a plan refusal's upgrade link rides here). */
|
|
39
|
+
readonly details: unknown;
|
|
40
|
+
|
|
41
|
+
constructor(status: number, code: string | null, message: string, details?: unknown) {
|
|
42
|
+
super(message);
|
|
43
|
+
this.name = 'ApiToolError';
|
|
44
|
+
this.status = status;
|
|
45
|
+
this.code = code;
|
|
46
|
+
this.details = details;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
toUserFacingText(hints: ErrorHints = DOMAIN_HINTS): string {
|
|
50
|
+
const hint = this.code ? hints[this.code] : undefined;
|
|
51
|
+
const codePart = this.code ? `, code: ${this.code}` : '';
|
|
52
|
+
// A plan refusal carries its upgrade link (the billing rail, §7): the
|
|
53
|
+
// agent relays it, a human follows it. Text, never a structured field.
|
|
54
|
+
const upgradeUrl = (this.details as { upgradeUrl?: unknown } | null)?.upgradeUrl;
|
|
55
|
+
const upgrade = typeof upgradeUrl === 'string' && upgradeUrl ? ` Upgrade: ${upgradeUrl}` : '';
|
|
56
|
+
// A credit refusal (402 `entitlement_denied`, PRDCT-2664) carries its
|
|
57
|
+
// top-up link, the price and the balance: the same, as text. The
|
|
58
|
+
// self-hosted cap's 413 has no details, so no top-up sentence.
|
|
59
|
+
const { topUpUrl, credits, balance } = (this.details ?? {}) as {
|
|
60
|
+
topUpUrl?: unknown;
|
|
61
|
+
credits?: unknown;
|
|
62
|
+
balance?: unknown;
|
|
63
|
+
};
|
|
64
|
+
let topUp = '';
|
|
65
|
+
// The gate's message already carries the link, the price and the balance;
|
|
66
|
+
// the sentence is added only for a message that does not (verifier
|
|
67
|
+
// round 1: the URL was printed twice).
|
|
68
|
+
if (typeof topUpUrl === 'string' && topUpUrl && !this.message.includes(topUpUrl)) {
|
|
69
|
+
const needs = typeof credits === 'number' ? [`this needs ${credits} credits`] : [];
|
|
70
|
+
const holds = typeof balance === 'number' ? [`the organization holds ${balance}`] : [];
|
|
71
|
+
const numbers = [...needs, ...holds].join(', ');
|
|
72
|
+
topUp = ` Top up: ${topUpUrl}${numbers ? ` (${numbers}).` : ''}`;
|
|
73
|
+
}
|
|
74
|
+
return `API error (HTTP ${this.status}${codePart}): ${this.message}${hint ? ` — Next: ${hint}` : ''}${upgrade}${topUp}`;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export interface ToolTextResult {
|
|
79
|
+
// Index signature: the MCP SDK's CallToolResult carries extra open fields.
|
|
80
|
+
[key: string]: unknown;
|
|
81
|
+
content: Array<{ type: 'text'; text: string }>;
|
|
82
|
+
isError?: true;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export const jsonText = (result: unknown): ToolTextResult => ({
|
|
86
|
+
content: [{ type: 'text' as const, text: JSON.stringify(result, null, 2) }]
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
export const deny = (text: string): ToolTextResult => ({
|
|
90
|
+
content: [{ type: 'text' as const, text }],
|
|
91
|
+
isError: true as const
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Wrap a tool body so any thrown ApiToolError surfaces as a structured MCP
|
|
96
|
+
* tool error (`isError: true` + text) rather than a protocol-level exception.
|
|
97
|
+
* Other errors propagate to the SDK.
|
|
98
|
+
*/
|
|
99
|
+
export async function wrapToolErrors(
|
|
100
|
+
fn: () => Promise<ToolTextResult>,
|
|
101
|
+
hints: ErrorHints = DOMAIN_HINTS
|
|
102
|
+
): Promise<ToolTextResult> {
|
|
103
|
+
try {
|
|
104
|
+
return await fn();
|
|
105
|
+
} catch (err) {
|
|
106
|
+
if (err instanceof ApiToolError) {
|
|
107
|
+
return { content: [{ type: 'text', text: err.toUserFacingText(hints) }], isError: true };
|
|
108
|
+
}
|
|
109
|
+
throw err;
|
|
110
|
+
}
|
|
111
|
+
}
|
package/src/mcp/http.ts
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { Hono } from 'hono';
|
|
2
|
+
import type { MiddlewareHandler } from 'hono';
|
|
3
|
+
import { bodyLimit } from 'hono/body-limit';
|
|
4
|
+
import { HTTPException } from 'hono/http-exception';
|
|
5
|
+
import { StreamableHTTPTransport } from '@hono/mcp';
|
|
6
|
+
import type { Principal } from '@antasphere/chassis-contract';
|
|
7
|
+
import { looksLikeJwt } from '../middleware/scopes.js';
|
|
8
|
+
import { buildMcpServer, type McpIdentity, type McpToolDefinition } from './server.js';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* The bundled /mcp endpoint (streamable HTTP, STATELESS — see ADR 002).
|
|
12
|
+
*
|
|
13
|
+
* Auth gate first: a valid OAuth bearer (token this instance minted) or an
|
|
14
|
+
* API key — both machine principals, resolved through the same machinery as
|
|
15
|
+
* /api/v1. Anything else gets the RFC 9728 401 challenge whose
|
|
16
|
+
* `resource_metadata` URL drives the whole client OAuth dance.
|
|
17
|
+
*
|
|
18
|
+
* Stateless transport: a fresh McpServer + transport per request, no session
|
|
19
|
+
* ids, no in-process stream state — the platform's statelessness invariant
|
|
20
|
+
* applies to MCP too (any replica can serve any request).
|
|
21
|
+
*/
|
|
22
|
+
export interface McpHttpDeps {
|
|
23
|
+
publicBaseUrl: string;
|
|
24
|
+
version: string;
|
|
25
|
+
/** In-process fetch against the root app; tools forward the caller's bearer. */
|
|
26
|
+
fetchApi: (path: string, init?: RequestInit) => Promise<Response>;
|
|
27
|
+
resolveOauthJwt: (token: string) => Promise<Principal | null>;
|
|
28
|
+
resolveApiKey: (token: string) => Promise<Principal | null>;
|
|
29
|
+
isApiKeyToken: (token: string) => boolean;
|
|
30
|
+
/** Instance display name for the server instructions (cached by the caller). */
|
|
31
|
+
instanceName: () => Promise<string>;
|
|
32
|
+
/** Per-IP rate limit, applied before the auth gate (floods rejected cheaply). */
|
|
33
|
+
limiter: MiddlewareHandler;
|
|
34
|
+
/** The tool's MCP definition: its tools, instructions, error hints and scope names. */
|
|
35
|
+
tool: McpToolDefinition;
|
|
36
|
+
/** The server's name and the tool names' prefix (`identity.mcp`). */
|
|
37
|
+
identity: McpIdentity;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function mcpRoutes(deps: McpHttpDeps): Hono {
|
|
41
|
+
const app = new Hono();
|
|
42
|
+
const challengeUrl = `${deps.publicBaseUrl.replace(/\/+$/, '')}/.well-known/oauth-protected-resource/mcp`;
|
|
43
|
+
|
|
44
|
+
app.use('*', deps.limiter);
|
|
45
|
+
// JSON-RPC bodies are small; cap them so a caller can't force the transport
|
|
46
|
+
// to buffer an arbitrarily large body (the /api/v1 cap does not cover /mcp).
|
|
47
|
+
app.use(
|
|
48
|
+
'*',
|
|
49
|
+
bodyLimit({
|
|
50
|
+
maxSize: 1024 * 1024,
|
|
51
|
+
onError: (c) =>
|
|
52
|
+
c.json({ error: 'payload_too_large', error_description: 'MCP request body exceeds 1 MiB' }, 413)
|
|
53
|
+
})
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
const unauthorized = (description: string) =>
|
|
57
|
+
new Response(JSON.stringify({ error: 'invalid_token', error_description: description }), {
|
|
58
|
+
status: 401,
|
|
59
|
+
headers: {
|
|
60
|
+
'content-type': 'application/json',
|
|
61
|
+
'www-authenticate': `Bearer resource_metadata="${challengeUrl}"`
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
app.post('/', async (c) => {
|
|
66
|
+
const authHeader = c.req.header('authorization')?.trim() ?? '';
|
|
67
|
+
const bearer = /^Bearer\s+(.+)$/i.exec(authHeader)?.[1]?.trim() ?? null;
|
|
68
|
+
if (!bearer) {
|
|
69
|
+
return unauthorized('Authentication required');
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
let principal: Principal | null = null;
|
|
73
|
+
if (deps.isApiKeyToken(bearer)) {
|
|
74
|
+
principal = await deps.resolveApiKey(bearer);
|
|
75
|
+
} else if (looksLikeJwt(bearer)) {
|
|
76
|
+
principal = await deps.resolveOauthJwt(bearer);
|
|
77
|
+
}
|
|
78
|
+
if (!principal) {
|
|
79
|
+
return unauthorized('Bearer token is invalid, expired, or revoked');
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const server = buildMcpServer(
|
|
83
|
+
{
|
|
84
|
+
fetchApi: deps.fetchApi,
|
|
85
|
+
principal,
|
|
86
|
+
authorizationHeader: `Bearer ${bearer}`,
|
|
87
|
+
publicBaseUrl: deps.publicBaseUrl
|
|
88
|
+
},
|
|
89
|
+
{ version: deps.version, instanceName: await deps.instanceName() },
|
|
90
|
+
deps.tool,
|
|
91
|
+
deps.identity
|
|
92
|
+
);
|
|
93
|
+
// No sessionIdGenerator = stateless: every POST is self-contained.
|
|
94
|
+
// enableJsonResponse returns plain JSON bodies (no SSE), so the Response
|
|
95
|
+
// handed back to Hono is complete — no stream outlives the request,
|
|
96
|
+
// nothing to tear down (the per-request server + transport are
|
|
97
|
+
// unreferenced after this).
|
|
98
|
+
const transport = new StreamableHTTPTransport({ enableJsonResponse: true });
|
|
99
|
+
await server.connect(transport);
|
|
100
|
+
try {
|
|
101
|
+
const res = await transport.handleRequest(c);
|
|
102
|
+
return res ?? c.body(null, 204);
|
|
103
|
+
} catch (cause) {
|
|
104
|
+
// The transport reports protocol failures by THROWING HTTPException
|
|
105
|
+
// (400 -32700 for a malformed / non-JSON-RPC body, 406/415 for header
|
|
106
|
+
// problems), each carrying a complete JSON-RPC error response. Return
|
|
107
|
+
// that response instead of letting the root onError turn it into a
|
|
108
|
+
// generic 500.
|
|
109
|
+
if (cause instanceof HTTPException) {
|
|
110
|
+
return cause.getResponse();
|
|
111
|
+
}
|
|
112
|
+
throw cause;
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
// Non-POST is rejected here EXPLICITLY (ADR 002): the stateless transport
|
|
117
|
+
// does NOT 405 on its own — its GET handler would happily open a long-lived
|
|
118
|
+
// server-initiated SSE stream, and DELETE would answer a session teardown.
|
|
119
|
+
// Neither exists in stateless mode, so every non-POST method gets
|
|
120
|
+
// 405 + Allow: POST with a JSON-RPC error envelope (the shape the official
|
|
121
|
+
// SDK expects and tolerates).
|
|
122
|
+
app.all('/', (c) =>
|
|
123
|
+
c.json({ jsonrpc: '2.0', error: { code: -32000, message: 'Method not allowed' }, id: null }, 405, {
|
|
124
|
+
allow: 'POST'
|
|
125
|
+
})
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
return app;
|
|
129
|
+
}
|
package/src/mcp/index.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export {
|
|
2
|
+
ApiToolError,
|
|
3
|
+
type ErrorHints,
|
|
4
|
+
type ToolTextResult,
|
|
5
|
+
deny,
|
|
6
|
+
jsonText,
|
|
7
|
+
mergeErrorHints,
|
|
8
|
+
wrapToolErrors
|
|
9
|
+
} from './errors.js';
|
|
10
|
+
export { mcpRoutes } from './http.js';
|
|
11
|
+
export { projectErrorHints, registerProjectTools } from './projects.js';
|
|
12
|
+
export { registerTeamTools } from './teams.js';
|
|
13
|
+
export { type McpInputs, mcpInputs } from './inputs.js';
|
|
14
|
+
export {
|
|
15
|
+
type McpIdentity,
|
|
16
|
+
type McpServerInfo,
|
|
17
|
+
type McpToolDefinition,
|
|
18
|
+
buildMcpServer,
|
|
19
|
+
composeErrorHints
|
|
20
|
+
} from './server.js';
|
|
21
|
+
export {
|
|
22
|
+
type McpToolContext,
|
|
23
|
+
callApi,
|
|
24
|
+
createScopeCheck,
|
|
25
|
+
fetchApiRaw,
|
|
26
|
+
forWorkspace,
|
|
27
|
+
pageQuery
|
|
28
|
+
} from './tool-kit.js';
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import type { ToolIdentity } from '@antasphere/chassis-contract';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The inputs every MCP tool of an instance shares, chassis tools and the
|
|
6
|
+
* tool's own alike: ONE definition, so the same argument reads the same in
|
|
7
|
+
* every tool of `tools/list`. The workspace input names `<toolPrefix>whoami`
|
|
8
|
+
* (the identity tool the chassis registers), hence a factory over the MCP half
|
|
9
|
+
* of the tool's identity. Build it once per server, next to `registerTools`.
|
|
10
|
+
*/
|
|
11
|
+
export function mcpInputs(identity: Pick<ToolIdentity['mcp'], 'toolPrefix'>) {
|
|
12
|
+
return {
|
|
13
|
+
/** The per-call target workspace; omitted, the caller's default one. */
|
|
14
|
+
workspaceInput: z
|
|
15
|
+
.uuid()
|
|
16
|
+
.optional()
|
|
17
|
+
.describe(
|
|
18
|
+
`Target organization (workspace id). Omit to use your default org — see ${identity.toolPrefix}whoami.`
|
|
19
|
+
),
|
|
20
|
+
/** The cursor of a paginated list, passed through to the API. */
|
|
21
|
+
cursorInput: z.string().optional().describe('nextCursor from a previous page.'),
|
|
22
|
+
/** The page size of a paginated list. */
|
|
23
|
+
limitInput: z
|
|
24
|
+
.number()
|
|
25
|
+
.int()
|
|
26
|
+
.min(1)
|
|
27
|
+
.max(100)
|
|
28
|
+
.optional()
|
|
29
|
+
.describe('Page size (server max 100). Default 50.')
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export type McpInputs = ReturnType<typeof mcpInputs>;
|