@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
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// Copyright (C) 2026 Kindgi Inc.
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Who can see and change which memory, for a caller of `/v1/memory`.
|
|
6
|
+
*
|
|
7
|
+
* Reads: the containers the caller may read (`MemoryReaders`), worked out
|
|
8
|
+
* here from the principal and handed to the binding, which applies them
|
|
9
|
+
* inside its query. A tenant admin (or a deployment without
|
|
10
|
+
* authorization) reads everything; anyone else reads tenant-wide facts,
|
|
11
|
+
* the projects and orgs they may read, their own user facts, and every
|
|
12
|
+
* end user's and conversation's facts in the projects they may write (an
|
|
13
|
+
* app's own credential acting for its users).
|
|
14
|
+
*
|
|
15
|
+
* Writes: what the fact's scope names decides the check.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import type { Context } from 'hono';
|
|
19
|
+
|
|
20
|
+
import { type Principal, ref } from '@kindgi/authz';
|
|
21
|
+
import type { MemoryReaders, MemoryScope } from '@kindgi/memory';
|
|
22
|
+
import type { ProjectBinding } from '@kindgi/platform';
|
|
23
|
+
import type { OrgId, ProjectId, TenantId, UserId } from '@kindgi/types';
|
|
24
|
+
|
|
25
|
+
import type { Authorizer } from '../middleware/authorize.js';
|
|
26
|
+
import type { AppEnv } from '../types.js';
|
|
27
|
+
|
|
28
|
+
export interface MemoryAccessDeps {
|
|
29
|
+
readonly authorizer?: Authorizer;
|
|
30
|
+
/** The tenant's projects, to check one by one when the authorizer can't list them. */
|
|
31
|
+
readonly projects?: Pick<ProjectBinding, 'list'>;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const PROJECT_PAGE = 200;
|
|
35
|
+
|
|
36
|
+
/** The caller's user id, when the caller is a Kindgi user. */
|
|
37
|
+
function callerUserId(c: Context<AppEnv>): UserId | undefined {
|
|
38
|
+
const actor = (c.get('principal') as Principal | undefined)?.actor;
|
|
39
|
+
return actor?.kind === 'user' ? (actor.id as UserId) : undefined;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** `user:<id>` or `service:<id>`: who did it, for a record. */
|
|
43
|
+
export function callerRef(c: Context<AppEnv>): string {
|
|
44
|
+
const actor = (c.get('principal') as Principal | undefined)?.actor;
|
|
45
|
+
if (actor === undefined) return 'service:unknown';
|
|
46
|
+
return `${actor.kind === 'user' ? 'user' : 'service'}:${actor.id}`;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Who asserts a fact the caller writes. */
|
|
50
|
+
export function callerAttribution(c: Context<AppEnv>): {
|
|
51
|
+
readonly kind: 'user' | 'service';
|
|
52
|
+
readonly id: string;
|
|
53
|
+
} {
|
|
54
|
+
const actor = (c.get('principal') as Principal | undefined)?.actor;
|
|
55
|
+
if (actor === undefined) return { kind: 'service', id: 'unknown' };
|
|
56
|
+
return { kind: actor.kind === 'user' ? 'user' : 'service', id: actor.id };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async function isTenantAdmin(c: Context<AppEnv>, authorizer: Authorizer): Promise<boolean> {
|
|
60
|
+
return authorizer.can(c, 'admin', ref('tenant', c.get('tenantId') as unknown as string));
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
type ProjectRow = { readonly id: ProjectId; readonly orgId?: OrgId };
|
|
64
|
+
|
|
65
|
+
/** The tenant's projects, every page. */
|
|
66
|
+
async function allProjects(c: Context<AppEnv>, deps: MemoryAccessDeps): Promise<ProjectRow[]> {
|
|
67
|
+
if (deps.projects === undefined) return [];
|
|
68
|
+
const tenantId = c.get('tenantId') as TenantId;
|
|
69
|
+
const all: ProjectRow[] = [];
|
|
70
|
+
let cursor: Parameters<ProjectBinding['list']>[1]['cursor'];
|
|
71
|
+
for (;;) {
|
|
72
|
+
const page = await deps.projects.list(tenantId, {
|
|
73
|
+
limit: PROJECT_PAGE,
|
|
74
|
+
...(cursor !== undefined && { cursor }),
|
|
75
|
+
});
|
|
76
|
+
all.push(
|
|
77
|
+
...page.items.map((p) => ({ id: p.id, ...(p.orgId !== undefined && { orgId: p.orgId }) })),
|
|
78
|
+
);
|
|
79
|
+
if (page.nextCursor === undefined) break;
|
|
80
|
+
cursor = page.nextCursor;
|
|
81
|
+
}
|
|
82
|
+
return all;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* The projects the caller may `action`, and the orgs whose org-wide facts
|
|
87
|
+
* it reads: listed by the authorizer (`read` on the org), else the
|
|
88
|
+
* projects checked one by one and the orgs of those it may read.
|
|
89
|
+
*/
|
|
90
|
+
async function readerContainers(
|
|
91
|
+
c: Context<AppEnv>,
|
|
92
|
+
deps: MemoryAccessDeps & { readonly authorizer: Authorizer },
|
|
93
|
+
): Promise<{ readable: ProjectId[]; writable: ProjectId[]; orgs: OrgId[] }> {
|
|
94
|
+
const { authorizer } = deps;
|
|
95
|
+
const [readable, writable, orgs] = await Promise.all([
|
|
96
|
+
authorizer.listObjects?.(c, 'read', 'project'),
|
|
97
|
+
authorizer.listObjects?.(c, 'write', 'project'),
|
|
98
|
+
authorizer.listObjects?.(c, 'read', 'org'),
|
|
99
|
+
]);
|
|
100
|
+
if (readable !== undefined && writable !== undefined && orgs !== undefined) {
|
|
101
|
+
return {
|
|
102
|
+
readable: readable.map((id) => id as ProjectId),
|
|
103
|
+
writable: writable.map((id) => id as ProjectId),
|
|
104
|
+
orgs: orgs.map((id) => id as OrgId),
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
const all = await allProjects(c, deps);
|
|
108
|
+
const projectRef = (p: ProjectRow) => ref('project', p.id as unknown as string);
|
|
109
|
+
const [canRead, canWrite] = await Promise.all([
|
|
110
|
+
authorizer.filterByCan(c, 'read', all, projectRef),
|
|
111
|
+
authorizer.filterByCan(c, 'write', all, projectRef),
|
|
112
|
+
]);
|
|
113
|
+
return {
|
|
114
|
+
readable: canRead.map((p) => p.id),
|
|
115
|
+
writable: canWrite.map((p) => p.id),
|
|
116
|
+
orgs: [...new Set(canRead.flatMap((p) => (p.orgId !== undefined ? [p.orgId] : [])))],
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** What the caller may read. */
|
|
121
|
+
export async function memoryReadersFor(
|
|
122
|
+
c: Context<AppEnv>,
|
|
123
|
+
deps: MemoryAccessDeps,
|
|
124
|
+
): Promise<MemoryReaders> {
|
|
125
|
+
const { authorizer } = deps;
|
|
126
|
+
// Without authorization (a development deployment), the one credential reads it all.
|
|
127
|
+
if (authorizer === undefined) return { all: true };
|
|
128
|
+
if (await isTenantAdmin(c, authorizer)) return { all: true };
|
|
129
|
+
const { readable, writable, orgs } = await readerContainers(c, { ...deps, authorizer });
|
|
130
|
+
const user = callerUserId(c);
|
|
131
|
+
return {
|
|
132
|
+
projectIds: readable,
|
|
133
|
+
orgIds: orgs,
|
|
134
|
+
...(user !== undefined && { userIds: [user] }),
|
|
135
|
+
onBehalfOfProjectIds: writable,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Why the caller may not write a fact in `scope`, if they may not:
|
|
141
|
+
* - a tenant-wide fact needs `admin` on the tenant;
|
|
142
|
+
* - an org-wide fact, `admin` on the org;
|
|
143
|
+
* - a project's fact (an end user's or a conversation's included),
|
|
144
|
+
* `write` on the project;
|
|
145
|
+
* - a conversation's fact outside a project, `write` on the conversation;
|
|
146
|
+
* - a user's fact, being that user (or a tenant admin).
|
|
147
|
+
*/
|
|
148
|
+
export async function memoryWriteProblem(
|
|
149
|
+
c: Context<AppEnv>,
|
|
150
|
+
deps: MemoryAccessDeps,
|
|
151
|
+
scope: MemoryScope,
|
|
152
|
+
): Promise<string | undefined> {
|
|
153
|
+
const { authorizer } = deps;
|
|
154
|
+
if (authorizer === undefined || (await isTenantAdmin(c, authorizer))) return undefined;
|
|
155
|
+
if (scope.userId !== undefined && scope.userId !== callerUserId(c)) {
|
|
156
|
+
return "Only that user (or a tenant admin) can write a user's memory.";
|
|
157
|
+
}
|
|
158
|
+
if (scope.projectId !== undefined) {
|
|
159
|
+
return (await authorizer.can(c, 'write', ref('project', scope.projectId as unknown as string)))
|
|
160
|
+
? undefined
|
|
161
|
+
: `Writing memory in project "${scope.projectId as unknown as string}" needs write on it.`;
|
|
162
|
+
}
|
|
163
|
+
if (scope.threadId !== undefined) {
|
|
164
|
+
return (await authorizer.can(
|
|
165
|
+
c,
|
|
166
|
+
'write',
|
|
167
|
+
ref('conversation', scope.threadId as unknown as string),
|
|
168
|
+
))
|
|
169
|
+
? undefined
|
|
170
|
+
: `Writing a conversation's memory needs write on conversation "${scope.threadId as unknown as string}".`;
|
|
171
|
+
}
|
|
172
|
+
if (scope.orgId !== undefined) {
|
|
173
|
+
return (await authorizer.can(c, 'admin', ref('org', scope.orgId as unknown as string)))
|
|
174
|
+
? undefined
|
|
175
|
+
: `Writing org-wide memory needs admin on org "${scope.orgId as unknown as string}".`;
|
|
176
|
+
}
|
|
177
|
+
if (scope.userId !== undefined) return undefined;
|
|
178
|
+
return 'Writing tenant-wide memory needs admin on the tenant.';
|
|
179
|
+
}
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// Copyright (C) 2026 Kindgi Inc.
|
|
3
|
+
|
|
4
|
+
import { type Context, Hono } from 'hono';
|
|
5
|
+
|
|
6
|
+
import { ref } from '@kindgi/authz';
|
|
7
|
+
import type { TenantId } from '@kindgi/types';
|
|
8
|
+
|
|
9
|
+
import { statusFor, toWireError } from '../errors.js';
|
|
10
|
+
import type {
|
|
11
|
+
MemoryErasureBinding,
|
|
12
|
+
MemoryErasureLedgerEntry,
|
|
13
|
+
MemoryErasureSelector,
|
|
14
|
+
MemoryErasureSelectorKind,
|
|
15
|
+
} from '../memory-erasure-binding.js';
|
|
16
|
+
import type { Authorizer } from '../middleware/authorize.js';
|
|
17
|
+
import type { AppEnv } from '../types.js';
|
|
18
|
+
import { callerRef } from './memory-access.js';
|
|
19
|
+
import { clampLimit } from './pagination.js';
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Erasing a person's words (T273 M-5):
|
|
23
|
+
*
|
|
24
|
+
* - `POST /v1/memory/erasures` start one (202); `409 legal-hold`
|
|
25
|
+
* when a fact it reaches is held
|
|
26
|
+
* - `GET /v1/memory/erasures` newest first, in pages
|
|
27
|
+
* - `GET /v1/memory/erasures/export` the whole ledger, content-free,
|
|
28
|
+
* to keep off-box
|
|
29
|
+
* - `GET /v1/memory/erasures/:id` one, and how far it got
|
|
30
|
+
* - `POST /v1/memory/erasures/:id/resume` try again now; `force`: stop
|
|
31
|
+
* waiting for a shared flow's run
|
|
32
|
+
* - `POST /v1/memory/erasures/replay` after a backup restore: the
|
|
33
|
+
* exported ledger back, its
|
|
34
|
+
* erasures run again
|
|
35
|
+
*
|
|
36
|
+
* PEP: a tenant admin only, for every selector (`admin` on
|
|
37
|
+
* `tenant:<tenantId>`), as retention; a service account only through an
|
|
38
|
+
* explicit tenant-admin grant.
|
|
39
|
+
*/
|
|
40
|
+
export function memoryErasuresRouter(
|
|
41
|
+
binding: MemoryErasureBinding,
|
|
42
|
+
authorizer?: Authorizer,
|
|
43
|
+
): Hono<AppEnv> {
|
|
44
|
+
const r = new Hono<AppEnv>();
|
|
45
|
+
|
|
46
|
+
if (authorizer !== undefined) {
|
|
47
|
+
r.use('/*', async (c, next) => {
|
|
48
|
+
const tenantId = c.get('tenantId') as TenantId;
|
|
49
|
+
const mw = authorizer.authorize('admin', async () =>
|
|
50
|
+
ref('tenant', tenantId as unknown as string),
|
|
51
|
+
);
|
|
52
|
+
return mw(c, next);
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
r.post('/', async (c) => {
|
|
57
|
+
const tenantId = c.get('tenantId') as TenantId;
|
|
58
|
+
const body = await jsonBody(c);
|
|
59
|
+
const selector = parseSelector(body);
|
|
60
|
+
if (typeof selector === 'string') return fail(c, 'bad-input', selector);
|
|
61
|
+
const outcome = await binding.create({ tenantId, selector, requestedBy: callerRef(c) });
|
|
62
|
+
if (outcome.kind === 'refused') {
|
|
63
|
+
const { code, message, factIds } = outcome.refusal;
|
|
64
|
+
return fail(c, code, message, factIds !== undefined ? { factIds: [...factIds] } : {});
|
|
65
|
+
}
|
|
66
|
+
c.status(202);
|
|
67
|
+
return c.json({
|
|
68
|
+
...outcome.erasure,
|
|
69
|
+
...(outcome.warnings.length > 0 && { warnings: outcome.warnings }),
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
r.get('/', async (c) => {
|
|
74
|
+
const tenantId = c.get('tenantId') as TenantId;
|
|
75
|
+
const cursor = c.req.query('cursor');
|
|
76
|
+
const page = await binding.list(tenantId, {
|
|
77
|
+
limit: clampLimit(c.req.query('limit')),
|
|
78
|
+
...(cursor !== undefined && cursor !== '' && { cursor }),
|
|
79
|
+
});
|
|
80
|
+
if (page === undefined) return fail(c, 'bad-input', '`cursor` is not one this list issued');
|
|
81
|
+
return c.json({
|
|
82
|
+
data: page.data,
|
|
83
|
+
hasMore: page.nextCursor !== undefined,
|
|
84
|
+
...(page.nextCursor !== undefined && { nextCursor: page.nextCursor }),
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
r.get('/export', async (c) => {
|
|
89
|
+
const tenantId = c.get('tenantId') as TenantId;
|
|
90
|
+
return c.json({ data: await binding.exportLedger(tenantId) });
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
r.post('/replay', async (c) => {
|
|
94
|
+
const tenantId = c.get('tenantId') as TenantId;
|
|
95
|
+
const body = await jsonBody(c);
|
|
96
|
+
const entries = parseLedger(body);
|
|
97
|
+
if (typeof entries === 'string') return fail(c, 'bad-input', entries);
|
|
98
|
+
return c.json(await binding.replay(tenantId, entries));
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
r.post('/:erasureId/resume', async (c) => {
|
|
102
|
+
const tenantId = c.get('tenantId') as TenantId;
|
|
103
|
+
const id = c.req.param('erasureId');
|
|
104
|
+
const body = (await jsonBody(c)) ?? {};
|
|
105
|
+
const force = (body as { force?: unknown }).force;
|
|
106
|
+
if (
|
|
107
|
+
typeof body !== 'object' ||
|
|
108
|
+
Array.isArray(body) ||
|
|
109
|
+
(force !== undefined && typeof force !== 'boolean')
|
|
110
|
+
) {
|
|
111
|
+
return fail(c, 'bad-input', 'The body is `{force?: boolean}`');
|
|
112
|
+
}
|
|
113
|
+
const erasure = UUID.test(id)
|
|
114
|
+
? await binding.resume(tenantId, id, { ...(force === true && { force: true }) })
|
|
115
|
+
: undefined;
|
|
116
|
+
if (erasure === undefined) return fail(c, 'not-found', `No erasure ${id}`);
|
|
117
|
+
return c.json(erasure);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
r.get('/:erasureId', async (c) => {
|
|
121
|
+
const tenantId = c.get('tenantId') as TenantId;
|
|
122
|
+
const id = c.req.param('erasureId');
|
|
123
|
+
const erasure = UUID.test(id) ? await binding.get(tenantId, id) : undefined;
|
|
124
|
+
if (erasure === undefined) return fail(c, 'not-found', `No erasure ${id}`);
|
|
125
|
+
return c.json(erasure);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
return r;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
132
|
+
const SUBJECT_KINDS = ['participant', 'external'] as const;
|
|
133
|
+
const SELECTOR_KINDS: readonly MemoryErasureSelectorKind[] = [
|
|
134
|
+
'fact',
|
|
135
|
+
'participant',
|
|
136
|
+
'external',
|
|
137
|
+
'conversation',
|
|
138
|
+
];
|
|
139
|
+
const STATUSES = ['pending', 'running', 'waiting-on-run', 'completed', 'failed'] as const;
|
|
140
|
+
const MAX_ID = 256;
|
|
141
|
+
const MAX_LEDGER = 10_000;
|
|
142
|
+
|
|
143
|
+
function fail(
|
|
144
|
+
c: Context<AppEnv>,
|
|
145
|
+
code: string,
|
|
146
|
+
message: string,
|
|
147
|
+
extra: Readonly<Record<string, unknown>> = {},
|
|
148
|
+
) {
|
|
149
|
+
c.status(statusFor(code as never) as never);
|
|
150
|
+
return c.json(toWireError({ code, message, ...extra }, c.get('requestId')));
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
async function jsonBody(c: Context<AppEnv>): Promise<unknown> {
|
|
154
|
+
try {
|
|
155
|
+
return await c.req.json();
|
|
156
|
+
} catch {
|
|
157
|
+
return undefined;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const isId = (v: unknown): v is string =>
|
|
162
|
+
typeof v === 'string' && v.trim() !== '' && v.length <= MAX_ID;
|
|
163
|
+
|
|
164
|
+
/** Exactly one of `factId`, `subject` or `conversationId`; or why not. */
|
|
165
|
+
function parseSelector(body: unknown): MemoryErasureSelector | string {
|
|
166
|
+
if (body === null || typeof body !== 'object' || Array.isArray(body)) {
|
|
167
|
+
return 'The body must be an object naming one of `factId`, `subject` or `conversationId`';
|
|
168
|
+
}
|
|
169
|
+
const b = body as Record<string, unknown>;
|
|
170
|
+
const named = ['factId', 'subject', 'conversationId'].filter((k) => b[k] !== undefined);
|
|
171
|
+
if (named.length !== 1) {
|
|
172
|
+
return 'Name exactly one of `factId`, `subject` (`{kind, id}`) or `conversationId`';
|
|
173
|
+
}
|
|
174
|
+
const unknown = Object.keys(b).filter(
|
|
175
|
+
(k) => !['factId', 'subject', 'conversationId'].includes(k),
|
|
176
|
+
);
|
|
177
|
+
if (unknown.length > 0) return `Unknown field \`${unknown[0]}\``;
|
|
178
|
+
if (b.factId !== undefined) {
|
|
179
|
+
return isId(b.factId) ? { factId: b.factId } : '`factId` must be a non-empty string';
|
|
180
|
+
}
|
|
181
|
+
if (b.conversationId !== undefined) {
|
|
182
|
+
return isId(b.conversationId)
|
|
183
|
+
? { conversationId: b.conversationId }
|
|
184
|
+
: '`conversationId` must be a non-empty string';
|
|
185
|
+
}
|
|
186
|
+
const s = b.subject as { kind?: unknown; id?: unknown } | null;
|
|
187
|
+
if (s === null || typeof s !== 'object') return '`subject` must be `{kind, id}`';
|
|
188
|
+
if (s.kind === 'user') {
|
|
189
|
+
return "Erasing a Kindgi user isn't offered: `subject.kind` must be `participant` (an app's end user) or `external`";
|
|
190
|
+
}
|
|
191
|
+
if (!SUBJECT_KINDS.includes(s.kind as (typeof SUBJECT_KINDS)[number])) {
|
|
192
|
+
return '`subject.kind` must be `participant` or `external`';
|
|
193
|
+
}
|
|
194
|
+
if (!isId(s.id)) return '`subject.id` must be a non-empty string';
|
|
195
|
+
return { subject: { kind: s.kind as (typeof SUBJECT_KINDS)[number], id: s.id } };
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/** `{erasures: MemoryErasureLedgerEntry[]}`, as `GET /export` gave it; or why not. */
|
|
199
|
+
function parseLedger(body: unknown): MemoryErasureLedgerEntry[] | string {
|
|
200
|
+
const list = (body as { erasures?: unknown } | null)?.erasures;
|
|
201
|
+
if (!Array.isArray(list)) return 'The body must be `{erasures: [...]}`, as the export gave them';
|
|
202
|
+
if (list.length > MAX_LEDGER) return `At most ${MAX_LEDGER} erasures per replay`;
|
|
203
|
+
const out: MemoryErasureLedgerEntry[] = [];
|
|
204
|
+
for (const [i, raw] of list.entries()) {
|
|
205
|
+
const e = raw as Record<string, unknown> | null;
|
|
206
|
+
const at = `erasures[${i}]`;
|
|
207
|
+
if (e === null || typeof e !== 'object') return `${at} must be an object`;
|
|
208
|
+
if (typeof e.id !== 'string' || !UUID.test(e.id)) return `${at}.id must be a uuid`;
|
|
209
|
+
if (!SELECTOR_KINDS.includes(e.selectorKind as MemoryErasureSelectorKind)) {
|
|
210
|
+
return `${at}.selectorKind must be one of ${SELECTOR_KINDS.join(', ')}`;
|
|
211
|
+
}
|
|
212
|
+
if (
|
|
213
|
+
e.selectorHmac !== undefined &&
|
|
214
|
+
(typeof e.selectorHmac !== 'string' || !/^[0-9a-f]{64}$/.test(e.selectorHmac))
|
|
215
|
+
) {
|
|
216
|
+
return `${at}.selectorHmac must be 64 hex characters`;
|
|
217
|
+
}
|
|
218
|
+
if (e.keyId !== undefined && !isId(e.keyId)) return `${at}.keyId must be a string`;
|
|
219
|
+
if (!isId(e.requestedBy)) return `${at}.requestedBy must be a string`;
|
|
220
|
+
if (!STATUSES.includes(e.status as (typeof STATUSES)[number])) {
|
|
221
|
+
return `${at}.status must be one of ${STATUSES.join(', ')}`;
|
|
222
|
+
}
|
|
223
|
+
for (const k of ['createdAt', 'completedAt'] as const) {
|
|
224
|
+
const v = e[k];
|
|
225
|
+
if (v === undefined && k === 'completedAt') continue;
|
|
226
|
+
if (typeof v !== 'string' || Number.isNaN(Date.parse(v))) return `${at}.${k} must be a time`;
|
|
227
|
+
}
|
|
228
|
+
out.push({
|
|
229
|
+
id: e.id,
|
|
230
|
+
selectorKind: e.selectorKind as MemoryErasureSelectorKind,
|
|
231
|
+
...(typeof e.selectorHmac === 'string' && { selectorHmac: e.selectorHmac }),
|
|
232
|
+
...(typeof e.keyId === 'string' && { keyId: e.keyId }),
|
|
233
|
+
requestedBy: e.requestedBy as string,
|
|
234
|
+
status: e.status as (typeof STATUSES)[number],
|
|
235
|
+
createdAt: e.createdAt as string,
|
|
236
|
+
...(typeof e.completedAt === 'string' && { completedAt: e.completedAt }),
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
return out;
|
|
240
|
+
}
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// Copyright (C) 2026 Kindgi Inc.
|
|
3
|
+
|
|
4
|
+
/** Request parsing for `/v1/memory`: fact writes, supersedes, retrieve intents and query values. */
|
|
5
|
+
|
|
6
|
+
import type { FactSubject, MemoryScope, Retention } from '@kindgi/memory';
|
|
7
|
+
import type { Timestamp } from '@kindgi/types';
|
|
8
|
+
|
|
9
|
+
import type { MemoryRetrieveIntent } from '../memory-binding.js';
|
|
10
|
+
|
|
11
|
+
export type ValidationResult<T> =
|
|
12
|
+
| { readonly kind: 'ok'; readonly value: T }
|
|
13
|
+
| { readonly kind: 'err'; readonly message: string };
|
|
14
|
+
|
|
15
|
+
const ok = <T>(value: T): ValidationResult<T> => ({ kind: 'ok', value });
|
|
16
|
+
const err = <T>(message: string): ValidationResult<T> => ({ kind: 'err', message });
|
|
17
|
+
|
|
18
|
+
const MAX_SUBJECTS = 20;
|
|
19
|
+
|
|
20
|
+
export function parseScopeParam(raw: string): Partial<MemoryScope> | null {
|
|
21
|
+
try {
|
|
22
|
+
const decoded = JSON.parse(raw) as unknown;
|
|
23
|
+
if (decoded === null || typeof decoded !== 'object' || Array.isArray(decoded)) return null;
|
|
24
|
+
return decoded as Partial<MemoryScope>;
|
|
25
|
+
} catch {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** An ISO 8601 time, as a query value or a body field. */
|
|
31
|
+
export function parseTime(raw: unknown, field: string): ValidationResult<Timestamp | undefined> {
|
|
32
|
+
if (raw === undefined) return ok(undefined);
|
|
33
|
+
if (typeof raw !== 'string' || Number.isNaN(Date.parse(raw))) {
|
|
34
|
+
return err(`\`${field}\` must be an ISO 8601 time`);
|
|
35
|
+
}
|
|
36
|
+
return ok(new Date(raw).toISOString() as Timestamp);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** A revision number: a whole number of 1 or more. */
|
|
40
|
+
export function parseVersion(raw: unknown, field: string): ValidationResult<number | undefined> {
|
|
41
|
+
if (raw === undefined) return ok(undefined);
|
|
42
|
+
const n = typeof raw === 'string' && raw.trim() !== '' ? Number(raw) : raw;
|
|
43
|
+
if (typeof n !== 'number' || !Number.isInteger(n) || n < 1) {
|
|
44
|
+
return err(`\`${field}\` must be a revision number (1 or more)`);
|
|
45
|
+
}
|
|
46
|
+
return ok(n);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function parseScope(raw: unknown): ValidationResult<MemoryScope> {
|
|
50
|
+
if (raw === null || typeof raw !== 'object' || Array.isArray(raw)) {
|
|
51
|
+
return err('Field `scope` must be an object');
|
|
52
|
+
}
|
|
53
|
+
const scope = raw as Record<string, unknown>;
|
|
54
|
+
if (typeof scope.tenantId !== 'string') return err('Field `scope.tenantId` must be a string');
|
|
55
|
+
if (
|
|
56
|
+
scope.participantId !== undefined &&
|
|
57
|
+
(typeof scope.participantId !== 'string' || scope.participantId.length === 0)
|
|
58
|
+
) {
|
|
59
|
+
return err('Field `scope.participantId` must be a non-empty string');
|
|
60
|
+
}
|
|
61
|
+
if (scope.participantId !== undefined && scope.projectId === undefined) {
|
|
62
|
+
return err(
|
|
63
|
+
"`scope.participantId` needs `scope.projectId`: an app's end users belong to a project",
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
return ok(scope as unknown as MemoryScope);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function parseRetention(raw: unknown): ValidationResult<Retention | undefined> {
|
|
70
|
+
if (raw === undefined) return ok(undefined);
|
|
71
|
+
if (raw === null || typeof raw !== 'object' || Array.isArray(raw)) {
|
|
72
|
+
return err('Field `retention` must be an object when present');
|
|
73
|
+
}
|
|
74
|
+
const r = raw as Record<string, unknown>;
|
|
75
|
+
const keepUntil = parseTime(r.keepUntil, 'retention.keepUntil');
|
|
76
|
+
if (keepUntil.kind === 'err') return keepUntil;
|
|
77
|
+
if (
|
|
78
|
+
r.keepDays !== undefined &&
|
|
79
|
+
(typeof r.keepDays !== 'number' || !Number.isInteger(r.keepDays) || r.keepDays < 1)
|
|
80
|
+
) {
|
|
81
|
+
return err('`retention.keepDays` must be a whole number of days (1 or more)');
|
|
82
|
+
}
|
|
83
|
+
if (r.legalHold !== undefined && typeof r.legalHold !== 'boolean') {
|
|
84
|
+
return err('`retention.legalHold` must be true or false');
|
|
85
|
+
}
|
|
86
|
+
return ok(r as Retention);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function parseSubjects(raw: unknown): ValidationResult<readonly FactSubject[] | undefined> {
|
|
90
|
+
if (raw === undefined) return ok(undefined);
|
|
91
|
+
const shape =
|
|
92
|
+
"Field `subjects` is a list of whom the fact is about: [{ kind: 'participant' | 'user' | 'external', id }]";
|
|
93
|
+
if (!Array.isArray(raw) || raw.length > MAX_SUBJECTS) {
|
|
94
|
+
return err(`${shape}, at most ${MAX_SUBJECTS}`);
|
|
95
|
+
}
|
|
96
|
+
for (const s of raw) {
|
|
97
|
+
const o = s as Record<string, unknown> | null;
|
|
98
|
+
if (
|
|
99
|
+
o === null ||
|
|
100
|
+
typeof o !== 'object' ||
|
|
101
|
+
!['participant', 'user', 'external'].includes(o.kind as string) ||
|
|
102
|
+
typeof o.id !== 'string' ||
|
|
103
|
+
o.id.length === 0 ||
|
|
104
|
+
Object.keys(o).length !== 2
|
|
105
|
+
) {
|
|
106
|
+
return err(shape);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return ok(raw as FactSubject[]);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** The fields a write and a supersede share: retention, subjects and the times. */
|
|
113
|
+
export interface ValidatedFactFields {
|
|
114
|
+
readonly retention?: Retention;
|
|
115
|
+
readonly subjects?: readonly FactSubject[];
|
|
116
|
+
readonly validFrom?: Timestamp;
|
|
117
|
+
readonly validUntil?: Timestamp;
|
|
118
|
+
readonly observedAt?: Timestamp;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function parseFactFields(body: Record<string, unknown>): ValidationResult<ValidatedFactFields> {
|
|
122
|
+
const retention = parseRetention(body.retention);
|
|
123
|
+
if (retention.kind === 'err') return retention;
|
|
124
|
+
const subjects = parseSubjects(body.subjects);
|
|
125
|
+
if (subjects.kind === 'err') return subjects;
|
|
126
|
+
const times: Record<string, Timestamp> = {};
|
|
127
|
+
for (const field of ['validFrom', 'validUntil', 'observedAt'] as const) {
|
|
128
|
+
const t = parseTime(body[field], field);
|
|
129
|
+
if (t.kind === 'err') return t;
|
|
130
|
+
if (t.value !== undefined) times[field] = t.value;
|
|
131
|
+
}
|
|
132
|
+
if (
|
|
133
|
+
times.validFrom !== undefined &&
|
|
134
|
+
times.validUntil !== undefined &&
|
|
135
|
+
times.validFrom > times.validUntil
|
|
136
|
+
) {
|
|
137
|
+
return err('`validFrom` must not be after `validUntil`');
|
|
138
|
+
}
|
|
139
|
+
return ok({
|
|
140
|
+
...(retention.value !== undefined && { retention: retention.value }),
|
|
141
|
+
...(subjects.value !== undefined && { subjects: subjects.value }),
|
|
142
|
+
...times,
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export interface ValidatedWriteFactBody extends ValidatedFactFields {
|
|
147
|
+
readonly type: string;
|
|
148
|
+
readonly scope: MemoryScope;
|
|
149
|
+
readonly content: unknown;
|
|
150
|
+
readonly contentHash?: string;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export function validateWriteFactBody(
|
|
154
|
+
body: Record<string, unknown>,
|
|
155
|
+
): ValidationResult<ValidatedWriteFactBody> {
|
|
156
|
+
const type = body.type;
|
|
157
|
+
if (typeof type !== 'string' || type.length === 0) {
|
|
158
|
+
return err('Field `type` must be a non-empty string');
|
|
159
|
+
}
|
|
160
|
+
const scope = parseScope(body.scope);
|
|
161
|
+
if (scope.kind === 'err') return scope;
|
|
162
|
+
if (!('content' in body)) return err('Field `content` is required');
|
|
163
|
+
const rawContentHash = body.contentHash;
|
|
164
|
+
if (
|
|
165
|
+
rawContentHash !== undefined &&
|
|
166
|
+
(typeof rawContentHash !== 'string' || rawContentHash.length === 0)
|
|
167
|
+
) {
|
|
168
|
+
return err('Field `contentHash` must be a non-empty string when present');
|
|
169
|
+
}
|
|
170
|
+
const fields = parseFactFields(body);
|
|
171
|
+
if (fields.kind === 'err') return fields;
|
|
172
|
+
return ok({
|
|
173
|
+
type,
|
|
174
|
+
scope: scope.value,
|
|
175
|
+
content: body.content,
|
|
176
|
+
...(typeof rawContentHash === 'string' && { contentHash: rawContentHash }),
|
|
177
|
+
...fields.value,
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export interface ValidatedSupersedeBody extends ValidatedFactFields {
|
|
182
|
+
readonly content: unknown;
|
|
183
|
+
readonly expectVersion?: number;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
export function validateSupersedeBody(
|
|
187
|
+
body: Record<string, unknown>,
|
|
188
|
+
): ValidationResult<ValidatedSupersedeBody> {
|
|
189
|
+
if (!('content' in body)) {
|
|
190
|
+
return err("Field `content` is required: the fact's next revision");
|
|
191
|
+
}
|
|
192
|
+
const expectVersion = parseVersion(body.expectVersion, 'expectVersion');
|
|
193
|
+
if (expectVersion.kind === 'err') return expectVersion;
|
|
194
|
+
const fields = parseFactFields(body);
|
|
195
|
+
if (fields.kind === 'err') return fields;
|
|
196
|
+
return ok({
|
|
197
|
+
content: body.content,
|
|
198
|
+
...(expectVersion.value !== undefined && { expectVersion: expectVersion.value }),
|
|
199
|
+
...fields.value,
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
export function validateRetrieveIntent(
|
|
204
|
+
body: Record<string, unknown>,
|
|
205
|
+
): ValidationResult<MemoryRetrieveIntent> {
|
|
206
|
+
const mode = body.mode;
|
|
207
|
+
if (mode !== 'list' && mode !== 'keyword' && mode !== 'semantic' && mode !== 'both') {
|
|
208
|
+
return err('Field `mode` must be one of "list", "keyword", "semantic", "both"');
|
|
209
|
+
}
|
|
210
|
+
const rawQuery = body.query;
|
|
211
|
+
if (rawQuery !== undefined && typeof rawQuery !== 'string') {
|
|
212
|
+
return err('Field `query` must be a string when present');
|
|
213
|
+
}
|
|
214
|
+
if ((mode === 'keyword' || mode === 'semantic' || mode === 'both') && rawQuery === undefined) {
|
|
215
|
+
return err(`Field \`query\` is required when \`mode\` is "${mode}"`);
|
|
216
|
+
}
|
|
217
|
+
const rawType = body.type;
|
|
218
|
+
if (rawType !== undefined && (typeof rawType !== 'string' || rawType.length === 0)) {
|
|
219
|
+
return err('Field `type` must be a non-empty string when present');
|
|
220
|
+
}
|
|
221
|
+
const rawScope = body.scope;
|
|
222
|
+
if (
|
|
223
|
+
rawScope !== undefined &&
|
|
224
|
+
(rawScope === null || typeof rawScope !== 'object' || Array.isArray(rawScope))
|
|
225
|
+
) {
|
|
226
|
+
return err('Field `scope` must be an object when present');
|
|
227
|
+
}
|
|
228
|
+
const rawLimit = body.limit;
|
|
229
|
+
if (
|
|
230
|
+
rawLimit !== undefined &&
|
|
231
|
+
(typeof rawLimit !== 'number' || !Number.isFinite(rawLimit) || rawLimit < 1)
|
|
232
|
+
) {
|
|
233
|
+
return err('Field `limit` must be a positive number when present');
|
|
234
|
+
}
|
|
235
|
+
const rawEmbeddingModel = body.embeddingModel;
|
|
236
|
+
if (
|
|
237
|
+
rawEmbeddingModel !== undefined &&
|
|
238
|
+
(typeof rawEmbeddingModel !== 'string' || rawEmbeddingModel.length === 0)
|
|
239
|
+
) {
|
|
240
|
+
return err('Field `embeddingModel` must be a non-empty string when present');
|
|
241
|
+
}
|
|
242
|
+
return ok({
|
|
243
|
+
mode,
|
|
244
|
+
...(typeof rawQuery === 'string' && { query: rawQuery }),
|
|
245
|
+
...(typeof rawType === 'string' && { type: rawType }),
|
|
246
|
+
...(rawScope !== undefined && { scope: rawScope as Partial<MemoryScope> }),
|
|
247
|
+
...(typeof rawLimit === 'number' && { limit: Math.floor(rawLimit) }),
|
|
248
|
+
...(typeof rawEmbeddingModel === 'string' && { embeddingModel: rawEmbeddingModel }),
|
|
249
|
+
});
|
|
250
|
+
}
|