@kindgi/api 0.1.4 → 0.1.5
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/dist/agent-binding.d.ts +13 -0
- package/dist/agent-binding.d.ts.map +1 -1
- package/dist/agent-pins.d.ts.map +1 -1
- package/dist/agent-pins.js +18 -2
- package/dist/agent-pins.js.map +1 -1
- package/dist/app.d.ts +110 -17
- package/dist/app.d.ts.map +1 -1
- package/dist/app.js +163 -35
- package/dist/app.js.map +1 -1
- package/dist/block-publish.d.ts +56 -0
- package/dist/block-publish.d.ts.map +1 -0
- package/dist/block-publish.js +132 -0
- package/dist/block-publish.js.map +1 -0
- package/dist/caller.d.ts +24 -0
- package/dist/caller.d.ts.map +1 -0
- package/dist/caller.js +47 -0
- package/dist/caller.js.map +1 -0
- package/dist/capability-binding.d.ts +11 -0
- package/dist/capability-binding.d.ts.map +1 -1
- package/dist/cost-binding.d.ts +19 -0
- package/dist/cost-binding.d.ts.map +1 -1
- package/dist/cost-binding.js.map +1 -1
- package/dist/derive-agent-version.d.ts +7 -0
- package/dist/derive-agent-version.d.ts.map +1 -1
- package/dist/derive-agent-version.js +4 -0
- package/dist/derive-agent-version.js.map +1 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +75 -7
- package/dist/errors.js.map +1 -1
- package/dist/eval-case-binding.d.ts +16 -4
- package/dist/eval-case-binding.d.ts.map +1 -1
- package/dist/eval-run-binding.d.ts +31 -0
- package/dist/eval-run-binding.d.ts.map +1 -1
- package/dist/eval-run-dispatcher.d.ts +7 -0
- package/dist/eval-run-dispatcher.d.ts.map +1 -1
- package/dist/eval-run-dispatcher.js.map +1 -1
- package/dist/eval-sample.d.ts +11 -0
- package/dist/eval-sample.d.ts.map +1 -0
- package/dist/eval-sample.js +34 -0
- package/dist/eval-sample.js.map +1 -0
- package/dist/eval-suite-binding.d.ts +13 -0
- package/dist/eval-suite-binding.d.ts.map +1 -1
- package/dist/eval-suite-binding.js.map +1 -1
- package/dist/flow-binding.d.ts +18 -0
- package/dist/flow-binding.d.ts.map +1 -1
- package/dist/flow-pins.d.ts.map +1 -1
- package/dist/flow-pins.js +18 -2
- package/dist/flow-pins.js.map +1 -1
- package/dist/gate.d.ts.map +1 -1
- package/dist/gate.js +25 -0
- package/dist/gate.js.map +1 -1
- package/dist/handler-binding.d.ts +32 -0
- package/dist/handler-binding.d.ts.map +1 -1
- package/dist/hitl-binding.d.ts +23 -0
- package/dist/hitl-binding.d.ts.map +1 -1
- package/dist/identity-directory-binding.d.ts +82 -4
- package/dist/identity-directory-binding.d.ts.map +1 -1
- package/dist/identity-provider-binding.d.ts +197 -17
- package/dist/identity-provider-binding.d.ts.map +1 -1
- package/dist/improvement-pass-binding.d.ts +196 -0
- package/dist/improvement-pass-binding.d.ts.map +1 -0
- package/dist/improvement-pass-binding.js +9 -0
- package/dist/improvement-pass-binding.js.map +1 -0
- package/dist/index.d.ts +35 -17
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +11 -3
- package/dist/index.js.map +1 -1
- package/dist/judged-dispatcher.d.ts +25 -4
- package/dist/judged-dispatcher.d.ts.map +1 -1
- package/dist/judged-dispatcher.js +43 -6
- package/dist/judged-dispatcher.js.map +1 -1
- package/dist/judgment-binding.d.ts +25 -1
- package/dist/judgment-binding.d.ts.map +1 -1
- package/dist/mcp-endpoint-binding.d.ts +7 -0
- package/dist/mcp-endpoint-binding.d.ts.map +1 -1
- package/dist/memory-binding.d.ts +124 -17
- package/dist/memory-binding.d.ts.map +1 -1
- package/dist/memory-erasure-binding.d.ts +145 -0
- package/dist/memory-erasure-binding.d.ts.map +1 -0
- package/dist/memory-erasure-binding.js +4 -0
- package/dist/memory-erasure-binding.js.map +1 -0
- package/dist/middleware/auth.d.ts +66 -13
- package/dist/middleware/auth.d.ts.map +1 -1
- package/dist/middleware/auth.js +150 -50
- package/dist/middleware/auth.js.map +1 -1
- package/dist/middleware/authorize.d.ts +9 -0
- package/dist/middleware/authorize.d.ts.map +1 -1
- package/dist/middleware/authorize.js +122 -4
- package/dist/middleware/authorize.js.map +1 -1
- package/dist/middleware/error-mapper.d.ts +5 -0
- package/dist/middleware/error-mapper.d.ts.map +1 -1
- package/dist/middleware/error-mapper.js +14 -1
- package/dist/middleware/error-mapper.js.map +1 -1
- package/dist/middleware/idempotency.d.ts +70 -5
- package/dist/middleware/idempotency.d.ts.map +1 -1
- package/dist/middleware/idempotency.js +174 -41
- package/dist/middleware/idempotency.js.map +1 -1
- package/dist/middleware/key-project.d.ts +17 -0
- package/dist/middleware/key-project.d.ts.map +1 -0
- package/dist/middleware/key-project.js +75 -0
- package/dist/middleware/key-project.js.map +1 -0
- package/dist/middleware/principal.d.ts.map +1 -1
- package/dist/middleware/principal.js +9 -5
- package/dist/middleware/principal.js.map +1 -1
- package/dist/middleware/project-ref.d.ts.map +1 -1
- package/dist/middleware/project-ref.js +3 -0
- package/dist/middleware/project-ref.js.map +1 -1
- package/dist/openapi/generate.d.ts.map +1 -1
- package/dist/openapi/generate.js +5 -2
- package/dist/openapi/generate.js.map +1 -1
- package/dist/openapi/operations.d.ts.map +1 -1
- package/dist/openapi/operations.js +919 -140
- package/dist/openapi/operations.js.map +1 -1
- package/dist/openapi/schemas.d.ts +90 -13
- package/dist/openapi/schemas.d.ts.map +1 -1
- package/dist/openapi/schemas.js +2081 -359
- package/dist/openapi/schemas.js.map +1 -1
- package/dist/person-grants-binding.d.ts +77 -0
- package/dist/person-grants-binding.d.ts.map +1 -0
- package/dist/person-grants-binding.js +4 -0
- package/dist/person-grants-binding.js.map +1 -0
- package/dist/proposal-service.d.ts +121 -0
- package/dist/proposal-service.d.ts.map +1 -0
- package/dist/proposal-service.js +447 -0
- package/dist/proposal-service.js.map +1 -0
- package/dist/proposal-status.d.ts +60 -0
- package/dist/proposal-status.d.ts.map +1 -0
- package/dist/proposal-status.js +109 -0
- package/dist/proposal-status.js.map +1 -0
- package/dist/provider-binding.d.ts +10 -0
- package/dist/provider-binding.d.ts.map +1 -1
- package/dist/publish-refused.d.ts +28 -3
- package/dist/publish-refused.d.ts.map +1 -1
- package/dist/publish-refused.js +24 -3
- package/dist/publish-refused.js.map +1 -1
- package/dist/routes/adapters.d.ts +2 -1
- package/dist/routes/adapters.d.ts.map +1 -1
- package/dist/routes/adapters.js +3 -1
- package/dist/routes/adapters.js.map +1 -1
- package/dist/routes/agent-releases.d.ts +55 -3
- package/dist/routes/agent-releases.d.ts.map +1 -1
- package/dist/routes/agent-releases.js +85 -55
- package/dist/routes/agent-releases.js.map +1 -1
- package/dist/routes/agents.d.ts +12 -1
- package/dist/routes/agents.d.ts.map +1 -1
- package/dist/routes/agents.js +100 -2
- package/dist/routes/agents.js.map +1 -1
- package/dist/routes/approvals.d.ts +15 -3
- package/dist/routes/approvals.d.ts.map +1 -1
- package/dist/routes/approvals.js +133 -122
- package/dist/routes/approvals.js.map +1 -1
- package/dist/routes/artifacts.d.ts +25 -3
- package/dist/routes/artifacts.d.ts.map +1 -1
- package/dist/routes/artifacts.js +116 -7
- package/dist/routes/artifacts.js.map +1 -1
- package/dist/routes/auth.d.ts +25 -1
- package/dist/routes/auth.d.ts.map +1 -1
- package/dist/routes/auth.js +655 -281
- package/dist/routes/auth.js.map +1 -1
- package/dist/routes/blocks.d.ts.map +1 -1
- package/dist/routes/blocks.js +2 -42
- package/dist/routes/blocks.js.map +1 -1
- package/dist/routes/capabilities.d.ts +2 -1
- package/dist/routes/capabilities.d.ts.map +1 -1
- package/dist/routes/capabilities.js +6 -1
- package/dist/routes/capabilities.js.map +1 -1
- package/dist/routes/compliance.d.ts +9 -10
- package/dist/routes/compliance.d.ts.map +1 -1
- package/dist/routes/compliance.js +42 -61
- package/dist/routes/compliance.js.map +1 -1
- package/dist/routes/conversations.d.ts +8 -1
- package/dist/routes/conversations.d.ts.map +1 -1
- package/dist/routes/conversations.js +87 -8
- package/dist/routes/conversations.js.map +1 -1
- package/dist/routes/cost.d.ts +12 -1
- package/dist/routes/cost.d.ts.map +1 -1
- package/dist/routes/cost.js +74 -3
- package/dist/routes/cost.js.map +1 -1
- package/dist/routes/denied.d.ts +11 -0
- package/dist/routes/denied.d.ts.map +1 -0
- package/dist/routes/denied.js +17 -0
- package/dist/routes/denied.js.map +1 -0
- package/dist/routes/deployments.d.ts +10 -1
- package/dist/routes/deployments.d.ts.map +1 -1
- package/dist/routes/deployments.js +171 -13
- package/dist/routes/deployments.js.map +1 -1
- package/dist/routes/eval-comparison.d.ts.map +1 -1
- package/dist/routes/eval-comparison.js +69 -1
- package/dist/routes/eval-comparison.js.map +1 -1
- package/dist/routes/eval-overrides.d.ts +18 -0
- package/dist/routes/eval-overrides.d.ts.map +1 -0
- package/dist/routes/eval-overrides.js +81 -0
- package/dist/routes/eval-overrides.js.map +1 -0
- package/dist/routes/eval-runs.d.ts +10 -1
- package/dist/routes/eval-runs.d.ts.map +1 -1
- package/dist/routes/eval-runs.js +66 -7
- package/dist/routes/eval-runs.js.map +1 -1
- package/dist/routes/eval-suites.d.ts.map +1 -1
- package/dist/routes/eval-suites.js +28 -2
- package/dist/routes/eval-suites.js.map +1 -1
- package/dist/routes/event-triggers.d.ts +5 -1
- package/dist/routes/event-triggers.d.ts.map +1 -1
- package/dist/routes/event-triggers.js +26 -3
- package/dist/routes/event-triggers.js.map +1 -1
- package/dist/routes/export-signing-keys.d.ts +12 -0
- package/dist/routes/export-signing-keys.d.ts.map +1 -0
- package/dist/routes/export-signing-keys.js +24 -0
- package/dist/routes/export-signing-keys.js.map +1 -0
- package/dist/routes/flows.d.ts.map +1 -1
- package/dist/routes/flows.js +9 -1
- package/dist/routes/flows.js.map +1 -1
- package/dist/routes/gate-policies.d.ts +4 -1
- package/dist/routes/gate-policies.d.ts.map +1 -1
- package/dist/routes/gate-policies.js +29 -5
- package/dist/routes/gate-policies.js.map +1 -1
- package/dist/routes/guardrails.d.ts +16 -14
- package/dist/routes/guardrails.d.ts.map +1 -1
- package/dist/routes/guardrails.js +39 -3
- package/dist/routes/guardrails.js.map +1 -1
- package/dist/routes/identity.d.ts +20 -4
- package/dist/routes/identity.d.ts.map +1 -1
- package/dist/routes/identity.js +246 -6
- package/dist/routes/identity.js.map +1 -1
- package/dist/routes/improvement-passes.d.ts +52 -0
- package/dist/routes/improvement-passes.d.ts.map +1 -0
- package/dist/routes/improvement-passes.js +449 -0
- package/dist/routes/improvement-passes.js.map +1 -0
- package/dist/routes/judged-suites.d.ts +50 -0
- package/dist/routes/judged-suites.d.ts.map +1 -1
- package/dist/routes/judged-suites.js +66 -27
- package/dist/routes/judged-suites.js.map +1 -1
- package/dist/routes/judgment-context.d.ts +18 -2
- package/dist/routes/judgment-context.d.ts.map +1 -1
- package/dist/routes/judgment-context.js +63 -5
- package/dist/routes/judgment-context.js.map +1 -1
- package/dist/routes/judgment-flow-context.d.ts.map +1 -1
- package/dist/routes/judgment-flow-context.js +14 -2
- package/dist/routes/judgment-flow-context.js.map +1 -1
- package/dist/routes/judgments.d.ts.map +1 -1
- package/dist/routes/judgments.js +8 -1
- package/dist/routes/judgments.js.map +1 -1
- package/dist/routes/mcp.d.ts.map +1 -1
- package/dist/routes/mcp.js +26 -1
- package/dist/routes/mcp.js.map +1 -1
- package/dist/routes/memory-access.d.ts +43 -0
- package/dist/routes/memory-access.d.ts.map +1 -0
- package/dist/routes/memory-access.js +114 -0
- package/dist/routes/memory-access.js.map +1 -0
- package/dist/routes/memory-erasures.d.ts +25 -0
- package/dist/routes/memory-erasures.d.ts.map +1 -0
- package/dist/routes/memory-erasures.js +214 -0
- package/dist/routes/memory-erasures.js.map +1 -0
- package/dist/routes/memory-parse.d.ts +38 -0
- package/dist/routes/memory-parse.d.ts.map +1 -0
- package/dist/routes/memory-parse.js +199 -0
- package/dist/routes/memory-parse.js.map +1 -0
- package/dist/routes/memory.d.ts +9 -4
- package/dist/routes/memory.d.ts.map +1 -1
- package/dist/routes/memory.js +226 -234
- package/dist/routes/memory.js.map +1 -1
- package/dist/routes/observations.d.ts +4 -1
- package/dist/routes/observations.d.ts.map +1 -1
- package/dist/routes/observations.js +8 -2
- package/dist/routes/observations.js.map +1 -1
- package/dist/routes/pagination.d.ts +7 -0
- package/dist/routes/pagination.d.ts.map +1 -1
- package/dist/routes/pagination.js +10 -0
- package/dist/routes/pagination.js.map +1 -1
- package/dist/routes/policies.d.ts +2 -1
- package/dist/routes/policies.d.ts.map +1 -1
- package/dist/routes/policies.js +3 -1
- package/dist/routes/policies.js.map +1 -1
- package/dist/routes/project-mismatch.d.ts +22 -0
- package/dist/routes/project-mismatch.d.ts.map +1 -0
- package/dist/routes/project-mismatch.js +34 -0
- package/dist/routes/project-mismatch.js.map +1 -0
- package/dist/routes/projects.d.ts +15 -4
- package/dist/routes/projects.d.ts.map +1 -1
- package/dist/routes/projects.js +100 -9
- package/dist/routes/projects.js.map +1 -1
- package/dist/routes/proposals.d.ts +25 -17
- package/dist/routes/proposals.d.ts.map +1 -1
- package/dist/routes/proposals.js +340 -507
- package/dist/routes/proposals.js.map +1 -1
- package/dist/routes/provenance.d.ts +17 -3
- package/dist/routes/provenance.d.ts.map +1 -1
- package/dist/routes/provenance.js +79 -103
- package/dist/routes/provenance.js.map +1 -1
- package/dist/routes/providers.d.ts +17 -1
- package/dist/routes/providers.d.ts.map +1 -1
- package/dist/routes/providers.js +110 -3
- package/dist/routes/providers.js.map +1 -1
- package/dist/routes/public-run-tokens.d.ts.map +1 -1
- package/dist/routes/public-run-tokens.js +3 -0
- package/dist/routes/public-run-tokens.js.map +1 -1
- package/dist/routes/readable-projects.d.ts +26 -0
- package/dist/routes/readable-projects.d.ts.map +1 -0
- package/dist/routes/readable-projects.js +39 -0
- package/dist/routes/readable-projects.js.map +1 -0
- package/dist/routes/runs.d.ts +10 -0
- package/dist/routes/runs.d.ts.map +1 -1
- package/dist/routes/runs.js +92 -42
- package/dist/routes/runs.js.map +1 -1
- package/dist/routes/schedules.d.ts +21 -13
- package/dist/routes/schedules.d.ts.map +1 -1
- package/dist/routes/schedules.js +416 -55
- package/dist/routes/schedules.js.map +1 -1
- package/dist/routes/segments.d.ts +2 -0
- package/dist/routes/segments.d.ts.map +1 -1
- package/dist/routes/segments.js +5 -0
- package/dist/routes/segments.js.map +1 -1
- package/dist/routes/service-accounts.d.ts +17 -0
- package/dist/routes/service-accounts.d.ts.map +1 -0
- package/dist/routes/service-accounts.js +212 -0
- package/dist/routes/service-accounts.js.map +1 -0
- package/dist/routes/sign-in-options.d.ts +50 -0
- package/dist/routes/sign-in-options.d.ts.map +1 -0
- package/dist/routes/sign-in-options.js +98 -0
- package/dist/routes/sign-in-options.js.map +1 -0
- package/dist/routes/signing-keys.d.ts +2 -1
- package/dist/routes/signing-keys.d.ts.map +1 -1
- package/dist/routes/signing-keys.js +3 -1
- package/dist/routes/signing-keys.js.map +1 -1
- package/dist/routes/tenant-access.d.ts +13 -0
- package/dist/routes/tenant-access.d.ts.map +1 -0
- package/dist/routes/tenant-access.js +26 -0
- package/dist/routes/tenant-access.js.map +1 -0
- package/dist/routes/tenant.d.ts.map +1 -1
- package/dist/routes/tenant.js +4 -0
- package/dist/routes/tenant.js.map +1 -1
- package/dist/routes/token-sign-in.d.ts +36 -0
- package/dist/routes/token-sign-in.d.ts.map +1 -0
- package/dist/routes/token-sign-in.js +101 -0
- package/dist/routes/token-sign-in.js.map +1 -0
- package/dist/routes/tokens.d.ts +15 -5
- package/dist/routes/tokens.d.ts.map +1 -1
- package/dist/routes/tokens.js +162 -43
- package/dist/routes/tokens.js.map +1 -1
- package/dist/routes/tools.d.ts.map +1 -1
- package/dist/routes/tools.js +9 -1
- package/dist/routes/tools.js.map +1 -1
- package/dist/routes/trigger-access.d.ts +29 -0
- package/dist/routes/trigger-access.d.ts.map +1 -0
- package/dist/routes/trigger-access.js +33 -0
- package/dist/routes/trigger-access.js.map +1 -0
- package/dist/routes/webhook-endpoints.d.ts +2 -1
- package/dist/routes/webhook-endpoints.d.ts.map +1 -1
- package/dist/routes/webhook-endpoints.js +11 -3
- package/dist/routes/webhook-endpoints.js.map +1 -1
- package/dist/routes/webhooks.d.ts +5 -1
- package/dist/routes/webhooks.d.ts.map +1 -1
- package/dist/routes/webhooks.js +25 -2
- package/dist/routes/webhooks.js.map +1 -1
- package/dist/run-failure.d.ts +21 -0
- package/dist/run-failure.d.ts.map +1 -0
- package/dist/run-failure.js +29 -0
- package/dist/run-failure.js.map +1 -0
- package/dist/service-account-binding.d.ts +108 -0
- package/dist/service-account-binding.d.ts.map +1 -0
- package/dist/service-account-binding.js +4 -0
- package/dist/service-account-binding.js.map +1 -0
- package/dist/session-store-binding.d.ts +61 -13
- package/dist/session-store-binding.d.ts.map +1 -1
- package/dist/signed-export.d.ts +89 -0
- package/dist/signed-export.d.ts.map +1 -0
- package/dist/signed-export.js +149 -0
- package/dist/signed-export.js.map +1 -0
- package/dist/supervisor-binding.d.ts +186 -413
- package/dist/supervisor-binding.d.ts.map +1 -1
- package/dist/testing/app-bindings.d.ts +33 -0
- package/dist/testing/app-bindings.d.ts.map +1 -0
- package/dist/testing/app-bindings.js +112 -0
- package/dist/testing/app-bindings.js.map +1 -0
- package/dist/testing/index.d.ts +11 -0
- package/dist/testing/index.d.ts.map +1 -0
- package/dist/testing/index.js +13 -0
- package/dist/testing/index.js.map +1 -0
- package/dist/testing/stub-binding.d.ts +34 -0
- package/dist/testing/stub-binding.d.ts.map +1 -0
- package/dist/testing/stub-binding.js +42 -0
- package/dist/testing/stub-binding.js.map +1 -0
- package/dist/testing/trigger-registry.d.ts +21 -0
- package/dist/testing/trigger-registry.d.ts.map +1 -0
- package/dist/testing/trigger-registry.js +206 -0
- package/dist/testing/trigger-registry.js.map +1 -0
- package/dist/token-admin.d.ts +46 -8
- package/dist/token-admin.d.ts.map +1 -1
- package/dist/token-admin.js.map +1 -1
- package/dist/tool-binding.d.ts +13 -0
- package/dist/tool-binding.d.ts.map +1 -1
- package/dist/trigger-binding.d.ts +2 -2
- package/dist/trigger-binding.d.ts.map +1 -1
- package/dist/trigger-binding.js +1 -1
- package/dist/trigger-binding.js.map +1 -1
- package/dist/types.d.ts +16 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/webhook-endpoint-binding.d.ts +19 -3
- package/dist/webhook-endpoint-binding.d.ts.map +1 -1
- package/dist/webhook-endpoint-binding.js +1 -1
- package/dist/webhook-endpoint-binding.js.map +1 -1
- package/openapi.json +15387 -8789
- package/package.json +26 -22
- package/src/agent-binding.ts +14 -0
- package/src/agent-pins.ts +18 -2
- package/src/app.ts +366 -80
- package/src/block-publish.ts +181 -0
- package/src/caller.ts +62 -0
- package/src/capability-binding.ts +12 -0
- package/src/cost-binding.ts +19 -0
- package/src/derive-agent-version.ts +9 -1
- package/src/errors.ts +75 -7
- package/src/eval-case-binding.ts +17 -1
- package/src/eval-run-binding.ts +31 -0
- package/src/eval-run-dispatcher.ts +7 -0
- package/src/eval-sample.ts +42 -0
- package/src/eval-suite-binding.ts +14 -0
- package/src/flow-binding.ts +19 -0
- package/src/flow-pins.ts +18 -2
- package/src/gate.ts +26 -0
- package/src/handler-binding.ts +36 -1
- package/src/hitl-binding.ts +24 -0
- package/src/identity-directory-binding.ts +85 -4
- package/src/identity-provider-binding.ts +216 -21
- package/src/improvement-pass-binding.ts +193 -0
- package/src/index.ts +153 -31
- package/src/judged-dispatcher.ts +78 -11
- package/src/judgment-binding.ts +25 -1
- package/src/mcp-endpoint-binding.ts +7 -0
- package/src/memory-binding.ts +121 -17
- package/src/memory-erasure-binding.ts +156 -0
- package/src/middleware/auth.ts +228 -62
- package/src/middleware/authorize.ts +155 -3
- package/src/middleware/error-mapper.ts +15 -1
- package/src/middleware/idempotency.ts +230 -43
- package/src/middleware/key-project.ts +86 -0
- package/src/middleware/principal.ts +9 -5
- package/src/middleware/project-ref.ts +3 -0
- package/src/openapi/generate.ts +11 -2
- package/src/openapi/operations.ts +1060 -143
- package/src/openapi/schemas.ts +2387 -469
- package/src/person-grants-binding.ts +76 -0
- package/src/proposal-service.ts +640 -0
- package/src/proposal-status.ts +176 -0
- package/src/provider-binding.ts +10 -0
- package/src/publish-refused.ts +56 -9
- package/src/routes/adapters.ts +4 -0
- package/src/routes/agent-releases.ts +121 -69
- package/src/routes/agents.ts +128 -1
- package/src/routes/approvals.ts +164 -154
- package/src/routes/artifacts.ts +181 -11
- package/src/routes/auth.ts +804 -336
- package/src/routes/blocks.ts +2 -53
- package/src/routes/capabilities.ts +10 -1
- package/src/routes/compliance.ts +60 -99
- package/src/routes/conversations.ts +101 -7
- package/src/routes/cost.ts +110 -4
- package/src/routes/denied.ts +28 -0
- package/src/routes/deployments.ts +210 -17
- package/src/routes/eval-comparison.ts +81 -2
- package/src/routes/eval-overrides.ts +108 -0
- package/src/routes/eval-runs.ts +89 -6
- package/src/routes/eval-suites.ts +32 -2
- package/src/routes/event-triggers.ts +27 -3
- package/src/routes/export-signing-keys.ts +31 -0
- package/src/routes/flows.ts +12 -1
- package/src/routes/gate-policies.ts +37 -6
- package/src/routes/guardrails.ts +61 -2
- package/src/routes/identity.ts +328 -10
- package/src/routes/improvement-passes.ts +541 -0
- package/src/routes/judged-suites.ts +113 -43
- package/src/routes/judgment-context.ts +76 -5
- package/src/routes/judgment-flow-context.ts +16 -2
- package/src/routes/judgments.ts +17 -3
- package/src/routes/mcp.ts +29 -1
- package/src/routes/memory-access.ts +160 -0
- package/src/routes/memory-erasures.ts +240 -0
- package/src/routes/memory-parse.ts +250 -0
- package/src/routes/memory.ts +244 -286
- package/src/routes/observations.ts +15 -2
- package/src/routes/pagination.ts +12 -0
- package/src/routes/policies.ts +7 -1
- package/src/routes/project-mismatch.ts +56 -0
- package/src/routes/projects.ts +107 -13
- package/src/routes/proposals.ts +370 -611
- package/src/routes/provenance.ts +99 -133
- package/src/routes/providers.ts +142 -3
- package/src/routes/public-run-tokens.ts +3 -0
- package/src/routes/readable-projects.ts +66 -0
- package/src/routes/runs.ts +119 -39
- package/src/routes/schedules.ts +472 -60
- package/src/routes/segments.ts +11 -0
- package/src/routes/service-accounts.ts +262 -0
- package/src/routes/sign-in-options.ts +168 -0
- package/src/routes/signing-keys.ts +7 -1
- package/src/routes/tenant-access.ts +37 -0
- package/src/routes/tenant.ts +6 -0
- package/src/routes/token-sign-in.ts +161 -0
- package/src/routes/tokens.ts +172 -46
- package/src/routes/tools.ts +12 -1
- package/src/routes/trigger-access.ts +72 -0
- package/src/routes/webhook-endpoints.ts +15 -3
- package/src/routes/webhooks.ts +26 -2
- package/src/run-failure.ts +42 -0
- package/src/service-account-binding.ts +113 -0
- package/src/session-store-binding.ts +65 -13
- package/src/signed-export.ts +250 -0
- package/src/supervisor-binding.ts +169 -409
- package/src/testing/app-bindings.ts +142 -0
- package/src/testing/index.ts +21 -0
- package/src/testing/stub-binding.ts +53 -0
- package/src/testing/trigger-registry.ts +259 -0
- package/src/token-admin.ts +40 -8
- package/src/tool-binding.ts +14 -0
- package/src/trigger-binding.ts +8 -1
- package/src/types.ts +16 -0
- package/src/webhook-endpoint-binding.ts +18 -3
package/src/routes/segments.ts
CHANGED
|
@@ -61,6 +61,17 @@ export function parseSegmentsBody(raw: unknown, field = 'segments'): Parsed {
|
|
|
61
61
|
return checked(segments, field);
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
+
/** Whether a segment path is `prefix` or below it (`company=acme/role=cfo` is in `company=acme`). */
|
|
65
|
+
export function segmentsStartWith(
|
|
66
|
+
path: readonly ScopeSegment[],
|
|
67
|
+
prefix: readonly ScopeSegment[],
|
|
68
|
+
): boolean {
|
|
69
|
+
return (
|
|
70
|
+
path.length >= prefix.length &&
|
|
71
|
+
prefix.every((s, i) => path[i]?.key === s.key && path[i]?.value === s.value)
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
64
75
|
/** The same from a query: repeated `segment=key:value`, in order. */
|
|
65
76
|
export function parseSegmentsQuery(values: readonly string[]): Parsed {
|
|
66
77
|
const segments: ScopeSegment[] = [];
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// Copyright (C) 2026 Kindgi Inc.
|
|
3
|
+
|
|
4
|
+
import type { Context } from 'hono';
|
|
5
|
+
import { Hono } from 'hono';
|
|
6
|
+
|
|
7
|
+
import type { ProjectRole } from '@kindgi/platform';
|
|
8
|
+
import type { Cursor, Result, TenantId } from '@kindgi/types';
|
|
9
|
+
|
|
10
|
+
import { callerRef, isTenantAdmin } from '../caller.js';
|
|
11
|
+
import { statusFor, toWireError } from '../errors.js';
|
|
12
|
+
import type { Authorizer } from '../middleware/authorize.js';
|
|
13
|
+
import type {
|
|
14
|
+
ServiceAccount,
|
|
15
|
+
ServiceAccountBinding,
|
|
16
|
+
ServiceAccountError,
|
|
17
|
+
ServiceAccountGrant,
|
|
18
|
+
ServiceAccountGrantTarget,
|
|
19
|
+
} from '../service-account-binding.js';
|
|
20
|
+
import type { AppEnv } from '../types.js';
|
|
21
|
+
import { clampLimit } from './pagination.js';
|
|
22
|
+
|
|
23
|
+
/** Lowercase letters, digits and hyphens, starting with a letter or digit. */
|
|
24
|
+
const NAME_RE = /^[a-z0-9][a-z0-9-]{0,62}$/;
|
|
25
|
+
const MAX_DESCRIPTION_LENGTH = 500;
|
|
26
|
+
const PROJECT_ROLES: readonly ProjectRole[] = ['viewer', 'editor', 'owner', 'admin', 'member'];
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Service accounts (`/v1/service-accounts`), for tenant admins:
|
|
30
|
+
*
|
|
31
|
+
* - `POST /` creates one, with its first grants: `{name, description?, grants?}`;
|
|
32
|
+
* - `GET /` lists them, oldest first (`?includeUnregistered=true` for all);
|
|
33
|
+
* - `GET /:serviceAccountId` reads one, unregistered or not;
|
|
34
|
+
* - `POST /:serviceAccountId/grant` adds a grant; `/ungrant` removes one;
|
|
35
|
+
* - `POST /:serviceAccountId/unregister` tombstones it: grants gone, keys dead.
|
|
36
|
+
*
|
|
37
|
+
* Keys for an account are minted at `POST /v1/tokens` with `for`.
|
|
38
|
+
*/
|
|
39
|
+
export function serviceAccountsRouter(
|
|
40
|
+
binding: ServiceAccountBinding,
|
|
41
|
+
authorizer?: Authorizer,
|
|
42
|
+
): Hono<AppEnv> {
|
|
43
|
+
const r = new Hono<AppEnv>();
|
|
44
|
+
|
|
45
|
+
r.use('/*', async (c, next) => {
|
|
46
|
+
if (!(await isTenantAdmin(c, authorizer))) {
|
|
47
|
+
return fail(c, {
|
|
48
|
+
code: 'permission-denied',
|
|
49
|
+
message: 'Only a tenant admin manages service accounts',
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
await next();
|
|
53
|
+
return;
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
r.post('/', async (c) => {
|
|
57
|
+
const body = await readBody(c);
|
|
58
|
+
if (body.kind === 'err') return fail(c, body.error);
|
|
59
|
+
const parsed = parseCreateBody(body.value);
|
|
60
|
+
if (parsed.kind === 'err') return fail(c, parsed.error);
|
|
61
|
+
const createdBy = callerRef(c);
|
|
62
|
+
const created = await binding.create({
|
|
63
|
+
tenantId: tenantOf(c),
|
|
64
|
+
...parsed.value,
|
|
65
|
+
...(createdBy !== undefined && { createdBy }),
|
|
66
|
+
});
|
|
67
|
+
return respond(c, created, 201);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
r.get('/', async (c) => {
|
|
71
|
+
const cursor = c.req.query('cursor');
|
|
72
|
+
const page = await binding.list({
|
|
73
|
+
tenantId: tenantOf(c),
|
|
74
|
+
limit: clampLimit(c.req.query('limit')),
|
|
75
|
+
...(cursor !== undefined && cursor !== '' && { cursor: cursor as Cursor }),
|
|
76
|
+
...(c.req.query('includeUnregistered') === 'true' && { includeUnregistered: true }),
|
|
77
|
+
});
|
|
78
|
+
return c.json({
|
|
79
|
+
data: page.data.map(toWire),
|
|
80
|
+
hasMore: page.nextCursor !== undefined,
|
|
81
|
+
...(page.nextCursor !== undefined && { nextCursor: page.nextCursor as unknown as string }),
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
r.get('/:serviceAccountId', async (c) => {
|
|
86
|
+
const serviceAccountId = c.req.param('serviceAccountId');
|
|
87
|
+
const account = await binding.get({ tenantId: tenantOf(c), serviceAccountId });
|
|
88
|
+
if (account === null) return fail(c, notFound(serviceAccountId));
|
|
89
|
+
return c.json(toWire(account));
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
r.post('/:serviceAccountId/grant', async (c) => {
|
|
93
|
+
const body = await readBody(c);
|
|
94
|
+
if (body.kind === 'err') return fail(c, body.error);
|
|
95
|
+
const grant = parseGrant(body.value);
|
|
96
|
+
if (grant.kind === 'err') return fail(c, grant.error);
|
|
97
|
+
const serviceAccountId = c.req.param('serviceAccountId');
|
|
98
|
+
return respond(
|
|
99
|
+
c,
|
|
100
|
+
await binding.grant({
|
|
101
|
+
tenantId: tenantOf(c),
|
|
102
|
+
serviceAccountId,
|
|
103
|
+
grant: grant.value,
|
|
104
|
+
...byCaller(c),
|
|
105
|
+
}),
|
|
106
|
+
);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
r.post('/:serviceAccountId/ungrant', async (c) => {
|
|
110
|
+
const body = await readBody(c);
|
|
111
|
+
if (body.kind === 'err') return fail(c, body.error);
|
|
112
|
+
const grant = parseGrantTarget(body.value);
|
|
113
|
+
if (grant.kind === 'err') return fail(c, grant.error);
|
|
114
|
+
const serviceAccountId = c.req.param('serviceAccountId');
|
|
115
|
+
return respond(
|
|
116
|
+
c,
|
|
117
|
+
await binding.ungrant({
|
|
118
|
+
tenantId: tenantOf(c),
|
|
119
|
+
serviceAccountId,
|
|
120
|
+
grant: grant.value,
|
|
121
|
+
...byCaller(c),
|
|
122
|
+
}),
|
|
123
|
+
);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
r.post('/:serviceAccountId/unregister', async (c) => {
|
|
127
|
+
const serviceAccountId = c.req.param('serviceAccountId');
|
|
128
|
+
return respond(
|
|
129
|
+
c,
|
|
130
|
+
await binding.unregister({ tenantId: tenantOf(c), serviceAccountId, ...byCaller(c) }),
|
|
131
|
+
);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
return r;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
type WireError = { readonly code: string; readonly message: string };
|
|
138
|
+
type Parsed<T> = { kind: 'ok'; value: T } | { kind: 'err'; error: WireError };
|
|
139
|
+
|
|
140
|
+
/** Who made the change, for the record. */
|
|
141
|
+
function byCaller(c: Context<AppEnv>): { by?: string } {
|
|
142
|
+
const by = callerRef(c);
|
|
143
|
+
return by !== undefined ? { by } : {};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function tenantOf(c: Context<AppEnv>): TenantId {
|
|
147
|
+
return c.get('tenantId') as TenantId;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function fail(c: Context<AppEnv>, error: WireError): Response {
|
|
151
|
+
c.status(statusFor(error.code) as never);
|
|
152
|
+
return c.json(toWireError(error, c.get('requestId')));
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function notFound(serviceAccountId: string): WireError {
|
|
156
|
+
return {
|
|
157
|
+
code: 'service-account-not-found',
|
|
158
|
+
message: `No service account with id "${serviceAccountId}" in this tenant`,
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function respond(
|
|
163
|
+
c: Context<AppEnv>,
|
|
164
|
+
result: Result<ServiceAccount, ServiceAccountError>,
|
|
165
|
+
status: 200 | 201 = 200,
|
|
166
|
+
): Response {
|
|
167
|
+
if (result.kind === 'err') return fail(c, result.error);
|
|
168
|
+
c.status(status);
|
|
169
|
+
return c.json(toWire(result.value));
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function toWire(a: ServiceAccount): Record<string, unknown> {
|
|
173
|
+
return {
|
|
174
|
+
serviceAccountId: a.serviceAccountId,
|
|
175
|
+
name: a.name,
|
|
176
|
+
...(a.description !== undefined && { description: a.description }),
|
|
177
|
+
grants: a.grants,
|
|
178
|
+
...(a.createdBy !== undefined && { createdBy: a.createdBy }),
|
|
179
|
+
createdAt: a.createdAt,
|
|
180
|
+
...(a.unregisteredAt !== undefined && { unregisteredAt: a.unregisteredAt }),
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
async function readBody(c: Context<AppEnv>): Promise<Parsed<Record<string, unknown>>> {
|
|
185
|
+
let body: unknown;
|
|
186
|
+
try {
|
|
187
|
+
body = await c.req.json();
|
|
188
|
+
} catch {
|
|
189
|
+
return badInput('Request body must be a JSON object');
|
|
190
|
+
}
|
|
191
|
+
if (body === null || typeof body !== 'object' || Array.isArray(body)) {
|
|
192
|
+
return badInput('Request body must be a JSON object');
|
|
193
|
+
}
|
|
194
|
+
return { kind: 'ok', value: body as Record<string, unknown> };
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function badInput(message: string): { kind: 'err'; error: WireError } {
|
|
198
|
+
return { kind: 'err', error: { code: 'bad-input', message } };
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function parseCreateBody(b: Record<string, unknown>): Parsed<{
|
|
202
|
+
name: string;
|
|
203
|
+
description?: string;
|
|
204
|
+
grants: readonly ServiceAccountGrant[];
|
|
205
|
+
}> {
|
|
206
|
+
if (typeof b.name !== 'string' || !NAME_RE.test(b.name)) {
|
|
207
|
+
return badInput(
|
|
208
|
+
'`name` must be 1-63 lowercase letters, digits or hyphens, starting with a letter or digit',
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
if (
|
|
212
|
+
b.description !== undefined &&
|
|
213
|
+
(typeof b.description !== 'string' || b.description.length > MAX_DESCRIPTION_LENGTH)
|
|
214
|
+
) {
|
|
215
|
+
return badInput(
|
|
216
|
+
`\`description\` must be a string of at most ${MAX_DESCRIPTION_LENGTH} characters`,
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
if (b.grants !== undefined && !Array.isArray(b.grants)) {
|
|
220
|
+
return badInput('`grants` must be an array of grants');
|
|
221
|
+
}
|
|
222
|
+
const grants: ServiceAccountGrant[] = [];
|
|
223
|
+
for (const raw of (b.grants as unknown[] | undefined) ?? []) {
|
|
224
|
+
const grant = parseGrant(raw);
|
|
225
|
+
if (grant.kind === 'err') return grant;
|
|
226
|
+
grants.push(grant.value);
|
|
227
|
+
}
|
|
228
|
+
return {
|
|
229
|
+
kind: 'ok',
|
|
230
|
+
value: {
|
|
231
|
+
name: b.name,
|
|
232
|
+
...(typeof b.description === 'string' && { description: b.description }),
|
|
233
|
+
grants,
|
|
234
|
+
},
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/** `{kind: 'tenant-admin'}`, `{kind: 'tenant-member'}` or `{kind: 'project', projectId, role}`. */
|
|
239
|
+
function parseGrant(raw: unknown): Parsed<ServiceAccountGrant> {
|
|
240
|
+
const target = parseGrantTarget(raw);
|
|
241
|
+
if (target.kind === 'err') return target;
|
|
242
|
+
if (target.value.kind !== 'project') return { kind: 'ok', value: target.value };
|
|
243
|
+
const role = (raw as { role?: unknown }).role;
|
|
244
|
+
if (typeof role !== 'string' || !PROJECT_ROLES.includes(role as ProjectRole)) {
|
|
245
|
+
return badInput(`A project grant's \`role\` must be one of: ${PROJECT_ROLES.join(', ')}`);
|
|
246
|
+
}
|
|
247
|
+
return { kind: 'ok', value: { ...target.value, role: role as ProjectRole } };
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/** `{kind: 'tenant-admin'}`, `{kind: 'tenant-member'}` or `{kind: 'project', projectId}`. */
|
|
251
|
+
function parseGrantTarget(raw: unknown): Parsed<ServiceAccountGrantTarget> {
|
|
252
|
+
const g = (raw ?? {}) as { kind?: unknown; projectId?: unknown };
|
|
253
|
+
if (g.kind === 'tenant-admin') return { kind: 'ok', value: { kind: 'tenant-admin' } };
|
|
254
|
+
if (g.kind === 'tenant-member') return { kind: 'ok', value: { kind: 'tenant-member' } };
|
|
255
|
+
if (g.kind === 'project') {
|
|
256
|
+
if (typeof g.projectId !== 'string' || g.projectId === '') {
|
|
257
|
+
return badInput('A project grant needs `projectId`');
|
|
258
|
+
}
|
|
259
|
+
return { kind: 'ok', value: { kind: 'project', projectId: g.projectId } };
|
|
260
|
+
}
|
|
261
|
+
return badInput("A grant's `kind` must be 'tenant-admin', 'tenant-member' or 'project'");
|
|
262
|
+
}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// Copyright (C) 2026 Kindgi Inc.
|
|
3
|
+
|
|
4
|
+
import { Hono } from 'hono';
|
|
5
|
+
|
|
6
|
+
import { statusFor, toWireError } from '../errors.js';
|
|
7
|
+
import type { IdentityProviderBinding, SignInOption } from '../identity-provider-binding.js';
|
|
8
|
+
import type { AppEnv } from '../types.js';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* `GET /v1/auth/sign-in-options?email=`: how a person can sign in, before
|
|
12
|
+
* anyone is signed in. Mounted outside the auth chain.
|
|
13
|
+
*
|
|
14
|
+
* It answers by the email's DOMAIN only, so the answer for two people at
|
|
15
|
+
* the same domain is the same byte for byte, whether or not either of
|
|
16
|
+
* them has an account: it can't be used to find out who does. Requests
|
|
17
|
+
* are rate-limited per client, as a speed bump against scraping.
|
|
18
|
+
*
|
|
19
|
+
* Email first: without an email, nothing is offered (an empty list), and
|
|
20
|
+
* the binding isn't asked. `methods` tells a sign-in page which ways in
|
|
21
|
+
* there are; always mounted, so with none it says so (both `false`).
|
|
22
|
+
*/
|
|
23
|
+
export interface SignInOptionsRouteOptions {
|
|
24
|
+
/** Absent: no sign-in with identity providers here (only an API token, if allowed). */
|
|
25
|
+
readonly identityProvider?: IdentityProviderBinding;
|
|
26
|
+
/** Whether a person may sign in to the console with an API token here. */
|
|
27
|
+
readonly tokenSignIn: boolean;
|
|
28
|
+
/**
|
|
29
|
+
* The emailed sign-in link, when the deployment offers it: a sign-in page
|
|
30
|
+
* shows "Email me a sign-in link", with the Turnstile widget when there's
|
|
31
|
+
* a site key. `allowedFor`: whether it's offered for an email's domain
|
|
32
|
+
* (say, not where a workspace signs its people in with its own identity
|
|
33
|
+
* provider). Absent: for every domain.
|
|
34
|
+
*/
|
|
35
|
+
readonly emailLink?: {
|
|
36
|
+
readonly captchaSiteKey?: string;
|
|
37
|
+
readonly allowedFor?: (emailDomain: string) => Promise<boolean>;
|
|
38
|
+
};
|
|
39
|
+
readonly rateLimit?: SignInOptionsRateLimit;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface SignInOptionsRateLimit {
|
|
43
|
+
/** Requests per client per window. Default 30. */
|
|
44
|
+
readonly limit?: number;
|
|
45
|
+
/** Default 60 000 ms. */
|
|
46
|
+
readonly windowMs?: number;
|
|
47
|
+
/**
|
|
48
|
+
* Who the client is. Default: the nearest `X-Forwarded-For` hop (the
|
|
49
|
+
* rightmost, written by the proxy in front), else one shared bucket;
|
|
50
|
+
* never the leftmost, which the client writes. A deployment that knows
|
|
51
|
+
* its client's address (the socket's, or past its trusted proxies)
|
|
52
|
+
* passes its own.
|
|
53
|
+
*/
|
|
54
|
+
readonly clientKey?: (request: Request) => string;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const DEFAULT_LIMIT = 30;
|
|
58
|
+
const DEFAULT_WINDOW_MS = 60_000;
|
|
59
|
+
// The bucket map is pruned once it holds this many clients.
|
|
60
|
+
const MAX_TRACKED_CLIENTS = 10_000;
|
|
61
|
+
|
|
62
|
+
// One `@`, a non-empty local part, and a domain with a dot.
|
|
63
|
+
const EMAIL_RE = /^[^\s@]+@([^\s@]+\.[^\s@]+)$/;
|
|
64
|
+
|
|
65
|
+
export function signInOptionsRouter(options: SignInOptionsRouteOptions): Hono<AppEnv> {
|
|
66
|
+
const { identityProvider } = options;
|
|
67
|
+
const limit = options.rateLimit?.limit ?? DEFAULT_LIMIT;
|
|
68
|
+
const windowMs = options.rateLimit?.windowMs ?? DEFAULT_WINDOW_MS;
|
|
69
|
+
const clientKey = options.rateLimit?.clientKey ?? defaultClientKey;
|
|
70
|
+
const buckets = new Map<string, { windowStart: number; count: number }>();
|
|
71
|
+
|
|
72
|
+
const router = new Hono<AppEnv>();
|
|
73
|
+
router.get('/', async (c) => {
|
|
74
|
+
const requestId = c.get('requestId');
|
|
75
|
+
|
|
76
|
+
const now = Date.now();
|
|
77
|
+
const key = clientKey(c.req.raw);
|
|
78
|
+
const bucket = buckets.get(key);
|
|
79
|
+
if (bucket === undefined || now - bucket.windowStart >= windowMs) {
|
|
80
|
+
if (buckets.size >= MAX_TRACKED_CLIENTS) pruneExpired(buckets, now, windowMs);
|
|
81
|
+
buckets.set(key, { windowStart: now, count: 1 });
|
|
82
|
+
} else if (bucket.count >= limit) {
|
|
83
|
+
const retryAfter = Math.ceil((bucket.windowStart + windowMs - now) / 1000);
|
|
84
|
+
c.header('Retry-After', String(Math.max(retryAfter, 1)));
|
|
85
|
+
c.status(statusFor('rate-limit-exceeded') as never);
|
|
86
|
+
return c.json(
|
|
87
|
+
toWireError(
|
|
88
|
+
{ code: 'rate-limit-exceeded', message: 'Too many sign-in lookups: try again shortly' },
|
|
89
|
+
requestId,
|
|
90
|
+
),
|
|
91
|
+
);
|
|
92
|
+
} else {
|
|
93
|
+
bucket.count += 1;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const email = c.req.query('email');
|
|
97
|
+
let emailDomain: string | undefined;
|
|
98
|
+
if (email !== undefined) {
|
|
99
|
+
const match = EMAIL_RE.exec(email.trim());
|
|
100
|
+
if (match === null) {
|
|
101
|
+
c.status(statusFor('bad-input') as never);
|
|
102
|
+
return c.json(
|
|
103
|
+
toWireError(
|
|
104
|
+
{ code: 'bad-input', message: '`email` must be an email address' },
|
|
105
|
+
requestId,
|
|
106
|
+
),
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
emailDomain = (match[1] as string).toLowerCase();
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const offered: readonly SignInOption[] =
|
|
113
|
+
identityProvider?.signInOptions === undefined || emailDomain === undefined
|
|
114
|
+
? []
|
|
115
|
+
: await identityProvider.signInOptions({ emailDomain });
|
|
116
|
+
const emailLink = options.emailLink;
|
|
117
|
+
const linkOffered =
|
|
118
|
+
emailLink !== undefined &&
|
|
119
|
+
(emailDomain === undefined ||
|
|
120
|
+
emailLink.allowedFor === undefined ||
|
|
121
|
+
(await emailLink.allowedFor(emailDomain)));
|
|
122
|
+
c.header('Cache-Control', 'no-store');
|
|
123
|
+
return c.json({
|
|
124
|
+
data: offered.map((o) => ({
|
|
125
|
+
providerId: o.providerId,
|
|
126
|
+
displayName: o.displayName,
|
|
127
|
+
signInUrl: o.signInUrl,
|
|
128
|
+
...(o.owner !== undefined && { owner: o.owner }),
|
|
129
|
+
})),
|
|
130
|
+
methods: {
|
|
131
|
+
identityProviders: identityProvider !== undefined,
|
|
132
|
+
apiToken: options.tokenSignIn,
|
|
133
|
+
...(linkOffered && {
|
|
134
|
+
emailLink: {
|
|
135
|
+
...(emailLink.captchaSiteKey !== undefined && {
|
|
136
|
+
captchaSiteKey: emailLink.captchaSiteKey,
|
|
137
|
+
}),
|
|
138
|
+
},
|
|
139
|
+
}),
|
|
140
|
+
},
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
return router;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function defaultClientKey(request: Request): string {
|
|
147
|
+
const hops = (request.headers.get('x-forwarded-for') ?? '')
|
|
148
|
+
.split(',')
|
|
149
|
+
.map((h) => h.trim())
|
|
150
|
+
.filter((h) => h !== '');
|
|
151
|
+
return hops.at(-1) ?? 'shared';
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function pruneExpired(
|
|
155
|
+
buckets: Map<string, { windowStart: number; count: number }>,
|
|
156
|
+
now: number,
|
|
157
|
+
windowMs: number,
|
|
158
|
+
): void {
|
|
159
|
+
for (const [k, b] of buckets) {
|
|
160
|
+
if (now - b.windowStart >= windowMs) buckets.delete(k);
|
|
161
|
+
}
|
|
162
|
+
// Still full: every client is mid-window. Drop the oldest half rather
|
|
163
|
+
// than grow without bound.
|
|
164
|
+
if (buckets.size >= MAX_TRACKED_CLIENTS) {
|
|
165
|
+
const keys = [...buckets.keys()].slice(0, Math.floor(MAX_TRACKED_CLIENTS / 2));
|
|
166
|
+
for (const k of keys) buckets.delete(k);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
@@ -6,10 +6,12 @@ import { type Context, Hono } from 'hono';
|
|
|
6
6
|
import type { Cursor, SigningKeyId, TenantId } from '@kindgi/types';
|
|
7
7
|
|
|
8
8
|
import { statusFor, toWireError } from '../errors.js';
|
|
9
|
+
import type { Authorizer } from '../middleware/authorize.js';
|
|
9
10
|
import type { SigningKeyBinding, TrustedKey } from '../signing-key-binding.js';
|
|
10
11
|
import type { AppEnv } from '../types.js';
|
|
11
12
|
import { hasCapability } from './env.js';
|
|
12
13
|
import { clampLimit } from './pagination.js';
|
|
14
|
+
import { tenantResourceAccess } from './tenant-access.js';
|
|
13
15
|
|
|
14
16
|
/**
|
|
15
17
|
* The tenant's trusted signing keys: the public keys whose signatures
|
|
@@ -29,8 +31,12 @@ import { clampLimit } from './pagination.js';
|
|
|
29
31
|
* Writes need the `signing-keys:write` capability; reads, the tenant's
|
|
30
32
|
* bearer.
|
|
31
33
|
*/
|
|
32
|
-
export function signingKeysRouter(
|
|
34
|
+
export function signingKeysRouter(
|
|
35
|
+
binding: SigningKeyBinding,
|
|
36
|
+
authorizer?: Authorizer,
|
|
37
|
+
): Hono<AppEnv> {
|
|
33
38
|
const r = new Hono<AppEnv>();
|
|
39
|
+
r.use('*', tenantResourceAccess(authorizer));
|
|
34
40
|
|
|
35
41
|
// ---------- POST / (trust a key) ----------
|
|
36
42
|
r.post('/', async (c) => {
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// Copyright (C) 2026 Kindgi Inc.
|
|
3
|
+
|
|
4
|
+
import type { MiddlewareHandler } from 'hono';
|
|
5
|
+
|
|
6
|
+
import { ref } from '@kindgi/authz';
|
|
7
|
+
|
|
8
|
+
import type { Authorizer } from '../middleware/authorize.js';
|
|
9
|
+
import type { AppEnv } from '../types.js';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Access to a tenant-wide resource (providers, policies, adapters, …):
|
|
13
|
+
* reading it needs `read` on the tenant, anything else `admin`. The
|
|
14
|
+
* authorization model gives a tenant no `write`, so a tenant-wide change
|
|
15
|
+
* is an admin's. Without an authorizer, nothing is checked (as before).
|
|
16
|
+
*/
|
|
17
|
+
export function tenantResourceAccess(
|
|
18
|
+
authorizer: Authorizer | undefined,
|
|
19
|
+
): MiddlewareHandler<AppEnv> {
|
|
20
|
+
return async (c, next) => {
|
|
21
|
+
if (authorizer === undefined) return next();
|
|
22
|
+
const reading = c.req.method === 'GET' || c.req.method === 'HEAD';
|
|
23
|
+
return authorizer.authorize(reading ? 'read' : 'admin', (ctx) =>
|
|
24
|
+
ref('tenant', ctx.get('tenantId') as unknown as string),
|
|
25
|
+
)(c, next);
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Access to a tenant setting every call of which is an admin's (webhook endpoints, compliance). */
|
|
30
|
+
export function tenantAdminAccess(authorizer: Authorizer | undefined): MiddlewareHandler<AppEnv> {
|
|
31
|
+
return async (c, next) => {
|
|
32
|
+
if (authorizer === undefined) return next();
|
|
33
|
+
return authorizer.authorize('admin', (ctx) =>
|
|
34
|
+
ref('tenant', ctx.get('tenantId') as unknown as string),
|
|
35
|
+
)(c, next);
|
|
36
|
+
};
|
|
37
|
+
}
|
package/src/routes/tenant.ts
CHANGED
|
@@ -148,6 +148,12 @@ export function tenantRouter(options: TenantRouterOptions): Hono<AppEnv> {
|
|
|
148
148
|
'*',
|
|
149
149
|
authorizer.authorize('read', (c) => ref('tenant', c.get('tenantId') as unknown as string)),
|
|
150
150
|
);
|
|
151
|
+
// Changing the config writes the tenant's secrets and env: `admin`
|
|
152
|
+
// on the tenant (T243 A), as every tenant-wide write.
|
|
153
|
+
const admin = authorizer.authorize('admin', (c) =>
|
|
154
|
+
ref('tenant', c.get('tenantId') as unknown as string),
|
|
155
|
+
);
|
|
156
|
+
r.use('/config', async (c, next) => (c.req.method === 'PATCH' ? admin(c, next) : next()));
|
|
151
157
|
}
|
|
152
158
|
|
|
153
159
|
// ---------- GET / ----------
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// Copyright (C) 2026 Kindgi Inc.
|
|
3
|
+
|
|
4
|
+
import { randomUUID } from 'node:crypto';
|
|
5
|
+
|
|
6
|
+
import { Hono } from 'hono';
|
|
7
|
+
import { setCookie } from 'hono/cookie';
|
|
8
|
+
|
|
9
|
+
import type { AuditEvent, AuditEventBinding } from '@kindgi/audit-events';
|
|
10
|
+
import type { Timestamp } from '@kindgi/types';
|
|
11
|
+
|
|
12
|
+
import { statusFor, toWireError } from '../errors.js';
|
|
13
|
+
import { encodeSessionToken } from '../middleware/auth.js';
|
|
14
|
+
import { withholdFromReplay } from '../middleware/idempotency.js';
|
|
15
|
+
import type { SessionStoreBinding } from '../session-store-binding.js';
|
|
16
|
+
import type { AppEnv } from '../types.js';
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* `POST /v1/auth/token-sign-in`: a person signs in to the console with an
|
|
20
|
+
* API token. The token comes once, in `Authorization`, and is exchanged for
|
|
21
|
+
* a browser session in the session cookie (HttpOnly, the same as a sign-in
|
|
22
|
+
* with an identity provider), so the browser never keeps it.
|
|
23
|
+
*
|
|
24
|
+
* Only a person's **full** key opens a session. A service account's key is
|
|
25
|
+
* for machines, and a narrowed key (a `member` role, or one project) would
|
|
26
|
+
* be widened to the person's full grants by a session: both are refused,
|
|
27
|
+
* 403 `token-sign-in-not-allowed`. The session acts as the key did: it
|
|
28
|
+
* carries the key's scopes (a deployment without an authorizer reads tenant
|
|
29
|
+
* admin from them; a key's scopes never change after it's minted). It never
|
|
30
|
+
* outlives the key: it ends when the key expires, and, with a store that has
|
|
31
|
+
* `revokeByProvider`, when the key is revoked (its `providerId` is
|
|
32
|
+
* `api-token:<tokenId>`).
|
|
33
|
+
* Refusals are audited (`sign-in-refused`), as is each sign-in.
|
|
34
|
+
*/
|
|
35
|
+
export type TokenSignInRouteOptions =
|
|
36
|
+
/** The deployment doesn't allow it, or has no browser sessions: 403 `token-sign-in-off`. */
|
|
37
|
+
| { readonly enabled: false }
|
|
38
|
+
| {
|
|
39
|
+
readonly enabled: true;
|
|
40
|
+
readonly sessionStore: SessionStoreBinding;
|
|
41
|
+
/** The session's lifetime, in milliseconds. */
|
|
42
|
+
readonly ttlMs: number;
|
|
43
|
+
readonly cookieName: string;
|
|
44
|
+
/** `signed-in` events, best effort. */
|
|
45
|
+
readonly auditEvents?: AuditEventBinding;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export function tokenSignInRouter(options: TokenSignInRouteOptions): Hono<AppEnv> {
|
|
49
|
+
const r = new Hono<AppEnv>();
|
|
50
|
+
|
|
51
|
+
r.post('/', async (c) => {
|
|
52
|
+
const requestId = c.get('requestId');
|
|
53
|
+
/** An audit event, best effort: a failure is logged, never the answer. */
|
|
54
|
+
const auditEvents = options.enabled ? options.auditEvents : undefined;
|
|
55
|
+
const audit = async (event: Omit<AuditEvent, 'id' | 'timestamp'>) => {
|
|
56
|
+
if (auditEvents === undefined) return;
|
|
57
|
+
try {
|
|
58
|
+
const appended = await auditEvents.append([
|
|
59
|
+
{ ...event, id: randomUUID(), timestamp: new Date().toISOString() as Timestamp },
|
|
60
|
+
]);
|
|
61
|
+
if (appended.kind === 'err') {
|
|
62
|
+
c.get('log').warn(`${event.kind} audit event failed: ${appended.error.message}`);
|
|
63
|
+
}
|
|
64
|
+
} catch (cause) {
|
|
65
|
+
c.get('log').warn(
|
|
66
|
+
`${event.kind} audit event failed: ${cause instanceof Error ? cause.message : String(cause)}`,
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
const refuse = async (code: string, message: string) => {
|
|
71
|
+
const userId = c.get('userId');
|
|
72
|
+
const serviceAccountId = c.get('serviceAccountId');
|
|
73
|
+
await audit({
|
|
74
|
+
tenantId: c.get('tenantId'),
|
|
75
|
+
kind: 'sign-in-refused',
|
|
76
|
+
actor:
|
|
77
|
+
serviceAccountId !== undefined
|
|
78
|
+
? `service_account:${serviceAccountId}`
|
|
79
|
+
: userId !== undefined
|
|
80
|
+
? `user:${userId}`
|
|
81
|
+
: 'system',
|
|
82
|
+
outcome: 'denied',
|
|
83
|
+
payload: { v: 1, doc: { method: 'api-token', reason: code } },
|
|
84
|
+
});
|
|
85
|
+
c.status(statusFor(code) as never);
|
|
86
|
+
return c.json(toWireError({ code, message }, requestId));
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
if (!options.enabled) {
|
|
90
|
+
return refuse(
|
|
91
|
+
'token-sign-in-off',
|
|
92
|
+
"This deployment doesn't allow signing in to the console with an API token. Sign in with your organization's identity provider, or ask whoever runs Kindgi to allow it.",
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
if (c.get('sessionId') !== undefined) {
|
|
96
|
+
return refuse(
|
|
97
|
+
'token-sign-in-needs-an-api-token',
|
|
98
|
+
'You are already signed in with a session: token sign-in takes an API token in the Authorization header.',
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
const tenantId = c.get('tenantId');
|
|
102
|
+
const userId = c.get('userId');
|
|
103
|
+
if (c.get('serviceAccountId') !== undefined || userId === undefined) {
|
|
104
|
+
return refuse(
|
|
105
|
+
'token-sign-in-not-allowed',
|
|
106
|
+
"Only a person's API key can open a console session; a service account's key is for machines.",
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
if (c.get('tokenRole') === 'member' || c.get('tokenProjectId') !== undefined) {
|
|
110
|
+
return refuse(
|
|
111
|
+
'token-sign-in-not-allowed',
|
|
112
|
+
"A narrowed key (a member role, or one project) can't open a console session: the session would carry all of your permissions. Use a full key, or sign in with your identity provider.",
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const keyExpiresAt = c.get('tokenExpiresAt');
|
|
117
|
+
const expires = Math.min(
|
|
118
|
+
Date.now() + options.ttlMs,
|
|
119
|
+
keyExpiresAt === undefined ? Number.POSITIVE_INFINITY : keyExpiresAt.getTime(),
|
|
120
|
+
);
|
|
121
|
+
const tokenId = c.get('tokenId');
|
|
122
|
+
const created = await options.sessionStore.create({
|
|
123
|
+
tenantId,
|
|
124
|
+
userId,
|
|
125
|
+
providerId: tokenId === undefined ? 'api-token' : `api-token:${tokenId}`,
|
|
126
|
+
expiresAt: new Date(expires).toISOString() as Timestamp,
|
|
127
|
+
// What the key may do, so the session acts as it did: with authz off,
|
|
128
|
+
// an admin key's `tenant-admin` is how the server and console know.
|
|
129
|
+
scopes: [...(c.get('scopes') ?? [])],
|
|
130
|
+
});
|
|
131
|
+
setCookie(c, options.cookieName, created.token ?? encodeSessionToken(created.sessionId), {
|
|
132
|
+
httpOnly: true,
|
|
133
|
+
secure: true,
|
|
134
|
+
sameSite: 'Lax',
|
|
135
|
+
path: '/',
|
|
136
|
+
maxAge: Math.max(0, Math.floor((expires - Date.now()) / 1000)),
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
await audit({
|
|
140
|
+
tenantId,
|
|
141
|
+
kind: 'signed-in',
|
|
142
|
+
actor: `user:${userId}`,
|
|
143
|
+
outcome: 'succeeded',
|
|
144
|
+
payload: {
|
|
145
|
+
v: 1,
|
|
146
|
+
doc: {
|
|
147
|
+
method: 'api-token',
|
|
148
|
+
sessionId: created.sessionId,
|
|
149
|
+
...(tokenId !== undefined && { tokenId }),
|
|
150
|
+
},
|
|
151
|
+
},
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
// The session is in the cookie, which a stored answer doesn't keep: a
|
|
155
|
+
// repeat with the same Idempotency-Key is refused, not "signed in".
|
|
156
|
+
withholdFromReplay(c);
|
|
157
|
+
return c.json({ userId, expiresAt: new Date(expires).toISOString() });
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
return r;
|
|
161
|
+
}
|