@kindgi/api 0.1.4-rc.5 → 0.1.5-rc.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/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/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 +73 -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 +6 -0
- package/dist/middleware/authorize.d.ts.map +1 -1
- package/dist/middleware/authorize.js +113 -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 +918 -139
- package/dist/openapi/operations.js.map +1 -1
- package/dist/openapi/schemas.d.ts +92 -13
- package/dist/openapi/schemas.d.ts.map +1 -1
- package/dist/openapi/schemas.js +2122 -370
- 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 +18 -3
- package/dist/publish-refused.d.ts.map +1 -1
- package/dist/publish-refused.js +18 -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 +7 -1
- package/dist/routes/cost.d.ts.map +1 -1
- package/dist/routes/cost.js +22 -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 +2 -1
- package/dist/routes/deployments.d.ts.map +1 -1
- package/dist/routes/deployments.js +44 -12
- 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 +63 -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 +20 -1
- 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 +129 -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 +188 -8
- 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/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 +15286 -8653
- 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/derive-agent-version.ts +9 -1
- package/src/errors.ts +73 -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 +139 -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 +1059 -142
- package/src/openapi/schemas.ts +2438 -485
- 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 +38 -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 +32 -3
- package/src/routes/denied.ts +28 -0
- package/src/routes/deployments.ts +62 -16
- package/src/routes/eval-comparison.ts +81 -2
- package/src/routes/eval-overrides.ts +108 -0
- package/src/routes/eval-runs.ts +86 -6
- package/src/routes/eval-suites.ts +25 -1
- 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 +179 -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 +223 -8
- package/src/routes/public-run-tokens.ts +3 -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
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
// SPDX-License-Identifier: Apache-2.0
|
|
2
2
|
// Copyright (C) 2026 Kindgi Inc.
|
|
3
3
|
|
|
4
|
-
import { createHash } from 'node:crypto';
|
|
4
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
5
5
|
|
|
6
|
-
import type { MiddlewareHandler } from 'hono';
|
|
6
|
+
import type { Context, MiddlewareHandler } from 'hono';
|
|
7
7
|
|
|
8
8
|
import type { TenantId } from '@kindgi/types';
|
|
9
9
|
|
|
10
|
+
import { callerRef } from '../caller.js';
|
|
10
11
|
import { statusFor, toWireError } from '../errors.js';
|
|
12
|
+
import type { AppEnv } from '../types.js';
|
|
11
13
|
|
|
12
14
|
/**
|
|
13
|
-
* Idempotency-key store: `(tenantId, route, key) → cached response`,
|
|
15
|
+
* Idempotency-key store: `(tenantId, caller, route, key) → cached response`,
|
|
14
16
|
* cached for `ttlMs` (default 24h per API-ROUTE-CONVENTIONS.md §3.1).
|
|
15
17
|
*
|
|
16
18
|
* Injected into the middleware so deployments can plug in a durable
|
|
@@ -20,38 +22,117 @@ import { statusFor, toWireError } from '../errors.js';
|
|
|
20
22
|
*/
|
|
21
23
|
export interface IdempotencyStore {
|
|
22
24
|
get(key: string): Promise<StoredIdempotencyEntry | null>;
|
|
25
|
+
/** Store an answer. It replaces a hold on the key, never a stored answer: the first stays. */
|
|
23
26
|
set(key: string, entry: StoredIdempotencyEntry): Promise<void>;
|
|
27
|
+
/**
|
|
28
|
+
* Hold a key while its request runs (T349), so a repeat sent meanwhile
|
|
29
|
+
* is answered 409 `idempotency-key-in-flight` instead of running the
|
|
30
|
+
* operation again. Optional: a store without holds holds nothing, and
|
|
31
|
+
* such a repeat runs again (the behaviour before 0.1.5).
|
|
32
|
+
*/
|
|
33
|
+
readonly holds?: IdempotencyHolds;
|
|
24
34
|
}
|
|
25
35
|
|
|
36
|
+
/** A store's holds: all three or none. */
|
|
37
|
+
export interface IdempotencyHolds {
|
|
38
|
+
/** Take the key for `hold.holder`, unless an unexpired hold or a stored answer has it. */
|
|
39
|
+
hold(key: string, hold: IdempotencyHold): Promise<IdempotencyHoldOutcome>;
|
|
40
|
+
/** Extend the holder's hold: its request still runs. */
|
|
41
|
+
renew(key: string, holder: string, expiresAt: number): Promise<void>;
|
|
42
|
+
/** Drop the holder's hold: its request ended with nothing to replay. */
|
|
43
|
+
release(key: string, holder: string): Promise<void>;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface IdempotencyHold {
|
|
47
|
+
/** Who holds it: one id per request. */
|
|
48
|
+
readonly holder: string;
|
|
49
|
+
readonly bodyHash: string;
|
|
50
|
+
readonly expiresAt: number;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export type IdempotencyHoldOutcome =
|
|
54
|
+
| { readonly kind: 'held' }
|
|
55
|
+
| { readonly kind: 'in-flight'; readonly bodyHash: string }
|
|
56
|
+
| { readonly kind: 'stored'; readonly entry: StoredIdempotencyEntry };
|
|
57
|
+
|
|
26
58
|
export interface StoredIdempotencyEntry {
|
|
27
59
|
readonly bodyHash: string;
|
|
28
60
|
readonly status: number;
|
|
29
61
|
readonly contentType: string;
|
|
62
|
+
/** The answer's body; empty when it's `withheld`. */
|
|
30
63
|
readonly bodyText: string;
|
|
31
64
|
readonly expiresAt: number;
|
|
65
|
+
/**
|
|
66
|
+
* The answer carried a secret (`withholdFromReplay`), so only that the
|
|
67
|
+
* request succeeded is kept: a repeat is answered 409
|
|
68
|
+
* `idempotency-key-replay-withheld`, never the secret. Optional: a store
|
|
69
|
+
* that doesn't keep it stores the empty body, and a repeat replays that.
|
|
70
|
+
*/
|
|
71
|
+
readonly withheld?: boolean;
|
|
72
|
+
/** When the answer was stored (ms since the epoch), for the withheld 409. */
|
|
73
|
+
readonly storedAt?: number;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* For a route whose answer carries a secret (an API key, a session token,
|
|
78
|
+
* a signing secret): the Idempotency-Key middleware keeps that the request
|
|
79
|
+
* succeeded and when, never the answer. A repeat with the same key gets 409
|
|
80
|
+
* `idempotency-key-replay-withheld` instead of the secret, and instead of
|
|
81
|
+
* running again (which would make a second one). Call it before answering.
|
|
82
|
+
*/
|
|
83
|
+
export function withholdFromReplay(c: Context<AppEnv>): void {
|
|
84
|
+
c.set('idempotencyWithhold', true);
|
|
32
85
|
}
|
|
33
86
|
|
|
34
87
|
/** Simple in-memory store with lazy expiry — swap for a DB store in prod. */
|
|
35
88
|
export function createInMemoryIdempotencyStore(): IdempotencyStore {
|
|
36
89
|
const table = new Map<string, StoredIdempotencyEntry>();
|
|
90
|
+
const held = new Map<string, IdempotencyHold>();
|
|
91
|
+
const stored = (key: string): StoredIdempotencyEntry | null => {
|
|
92
|
+
const hit = table.get(key);
|
|
93
|
+
if (hit === undefined) return null;
|
|
94
|
+
if (hit.expiresAt <= Date.now()) {
|
|
95
|
+
table.delete(key);
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
return hit;
|
|
99
|
+
};
|
|
37
100
|
return {
|
|
38
101
|
async get(key) {
|
|
39
|
-
|
|
40
|
-
if (hit === undefined) return null;
|
|
41
|
-
if (hit.expiresAt <= Date.now()) {
|
|
42
|
-
table.delete(key);
|
|
43
|
-
return null;
|
|
44
|
-
}
|
|
45
|
-
return hit;
|
|
102
|
+
return stored(key);
|
|
46
103
|
},
|
|
47
104
|
async set(key, entry) {
|
|
48
|
-
|
|
105
|
+
held.delete(key);
|
|
106
|
+
if (stored(key) === null) table.set(key, entry);
|
|
107
|
+
},
|
|
108
|
+
holds: {
|
|
109
|
+
async hold(key, hold) {
|
|
110
|
+
const entry = stored(key);
|
|
111
|
+
if (entry !== null) return { kind: 'stored', entry };
|
|
112
|
+
const current = held.get(key);
|
|
113
|
+
if (current !== undefined && current.expiresAt > Date.now()) {
|
|
114
|
+
return { kind: 'in-flight', bodyHash: current.bodyHash };
|
|
115
|
+
}
|
|
116
|
+
held.set(key, hold);
|
|
117
|
+
return { kind: 'held' };
|
|
118
|
+
},
|
|
119
|
+
async renew(key, holder, expiresAt) {
|
|
120
|
+
const current = held.get(key);
|
|
121
|
+
if (current?.holder === holder) held.set(key, { ...current, expiresAt });
|
|
122
|
+
},
|
|
123
|
+
async release(key, holder) {
|
|
124
|
+
if (held.get(key)?.holder === holder) held.delete(key);
|
|
125
|
+
},
|
|
49
126
|
},
|
|
50
127
|
};
|
|
51
128
|
}
|
|
52
129
|
|
|
53
130
|
const MUTATING_METHODS: ReadonlySet<string> = new Set(['POST', 'PUT', 'PATCH', 'DELETE']);
|
|
54
131
|
const DEFAULT_TTL_MS = 24 * 60 * 60 * 1000;
|
|
132
|
+
/** How long a hold lasts unless renewed; renewed every third of it while the request runs. */
|
|
133
|
+
const DEFAULT_HOLD_MS = 30 * 1000;
|
|
134
|
+
/** What a repeat in flight is told to wait, in seconds. */
|
|
135
|
+
const IN_FLIGHT_RETRY_AFTER_S = 5;
|
|
55
136
|
|
|
56
137
|
/**
|
|
57
138
|
* Idempotency-Key middleware per API-ROUTE-CONVENTIONS.md §3.1.
|
|
@@ -68,16 +149,30 @@ const DEFAULT_TTL_MS = 24 * 60 * 60 * 1000;
|
|
|
68
149
|
* nothing, and a failure (5xx) may be transient: neither is stored, so
|
|
69
150
|
* a retry with the same key after fixing the cause runs again instead
|
|
70
151
|
* of replaying the old error.
|
|
152
|
+
* - With a store that holds keys (`store.holds`, T349): the key is held
|
|
153
|
+
* while the handler runs (for `holdMs`, renewed every third of it), so
|
|
154
|
+
* a repeat sent meanwhile gets 409 `idempotency-key-in-flight` with
|
|
155
|
+
* `Retry-After`, or `idempotency-key-body-mismatch` for another body,
|
|
156
|
+
* instead of running the operation again. The hold becomes the stored
|
|
157
|
+
* answer, or is released when nothing is stored. A crashed request's
|
|
158
|
+
* hold lapses within `holdMs`.
|
|
159
|
+
*
|
|
160
|
+
* The middleware sits AFTER auth so `tenantId` is available. The cache
|
|
161
|
+
* key is the tenant, the caller (`user:…`, `service_account:…`, else the
|
|
162
|
+
* key or session it came with), the route and the key: someone else in
|
|
163
|
+
* the tenant who sends the same key and body runs the request themselves,
|
|
164
|
+
* and never gets another caller's answer (T392).
|
|
71
165
|
*
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
166
|
+
* A route whose answer carries a secret calls `withholdFromReplay`: its
|
|
167
|
+
* answer isn't kept, only that it succeeded (status, when). A repeat with
|
|
168
|
+
* the same key and body gets 409 `idempotency-key-replay-withheld`.
|
|
75
169
|
*/
|
|
76
170
|
export function idempotencyMiddleware(
|
|
77
171
|
store: IdempotencyStore,
|
|
78
|
-
opts: { ttlMs?: number } = {},
|
|
172
|
+
opts: { ttlMs?: number; holdMs?: number } = {},
|
|
79
173
|
): MiddlewareHandler {
|
|
80
174
|
const ttlMs = opts.ttlMs ?? DEFAULT_TTL_MS;
|
|
175
|
+
const holdMs = opts.holdMs ?? DEFAULT_HOLD_MS;
|
|
81
176
|
return async (c, next) => {
|
|
82
177
|
if (!MUTATING_METHODS.has(c.req.method.toUpperCase())) {
|
|
83
178
|
return next();
|
|
@@ -96,51 +191,143 @@ export function idempotencyMiddleware(
|
|
|
96
191
|
}
|
|
97
192
|
|
|
98
193
|
const bodyText = await readRawBody(c.req.raw);
|
|
99
|
-
const bodyHash =
|
|
100
|
-
const cacheKey = `${tenantId}|${c.req.method.toUpperCase()}:${c.req.path}|${rawKey}`;
|
|
194
|
+
const bodyHash = sha256Hex(bodyText);
|
|
195
|
+
const cacheKey = `${tenantId}|${callerKey(c as Context<AppEnv>)}|${c.req.method.toUpperCase()}:${c.req.path}|${rawKey}`;
|
|
101
196
|
const hit = await store.get(cacheKey);
|
|
197
|
+
if (hit !== null) return replay(c, hit, bodyHash, requestId, ttlMs);
|
|
102
198
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
199
|
+
const { holds } = store;
|
|
200
|
+
const holder = randomUUID();
|
|
201
|
+
if (holds !== undefined) {
|
|
202
|
+
const held = await holds.hold(cacheKey, {
|
|
203
|
+
holder,
|
|
204
|
+
bodyHash,
|
|
205
|
+
expiresAt: Date.now() + holdMs,
|
|
206
|
+
});
|
|
207
|
+
if (held.kind === 'stored') return replay(c, held.entry, bodyHash, requestId, ttlMs);
|
|
208
|
+
if (held.kind === 'in-flight') {
|
|
209
|
+
if (held.bodyHash !== bodyHash) return bodyMismatch(c, requestId);
|
|
210
|
+
c.header('Retry-After', String(IN_FLIGHT_RETRY_AFTER_S));
|
|
211
|
+
c.status(statusFor('idempotency-key-in-flight') as never);
|
|
106
212
|
return c.json(
|
|
107
213
|
toWireError(
|
|
108
214
|
{
|
|
109
|
-
code: 'idempotency-key-
|
|
215
|
+
code: 'idempotency-key-in-flight',
|
|
110
216
|
message:
|
|
111
|
-
|
|
217
|
+
"A request with this Idempotency-Key is still running. Retry after it answers (Retry-After), and you'll get its answer.",
|
|
112
218
|
},
|
|
113
219
|
requestId,
|
|
114
220
|
),
|
|
115
221
|
);
|
|
116
222
|
}
|
|
117
|
-
return c.body(hit.bodyText, hit.status as never, {
|
|
118
|
-
'Content-Type': hit.contentType,
|
|
119
|
-
'X-Idempotent-Replay': 'true',
|
|
120
|
-
});
|
|
121
223
|
}
|
|
122
224
|
|
|
123
225
|
// Re-attach the buffered body so downstream handlers reading
|
|
124
226
|
// `c.req.json()` see the same bytes.
|
|
125
227
|
if (bodyText.length > 0) rebindBody(c, bodyText);
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
228
|
+
const renewal =
|
|
229
|
+
holds === undefined
|
|
230
|
+
? undefined
|
|
231
|
+
: setInterval(
|
|
232
|
+
() => {
|
|
233
|
+
holds.renew(cacheKey, holder, Date.now() + holdMs).catch(() => undefined);
|
|
234
|
+
},
|
|
235
|
+
Math.max(1, Math.floor(holdMs / 3)),
|
|
236
|
+
);
|
|
237
|
+
renewal?.unref?.();
|
|
238
|
+
let answered = false;
|
|
239
|
+
try {
|
|
240
|
+
await next();
|
|
241
|
+
|
|
242
|
+
const res = c.res;
|
|
243
|
+
// Only what took effect is replayed; see the doc above.
|
|
244
|
+
if (res.status >= 400) return;
|
|
245
|
+
const withheld = (c as Context<AppEnv>).get('idempotencyWithhold') === true;
|
|
246
|
+
const savedBody = withheld ? '' : await res.clone().text();
|
|
247
|
+
const contentType = res.headers.get('Content-Type') ?? 'application/octet-stream';
|
|
248
|
+
const now = Date.now();
|
|
249
|
+
await store.set(cacheKey, {
|
|
250
|
+
bodyHash,
|
|
251
|
+
status: res.status,
|
|
252
|
+
contentType,
|
|
253
|
+
bodyText: savedBody,
|
|
254
|
+
expiresAt: now + ttlMs,
|
|
255
|
+
...(withheld && { withheld: true }),
|
|
256
|
+
storedAt: now,
|
|
257
|
+
});
|
|
258
|
+
answered = true;
|
|
259
|
+
} finally {
|
|
260
|
+
if (renewal !== undefined) clearInterval(renewal);
|
|
261
|
+
// Nothing stored (a refusal, a failure, a throw): let a retry run.
|
|
262
|
+
if (!answered && holds !== undefined) {
|
|
263
|
+
await holds.release(cacheKey, holder).catch(() => undefined);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
141
266
|
};
|
|
142
267
|
}
|
|
143
268
|
|
|
269
|
+
/**
|
|
270
|
+
* Who the request comes from, for the cache key: the caller's principal
|
|
271
|
+
* (`user:…`, `service_account:…`; an API key with neither is its own
|
|
272
|
+
* service account), else the session it came with, else the credential
|
|
273
|
+
* itself (`token:` and the first 16 hex of its sha256: one bucket per
|
|
274
|
+
* credential, never a shared one, and nothing a reader could use).
|
|
275
|
+
*/
|
|
276
|
+
function callerKey(c: Context<AppEnv>): string {
|
|
277
|
+
const ref = callerRef(c);
|
|
278
|
+
if (ref !== undefined) return ref;
|
|
279
|
+
const sessionId = c.get('sessionId');
|
|
280
|
+
if (sessionId !== undefined) return `session:${sessionId as unknown as string}`;
|
|
281
|
+
return `token:${sha256Hex(c.req.header('authorization') ?? '').slice(0, 16)}`;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* A stored answer, replayed; or the refusal for the same key with another
|
|
286
|
+
* body; or, for an answer that carried a secret, what succeeded and when.
|
|
287
|
+
*/
|
|
288
|
+
function replay(
|
|
289
|
+
c: Context,
|
|
290
|
+
hit: StoredIdempotencyEntry,
|
|
291
|
+
bodyHash: string,
|
|
292
|
+
requestId: string,
|
|
293
|
+
ttlMs: number,
|
|
294
|
+
) {
|
|
295
|
+
if (hit.bodyHash !== bodyHash) return bodyMismatch(c, requestId);
|
|
296
|
+
if (hit.withheld === true) {
|
|
297
|
+
const at = new Date(hit.storedAt ?? hit.expiresAt - ttlMs).toISOString();
|
|
298
|
+
c.status(statusFor('idempotency-key-replay-withheld') as never);
|
|
299
|
+
return c.json(
|
|
300
|
+
toWireError(
|
|
301
|
+
{
|
|
302
|
+
code: 'idempotency-key-replay-withheld',
|
|
303
|
+
message: `The request with this Idempotency-Key succeeded (${hit.status}, at ${at}). Its answer carried a secret, which isn't kept, so it can't be sent again: read what it made (for an API key, \`GET /v1/tokens\`), or make a new one with a fresh key.`,
|
|
304
|
+
status: hit.status,
|
|
305
|
+
at,
|
|
306
|
+
},
|
|
307
|
+
requestId,
|
|
308
|
+
),
|
|
309
|
+
);
|
|
310
|
+
}
|
|
311
|
+
return c.body(hit.bodyText, hit.status as never, {
|
|
312
|
+
'Content-Type': hit.contentType,
|
|
313
|
+
'X-Idempotent-Replay': 'true',
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
function bodyMismatch(c: Context, requestId: string) {
|
|
318
|
+
c.status(statusFor('idempotency-key-body-mismatch') as never);
|
|
319
|
+
return c.json(
|
|
320
|
+
toWireError(
|
|
321
|
+
{
|
|
322
|
+
code: 'idempotency-key-body-mismatch',
|
|
323
|
+
message:
|
|
324
|
+
'Idempotency-Key was reused with a different request body. Use a fresh key or the original body.',
|
|
325
|
+
},
|
|
326
|
+
requestId,
|
|
327
|
+
),
|
|
328
|
+
);
|
|
329
|
+
}
|
|
330
|
+
|
|
144
331
|
async function readRawBody(req: Request): Promise<string> {
|
|
145
332
|
const method = req.method.toUpperCase();
|
|
146
333
|
if (method === 'GET' || method === 'HEAD') return '';
|
|
@@ -151,8 +338,8 @@ async function readRawBody(req: Request): Promise<string> {
|
|
|
151
338
|
}
|
|
152
339
|
}
|
|
153
340
|
|
|
154
|
-
function
|
|
155
|
-
return createHash('sha256').update(
|
|
341
|
+
function sha256Hex(text: string): string {
|
|
342
|
+
return createHash('sha256').update(text).digest('hex');
|
|
156
343
|
}
|
|
157
344
|
|
|
158
345
|
function rebindBody(c: { req: { raw: Request } }, body: string): void {
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// Copyright (C) 2026 Kindgi Inc.
|
|
3
|
+
|
|
4
|
+
import type { MiddlewareHandler } from 'hono';
|
|
5
|
+
|
|
6
|
+
import { statusFor, toWireError } from '../errors.js';
|
|
7
|
+
import type { AppEnv } from '../types.js';
|
|
8
|
+
|
|
9
|
+
const PROJECT_PATH_RE = /\/projects\/([^/]+)/;
|
|
10
|
+
const WRITE_METHODS: ReadonlySet<string> = new Set(['POST', 'PUT', 'PATCH']);
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* A key limited to a project (`projectId` at mint) is refused, `403
|
|
14
|
+
* key-project-mismatch`, on a request that names another project in any
|
|
15
|
+
* of the places a request names one:
|
|
16
|
+
*
|
|
17
|
+
* - the path: `/projects/<id>/…`;
|
|
18
|
+
* - the query: `?projectId=<id>`, or `?scopeKind=project&scopeId=<id>`;
|
|
19
|
+
* - a JSON write body: `projectId`, or `scope.projectId`.
|
|
20
|
+
*
|
|
21
|
+
* Requests from any other caller pass untouched. What the key may do in
|
|
22
|
+
* its own project is the authorizer's to decide, under the principal's
|
|
23
|
+
* grants.
|
|
24
|
+
*/
|
|
25
|
+
export function refuseOtherProjectForKey(): MiddlewareHandler<AppEnv> {
|
|
26
|
+
return async (c, next) => {
|
|
27
|
+
const keyProject = c.get('tokenProjectId');
|
|
28
|
+
if (keyProject === undefined) return next();
|
|
29
|
+
for (const named of await projectsNamed(c.req)) {
|
|
30
|
+
if (named !== keyProject) {
|
|
31
|
+
c.status(statusFor('key-project-mismatch') as never);
|
|
32
|
+
return c.json(
|
|
33
|
+
toWireError(
|
|
34
|
+
{
|
|
35
|
+
code: 'key-project-mismatch',
|
|
36
|
+
message: `This API key is limited to project ${keyProject}; the request names project ${named}`,
|
|
37
|
+
keyProjectId: keyProject,
|
|
38
|
+
projectId: named,
|
|
39
|
+
},
|
|
40
|
+
c.get('requestId'),
|
|
41
|
+
),
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return next();
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
type RequestLike = {
|
|
50
|
+
readonly path: string;
|
|
51
|
+
readonly method: string;
|
|
52
|
+
query(name: string): string | undefined;
|
|
53
|
+
json(): Promise<unknown>;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
/** Every project id the request names, in path, query and body. */
|
|
57
|
+
async function projectsNamed(req: RequestLike): Promise<string[]> {
|
|
58
|
+
const named: string[] = [];
|
|
59
|
+
const inPath = PROJECT_PATH_RE.exec(req.path)?.[1];
|
|
60
|
+
if (inPath !== undefined) named.push(decodeURIComponent(inPath));
|
|
61
|
+
const inQuery = req.query('projectId');
|
|
62
|
+
if (inQuery !== undefined && inQuery !== '') named.push(inQuery);
|
|
63
|
+
const scopeId = req.query('scopeId');
|
|
64
|
+
if (req.query('scopeKind') === 'project' && scopeId !== undefined) named.push(scopeId);
|
|
65
|
+
if (WRITE_METHODS.has(req.method)) named.push(...(await projectsInBody(req)));
|
|
66
|
+
return named;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** `projectId` and `scope.projectId` in a JSON body; none when it isn't JSON. */
|
|
70
|
+
async function projectsInBody(req: RequestLike): Promise<string[]> {
|
|
71
|
+
let body: unknown;
|
|
72
|
+
try {
|
|
73
|
+
body = await req.json();
|
|
74
|
+
} catch {
|
|
75
|
+
return [];
|
|
76
|
+
}
|
|
77
|
+
if (body === null || typeof body !== 'object') return [];
|
|
78
|
+
const b = body as { readonly projectId?: unknown; readonly scope?: unknown };
|
|
79
|
+
const named: string[] = [];
|
|
80
|
+
if (typeof b.projectId === 'string') named.push(b.projectId);
|
|
81
|
+
const scope = b.scope as { readonly projectId?: unknown } | null | undefined;
|
|
82
|
+
if (scope !== null && typeof scope === 'object' && typeof scope.projectId === 'string') {
|
|
83
|
+
named.push(scope.projectId);
|
|
84
|
+
}
|
|
85
|
+
return named;
|
|
86
|
+
}
|
|
@@ -28,6 +28,7 @@ export function principalMiddleware(): MiddlewareHandler<AppEnv> {
|
|
|
28
28
|
const userId = c.get('userId');
|
|
29
29
|
const tokenId = c.get('tokenId');
|
|
30
30
|
const sessionId = c.get('sessionId');
|
|
31
|
+
const keyServiceAccount = c.get('serviceAccountId');
|
|
31
32
|
|
|
32
33
|
if (tenantId === undefined) {
|
|
33
34
|
// Bearer middleware didn't populate — either the route is
|
|
@@ -37,12 +38,15 @@ export function principalMiddleware(): MiddlewareHandler<AppEnv> {
|
|
|
37
38
|
return next();
|
|
38
39
|
}
|
|
39
40
|
|
|
40
|
-
// A user when the token names one; otherwise a service account:
|
|
41
|
-
//
|
|
42
|
-
//
|
|
43
|
-
//
|
|
41
|
+
// A user when the token names one; otherwise a service account: the
|
|
42
|
+
// one an API key acts for, else the key's own id (a store built before
|
|
43
|
+
// principals), else the session id (which uniquely identifies the
|
|
44
|
+
// token record). With none of them, leave `principal` unset;
|
|
45
|
+
// `authorize()` treats that as "no principal" and 403s cleanly.
|
|
44
46
|
const serviceAccountId =
|
|
45
|
-
|
|
47
|
+
keyServiceAccount ??
|
|
48
|
+
(tokenId as unknown as string | undefined) ??
|
|
49
|
+
(sessionId as unknown as string | undefined);
|
|
46
50
|
if (userId === undefined && serviceAccountId === undefined) {
|
|
47
51
|
return next();
|
|
48
52
|
}
|
|
@@ -33,6 +33,9 @@ export const PROJECT_REF_ROUTES: readonly string[] = [
|
|
|
33
33
|
'/eval-suites/:suiteId/versions/from-judgments',
|
|
34
34
|
'/eval-suites/:suiteId/runs',
|
|
35
35
|
'/blocks',
|
|
36
|
+
'/schedules',
|
|
37
|
+
'/service-accounts/:serviceAccountId/grant',
|
|
38
|
+
'/service-accounts/:serviceAccountId/ungrant',
|
|
36
39
|
];
|
|
37
40
|
|
|
38
41
|
/**
|
package/src/openapi/generate.ts
CHANGED
|
@@ -49,6 +49,8 @@ const DEFAULT_SERVERS: readonly OpenApiServer[] = [
|
|
|
49
49
|
const TAG_DESCRIPTIONS: Readonly<Record<string, string>> = {
|
|
50
50
|
system: 'Health + spec discovery.',
|
|
51
51
|
runs: 'Run lifecycle (start, list, get, cancel, resume, journal, stream).',
|
|
52
|
+
'export-signing-keys':
|
|
53
|
+
'The public keys this deployment signs its exports with (audit bundles, provenance, compliance evidence): what a verifier pins.',
|
|
52
54
|
'signing-keys':
|
|
53
55
|
'The deployment trust list: the public keys whose signatures `POST /v1/deployments` accepts. Revoked keys stay readable for audit.',
|
|
54
56
|
tokens:
|
|
@@ -69,7 +71,7 @@ const TAG_DESCRIPTIONS: Readonly<Record<string, string>> = {
|
|
|
69
71
|
proposals:
|
|
70
72
|
'Supervisor fix-proposal lifecycle (draft, dry-run, submit-review, apply, rollback, withdraw — caller-plugged binding wraps the supervisor).',
|
|
71
73
|
provenance:
|
|
72
|
-
|
|
74
|
+
"Causal DAG readback + signed exports (list, get, export), signed with the deployment's export key.",
|
|
73
75
|
artifacts:
|
|
74
76
|
'Blob storage (list metadata, multipart upload, streaming download, HEAD, delete). Caller-plugged via `BlobStorageBinding` (from `@kindgi/blob-binding`).',
|
|
75
77
|
capabilities:
|
|
@@ -100,7 +102,7 @@ const TAG_DESCRIPTIONS: Readonly<Record<string, string>> = {
|
|
|
100
102
|
deployments:
|
|
101
103
|
'Signed pack deployments (register, list, get). The single wire surface that lands a signed OCI image + index.json into the platform. Register runs a six-step atomic transaction: Ed25519 signature verify over the canonical `{imageDigest, artifactVersion, indexHash, tenantId, publishedAt}` envelope, tenant-scoped trust-list check via `SigningKeyRegistryBinding`, image pullability + `/app/index.json` sha256 match via `ImageRegistryBinding`, per-primitive manifest validation, registry upserts (tools + guardrails + agents + flows), append-only deployment ledger row via `DeploymentBinding`. All-or-nothing rollback on any failure; digest-based idempotency (redeploying the same image returns the existing record). Records are immutable — rollback = re-register the previous digest.',
|
|
102
104
|
compliance:
|
|
103
|
-
'Compliance-evidence readback + signed exports (list, get, export). Reads are a classification lens over `AuditEventBinding`; only classifier-marked `exportable` kinds appear on the wire.
|
|
105
|
+
'Compliance-evidence readback + signed exports (list, get, export). Reads are a classification lens over `AuditEventBinding`; only classifier-marked `exportable` kinds appear on the wire. A signed export is signed with the export signing key of the deployment (`exportSigning`); a deployment without one answers `404 signing-not-configured`. It is the same envelope as the audit-bundle and provenance exports, so one verifier reads all three. Redaction happens when evidence is generated, not at the export boundary — records are stored already redacted.',
|
|
104
106
|
audit:
|
|
105
107
|
'PDP decision audit stream (list). Every route-level authz `check()` emits one `AuditEvent` (kind `authz-decision`) via the `AuditEventBinding`; this route pages through them, tenant-scoped, with `?actorSubject=` / `?onBehalfOf=` / `?action=` / `?resource=` / `?outcome=` / `?runId=` / `?from=` / `?to=` filters. Admin@tenant only. This view is a filtered projection of the tenant audit events.',
|
|
106
108
|
orgs: 'Multi-tenant hierarchy — Org CRUD (list, create, get, patch, delete). Optional structural subdivision within a tenant; small tenants ignore Orgs entirely. Caller-plugged via `OrgBinding` from `@kindgi/platform`.',
|
|
@@ -111,6 +113,8 @@ const TAG_DESCRIPTIONS: Readonly<Record<string, string>> = {
|
|
|
111
113
|
tenant:
|
|
112
114
|
'Sovereignty boundary readback + tenant-scoped config (get tenant; list + upsert entries routed to the env / secrets bindings). There is no `/v1/tenants` collection — the caller\'s tenant is implicit from the bearer token. `PATCH /config` routes writes to `SecretBinding` when `sensitive: true` OR `kind: "secret"`; else to `EnvBinding`. `GET /config` merges both bindings at tenant scope; secret values are ALWAYS redacted here. The `/config` sub-routes mount when at least one of `envBinding` / `secretsBinding` is wired. Prefer `/v1/env/*` + `/v1/secrets/*` for new callers.',
|
|
113
115
|
env: 'Non-sensitive per-env values (`/v1/env/*`). Every route requires `envName` + `scopeKind` (+ `scopeId` for org / project); `value` is present on every read path (env is non-sensitive by definition). Writes require the `env:write` capability. Caller-plugged via `EnvBinding` (`@kindgi/env-inmemory` is an in-memory implementation for development and tests).',
|
|
116
|
+
schedules:
|
|
117
|
+
"Run an agent or a flow on a schedule: a cron expression in a timezone. Each occurrence is one fire, which starts one run as the schedule's owner (re-checked at every fire), through the same path as `POST /v1/runs`; a run a schedule started names it (`Run.trigger`). After a gap, `catchUp` runs once for the latest missed occurrence (or skips them), never once per missed one; `overlap` skips an occurrence while the previous run is still going. `GET …/fires` is the schedule's history; `run-now` fires it outside the schedule. Mounted when the deployment fires schedules.",
|
|
114
118
|
'webhook-endpoints':
|
|
115
119
|
'Outbound webhooks: endpoints the platform sends signed events to (`run.finished` when a top-level run completes, fails or is cancelled), their delivery log, redelivery and a test event. Signed in the Standard Webhooks format with a secret the endpoint references by name (`secretRef`); delivered at least once, so receivers deduplicate on `webhook-id`. Event bodies are under `webhooks`. Not to be confused with `/v1/webhooks`, which are inbound triggers. Caller-plugged via `WebhookEndpointBinding`.',
|
|
116
120
|
secrets:
|
|
@@ -212,6 +216,11 @@ function buildOutboundWebhooks(): Record<string, unknown> {
|
|
|
212
216
|
'A top-level run completed, failed or was cancelled',
|
|
213
217
|
'RunFinishedEvent',
|
|
214
218
|
),
|
|
219
|
+
'improvement-pass.finished': event(
|
|
220
|
+
'improvement-pass.finished',
|
|
221
|
+
'An improvement pass completed, failed or was cancelled',
|
|
222
|
+
'ImprovementPassFinishedEvent',
|
|
223
|
+
),
|
|
215
224
|
'webhook.test': event(
|
|
216
225
|
'webhook.test',
|
|
217
226
|
'A test event sent on request (`POST /v1/webhook-endpoints/{endpointId}/test`)',
|