@retinue/agentkit 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +22 -0
- package/README.md +310 -0
- package/dist/adapters/bullmq/consumer.d.ts +33 -0
- package/dist/adapters/bullmq/consumer.js +41 -0
- package/dist/adapters/bullmq/dispatcher.d.ts +74 -0
- package/dist/adapters/bullmq/dispatcher.js +160 -0
- package/dist/adapters/bullmq/export.d.ts +31 -0
- package/dist/adapters/bullmq/export.js +53 -0
- package/dist/adapters/bullmq/extraction.d.ts +42 -0
- package/dist/adapters/bullmq/extraction.js +63 -0
- package/dist/adapters/bullmq/index.d.ts +13 -0
- package/dist/adapters/bullmq/index.js +13 -0
- package/dist/adapters/bullmq/lock.d.ts +77 -0
- package/dist/adapters/bullmq/lock.js +126 -0
- package/dist/adapters/bullmq/queue.d.ts +50 -0
- package/dist/adapters/bullmq/queue.js +81 -0
- package/dist/adapters/memory/artifact-exports.d.ts +11 -0
- package/dist/adapters/memory/artifact-exports.js +102 -0
- package/dist/adapters/memory/artifacts.d.ts +15 -0
- package/dist/adapters/memory/artifacts.js +134 -0
- package/dist/adapters/memory/blobs.d.ts +7 -0
- package/dist/adapters/memory/blobs.js +27 -0
- package/dist/adapters/memory/evaluation.d.ts +18 -0
- package/dist/adapters/memory/evaluation.js +148 -0
- package/dist/adapters/memory/files.d.ts +27 -0
- package/dist/adapters/memory/files.js +0 -0
- package/dist/adapters/memory/flows.d.ts +16 -0
- package/dist/adapters/memory/flows.js +117 -0
- package/dist/adapters/memory/hitl.d.ts +9 -0
- package/dist/adapters/memory/hitl.js +130 -0
- package/dist/adapters/memory/idempotency.d.ts +13 -0
- package/dist/adapters/memory/idempotency.js +32 -0
- package/dist/adapters/memory/index.d.ts +39 -0
- package/dist/adapters/memory/index.js +107 -0
- package/dist/adapters/memory/knowledge.d.ts +43 -0
- package/dist/adapters/memory/knowledge.js +248 -0
- package/dist/adapters/memory/mcp.d.ts +9 -0
- package/dist/adapters/memory/mcp.js +37 -0
- package/dist/adapters/memory/message-store.d.ts +17 -0
- package/dist/adapters/memory/message-store.js +70 -0
- package/dist/adapters/memory/principal-memory.d.ts +7 -0
- package/dist/adapters/memory/principal-memory.js +83 -0
- package/dist/adapters/memory/runtime.d.ts +29 -0
- package/dist/adapters/memory/runtime.js +0 -0
- package/dist/adapters/memory/sessions.d.ts +29 -0
- package/dist/adapters/memory/sessions.js +0 -0
- package/dist/adapters/memory/skills.d.ts +10 -0
- package/dist/adapters/memory/skills.js +41 -0
- package/dist/adapters/memory/thread-summary.d.ts +7 -0
- package/dist/adapters/memory/thread-summary.js +29 -0
- package/dist/adapters/memory/usage-limits.d.ts +13 -0
- package/dist/adapters/memory/usage-limits.js +72 -0
- package/dist/adapters/memory/usage.d.ts +16 -0
- package/dist/adapters/memory/usage.js +279 -0
- package/dist/adapters/otel/index.d.ts +111 -0
- package/dist/adapters/otel/index.js +133 -0
- package/dist/adapters/postgres/artifact-exports.d.ts +12 -0
- package/dist/adapters/postgres/artifact-exports.js +117 -0
- package/dist/adapters/postgres/artifacts.d.ts +16 -0
- package/dist/adapters/postgres/artifacts.js +172 -0
- package/dist/adapters/postgres/checkpoint-store.d.ts +16 -0
- package/dist/adapters/postgres/checkpoint-store.js +34 -0
- package/dist/adapters/postgres/config.d.ts +15 -0
- package/dist/adapters/postgres/config.js +187 -0
- package/dist/adapters/postgres/conversation-store.d.ts +4 -0
- package/dist/adapters/postgres/conversation-store.js +82 -0
- package/dist/adapters/postgres/evaluation.d.ts +17 -0
- package/dist/adapters/postgres/evaluation.js +193 -0
- package/dist/adapters/postgres/file-content.d.ts +30 -0
- package/dist/adapters/postgres/file-content.js +111 -0
- package/dist/adapters/postgres/files.d.ts +19 -0
- package/dist/adapters/postgres/files.js +209 -0
- package/dist/adapters/postgres/flows.d.ts +20 -0
- package/dist/adapters/postgres/flows.js +206 -0
- package/dist/adapters/postgres/hitl.d.ts +5 -0
- package/dist/adapters/postgres/hitl.js +247 -0
- package/dist/adapters/postgres/index.d.ts +35 -0
- package/dist/adapters/postgres/index.js +35 -0
- package/dist/adapters/postgres/knowledge.d.ts +48 -0
- package/dist/adapters/postgres/knowledge.js +255 -0
- package/dist/adapters/postgres/memory.d.ts +14 -0
- package/dist/adapters/postgres/memory.js +194 -0
- package/dist/adapters/postgres/message-store.d.ts +11 -0
- package/dist/adapters/postgres/message-store.js +145 -0
- package/dist/adapters/postgres/migrations.d.ts +69 -0
- package/dist/adapters/postgres/migrations.js +1594 -0
- package/dist/adapters/postgres/pg-executor.d.ts +19 -0
- package/dist/adapters/postgres/pg-executor.js +32 -0
- package/dist/adapters/postgres/retention.d.ts +26 -0
- package/dist/adapters/postgres/retention.js +59 -0
- package/dist/adapters/postgres/rollups.d.ts +17 -0
- package/dist/adapters/postgres/rollups.js +267 -0
- package/dist/adapters/postgres/run-coordinator.d.ts +5 -0
- package/dist/adapters/postgres/run-coordinator.js +98 -0
- package/dist/adapters/postgres/run-event-log.d.ts +26 -0
- package/dist/adapters/postgres/run-event-log.js +30 -0
- package/dist/adapters/postgres/run-store.d.ts +4 -0
- package/dist/adapters/postgres/run-store.js +199 -0
- package/dist/adapters/postgres/schema.d.ts +39 -0
- package/dist/adapters/postgres/schema.js +70 -0
- package/dist/adapters/postgres/session-state.d.ts +7 -0
- package/dist/adapters/postgres/session-state.js +99 -0
- package/dist/adapters/postgres/sql.d.ts +8 -0
- package/dist/adapters/postgres/sql.js +2 -0
- package/dist/adapters/postgres/transaction.d.ts +37 -0
- package/dist/adapters/postgres/transaction.js +93 -0
- package/dist/adapters/postgres/unit-of-work.d.ts +18 -0
- package/dist/adapters/postgres/unit-of-work.js +8 -0
- package/dist/adapters/postgres/usage-limits.d.ts +15 -0
- package/dist/adapters/postgres/usage-limits.js +136 -0
- package/dist/adapters/postgres/usage.d.ts +15 -0
- package/dist/adapters/postgres/usage.js +226 -0
- package/dist/adapters/redis/index.d.ts +9 -0
- package/dist/adapters/redis/index.js +9 -0
- package/dist/adapters/redis/realtime.d.ts +74 -0
- package/dist/adapters/redis/realtime.js +112 -0
- package/dist/adapters/supabase/index.d.ts +88 -0
- package/dist/adapters/supabase/index.js +84 -0
- package/dist/adapters/supabase/realtime.d.ts +30 -0
- package/dist/adapters/supabase/realtime.js +53 -0
- package/dist/adapters/supabase/rls.d.ts +99 -0
- package/dist/adapters/supabase/rls.js +216 -0
- package/dist/adapters/supabase/storage.d.ts +50 -0
- package/dist/adapters/supabase/storage.js +207 -0
- package/dist/agents/agent.d.ts +66 -0
- package/dist/agents/agent.js +209 -0
- package/dist/agents/define.d.ts +21 -0
- package/dist/agents/define.js +63 -0
- package/dist/agents/engine.d.ts +98 -0
- package/dist/agents/engine.js +462 -0
- package/dist/agents/index.d.ts +50 -0
- package/dist/agents/index.js +17 -0
- package/dist/artifacts/index.d.ts +114 -0
- package/dist/artifacts/index.js +219 -0
- package/dist/authorization/index.d.ts +76 -0
- package/dist/authorization/index.js +76 -0
- package/dist/capabilities/index.d.ts +120 -0
- package/dist/capabilities/index.js +167 -0
- package/dist/capabilities/runtime.d.ts +89 -0
- package/dist/capabilities/runtime.js +84 -0
- package/dist/citations/index.d.ts +161 -0
- package/dist/citations/index.js +182 -0
- package/dist/context/assembler.d.ts +82 -0
- package/dist/context/assembler.js +129 -0
- package/dist/context/compaction.d.ts +45 -0
- package/dist/context/compaction.js +55 -0
- package/dist/context/index.d.ts +75 -0
- package/dist/context/index.js +17 -0
- package/dist/core/content-parts.d.ts +194 -0
- package/dist/core/content-parts.js +23 -0
- package/dist/core/context.d.ts +51 -0
- package/dist/core/context.js +9 -0
- package/dist/core/env.d.ts +25 -0
- package/dist/core/env.js +41 -0
- package/dist/core/errors.d.ts +30 -0
- package/dist/core/errors.js +65 -0
- package/dist/core/events.d.ts +139 -0
- package/dist/core/events.js +99 -0
- package/dist/core/ids.d.ts +52 -0
- package/dist/core/ids.js +13 -0
- package/dist/core/index.d.ts +9 -0
- package/dist/core/index.js +9 -0
- package/dist/core/tokens.d.ts +22 -0
- package/dist/core/tokens.js +22 -0
- package/dist/core/validation.d.ts +34 -0
- package/dist/core/validation.js +176 -0
- package/dist/documents/extraction.d.ts +121 -0
- package/dist/documents/extraction.js +293 -0
- package/dist/documents/index.d.ts +199 -0
- package/dist/documents/index.js +65 -0
- package/dist/documents/parsers/pdf.d.ts +47 -0
- package/dist/documents/parsers/pdf.js +508 -0
- package/dist/documents/parsers/text.d.ts +59 -0
- package/dist/documents/parsers/text.js +325 -0
- package/dist/documents/read-tool.d.ts +52 -0
- package/dist/documents/read-tool.js +109 -0
- package/dist/documents/render.d.ts +29 -0
- package/dist/documents/render.js +59 -0
- package/dist/documents/vision.d.ts +159 -0
- package/dist/documents/vision.js +214 -0
- package/dist/entries/adapters-bullmq.d.ts +8 -0
- package/dist/entries/adapters-bullmq.js +8 -0
- package/dist/entries/adapters-otel.d.ts +13 -0
- package/dist/entries/adapters-otel.js +13 -0
- package/dist/entries/adapters-postgres.d.ts +10 -0
- package/dist/entries/adapters-postgres.js +10 -0
- package/dist/entries/adapters-redis.d.ts +3 -0
- package/dist/entries/adapters-redis.js +3 -0
- package/dist/entries/context.d.ts +20 -0
- package/dist/entries/context.js +20 -0
- package/dist/entries/flows.d.ts +15 -0
- package/dist/entries/flows.js +15 -0
- package/dist/entries/hitl.d.ts +10 -0
- package/dist/entries/hitl.js +10 -0
- package/dist/entries/knowledge.d.ts +18 -0
- package/dist/entries/knowledge.js +19 -0
- package/dist/entries/mcp.d.ts +10 -0
- package/dist/entries/mcp.js +10 -0
- package/dist/entries/observability.d.ts +14 -0
- package/dist/entries/observability.js +16 -0
- package/dist/entries/persistence.d.ts +11 -0
- package/dist/entries/persistence.js +11 -0
- package/dist/entries/providers.d.ts +14 -0
- package/dist/entries/providers.js +14 -0
- package/dist/entries/runtime.d.ts +13 -0
- package/dist/entries/runtime.js +13 -0
- package/dist/entries/server.d.ts +24 -0
- package/dist/entries/server.js +24 -0
- package/dist/entries/tools.d.ts +21 -0
- package/dist/entries/tools.js +21 -0
- package/dist/entries/usage.d.ts +10 -0
- package/dist/entries/usage.js +10 -0
- package/dist/evaluation/gate.d.ts +168 -0
- package/dist/evaluation/gate.js +180 -0
- package/dist/evaluation/graders.d.ts +125 -0
- package/dist/evaluation/graders.js +203 -0
- package/dist/evaluation/index.d.ts +120 -0
- package/dist/evaluation/index.js +183 -0
- package/dist/evaluation/judge.d.ts +75 -0
- package/dist/evaluation/judge.js +111 -0
- package/dist/export/index.d.ts +162 -0
- package/dist/export/index.js +363 -0
- package/dist/export/markdown.d.ts +19 -0
- package/dist/export/markdown.js +29 -0
- package/dist/export/pdf.d.ts +73 -0
- package/dist/export/pdf.js +407 -0
- package/dist/files/context.d.ts +97 -0
- package/dist/files/context.js +185 -0
- package/dist/files/index.d.ts +210 -0
- package/dist/files/index.js +338 -0
- package/dist/files/read-tool.d.ts +81 -0
- package/dist/files/read-tool.js +163 -0
- package/dist/files/turn-parts.d.ts +96 -0
- package/dist/files/turn-parts.js +171 -0
- package/dist/flows/index.d.ts +270 -0
- package/dist/flows/index.js +62 -0
- package/dist/flows/interpreter.d.ts +146 -0
- package/dist/flows/interpreter.js +426 -0
- package/dist/flows/runner.d.ts +145 -0
- package/dist/flows/runner.js +270 -0
- package/dist/graphql/index.d.ts +8 -0
- package/dist/graphql/index.js +8 -0
- package/dist/graphql/resolvers.d.ts +237 -0
- package/dist/graphql/resolvers.js +253 -0
- package/dist/graphql/schema.d.ts +11 -0
- package/dist/graphql/schema.js +258 -0
- package/dist/graphql/sse.d.ts +77 -0
- package/dist/graphql/sse.js +100 -0
- package/dist/hitl/approved-execution.d.ts +127 -0
- package/dist/hitl/approved-execution.js +177 -0
- package/dist/hitl/index.d.ts +79 -0
- package/dist/hitl/index.js +12 -0
- package/dist/hitl/service.d.ts +221 -0
- package/dist/hitl/service.js +268 -0
- package/dist/idempotency/index.d.ts +70 -0
- package/dist/idempotency/index.js +59 -0
- package/dist/index.d.ts +103 -0
- package/dist/index.js +60 -0
- package/dist/knowledge/chunking.d.ts +57 -0
- package/dist/knowledge/chunking.js +158 -0
- package/dist/knowledge/index.d.ts +119 -0
- package/dist/knowledge/index.js +166 -0
- package/dist/knowledge/retrieval.d.ts +146 -0
- package/dist/knowledge/retrieval.js +170 -0
- package/dist/loadtest/harness.d.ts +168 -0
- package/dist/loadtest/harness.js +507 -0
- package/dist/loadtest/index.d.ts +13 -0
- package/dist/loadtest/index.js +13 -0
- package/dist/loadtest/injection.d.ts +89 -0
- package/dist/loadtest/injection.js +147 -0
- package/dist/loadtest/metrics.d.ts +197 -0
- package/dist/loadtest/metrics.js +160 -0
- package/dist/loadtest/runbooks.d.ts +28 -0
- package/dist/loadtest/runbooks.js +159 -0
- package/dist/loadtest/scenario.d.ts +104 -0
- package/dist/loadtest/scenario.js +208 -0
- package/dist/mcp/egress.d.ts +53 -0
- package/dist/mcp/egress.js +115 -0
- package/dist/mcp/index.d.ts +93 -0
- package/dist/mcp/index.js +33 -0
- package/dist/mcp/provider.d.ts +62 -0
- package/dist/mcp/provider.js +0 -0
- package/dist/models/index.d.ts +98 -0
- package/dist/models/index.js +74 -0
- package/dist/models/pricing.d.ts +24 -0
- package/dist/models/pricing.js +37 -0
- package/dist/models/provider-factory.d.ts +31 -0
- package/dist/models/provider-factory.js +67 -0
- package/dist/models/streaming.d.ts +145 -0
- package/dist/models/streaming.js +272 -0
- package/dist/models/vision.d.ts +38 -0
- package/dist/models/vision.js +62 -0
- package/dist/persistence/index.d.ts +1654 -0
- package/dist/persistence/index.js +226 -0
- package/dist/principal-memory/index.d.ts +106 -0
- package/dist/principal-memory/index.js +89 -0
- package/dist/retention/index.d.ts +89 -0
- package/dist/retention/index.js +70 -0
- package/dist/runtime/checkpoint.d.ts +37 -0
- package/dist/runtime/checkpoint.js +22 -0
- package/dist/runtime/index.d.ts +118 -0
- package/dist/runtime/index.js +69 -0
- package/dist/runtime/retry.d.ts +95 -0
- package/dist/runtime/retry.js +126 -0
- package/dist/runtime/serialization.d.ts +85 -0
- package/dist/runtime/serialization.js +95 -0
- package/dist/runtime/streaming.d.ts +54 -0
- package/dist/runtime/streaming.js +115 -0
- package/dist/runtime/worker.d.ts +130 -0
- package/dist/runtime/worker.js +405 -0
- package/dist/security/checklist.d.ts +53 -0
- package/dist/security/checklist.js +204 -0
- package/dist/security/findings.d.ts +56 -0
- package/dist/security/findings.js +168 -0
- package/dist/security/index.d.ts +14 -0
- package/dist/security/index.js +14 -0
- package/dist/security/prompt-safety.d.ts +100 -0
- package/dist/security/prompt-safety.js +133 -0
- package/dist/server/boot.d.ts +32 -0
- package/dist/server/boot.js +36 -0
- package/dist/server/cli-worker.d.ts +37 -0
- package/dist/server/cli-worker.js +151 -0
- package/dist/server/cli.d.ts +27 -0
- package/dist/server/cli.js +74 -0
- package/dist/server/config.d.ts +42 -0
- package/dist/server/config.js +127 -0
- package/dist/server/health.d.ts +59 -0
- package/dist/server/health.js +90 -0
- package/dist/server/host.d.ts +39 -0
- package/dist/server/host.js +124 -0
- package/dist/server/index.d.ts +15 -0
- package/dist/server/index.js +15 -0
- package/dist/server/main.d.ts +16 -0
- package/dist/server/main.js +31 -0
- package/dist/server/sse-route.d.ts +21 -0
- package/dist/server/sse-route.js +282 -0
- package/dist/skills/index.d.ts +67 -0
- package/dist/skills/index.js +31 -0
- package/dist/skills/resolver.d.ts +54 -0
- package/dist/skills/resolver.js +121 -0
- package/dist/teams/index.d.ts +93 -0
- package/dist/teams/index.js +207 -0
- package/dist/telemetry/index.d.ts +157 -0
- package/dist/telemetry/index.js +71 -0
- package/dist/telemetry/instrument.d.ts +108 -0
- package/dist/telemetry/instrument.js +232 -0
- package/dist/telemetry/log-events.d.ts +17 -0
- package/dist/telemetry/log-events.js +58 -0
- package/dist/telemetry/metrics.d.ts +123 -0
- package/dist/telemetry/metrics.js +135 -0
- package/dist/telemetry/noop.d.ts +39 -0
- package/dist/telemetry/noop.js +143 -0
- package/dist/telemetry/redaction.d.ts +64 -0
- package/dist/telemetry/redaction.js +153 -0
- package/dist/telemetry/spans.d.ts +56 -0
- package/dist/telemetry/spans.js +78 -0
- package/dist/telemetry/trace-context.d.ts +55 -0
- package/dist/telemetry/trace-context.js +60 -0
- package/dist/toolkit/compute.d.ts +53 -0
- package/dist/toolkit/compute.js +152 -0
- package/dist/toolkit/data.d.ts +98 -0
- package/dist/toolkit/data.js +235 -0
- package/dist/toolkit/http.d.ts +113 -0
- package/dist/toolkit/http.js +205 -0
- package/dist/toolkit/index.d.ts +21 -0
- package/dist/toolkit/index.js +17 -0
- package/dist/toolkit/web.d.ts +107 -0
- package/dist/toolkit/web.js +147 -0
- package/dist/tools/define.d.ts +25 -0
- package/dist/tools/define.js +45 -0
- package/dist/tools/delegating.d.ts +132 -0
- package/dist/tools/delegating.js +211 -0
- package/dist/tools/index.d.ts +129 -0
- package/dist/tools/index.js +33 -0
- package/dist/tools/library/compute.d.ts +11 -0
- package/dist/tools/library/compute.js +46 -0
- package/dist/tools/library/data.d.ts +16 -0
- package/dist/tools/library/data.js +92 -0
- package/dist/tools/library/http.d.ts +28 -0
- package/dist/tools/library/http.js +71 -0
- package/dist/tools/library/index.d.ts +97 -0
- package/dist/tools/library/index.js +134 -0
- package/dist/tools/library/knowledge.d.ts +39 -0
- package/dist/tools/library/knowledge.js +58 -0
- package/dist/tools/library/web.d.ts +19 -0
- package/dist/tools/library/web.js +65 -0
- package/dist/tools/meta-tools.d.ts +19 -0
- package/dist/tools/meta-tools.js +36 -0
- package/dist/tools/registry.d.ts +146 -0
- package/dist/tools/registry.js +291 -0
- package/dist/usage/index.d.ts +105 -0
- package/dist/usage/index.js +20 -0
- package/dist/usage/quota.d.ts +258 -0
- package/dist/usage/quota.js +510 -0
- package/dist/usage/recorder.d.ts +29 -0
- package/dist/usage/recorder.js +96 -0
- package/dist/usage/rollups.d.ts +121 -0
- package/dist/usage/rollups.js +157 -0
- package/dist/worker/export.d.ts +57 -0
- package/dist/worker/export.js +81 -0
- package/dist/worker/extraction.d.ts +57 -0
- package/dist/worker/extraction.js +84 -0
- package/dist/worker/main.d.ts +103 -0
- package/dist/worker/main.js +159 -0
- package/package.json +187 -0
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The artifact lifecycle — `docs/05-knowledge-and-documents.md`, REQ-028 (#133).
|
|
3
|
+
*
|
|
4
|
+
* The store holds rows; what lives here is the part no store can hold: **entitlement**, and the pairing of a
|
|
5
|
+
* version row with the content it references.
|
|
6
|
+
*
|
|
7
|
+
* - **AC-4 is enforced, not asserted.** `AuthorizationPolicy` is a required dependency and the decision is
|
|
8
|
+
* about the *conversation*, so an artifact is never more accessible than the conversation that produced it
|
|
9
|
+
* and there is no second permission model to keep in step with the first. The same shape #129 settled on
|
|
10
|
+
* for files, deliberately: two different answers to "who may read this" is one too many.
|
|
11
|
+
* - **A version and its content are written in one call**, in the order that makes a crash survivable: blob
|
|
12
|
+
* first, row second. The reverse leaves a row pointing at content that does not exist, and a dangling
|
|
13
|
+
* reference reads as corruption while an unreferenced blob is merely waste.
|
|
14
|
+
*/
|
|
15
|
+
import type { ExecutionContext, Page } from "../core/context.js";
|
|
16
|
+
import type { ArtifactId, ConversationId } from "../core/ids.js";
|
|
17
|
+
import type { AuthorizationPolicy } from "../authorization/index.js";
|
|
18
|
+
import type { Artifact, ArtifactKind, ArtifactProvenance, ArtifactStore, ArtifactVersion, BlobStore } from "../persistence/index.js";
|
|
19
|
+
/**
|
|
20
|
+
* The largest artifact this platform will store.
|
|
21
|
+
*
|
|
22
|
+
* A ceiling exists because an artifact is model output and model output has no natural bound. 4 MiB is a very
|
|
23
|
+
* long report and far past anything a context window produced in one turn — so in practice it catches a loop
|
|
24
|
+
* writing the same paragraph a thousand times, which is what it is for.
|
|
25
|
+
*/
|
|
26
|
+
export declare const MAX_ARTIFACT_BYTES: number;
|
|
27
|
+
export type ArtifactContent = {
|
|
28
|
+
readonly kind: ArtifactKind;
|
|
29
|
+
/** The content itself. Markdown and structured content are both strings or JSON values. */
|
|
30
|
+
readonly value: unknown;
|
|
31
|
+
};
|
|
32
|
+
export type ArtifactServiceDeps = {
|
|
33
|
+
readonly artifacts: ArtifactStore;
|
|
34
|
+
readonly blobs: BlobStore;
|
|
35
|
+
/**
|
|
36
|
+
* AC-4, enforced rather than asserted.
|
|
37
|
+
*
|
|
38
|
+
* **Required.** A service that would run without a policy is a service someone constructs without one, and
|
|
39
|
+
* at that point every artifact in the tenant is readable by every member of it. Tenant scoping is not AC-4.
|
|
40
|
+
*/
|
|
41
|
+
readonly authorization: AuthorizationPolicy;
|
|
42
|
+
readonly clock?: () => string;
|
|
43
|
+
readonly artifactId?: () => string;
|
|
44
|
+
readonly versionId?: () => string;
|
|
45
|
+
};
|
|
46
|
+
/**
|
|
47
|
+
* The size of a content value once stored.
|
|
48
|
+
*
|
|
49
|
+
* Measured on the serialised form, because that is what is stored and what is billed. Measuring the string
|
|
50
|
+
* before serialisation would under-count a structured value by exactly the amount its encoding adds.
|
|
51
|
+
*/
|
|
52
|
+
export declare const contentByteSize: (value: unknown) => number;
|
|
53
|
+
export declare const createArtifactService: (deps: ArtifactServiceDeps) => {
|
|
54
|
+
/** Create an artifact at version 1. */
|
|
55
|
+
create(context: ExecutionContext, input: {
|
|
56
|
+
readonly conversationId: ConversationId;
|
|
57
|
+
readonly name: string;
|
|
58
|
+
readonly content: ArtifactContent;
|
|
59
|
+
readonly provenance: ArtifactProvenance;
|
|
60
|
+
}): Promise<Artifact>;
|
|
61
|
+
/**
|
|
62
|
+
* Regenerate: a new version, never an overwrite — AC-2.
|
|
63
|
+
*
|
|
64
|
+
* `expectedLatestVersion` is read here rather than taken from the caller, and then passed through as the
|
|
65
|
+
* compare. That is not redundant: the read establishes what the caller is regenerating *from*, and the
|
|
66
|
+
* compare makes a concurrent regeneration lose loudly instead of silently replacing this one.
|
|
67
|
+
*/
|
|
68
|
+
regenerate(context: ExecutionContext, input: {
|
|
69
|
+
readonly id: ArtifactId;
|
|
70
|
+
readonly content: ArtifactContent;
|
|
71
|
+
readonly provenance: ArtifactProvenance;
|
|
72
|
+
}): Promise<{
|
|
73
|
+
readonly version: number;
|
|
74
|
+
}>;
|
|
75
|
+
/** A version's row and its content together. The latest when no version is asked for. */
|
|
76
|
+
read(context: ExecutionContext, input: {
|
|
77
|
+
readonly id: ArtifactId;
|
|
78
|
+
readonly version?: number;
|
|
79
|
+
}): Promise<{
|
|
80
|
+
readonly artifact: Artifact;
|
|
81
|
+
readonly version: ArtifactVersion;
|
|
82
|
+
readonly content: unknown;
|
|
83
|
+
}>;
|
|
84
|
+
/** The version history, for a restore or an audit. Metadata only — no content is loaded. */
|
|
85
|
+
history(context: ExecutionContext, input: {
|
|
86
|
+
readonly id: ArtifactId;
|
|
87
|
+
readonly limit: number;
|
|
88
|
+
readonly cursor?: string;
|
|
89
|
+
}): Promise<Page<ArtifactVersion>>;
|
|
90
|
+
/**
|
|
91
|
+
* Restore an earlier version by making it the newest one.
|
|
92
|
+
*
|
|
93
|
+
* A *new* version whose content is the old one's, not a pointer moved backwards. Moving a pointer would
|
|
94
|
+
* make the history lie about what happened — and "the version that was current on Tuesday" is exactly the
|
|
95
|
+
* question a shared link asks.
|
|
96
|
+
*/
|
|
97
|
+
restore(context: ExecutionContext, input: {
|
|
98
|
+
readonly id: ArtifactId;
|
|
99
|
+
readonly version: number;
|
|
100
|
+
readonly producedBy?: string;
|
|
101
|
+
}): Promise<{
|
|
102
|
+
readonly version: number;
|
|
103
|
+
}>;
|
|
104
|
+
listForConversation(context: ExecutionContext, input: {
|
|
105
|
+
readonly conversationId: ConversationId;
|
|
106
|
+
readonly limit: number;
|
|
107
|
+
readonly cursor?: string;
|
|
108
|
+
}): Promise<Page<Artifact>>;
|
|
109
|
+
softDelete(context: ExecutionContext, id: ArtifactId): Promise<{
|
|
110
|
+
readonly deleted: boolean;
|
|
111
|
+
}>;
|
|
112
|
+
};
|
|
113
|
+
export type ArtifactService = ReturnType<typeof createArtifactService>;
|
|
114
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The artifact lifecycle — `docs/05-knowledge-and-documents.md`, REQ-028 (#133).
|
|
3
|
+
*
|
|
4
|
+
* The store holds rows; what lives here is the part no store can hold: **entitlement**, and the pairing of a
|
|
5
|
+
* version row with the content it references.
|
|
6
|
+
*
|
|
7
|
+
* - **AC-4 is enforced, not asserted.** `AuthorizationPolicy` is a required dependency and the decision is
|
|
8
|
+
* about the *conversation*, so an artifact is never more accessible than the conversation that produced it
|
|
9
|
+
* and there is no second permission model to keep in step with the first. The same shape #129 settled on
|
|
10
|
+
* for files, deliberately: two different answers to "who may read this" is one too many.
|
|
11
|
+
* - **A version and its content are written in one call**, in the order that makes a crash survivable: blob
|
|
12
|
+
* first, row second. The reverse leaves a row pointing at content that does not exist, and a dangling
|
|
13
|
+
* reference reads as corruption while an unreferenced blob is merely waste.
|
|
14
|
+
*/
|
|
15
|
+
import { AgentPlatformError } from "../core/errors.js";
|
|
16
|
+
import { asId } from "../core/ids.js";
|
|
17
|
+
/**
|
|
18
|
+
* The largest artifact this platform will store.
|
|
19
|
+
*
|
|
20
|
+
* A ceiling exists because an artifact is model output and model output has no natural bound. 4 MiB is a very
|
|
21
|
+
* long report and far past anything a context window produced in one turn — so in practice it catches a loop
|
|
22
|
+
* writing the same paragraph a thousand times, which is what it is for.
|
|
23
|
+
*/
|
|
24
|
+
export const MAX_ARTIFACT_BYTES = 4 * 1024 * 1024;
|
|
25
|
+
const refuse = (code, message) => new AgentPlatformError({ code, message, retryable: false });
|
|
26
|
+
/**
|
|
27
|
+
* The size of a content value once stored.
|
|
28
|
+
*
|
|
29
|
+
* Measured on the serialised form, because that is what is stored and what is billed. Measuring the string
|
|
30
|
+
* before serialisation would under-count a structured value by exactly the amount its encoding adds.
|
|
31
|
+
*/
|
|
32
|
+
export const contentByteSize = (value) => new TextEncoder().encode(typeof value === "string" ? value : JSON.stringify(value)).byteLength;
|
|
33
|
+
/** Hex SHA-256 of the stored form, so a read-back can be checked rather than assumed. */
|
|
34
|
+
const checksumOf = async (value) => {
|
|
35
|
+
const bytes = new TextEncoder().encode(typeof value === "string" ? value : JSON.stringify(value));
|
|
36
|
+
const digest = await crypto.subtle.digest("SHA-256", bytes);
|
|
37
|
+
return [...new Uint8Array(digest)].map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
38
|
+
};
|
|
39
|
+
export const createArtifactService = (deps) => {
|
|
40
|
+
const clock = deps.clock ?? (() => new Date().toISOString());
|
|
41
|
+
const newArtifactId = deps.artifactId ?? (() => `art_${crypto.randomUUID()}`);
|
|
42
|
+
const newVersionId = deps.versionId ?? (() => `ver_${crypto.randomUUID()}`);
|
|
43
|
+
const authorise = async (context, action, conversationId, absent) => {
|
|
44
|
+
const decision = await deps.authorization.can(context, action, {
|
|
45
|
+
type: "conversation",
|
|
46
|
+
id: conversationId,
|
|
47
|
+
});
|
|
48
|
+
// `not_found`, never `forbidden`: a distinct answer here confirms the artifact exists to precisely the
|
|
49
|
+
// caller who must not learn that.
|
|
50
|
+
if (!decision.allow)
|
|
51
|
+
throw refuse("not_found", absent);
|
|
52
|
+
};
|
|
53
|
+
const store = async (context, content) => {
|
|
54
|
+
const byteSize = contentByteSize(content.value);
|
|
55
|
+
if (byteSize > MAX_ARTIFACT_BYTES)
|
|
56
|
+
// The limit named, because "too large" sends someone to guess.
|
|
57
|
+
throw refuse("invalid_input", `that artifact is ${byteSize} bytes and the limit is ${MAX_ARTIFACT_BYTES} bytes`);
|
|
58
|
+
const checksum = await checksumOf(content.value);
|
|
59
|
+
// Blob first. A crash here leaves an unreferenced blob, which is waste; the reverse leaves a row pointing
|
|
60
|
+
// at nothing, which reads as corruption.
|
|
61
|
+
const ref = await deps.blobs.put({ tenantId: context.tenantId, value: content.value });
|
|
62
|
+
return { ref, byteSize, checksum };
|
|
63
|
+
};
|
|
64
|
+
return {
|
|
65
|
+
/** Create an artifact at version 1. */
|
|
66
|
+
async create(context, input) {
|
|
67
|
+
await authorise(context, "write", input.conversationId, "no such conversation");
|
|
68
|
+
const { ref, byteSize, checksum } = await store(context, input.content);
|
|
69
|
+
return deps.artifacts.create({
|
|
70
|
+
tenantId: context.tenantId,
|
|
71
|
+
artifact: {
|
|
72
|
+
id: asId(newArtifactId()),
|
|
73
|
+
conversationId: input.conversationId,
|
|
74
|
+
kind: input.content.kind,
|
|
75
|
+
name: input.name,
|
|
76
|
+
createdAt: clock(),
|
|
77
|
+
},
|
|
78
|
+
version: {
|
|
79
|
+
id: asId(newVersionId()),
|
|
80
|
+
contentRef: ref,
|
|
81
|
+
byteSize,
|
|
82
|
+
checksum,
|
|
83
|
+
provenance: input.provenance,
|
|
84
|
+
createdBy: context.principalId,
|
|
85
|
+
createdAt: clock(),
|
|
86
|
+
},
|
|
87
|
+
});
|
|
88
|
+
},
|
|
89
|
+
/**
|
|
90
|
+
* Regenerate: a new version, never an overwrite — AC-2.
|
|
91
|
+
*
|
|
92
|
+
* `expectedLatestVersion` is read here rather than taken from the caller, and then passed through as the
|
|
93
|
+
* compare. That is not redundant: the read establishes what the caller is regenerating *from*, and the
|
|
94
|
+
* compare makes a concurrent regeneration lose loudly instead of silently replacing this one.
|
|
95
|
+
*/
|
|
96
|
+
async regenerate(context, input) {
|
|
97
|
+
const artifact = await deps.artifacts.get({ tenantId: context.tenantId, id: input.id });
|
|
98
|
+
if (artifact === null || artifact.deletedAt !== undefined)
|
|
99
|
+
throw refuse("not_found", "no such artifact");
|
|
100
|
+
await authorise(context, "write", artifact.conversationId, "no such artifact");
|
|
101
|
+
const { ref, byteSize, checksum } = await store(context, input.content);
|
|
102
|
+
const result = await deps.artifacts.addVersion({
|
|
103
|
+
tenantId: context.tenantId,
|
|
104
|
+
id: input.id,
|
|
105
|
+
expectedLatestVersion: artifact.latestVersion,
|
|
106
|
+
version: {
|
|
107
|
+
id: asId(newVersionId()),
|
|
108
|
+
contentRef: ref,
|
|
109
|
+
byteSize,
|
|
110
|
+
checksum,
|
|
111
|
+
provenance: input.provenance,
|
|
112
|
+
createdBy: context.principalId,
|
|
113
|
+
createdAt: clock(),
|
|
114
|
+
},
|
|
115
|
+
});
|
|
116
|
+
if (!result.added || result.version === undefined)
|
|
117
|
+
// Lost the race, or the artifact was deleted between the read and the write. The blob written above is
|
|
118
|
+
// now unreferenced — waste, and deliberately not cleaned up here: deleting it would risk removing
|
|
119
|
+
// content the *winning* version happens to reference if two regenerations produced identical output.
|
|
120
|
+
throw refuse("conflict", "that artifact changed while this version was being written");
|
|
121
|
+
return { version: result.version };
|
|
122
|
+
},
|
|
123
|
+
/** A version's row and its content together. The latest when no version is asked for. */
|
|
124
|
+
async read(context, input) {
|
|
125
|
+
const artifact = await deps.artifacts.get({ tenantId: context.tenantId, id: input.id });
|
|
126
|
+
if (artifact === null || artifact.deletedAt !== undefined)
|
|
127
|
+
throw refuse("not_found", "no such artifact");
|
|
128
|
+
await authorise(context, "read", artifact.conversationId, "no such artifact");
|
|
129
|
+
const version = await deps.artifacts.getVersion({
|
|
130
|
+
tenantId: context.tenantId,
|
|
131
|
+
id: input.id,
|
|
132
|
+
...(input.version === undefined ? {} : { version: input.version }),
|
|
133
|
+
});
|
|
134
|
+
if (version === null)
|
|
135
|
+
throw refuse("not_found", "no such version of that artifact");
|
|
136
|
+
const content = await deps.blobs.get({ tenantId: context.tenantId, ref: version.contentRef });
|
|
137
|
+
if (content === null)
|
|
138
|
+
// The row says the content is there and it is not. Reported as what it is rather than as an empty
|
|
139
|
+
// artifact, because an empty artifact is something a caller might reasonably export.
|
|
140
|
+
throw refuse("not_found", "that artifact version's content is missing");
|
|
141
|
+
return { artifact, version, content };
|
|
142
|
+
},
|
|
143
|
+
/** The version history, for a restore or an audit. Metadata only — no content is loaded. */
|
|
144
|
+
async history(context, input) {
|
|
145
|
+
const artifact = await deps.artifacts.get({ tenantId: context.tenantId, id: input.id });
|
|
146
|
+
if (artifact === null || artifact.deletedAt !== undefined)
|
|
147
|
+
throw refuse("not_found", "no such artifact");
|
|
148
|
+
await authorise(context, "read", artifact.conversationId, "no such artifact");
|
|
149
|
+
return deps.artifacts.listVersions({
|
|
150
|
+
tenantId: context.tenantId,
|
|
151
|
+
id: input.id,
|
|
152
|
+
limit: input.limit,
|
|
153
|
+
...(input.cursor === undefined ? {} : { cursor: input.cursor }),
|
|
154
|
+
});
|
|
155
|
+
},
|
|
156
|
+
/**
|
|
157
|
+
* Restore an earlier version by making it the newest one.
|
|
158
|
+
*
|
|
159
|
+
* A *new* version whose content is the old one's, not a pointer moved backwards. Moving a pointer would
|
|
160
|
+
* make the history lie about what happened — and "the version that was current on Tuesday" is exactly the
|
|
161
|
+
* question a shared link asks.
|
|
162
|
+
*/
|
|
163
|
+
async restore(context, input) {
|
|
164
|
+
const artifact = await deps.artifacts.get({ tenantId: context.tenantId, id: input.id });
|
|
165
|
+
if (artifact === null || artifact.deletedAt !== undefined)
|
|
166
|
+
throw refuse("not_found", "no such artifact");
|
|
167
|
+
await authorise(context, "write", artifact.conversationId, "no such artifact");
|
|
168
|
+
const source = await deps.artifacts.getVersion({
|
|
169
|
+
tenantId: context.tenantId,
|
|
170
|
+
id: input.id,
|
|
171
|
+
version: input.version,
|
|
172
|
+
});
|
|
173
|
+
if (source === null)
|
|
174
|
+
throw refuse("not_found", "no such version of that artifact");
|
|
175
|
+
const result = await deps.artifacts.addVersion({
|
|
176
|
+
tenantId: context.tenantId,
|
|
177
|
+
id: input.id,
|
|
178
|
+
expectedLatestVersion: artifact.latestVersion,
|
|
179
|
+
version: {
|
|
180
|
+
id: asId(newVersionId()),
|
|
181
|
+
// The *same* blob reference. Copying the content would double the storage for a byte-identical
|
|
182
|
+
// value, and a blob is immutable so sharing it is safe.
|
|
183
|
+
contentRef: source.contentRef,
|
|
184
|
+
byteSize: source.byteSize,
|
|
185
|
+
...(source.checksum === undefined ? {} : { checksum: source.checksum }),
|
|
186
|
+
provenance: {
|
|
187
|
+
producedBy: input.producedBy ?? "restore",
|
|
188
|
+
// The restore records what it restored *from*, so the history explains itself without a reader
|
|
189
|
+
// having to compare content refs.
|
|
190
|
+
inputs: { restoredFromVersion: input.version },
|
|
191
|
+
...(source.provenance.runId === undefined ? {} : { runId: source.provenance.runId }),
|
|
192
|
+
...(source.provenance.sourceFileIds === undefined
|
|
193
|
+
? {}
|
|
194
|
+
: { sourceFileIds: source.provenance.sourceFileIds }),
|
|
195
|
+
},
|
|
196
|
+
createdBy: context.principalId,
|
|
197
|
+
createdAt: clock(),
|
|
198
|
+
},
|
|
199
|
+
});
|
|
200
|
+
if (!result.added || result.version === undefined)
|
|
201
|
+
throw refuse("conflict", "that artifact changed while the restore was being written");
|
|
202
|
+
return { version: result.version };
|
|
203
|
+
},
|
|
204
|
+
async listForConversation(context, input) {
|
|
205
|
+
// Authorised before the query runs, not filtered after: for an unentitled caller a page count or a
|
|
206
|
+
// timing difference still answers "does this conversation have artifacts".
|
|
207
|
+
await authorise(context, "read", input.conversationId, "no such conversation");
|
|
208
|
+
return deps.artifacts.listByConversation({ tenantId: context.tenantId, ...input });
|
|
209
|
+
},
|
|
210
|
+
async softDelete(context, id) {
|
|
211
|
+
const artifact = await deps.artifacts.get({ tenantId: context.tenantId, id });
|
|
212
|
+
if (artifact === null)
|
|
213
|
+
throw refuse("not_found", "no such artifact");
|
|
214
|
+
await authorise(context, "write", artifact.conversationId, "no such artifact");
|
|
215
|
+
return deps.artifacts.softDelete({ tenantId: context.tenantId, id, at: clock() });
|
|
216
|
+
},
|
|
217
|
+
};
|
|
218
|
+
};
|
|
219
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Authorization port — `docs/11-authorization.md`. **Frozen v1.**
|
|
3
|
+
*
|
|
4
|
+
* A first-class port, not scattered `if` checks. The engine lands in REQ-004; this is the shape
|
|
5
|
+
* every store, tool and retrieval path takes decisions from. Imports only core + tool contracts —
|
|
6
|
+
* never an adapter.
|
|
7
|
+
*/
|
|
8
|
+
import type { ExecutionContext } from "../core/context.js";
|
|
9
|
+
import type { ToolDescriptor } from "../tools/index.js";
|
|
10
|
+
export type ResourceRef = {
|
|
11
|
+
readonly type: string;
|
|
12
|
+
readonly id?: string;
|
|
13
|
+
readonly attributes?: Readonly<Record<string, unknown>>;
|
|
14
|
+
};
|
|
15
|
+
export type Obligation = {
|
|
16
|
+
readonly kind: "requires-approval";
|
|
17
|
+
readonly riskCategory?: string;
|
|
18
|
+
} | {
|
|
19
|
+
readonly kind: "redact";
|
|
20
|
+
readonly fields: readonly string[];
|
|
21
|
+
};
|
|
22
|
+
export type Decision = {
|
|
23
|
+
readonly allow: boolean;
|
|
24
|
+
readonly reason?: string;
|
|
25
|
+
readonly obligations?: readonly Obligation[];
|
|
26
|
+
};
|
|
27
|
+
/** The query-time filter applied *before* search, so retrieval never returns unauthorized rows. */
|
|
28
|
+
export type PermissionScope = {
|
|
29
|
+
readonly tenantId: string;
|
|
30
|
+
readonly roleIds: readonly string[];
|
|
31
|
+
readonly filter?: Readonly<Record<string, unknown>>;
|
|
32
|
+
};
|
|
33
|
+
export interface AuthorizationPolicy {
|
|
34
|
+
/** A single point decision; may carry obligations (e.g. requires-approval, redact). */
|
|
35
|
+
can(context: ExecutionContext, action: string, resource: ResourceRef): Promise<Decision>;
|
|
36
|
+
/** The permission-filtered catalog, computed before discovery. */
|
|
37
|
+
filterTools(context: ExecutionContext, tools: readonly ToolDescriptor[]): Promise<readonly ToolDescriptor[]>;
|
|
38
|
+
/** The scope filter for a resource type, applied before any list/search. */
|
|
39
|
+
scope(context: ExecutionContext, resourceType: string): Promise<PermissionScope>;
|
|
40
|
+
}
|
|
41
|
+
export type Permission = {
|
|
42
|
+
/** e.g. "read" | "execute" | "publish" | "*" */
|
|
43
|
+
readonly action: string;
|
|
44
|
+
/** e.g. "conversation" | "tool" | "*" */
|
|
45
|
+
readonly resourceType: string;
|
|
46
|
+
/** When true, a granted decision carries a requires-approval obligation. */
|
|
47
|
+
readonly requiresApproval?: boolean;
|
|
48
|
+
};
|
|
49
|
+
export type RoleDefinition = {
|
|
50
|
+
readonly roleId: string;
|
|
51
|
+
readonly permissions: readonly Permission[];
|
|
52
|
+
/** Tool names or categories this role may discover and execute. */
|
|
53
|
+
readonly tools?: readonly string[];
|
|
54
|
+
};
|
|
55
|
+
export type AuditEvent = {
|
|
56
|
+
readonly kind: "denied" | "allowed-write";
|
|
57
|
+
readonly tenantId: string;
|
|
58
|
+
readonly principalId: string;
|
|
59
|
+
readonly action: string;
|
|
60
|
+
readonly resource: ResourceRef;
|
|
61
|
+
readonly reason?: string;
|
|
62
|
+
};
|
|
63
|
+
export type AuthorizationConfig = {
|
|
64
|
+
readonly roles: readonly RoleDefinition[];
|
|
65
|
+
/** Sink for denials and allowed external writes (docs/11 auditing). */
|
|
66
|
+
readonly audit?: (event: AuditEvent) => void;
|
|
67
|
+
};
|
|
68
|
+
/** The tenant predicate `scope()` produces — the same rule a Supabase RLS policy enforces. */
|
|
69
|
+
export declare const tenantRlsFilter: (tenantId: string) => Readonly<Record<string, unknown>>;
|
|
70
|
+
export declare const createAuthorizationPolicy: (config: AuthorizationConfig) => AuthorizationPolicy;
|
|
71
|
+
/** Guard for the execution path: throws `forbidden` when the tool may not run. */
|
|
72
|
+
export declare const assertToolAuthorized: (policy: AuthorizationPolicy, context: ExecutionContext, tool: {
|
|
73
|
+
readonly name: string;
|
|
74
|
+
readonly category: string;
|
|
75
|
+
}) => Promise<void>;
|
|
76
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Authorization port — `docs/11-authorization.md`. **Frozen v1.**
|
|
3
|
+
*
|
|
4
|
+
* A first-class port, not scattered `if` checks. The engine lands in REQ-004; this is the shape
|
|
5
|
+
* every store, tool and retrieval path takes decisions from. Imports only core + tool contracts —
|
|
6
|
+
* never an adapter.
|
|
7
|
+
*/
|
|
8
|
+
import { AgentPlatformError } from "../core/errors.js";
|
|
9
|
+
const WRITE_ACTIONS = new Set(["create", "update", "delete", "publish", "send", "execute"]);
|
|
10
|
+
/** The tenant predicate `scope()` produces — the same rule a Supabase RLS policy enforces. */
|
|
11
|
+
export const tenantRlsFilter = (tenantId) => ({
|
|
12
|
+
tenant_id: tenantId,
|
|
13
|
+
});
|
|
14
|
+
export const createAuthorizationPolicy = (config) => {
|
|
15
|
+
const roleMap = new Map(config.roles.map((r) => [r.roleId, r]));
|
|
16
|
+
const rolesOf = (ctx) => ctx.roleIds.map((id) => roleMap.get(id)).filter((r) => r !== undefined);
|
|
17
|
+
const matches = (p, action, type) => (p.action === "*" || p.action === action) && (p.resourceType === "*" || p.resourceType === type);
|
|
18
|
+
return {
|
|
19
|
+
async can(context, action, resource) {
|
|
20
|
+
const audit = (kind, reason) => config.audit?.({ kind, tenantId: context.tenantId, principalId: context.principalId, action, resource, ...(reason ? { reason } : {}) });
|
|
21
|
+
// Cross-tenant guard: a resource carrying another tenant's id is never visible.
|
|
22
|
+
const rt = resource.attributes?.["tenantId"];
|
|
23
|
+
if (typeof rt === "string" && rt !== context.tenantId) {
|
|
24
|
+
audit("denied", "cross-tenant");
|
|
25
|
+
return { allow: false, reason: "cross-tenant" };
|
|
26
|
+
}
|
|
27
|
+
// Tools are governed by the role's allow-list (name or category), so discovery and
|
|
28
|
+
// execution use the exact same rule.
|
|
29
|
+
if (resource.type === "tool") {
|
|
30
|
+
const allowed = new Set(rolesOf(context).flatMap((r) => r.tools ?? []));
|
|
31
|
+
const category = resource.attributes?.["category"];
|
|
32
|
+
const ok = (resource.id !== undefined && allowed.has(resource.id)) ||
|
|
33
|
+
(typeof category === "string" && allowed.has(category));
|
|
34
|
+
if (!ok) {
|
|
35
|
+
audit("denied", "tool not permitted");
|
|
36
|
+
return { allow: false, reason: `tool ${resource.id ?? "?"} not permitted` };
|
|
37
|
+
}
|
|
38
|
+
audit("allowed-write");
|
|
39
|
+
return { allow: true };
|
|
40
|
+
}
|
|
41
|
+
const perm = rolesOf(context).flatMap((r) => r.permissions).find((p) => matches(p, action, resource.type));
|
|
42
|
+
if (!perm) {
|
|
43
|
+
audit("denied", "no permission");
|
|
44
|
+
return { allow: false, reason: `no permission for ${action} on ${resource.type}` };
|
|
45
|
+
}
|
|
46
|
+
if (WRITE_ACTIONS.has(action))
|
|
47
|
+
audit("allowed-write");
|
|
48
|
+
return perm.requiresApproval
|
|
49
|
+
? { allow: true, obligations: [{ kind: "requires-approval" }] }
|
|
50
|
+
: { allow: true };
|
|
51
|
+
},
|
|
52
|
+
async filterTools(context, tools) {
|
|
53
|
+
const allowed = new Set(rolesOf(context).flatMap((r) => r.tools ?? []));
|
|
54
|
+
return tools.filter((t) => allowed.has(t.name) || allowed.has(t.category));
|
|
55
|
+
},
|
|
56
|
+
async scope(context) {
|
|
57
|
+
return { tenantId: context.tenantId, roleIds: context.roleIds, filter: tenantRlsFilter(context.tenantId) };
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
};
|
|
61
|
+
/** Guard for the execution path: throws `forbidden` when the tool may not run. */
|
|
62
|
+
export const assertToolAuthorized = async (policy, context, tool) => {
|
|
63
|
+
const decision = await policy.can(context, "execute", {
|
|
64
|
+
type: "tool",
|
|
65
|
+
id: tool.name,
|
|
66
|
+
attributes: { category: tool.category },
|
|
67
|
+
});
|
|
68
|
+
if (!decision.allow) {
|
|
69
|
+
throw new AgentPlatformError({
|
|
70
|
+
code: "forbidden",
|
|
71
|
+
message: decision.reason ?? `Tool ${tool.name} is not authorized`,
|
|
72
|
+
retryable: false,
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a runtime does, declared — #198.
|
|
3
|
+
*
|
|
4
|
+
* ## Why a declaration exists at all
|
|
5
|
+
*
|
|
6
|
+
* Capabilities were already switchable: `usage?`, `messages?`, `quota?`, `approvals?`, `questions?`,
|
|
7
|
+
* `citations?` are all optional, and a host that wants a lean automation runtime passes fewer of them. The
|
|
8
|
+
* mechanism is right. The ergonomics are not, because **an unwired capability and a deliberately disabled one
|
|
9
|
+
* are indistinguishable**, and that ambiguity is this codebase's most repeated defect: #157, #159, #161, #163,
|
|
10
|
+
* #165 and #185 were each a capability that existed, passed its tests, and was wired to nothing.
|
|
11
|
+
* `check-reachability.mjs` exists because of it — and it can only ever check *this* repository, never a
|
|
12
|
+
* customer's.
|
|
13
|
+
*
|
|
14
|
+
* ## Why not just booleans
|
|
15
|
+
*
|
|
16
|
+
* A boolean cannot carry its dependency. `memory: true` still needs a store, so true-with-nothing-wired is a
|
|
17
|
+
* crash or a silent no-op — the same failure with a friendlier spelling. Twelve booleans is also 4,096
|
|
18
|
+
* combinations, almost none of them ever exercised.
|
|
19
|
+
*
|
|
20
|
+
* ## So: both, cross-checked
|
|
21
|
+
*
|
|
22
|
+
* The host declares, the host wires, and `resolveCapabilities` refuses to return a runtime whose declaration and
|
|
23
|
+
* wiring disagree — **in either direction**. Declared-on-but-unwired is the obvious one. Wired-but-undeclared
|
|
24
|
+
* matters just as much: it means the declaration has drifted into a lie, and the next reader trusts it.
|
|
25
|
+
*
|
|
26
|
+
* One error listing every mismatch, never the first one found. A caller fixing these one restart at a time is
|
|
27
|
+
* why configuration surfaces get abandoned.
|
|
28
|
+
*/
|
|
29
|
+
/**
|
|
30
|
+
* The switchable capabilities.
|
|
31
|
+
*
|
|
32
|
+
* Deliberately **not** including approvals or quota enforcement. An automation with no human is a legitimate
|
|
33
|
+
* case, and a flag that removes the enforcement is not the way to express it — that is an approval *policy*
|
|
34
|
+
* which auto-approves a stated set of effects, and is auditable afterwards where a flag leaves no record. The
|
|
35
|
+
* difference is between "nobody had to approve this" and "we cannot tell whether anyone should have".
|
|
36
|
+
*/
|
|
37
|
+
export declare const CAPABILITIES: readonly ["history", "memory", "compaction", "citations", "questions", "skills", "mcp", "usage"];
|
|
38
|
+
export type Capability = (typeof CAPABILITIES)[number];
|
|
39
|
+
export type CapabilityState = "on" | "off";
|
|
40
|
+
export type CapabilityMap = Readonly<Record<Capability, CapabilityState>>;
|
|
41
|
+
/**
|
|
42
|
+
* What each capability cannot work without, by the name a host wires it under.
|
|
43
|
+
*
|
|
44
|
+
* Names rather than types, because the check is about *presence*: the compiler already proves a supplied store
|
|
45
|
+
* is the right shape, and what it cannot prove is that one was supplied at all.
|
|
46
|
+
*/
|
|
47
|
+
export declare const CAPABILITY_REQUIRES: Readonly<Record<Capability, readonly string[]>>;
|
|
48
|
+
/**
|
|
49
|
+
* Named starting points, so the two common shapes are one line.
|
|
50
|
+
*
|
|
51
|
+
* A profile is **only** a set of defaults and must be expressible by writing the capabilities out — otherwise it
|
|
52
|
+
* is a second configuration language, and the two drift. `profileToMap` and a test hold that.
|
|
53
|
+
*/
|
|
54
|
+
export declare const PROFILES: {
|
|
55
|
+
/** A chat assistant: everything a person interacts with, on. */
|
|
56
|
+
readonly assistant: {
|
|
57
|
+
readonly history: "on";
|
|
58
|
+
readonly memory: "on";
|
|
59
|
+
readonly compaction: "on";
|
|
60
|
+
readonly citations: "on";
|
|
61
|
+
readonly questions: "on";
|
|
62
|
+
readonly skills: "on";
|
|
63
|
+
readonly mcp: "off";
|
|
64
|
+
readonly usage: "on";
|
|
65
|
+
};
|
|
66
|
+
/**
|
|
67
|
+
* A headless automation: no conversation, no person, no recall.
|
|
68
|
+
*
|
|
69
|
+
* `usage` stays on because an automation that runs unattended is precisely the one whose spend nobody is
|
|
70
|
+
* watching. Turning metering off by default there would be the expensive mistake.
|
|
71
|
+
*/
|
|
72
|
+
readonly automation: {
|
|
73
|
+
readonly history: "off";
|
|
74
|
+
readonly memory: "off";
|
|
75
|
+
readonly compaction: "off";
|
|
76
|
+
readonly citations: "off";
|
|
77
|
+
readonly questions: "off";
|
|
78
|
+
readonly skills: "off";
|
|
79
|
+
readonly mcp: "off";
|
|
80
|
+
readonly usage: "on";
|
|
81
|
+
};
|
|
82
|
+
};
|
|
83
|
+
export type ProfileName = keyof typeof PROFILES;
|
|
84
|
+
/** A profile as a plain map, so a caller can read a default before adopting it. */
|
|
85
|
+
export declare const profileToMap: (profile: ProfileName) => CapabilityMap;
|
|
86
|
+
export type ResolveCapabilitiesInput = {
|
|
87
|
+
readonly profile?: ProfileName;
|
|
88
|
+
/** Overrides on top of the profile, or the whole declaration when no profile is named. */
|
|
89
|
+
readonly capabilities?: Partial<CapabilityMap>;
|
|
90
|
+
/**
|
|
91
|
+
* The names the host actually wired, as supplied. A `Set` rather than the objects themselves: this function's
|
|
92
|
+
* job is the *cross-check*, and taking the objects would tempt it into validating their shapes, which the
|
|
93
|
+
* compiler already does better.
|
|
94
|
+
*/
|
|
95
|
+
readonly wired: ReadonlySet<string>;
|
|
96
|
+
};
|
|
97
|
+
/**
|
|
98
|
+
* The effective capability map, or a refusal naming every disagreement.
|
|
99
|
+
*
|
|
100
|
+
* Absent from both profile and overrides means **off**. That direction is deliberate: a capability nobody
|
|
101
|
+
* mentioned is one nobody asked for, and defaulting to on would resurrect the problem this exists to solve — a
|
|
102
|
+
* feature quietly present, wired to nothing, until the day it matters.
|
|
103
|
+
*/
|
|
104
|
+
export declare const resolveCapabilities: (input: ResolveCapabilitiesInput) => CapabilityMap;
|
|
105
|
+
/**
|
|
106
|
+
* ## What is deliberately *not* here yet: a point-of-use gate
|
|
107
|
+
*
|
|
108
|
+
* The obvious companion to `resolveCapabilities` is a `requireCapability(map, cap, forWhat)` that a code path
|
|
109
|
+
* calls to refuse when its capability is off. It was written, tested, and **removed before shipping** — because
|
|
110
|
+
* nothing could call it.
|
|
111
|
+
*
|
|
112
|
+
* No runtime object holds a capability map today: a host wires `ResolverDeps` by hand, and the map produced here
|
|
113
|
+
* is consulted by nobody. So the gate would have been exported, covered by its own tests, and reachable from
|
|
114
|
+
* nothing — which is #157, #159, #161, #163, #165 and #185, in the module whose entire purpose is to stop that.
|
|
115
|
+
* Leaving it in because it "will be used soon" is precisely the reasoning that produced those six.
|
|
116
|
+
*
|
|
117
|
+
* It returns with #196, when the composition root exists and there is something to consult the map. The check
|
|
118
|
+
* that matters until then is the construction-time one above, and that one *is* called.
|
|
119
|
+
*/
|
|
120
|
+
//# sourceMappingURL=index.d.ts.map
|