From 34e278ce0123bfafe77d53c93720e492519fa6f3 Mon Sep 17 00:00:00 2001 From: Alfazal007 Date: Sun, 4 Oct 2026 13:19:04 +0530 Subject: [PATCH] feat(core): add durable operation replay protection --- docs/adr/0028-durable-operation-replay.md | 122 ++++ docs/api/sharedos-contracts.md | 36 + docs/api/sharedos-core.md | 561 +++++++++++++--- docs/api/sharedos-os.md | 86 +-- docs/api/sharedos-runtime.md | 50 +- docs/api/sharedos-testkit.md | 266 +++++--- docs/errors.md | 21 + docs/release-readiness.md | 13 +- docs/security/threat-model.md | 28 +- packages/conformance/package.json | 3 + packages/conformance/src/replay.test.ts | 694 ++++++++++++++++++++ packages/conformance/tsconfig.json | 1 + packages/contracts/src/index.ts | 1 + packages/contracts/src/replay.ts | 36 + packages/core/README.md | 13 + packages/core/src/index.ts | 1 + packages/core/src/kernel.ts | 191 +++++- packages/core/src/message-tool.ts | 5 +- packages/core/src/replay.ts | 174 +++++ packages/core/src/tool-registry.ts | 11 +- packages/os/src/index.ts | 12 +- packages/runtime/src/executor.ts | 64 +- packages/runtime/src/runtime-plugin.test.ts | 2 + packages/runtime/src/runtime.test.ts | 2 + packages/testkit/README.md | 13 + packages/testkit/src/index.ts | 3 + packages/testkit/src/replay.ts | 54 ++ pnpm-lock.yaml | 4 + 28 files changed, 2188 insertions(+), 279 deletions(-) create mode 100644 docs/adr/0028-durable-operation-replay.md create mode 100644 packages/conformance/src/replay.test.ts create mode 100644 packages/contracts/src/replay.ts create mode 100644 packages/core/src/replay.ts create mode 100644 packages/testkit/src/replay.ts diff --git a/docs/adr/0028-durable-operation-replay.md b/docs/adr/0028-durable-operation-replay.md new file mode 100644 index 00000000..63710774 --- /dev/null +++ b/docs/adr/0028-durable-operation-replay.md @@ -0,0 +1,122 @@ +# ADR 0028: Durable operation replay + +Status: Accepted for opt-in deployment; protocol activation requires the protocol-versioning release. + +## Problem + +IDs currently correlate events. Repeating a resource operation ID and its input +calls the provider twice. HTTP delegates to the same paths. Kernel tool calls, +message sends and executor turns have the same gap. Grant-use and audit commits +are separate concerns and are unchanged by this decision. + +## Identity and fingerprint + +The trusted `AccessContext.namespaceId` is the tenant/world boundary. A replay +key is the tuple `(namespaceId, kind, scope, id)`, encoded structurally, never +by delimiter concatenation. Execution, resource and message IDs are unique +within their tenant and kind (`scope = ""`). Tool-call IDs are unique within +an execution; embedded calls outside an execution use their trusted trace ID +as scope. The executor supplies its execution ID, not a model-supplied value. +Hosts must keep namespace IDs stable and never recycle them between tenants. + +SharedOS computes a SHA-256 fingerprint of canonical JSON with object keys +sorted by Unicode code-unit order, array order preserved, and JSON number/string +encoding. It includes actor, authority, owner, purpose, trace identity, namespace +settings, operation target and all semantic input (including metadata). Context +`now` and tool `requestedAt` are observation times and are excluded. Envelope +`createdAt` is retained. An omitted resource owner is normalized to context owner. +Execution fingerprints additionally include the selected runtime manifest and +executor defaults. Inputs are cloned before hashing and dispatch so mutations +cannot change an already claimed operation. Non-JSON inputs fail validation. +Same key with different fingerprint is `replay_conflict`, including different +actors. A replay can return data only to the same trusted identity/context; it +is retrieval of a previously admitted result, not new authority or a new effect. +Changing authority or retrying a denial requires a new operation ID. + +## Atomic claim and states + +A host `ReplayStore` atomically inserts a pending record or returns the existing +record; comparing fingerprints is kernel policy. Exactly one worker owns the +claim. Its opaque token fences every settlement; all records and results are +JSON-safe. Independent kernel instances must share the same durable store. + +- Pending duplicates return `replay_pending` promptly. They do not wait for or + cancel the owner and never enter a provider. +- Completed results, including denied results, are replayed verbatim. Failed + results are also retained and replayed; an error may follow a committed effect. +- Effect cancellation or an exception before a durable result leaves an interrupted + tombstone. Later duplicates return `replay_interrupted`. Cancellation before + claim creates no record. A late provider result cannot reopen a settled claim. + An executor that finishes with a typed cancelled result retains that result; + its interrupted child effects remain blocked. Pre-aborted executions do not claim. +- A process crash leaves pending state. There is no automatic lease expiry or + takeover. A trusted host recovery worker may use the claim token to mark the + abandoned record interrupted after establishing that the owner is stopped. + SharedOS never automatically re-executes it. +- Store errors fail closed with `replay_unavailable` and invoke no effect before + claim. A settlement outage after an effect leaves pending/ambiguous state and + returns `replay_unavailable`; retrying that ID still cannot perform the effect. + +## Retention and recovery + +The store retains identity, fingerprint and terminal state permanently (or for +the lifetime of a tenant whose identifiers will never be reused). Hosts may +expire sensitive result bodies to an `expired` tombstone: duplicates return +`replay_expired`, never execute again. Retained successful/failed results must be +validated by SharedOS before release. Retention clocks, quotas, encryption, +access controls and administrative recovery belong to the host. Recovery can +reconcile a pending effect with external evidence and settle the original token; +it must not clear the record to invite an automatic retry. + +An atomic claim does not atomically commit an external effect and its result. +A crash between them is ambiguous. Exactly-once execution is not promised for +external systems. Hosts should forward stable operation identity to providers +with idempotency support, or reconcile using provider receipts. Arbitrary +external systems may require a human to resolve the ambiguity. + +## Nested operations and transport + +An execution claim encloses the complete turn. Replaying it returns stored events +and result and does not run the plugin or stream new lifecycle events. Tool calls +claim independently in that execution's scope. Runtime refusal before reaching +the kernel is part of the enclosing execution result. Resource calls through +standard OS tool adapters are effects of the claimed tool call: their provider +operation ID is derived with `childOperationId` from the full trusted tool key +and the adapter child slot, so identical call IDs in separate executions cannot +collide at a provider. They do not re-enter the resource kernel gate. Legacy +unprotected adapters and direct handler calls retain their call-ID correlation. +Direct kernel resource calls have their own tenant-scoped resource identity. +The kernel passes an optional trusted replay key as the fourth `ToolHandler.invoke` +argument when replay is enabled. Composite handlers can use `childOperationId` +with distinct child slots. Custom composite handlers must assign stable, distinct child IDs and route +separate effects through the appropriate kernel method; they must not use the +parent ID for multiple children of the same kind. A parent replay never reruns +children. Cycles/self-reentry encounter pending rather than recursively dispatch. + +`messages.request` generates a stable message ID from tenant, trusted trace, +actor, execution scope and call ID. Both direct sends and its already-authorized transport path +claim that message ID. The delivery receipt and tool reply are distinct retained +results. Receiver execution uses a separately assigned execution ID; message +receipt does not deduplicate arbitrary host scheduling. Delivery never forwards +authority. Fan-out needs a distinct message ID per recipient. + +HTTP is an adapter: it keeps the existing typed result/error shapes and delegates +to the same gates. Caller IDs remain untrusted; authenticated context establishes +the tenant. Replay is not freshness: timestamps do not grant authority and no +universal wall-clock freshness window is introduced. + +## Contracts and rollout + +The store port and replay error codes are additive. Installing `replayStore` on +the kernel activates all four gates; executors use the kernel's replay port. +Without it, v1 preserves its existing unprotected behavior for compatibility. +Production hosts MUST supply a durable store; the isolated testkit adapter is +not production storage. This transitional omission is deliberately documented, +not an exactly-once guarantee. The protocol-versioning work on `t3code/wire-protocol-versioning` (ADR 0027, +compatibility epoch 2 and `/v2` routes) establishes the coordinated migration +boundary. It must activate a +mandatory fail-closed store requirement in the next semantic protocol version, +coordinate host construction migration, and document the new refusal codes. +Do not independently change the global version constant in this PR: older v1 +clients still parse these existing result shapes. No grant reservation or audit +transaction changes are included. diff --git a/docs/api/sharedos-contracts.md b/docs/api/sharedos-contracts.md index 11e512ab..47da8a6a 100644 --- a/docs/api/sharedos-contracts.md +++ b/docs/api/sharedos-contracts.md @@ -428,6 +428,22 @@ Defined in: [http.ts:21](https://github.com/Aicoo-Team/SharedOS/blob/main/packag --- +### ReplayKey + +> **ReplayKey** = `z.infer`\<_typeof_ [`ReplayKeySchema`](#replaykeyschema)>\> + +Defined in: [replay.ts:15](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/contracts/src/replay.ts#L15) + +--- + +### ReplayRecord + +> **ReplayRecord** = `z.infer`\<_typeof_ [`ReplayRecordSchema`](#replayrecordschema)>\> + +Defined in: [replay.ts:36](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/contracts/src/replay.ts#L36) + +--- + ### ResourceOperation > **ResourceOperation** = `z.infer`\<_typeof_ [`ResourceOperationSchema`](#resourceoperationschema)>\> @@ -1238,6 +1254,26 @@ Resource operation accepted over HTTP; authority is injected by the host. --- +### ReplayKeySchema + +> `const` **ReplayKeySchema**: `ZodObject`\<\{ `id`: `ZodString`; `kind`: `ZodEnum`\<\[`"execution"`, `"tool"`, `"resource"`, `"message"`\]\>; `namespaceId`: `ZodString`; `scope`: `ZodString`; \}, `"strict"`, `ZodTypeAny`, \{ `id`: `string`; `kind`: `"message"` \| `"resource"` \| `"tool"` \| `"execution"`; `namespaceId`: `string`; `scope`: `string`; \}, \{ `id`: `string`; `kind`: `"message"` \| `"resource"` \| `"tool"` \| `"execution"`; `namespaceId`: `string`; `scope`: `string`; \}\> + +Defined in: [replay.ts:7](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/contracts/src/replay.ts#L7) + +A structural tenant-scoped identity; scope is an execution ID for tool calls. + +--- + +### ReplayRecordSchema + +> `const` **ReplayRecordSchema**: `ZodEffects`\<`ZodObject`\<\{ `fingerprint`: `ZodString`; `key`: `ZodObject`\<\{ `id`: `ZodString`; `kind`: `ZodEnum`\<\[`"execution"`, `"tool"`, `"resource"`, `"message"`\]\>; `namespaceId`: `ZodString`; `scope`: `ZodString`; \}, `"strict"`, `ZodTypeAny`, \{ `id`: `string`; `kind`: `"message"` \| `"resource"` \| `"tool"` \| `"execution"`; `namespaceId`: `string`; `scope`: `string`; \}, \{ `id`: `string`; `kind`: `"message"` \| `"resource"` \| `"tool"` \| `"execution"`; `namespaceId`: `string`; `scope`: `string`; \}\>; `result`: `ZodOptional`\<`ZodType`\<[`JsonValue`](#jsonvalue), `ZodTypeDef`, [`JsonValue`](#jsonvalue)>>>>\>\>; `state`: `ZodEnum`\<\[`"pending"`, `"completed"`, `"failed"`, `"interrupted"`, `"expired"`\]\>; `token`: `ZodString`; \}, `"strict"`, `ZodTypeAny`, \{ `fingerprint`: `string`; `key`: \{ `id`: `string`; `kind`: `"message"` \| `"resource"` \| `"tool"` \| `"execution"`; `namespaceId`: `string`; `scope`: `string`; \}; `result?`: [`JsonValue`](#jsonvalue); `state`: `"failed"` \| `"interrupted"` \| `"pending"` \| `"completed"` \| `"expired"`; `token`: `string`; \}, \{ `fingerprint`: `string`; `key`: \{ `id`: `string`; `kind`: `"message"` \| `"resource"` \| `"tool"` \| `"execution"`; `namespaceId`: `string`; `scope`: `string`; \}; `result?`: [`JsonValue`](#jsonvalue); `state`: `"failed"` \| `"interrupted"` \| `"pending"` \| `"completed"` \| `"expired"`; `token`: `string`; \}\>, \{ `fingerprint`: `string`; `key`: \{ `id`: `string`; `kind`: `"message"` \| `"resource"` \| `"tool"` \| `"execution"`; `namespaceId`: `string`; `scope`: `string`; \}; `result?`: [`JsonValue`](#jsonvalue); `state`: `"failed"` \| `"interrupted"` \| `"pending"` \| `"completed"` \| `"expired"`; `token`: `string`; \}, \{ `fingerprint`: `string`; `key`: \{ `id`: `string`; `kind`: `"message"` \| `"resource"` \| `"tool"` \| `"execution"`; `namespaceId`: `string`; `scope`: `string`; \}; `result?`: [`JsonValue`](#jsonvalue); `state`: `"failed"` \| `"interrupted"` \| `"pending"` \| `"completed"` \| `"expired"`; `token`: `string`; \}\> + +Defined in: [replay.ts:18](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/contracts/src/replay.ts#L18) + +Durable identity survives result-body expiry and never becomes claimable again. + +--- + ### ResourceOperationSchema > `const` **ResourceOperationSchema**: `ZodObject`\<\{ `action`: `ZodString`; `context`: `ZodObject`\<\{ `actor`: `ZodDiscriminatedUnion`\<`"kind"`, \[`ZodObject`\<\{ `kind`: `ZodLiteral`\<`"human"`>\>; `userId`: `ZodString`; \}, `"strict"`, `ZodTypeAny`, \{ `kind`: `"human"`; `userId`: `string`; \}, \{ `kind`: `"human"`; `userId`: `string`; \}\>, `ZodObject`\<\{ `agentId`: `ZodString`; `kind`: `ZodLiteral`\<`"agent"`>\>; \}, `"strict"`, `ZodTypeAny`, \{ `agentId`: `string`; `kind`: `"agent"`; \}, \{ `agentId`: `string`; `kind`: `"agent"`; \}\>, `ZodObject`\<\{ `conversationId`: `ZodString`; `kind`: `ZodLiteral`\<`"group"`>\>; \}, `"strict"`, `ZodTypeAny`, \{ `conversationId`: `string`; `kind`: `"group"`; \}, \{ `conversationId`: `string`; `kind`: `"group"`; \}\>, `ZodObject`\<\{ `kind`: `ZodLiteral`\<`"service"`>\>; `serviceId`: `ZodString`; \}, `"strict"`, `ZodTypeAny`, \{ `kind`: `"service"`; `serviceId`: `string`; \}, \{ `kind`: `"service"`; `serviceId`: `string`; \}\>\]\>; `authority`: `ZodDiscriminatedUnion`\<`"kind"`, \[`ZodObject`\<\{ `kind`: `ZodLiteral`\<`"human"`>\>; `userId`: `ZodString`; \}, `"strict"`, `ZodTypeAny`, \{ `kind`: `"human"`; `userId`: `string`; \}, \{ `kind`: `"human"`; `userId`: `string`; \}\>, `ZodObject`\<\{ `agentId`: `ZodString`; `kind`: `ZodLiteral`\<`"agent"`>\>; \}, `"strict"`, `ZodTypeAny`, \{ `agentId`: `string`; `kind`: `"agent"`; \}, \{ `agentId`: `string`; `kind`: `"agent"`; \}\>, `ZodObject`\<\{ `conversationId`: `ZodString`; `kind`: `ZodLiteral`\<`"group"`>\>; \}, `"strict"`, `ZodTypeAny`, \{ `conversationId`: `string`; `kind`: `"group"`; \}, \{ `conversationId`: `string`; `kind`: `"group"`; \}\>, `ZodObject`\<\{ `kind`: `ZodLiteral`\<`"service"`>\>; `serviceId`: `ZodString`; \}, `"strict"`, `ZodTypeAny`, \{ `kind`: `"service"`; `serviceId`: `string`; \}, \{ `kind`: `"service"`; `serviceId`: `string`; \}\>\]\>; `enabledToolNamespaces`: `ZodEffects`\<`ZodArray`\<`ZodString`, `"many"`>\>, `string`[], `string`[]\>; `namespaceId`: `ZodString`; `now`: `ZodString`; `owner`: `ZodDiscriminatedUnion`\<`"kind"`, \[`ZodObject`\<\{ `kind`: `ZodLiteral`\<`"human"`>\>; `userId`: `ZodString`; \}, `"strict"`, `ZodTypeAny`, \{ `kind`: `"human"`; `userId`: `string`; \}, \{ `kind`: `"human"`; `userId`: `string`; \}\>, `ZodObject`\<\{ `agentId`: `ZodString`; `kind`: `ZodLiteral`\<`"agent"`>\>; \}, `"strict"`, `ZodTypeAny`, \{ `agentId`: `string`; `kind`: `"agent"`; \}, \{ `agentId`: `string`; `kind`: `"agent"`; \}\>, `ZodObject`\<\{ `conversationId`: `ZodString`; `kind`: `ZodLiteral`\<`"group"`>\>; \}, `"strict"`, `ZodTypeAny`, \{ `conversationId`: `string`; `kind`: `"group"`; \}, \{ `conversationId`: `string`; `kind`: `"group"`; \}\>, `ZodObject`\<\{ `kind`: `ZodLiteral`\<`"service"`>\>; `serviceId`: `ZodString`; \}, `"strict"`, `ZodTypeAny`, \{ `kind`: `"service"`; `serviceId`: `string`; \}, \{ `kind`: `"service"`; `serviceId`: `string`; \}\>\]\>; `purpose`: `ZodString`; `traceId`: `ZodString`; \}, `"strict"`, `ZodTypeAny`, \{ `actor`: \{ `kind`: `"human"`; `userId`: `string`; \} \| \{ `agentId`: `string`; `kind`: `"agent"`; \} \| \{ `conversationId`: `string`; `kind`: `"group"`; \} \| \{ `kind`: `"service"`; `serviceId`: `string`; \}; `authority`: \{ `kind`: `"human"`; `userId`: `string`; \} \| \{ `agentId`: `string`; `kind`: `"agent"`; \} \| \{ `conversationId`: `string`; `kind`: `"group"`; \} \| \{ `kind`: `"service"`; `serviceId`: `string`; \}; `enabledToolNamespaces`: `string`[]; `namespaceId`: `string`; `now`: `string`; `owner`: \{ `kind`: `"human"`; `userId`: `string`; \} \| \{ `agentId`: `string`; `kind`: `"agent"`; \} \| \{ `conversationId`: `string`; `kind`: `"group"`; \} \| \{ `kind`: `"service"`; `serviceId`: `string`; \}; `purpose`: `string`; `traceId`: `string`; \}, \{ `actor`: \{ `kind`: `"human"`; `userId`: `string`; \} \| \{ `agentId`: `string`; `kind`: `"agent"`; \} \| \{ `conversationId`: `string`; `kind`: `"group"`; \} \| \{ `kind`: `"service"`; `serviceId`: `string`; \}; `authority`: \{ `kind`: `"human"`; `userId`: `string`; \} \| \{ `agentId`: `string`; `kind`: `"agent"`; \} \| \{ `conversationId`: `string`; `kind`: `"group"`; \} \| \{ `kind`: `"service"`; `serviceId`: `string`; \}; `enabledToolNamespaces`: `string`[]; `namespaceId`: `string`; `now`: `string`; `owner`: \{ `kind`: `"human"`; `userId`: `string`; \} \| \{ `agentId`: `string`; `kind`: `"agent"`; \} \| \{ `conversationId`: `string`; `kind`: `"group"`; \} \| \{ `kind`: `"service"`; `serviceId`: `string`; \}; `purpose`: `string`; `traceId`: `string`; \}\>; `input`: `ZodOptional`\<`ZodType`\<[`JsonValue`](#jsonvalue), `ZodTypeDef`, [`JsonValue`](#jsonvalue)>>\>\>; `metadata`: `ZodOptional`\<`ZodType`\<[`JsonObject`](#jsonobject), `ZodTypeDef`, [`JsonObject`](#jsonobject)>>\>\>; `operationId`: `ZodString`; `resource`: `ZodObject`\<\{ `namespace`: `ZodString`; `owner`: `ZodOptional`\<`ZodDiscriminatedUnion`\<`"kind"`, \[`ZodObject`\<\{ `kind`: `ZodLiteral`\<`"human"`>\>; `userId`: `ZodString`; \}, `"strict"`, `ZodTypeAny`, \{ `kind`: `"human"`; `userId`: `string`; \}, \{ `kind`: `"human"`; `userId`: `string`; \}\>, `ZodObject`\<\{ `agentId`: `ZodString`; `kind`: `ZodLiteral`\<`"agent"`>\>; \}, `"strict"`, `ZodTypeAny`, \{ `agentId`: `string`; `kind`: `"agent"`; \}, \{ `agentId`: `string`; `kind`: `"agent"`; \}\>, `ZodObject`\<\{ `conversationId`: `ZodString`; `kind`: `ZodLiteral`\<`"group"`>\>; \}, `"strict"`, `ZodTypeAny`, \{ `conversationId`: `string`; `kind`: `"group"`; \}, \{ `conversationId`: `string`; `kind`: `"group"`; \}\>, `ZodObject`\<\{ `kind`: `ZodLiteral`\<`"service"`>\>; `serviceId`: `ZodString`; \}, `"strict"`, `ZodTypeAny`, \{ `kind`: `"service"`; `serviceId`: `string`; \}, \{ `kind`: `"service"`; `serviceId`: `string`; \}\>\]\>\>; `path`: `ZodArray`\<`ZodString`, `"many"`>\>; \}, `"strict"`, `ZodTypeAny`, \{ `namespace`: `string`; `owner?`: \{ `kind`: `"human"`; `userId`: `string`; \} \| \{ `agentId`: `string`; `kind`: `"agent"`; \} \| \{ `conversationId`: `string`; `kind`: `"group"`; \} \| \{ `kind`: `"service"`; `serviceId`: `string`; \}; `path`: `string`[]; \}, \{ `namespace`: `string`; `owner?`: \{ `kind`: `"human"`; `userId`: `string`; \} \| \{ `agentId`: `string`; `kind`: `"agent"`; \} \| \{ `conversationId`: `string`; `kind`: `"group"`; \} \| \{ `kind`: `"service"`; `serviceId`: `string`; \}; `path`: `string`[]; \}\>; \}, `"strict"`, `ZodTypeAny`, \{ `action`: `string`; `context`: \{ `actor`: \{ `kind`: `"human"`; `userId`: `string`; \} \| \{ `agentId`: `string`; `kind`: `"agent"`; \} \| \{ `conversationId`: `string`; `kind`: `"group"`; \} \| \{ `kind`: `"service"`; `serviceId`: `string`; \}; `authority`: \{ `kind`: `"human"`; `userId`: `string`; \} \| \{ `agentId`: `string`; `kind`: `"agent"`; \} \| \{ `conversationId`: `string`; `kind`: `"group"`; \} \| \{ `kind`: `"service"`; `serviceId`: `string`; \}; `enabledToolNamespaces`: `string`[]; `namespaceId`: `string`; `now`: `string`; `owner`: \{ `kind`: `"human"`; `userId`: `string`; \} \| \{ `agentId`: `string`; `kind`: `"agent"`; \} \| \{ `conversationId`: `string`; `kind`: `"group"`; \} \| \{ `kind`: `"service"`; `serviceId`: `string`; \}; `purpose`: `string`; `traceId`: `string`; \}; `input?`: [`JsonValue`](#jsonvalue); `metadata?`: [`JsonObject`](#jsonobject); `operationId`: `string`; `resource`: \{ `namespace`: `string`; `owner?`: \{ `kind`: `"human"`; `userId`: `string`; \} \| \{ `agentId`: `string`; `kind`: `"agent"`; \} \| \{ `conversationId`: `string`; `kind`: `"group"`; \} \| \{ `kind`: `"service"`; `serviceId`: `string`; \}; `path`: `string`[]; \}; \}, \{ `action`: `string`; `context`: \{ `actor`: \{ `kind`: `"human"`; `userId`: `string`; \} \| \{ `agentId`: `string`; `kind`: `"agent"`; \} \| \{ `conversationId`: `string`; `kind`: `"group"`; \} \| \{ `kind`: `"service"`; `serviceId`: `string`; \}; `authority`: \{ `kind`: `"human"`; `userId`: `string`; \} \| \{ `agentId`: `string`; `kind`: `"agent"`; \} \| \{ `conversationId`: `string`; `kind`: `"group"`; \} \| \{ `kind`: `"service"`; `serviceId`: `string`; \}; `enabledToolNamespaces`: `string`[]; `namespaceId`: `string`; `now`: `string`; `owner`: \{ `kind`: `"human"`; `userId`: `string`; \} \| \{ `agentId`: `string`; `kind`: `"agent"`; \} \| \{ `conversationId`: `string`; `kind`: `"group"`; \} \| \{ `kind`: `"service"`; `serviceId`: `string`; \}; `purpose`: `string`; `traceId`: `string`; \}; `input?`: [`JsonValue`](#jsonvalue); `metadata?`: [`JsonObject`](#jsonobject); `operationId`: `string`; `resource`: \{ `namespace`: `string`; `owner?`: \{ `kind`: `"human"`; `userId`: `string`; \} \| \{ `agentId`: `string`; `kind`: `"agent"`; \} \| \{ `conversationId`: `string`; `kind`: `"group"`; \} \| \{ `kind`: `"service"`; `serviceId`: `string`; \}; `path`: `string`[]; \}; \}\> diff --git a/docs/api/sharedos-core.md b/docs/api/sharedos-core.md index f44a1039..5eb204fa 100644 --- a/docs/api/sharedos-core.md +++ b/docs/api/sharedos-core.md @@ -23,6 +23,19 @@ atomic, durable namespace updates while keeping its database and product policy. SharedOS is currently a `1.0.0` preview. +## Replay storage + +`SharedOSKernelOptions.replayStore` enables the kernel-owned replay state machine +for execution, tool, resource and message IDs. Hosts implement `ReplayStore` with +atomic durable claims and fenced settlement. Keep identity tombstones after +result expiry; never reclaim pending operations solely because a timeout passed. +See [ADR 0028](https://github.com/systemind-team/SharedOS/blob/main/docs/adr/0028-durable-operation-replay.md). + +`InMemoryReplayStore` is exported by `@aicoo/sharedos-testkit` for isolated tests. +It is not durable or suitable for production. Protocol v1 without a configured +store preserves legacy unprotected behavior. External effects still require +provider idempotency or reconciliation for crash ambiguity. + ## Classes ### AuditUnavailableError @@ -985,6 +998,205 @@ Defined in: [packages/core/src/message-service.ts:58](https://github.com/Aicoo-T --- +### ReplayError + +Defined in: [packages/core/src/replay.ts:34](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/replay.ts#L34) + +#### Extends + +- `Error` + +#### Constructors + +##### Constructor + +> **new ReplayError**(`code`): [`ReplayError`](#replayerror) + +Defined in: [packages/core/src/replay.ts:35](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/replay.ts#L35) + +###### Parameters + +| Parameter | Type | +| --------- | ------------------------------------------------------------------------------------------------------------------- | +| `code` | `"replay_conflict"` \| `"replay_pending"` \| `"replay_interrupted"` \| `"replay_expired"` \| `"replay_unavailable"` | + +###### Returns + +[`ReplayError`](#replayerror) + +###### Overrides + +`Error.constructor` + +#### Properties + +| Property | Modifier | Type | Description | Inherited from | Defined in | +| --------------------------------------------------------- | ---------- | ------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------- | ------------------------------------------------------------------------------------------------------------------ | +| `cause?` | `public` | `unknown` | - | `Error.cause` | node\_modules/.pnpm/typescript@5.9.3/node\_modules/typescript/lib/lib.es2022.error.d.ts:26 | +| `code` | `readonly` | `"replay_conflict"` \| `"replay_pending"` \| `"replay_interrupted"` \| `"replay_expired"` \| `"replay_unavailable"` | - | - | [packages/core/src/replay.ts:36](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/replay.ts#L36) | +| `message` | `public` | `string` | - | `Error.message` | node\_modules/.pnpm/typescript@5.9.3/node\_modules/typescript/lib/lib.es5.d.ts:1077 | +| `name` | `public` | `string` | - | `Error.name` | node\_modules/.pnpm/typescript@5.9.3/node\_modules/typescript/lib/lib.es5.d.ts:1076 | +| `stack?` | `public` | `string` | - | `Error.stack` | node\_modules/.pnpm/typescript@5.9.3/node\_modules/typescript/lib/lib.es5.d.ts:1078 | +| `stackTraceLimit` | `static` | `number` | The `Error.stackTraceLimit` property specifies the number of stack frames collected by a stack trace (whether generated by `new Error().stack` or `Error.captureStackTrace(obj)`). The default value is `10` but may be set to any valid JavaScript number. Changes will affect any stack trace captured _after_ the value has been changed. If set to a non-number value, or set to a negative number, stack traces will not capture any frames. | `Error.stackTraceLimit` | node\_modules/.pnpm/@types+node@22.20.1/node\_modules/@types/node/globals.d.ts:68 | + +#### Methods + +##### captureStackTrace() + +> `static` **captureStackTrace**(`targetObject`, `constructorOpt?`): `void` + +Defined in: node\_modules/.pnpm/@types+node@22.20.1/node\_modules/@types/node/globals.d.ts:52 + +Creates a `.stack` property on `targetObject`, which when accessed returns +a string representing the location in the code at which +`Error.captureStackTrace()` was called. + +```js +const myObject = {}; +Error.captureStackTrace(myObject); +myObject.stack; // Similar to `new Error().stack` +``` + +The first line of the trace will be prefixed with +`${myObject.name}: ${myObject.message}`. + +The optional `constructorOpt` argument accepts a function. If given, all frames +above `constructorOpt`, including `constructorOpt`, will be omitted from the +generated stack trace. + +The `constructorOpt` argument is useful for hiding implementation +details of error generation from the user. For instance: + +```js +function a() { + b(); +} + +function b() { + c(); +} + +function c() { + // Create an error without stack trace to avoid calculating the stack trace twice. + const { stackTraceLimit } = Error; + Error.stackTraceLimit = 0; + const error = new Error(); + Error.stackTraceLimit = stackTraceLimit; + + // Capture the stack trace above function b + Error.captureStackTrace(error, b); // Neither function c, nor b is included in the stack trace + throw error; +} + +a(); +``` + +###### Parameters + +| Parameter | Type | +| ----------------- | ---------- | +| `targetObject` | `object` | +| `constructorOpt?` | `Function` | + +###### Returns + +`void` + +###### Inherited from + +`Error.captureStackTrace` + +##### prepareStackTrace() + +> `static` **prepareStackTrace**(`err`, `stackTraces`): `any` + +Defined in: node\_modules/.pnpm/@types+node@22.20.1/node\_modules/@types/node/globals.d.ts:56 + +###### Parameters + +| Parameter | Type | +| ------------- | ------------ | +| `err` | `Error` | +| `stackTraces` | `CallSite`[] | + +###### Returns + +`any` + +###### See + +https://v8.dev/docs/stack-trace-api#customizing-stack-traces + +###### Inherited from + +`Error.prepareStackTrace` + +--- + +### ReplayProtection + +Defined in: [packages/core/src/replay.ts:90](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/replay.ts#L90) + +Kernel-owned state machine. An omitted store retains legacy v1 behavior (ADR 0028). + +#### Constructors + +##### Constructor + +> **new ReplayProtection**(`store?`): [`ReplayProtection`](#replayprotection) + +Defined in: [packages/core/src/replay.ts:91](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/replay.ts#L91) + +###### Parameters + +| Parameter | Type | +| --------- | ----------------------------- | +| `store?` | [`ReplayStore`](#replaystore) | + +###### Returns + +[`ReplayProtection`](#replayprotection) + +#### Properties + +| Property | Modifier | Type | Defined in | +| ------------------------------------ | ---------- | ----------------------------- | ------------------------------------------------------------------------------------------------------------------ | +| `store?` | `readonly` | [`ReplayStore`](#replaystore) | [packages/core/src/replay.ts:91](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/replay.ts#L91) | + +#### Methods + +##### run() + +> **run**\<`Result`>>\>(`key`, `input`, `invoke`, `accept`, `signal?`): `Promise`\<`Result`>>\> + +Defined in: [packages/core/src/replay.ts:93](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/replay.ts#L93) + +###### Type Parameters + +| Type Parameter | +| -------------- | +| `Result` | + +###### Parameters + +| Parameter | Type | +| ----------------- | ----------------------------------------------------------------------------------------------------------------------------------- | +| `key` | \{ `id`: `string`; `kind`: `"tool"` \| `"resource"` \| `"message"` \| `"execution"`; `namespaceId`: `string`; `scope`: `string`; \} | +| `key.id` | `string` | +| `key.kind` | `"tool"` \| `"resource"` \| `"message"` \| `"execution"` | +| `key.namespaceId` | `string` | +| `key.scope` | `string` | +| `input?` | `unknown` | +| `invoke?` | () => `Promise`\<`Result`\> | +| `accept?` | (`value`) => `Result` | +| `signal?` | `AbortSignal` | + +###### Returns + +`Promise`\<`Result`\> + +--- + ### ResourceProviderRegistry Defined in: [packages/core/src/resource-registry.ts:23](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/resource-registry.ts#L23) @@ -1063,7 +1275,7 @@ Defined in: [packages/core/src/resource-registry.ts:26](https://github.com/Aicoo ### SharedOSKernel -Defined in: [packages/core/src/kernel.ts:335](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/kernel.ts#L335) +Defined in: [packages/core/src/kernel.ts:347](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/kernel.ts#L347) Host-neutral facade for every permission-controlled SharedOS operation. AccessContext is a trusted host-created boundary; never construct it from an @@ -1075,7 +1287,7 @@ unverified request body. > **new SharedOSKernel**(`options`): [`SharedOSKernel`](#sharedoskernel) -Defined in: [packages/core/src/kernel.ts:368](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/kernel.ts#L368) +Defined in: [packages/core/src/kernel.ts:382](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/kernel.ts#L382) ###### Parameters @@ -1087,13 +1299,19 @@ Defined in: [packages/core/src/kernel.ts:368](https://github.com/Aicoo-Team/Shar [`SharedOSKernel`](#sharedoskernel) +#### Properties + +| Property | Modifier | Type | Defined in | +| --------------------------------------------------------- | ---------- | --------------------------------------- | -------------------------------------------------------------------------------------------------------------------- | +| `replayProtection` | `readonly` | [`ReplayProtection`](#replayprotection) | [packages/core/src/kernel.ts:348](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/kernel.ts#L348) | + #### Methods ##### admitTurn() > **admitTurn**(`context`, `agent`, `options?`): `Promise`\<\{ `allowed`: `boolean`; `matchedGrantId?`: `string`; `metadata?`: [`JsonObject`](sharedos-contracts.md#jsonobject); `reasonCode`: `string`; `requiredAuthority?`: \{ `capabilities`: `object`[]; `constraints?`: \{ `delegationDepth?`: `number`; `expiresAt?`: `string`; `maxUses?`: `number`; `notBefore?`: `string`; `purposes?`: `string`[]; \}; `id`: `string`; `metadata?`: [`JsonObject`](sharedos-contracts.md#jsonobject); `namespaceId`: `string`; `owner`: \{ `kind`: `"human"`; `userId`: `string`; \} \| \{ `agentId`: `string`; `kind`: `"agent"`; \} \| \{ `conversationId`: `string`; `kind`: `"group"`; \} \| \{ `kind`: `"service"`; `serviceId`: `string`; \}; `purpose`: `string`; `requestedAt`: `string`; `requester`: \{ `kind`: `"human"`; `userId`: `string`; \} \| \{ `agentId`: `string`; `kind`: `"agent"`; \} \| \{ `conversationId`: `string`; `kind`: `"group"`; \} \| \{ `kind`: `"service"`; `serviceId`: `string`; \}; \}; \}\> -Defined in: [packages/core/src/kernel.ts:483](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/kernel.ts#L483) +Defined in: [packages/core/src/kernel.ts:504](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/kernel.ts#L504) Consume permission to invoke exactly one target agent turn. @@ -1121,7 +1339,7 @@ Consume permission to invoke exactly one target agent turn. > **authorize**(`context`, `request`, `options?`): `Promise`\<\{ `allowed`: `boolean`; `matchedGrantId?`: `string`; `metadata?`: [`JsonObject`](sharedos-contracts.md#jsonobject); `reasonCode`: `string`; `requiredAuthority?`: \{ `capabilities`: `object`[]; `constraints?`: \{ `delegationDepth?`: `number`; `expiresAt?`: `string`; `maxUses?`: `number`; `notBefore?`: `string`; `purposes?`: `string`[]; \}; `id`: `string`; `metadata?`: [`JsonObject`](sharedos-contracts.md#jsonobject); `namespaceId`: `string`; `owner`: \{ `kind`: `"human"`; `userId`: `string`; \} \| \{ `agentId`: `string`; `kind`: `"agent"`; \} \| \{ `conversationId`: `string`; `kind`: `"group"`; \} \| \{ `kind`: `"service"`; `serviceId`: `string`; \}; `purpose`: `string`; `requestedAt`: `string`; `requester`: \{ `kind`: `"human"`; `userId`: `string`; \} \| \{ `agentId`: `string`; `kind`: `"agent"`; \} \| \{ `conversationId`: `string`; `kind`: `"group"`; \} \| \{ `kind`: `"service"`; `serviceId`: `string`; \}; \}; \}\> -Defined in: [packages/core/src/kernel.ts:467](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/kernel.ts#L467) +Defined in: [packages/core/src/kernel.ts:488](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/kernel.ts#L488) ###### Parameters @@ -1152,7 +1370,7 @@ Defined in: [packages/core/src/kernel.ts:467](https://github.com/Aicoo-Team/Shar > **invokeResource**(`context`, `request`, `options?`): `Promise`\<\{ `completedAt`: `string`; `metadata?`: [`JsonObject`](sharedos-contracts.md#jsonobject); `operationId`: `string`; `output`: [`JsonValue`](sharedos-contracts.md#jsonvalue); `status`: `"succeeded"`; \} \| \{ `completedAt`: `string`; `error`: \{ `code`: `string`; `details?`: [`JsonObject`](sharedos-contracts.md#jsonobject); `message`: `string`; `retryable?`: `boolean`; \}; `metadata?`: [`JsonObject`](sharedos-contracts.md#jsonobject); `operationId`: `string`; `status`: `"denied"`; \} \| \{ `completedAt`: `string`; `error`: \{ `code`: `string`; `details?`: [`JsonObject`](sharedos-contracts.md#jsonobject); `message`: `string`; `retryable?`: `boolean`; \}; `metadata?`: [`JsonObject`](sharedos-contracts.md#jsonobject); `operationId`: `string`; `status`: `"failed"`; \}\> -Defined in: [packages/core/src/kernel.ts:1336](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/kernel.ts#L1336) +Defined in: [packages/core/src/kernel.ts:1422](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/kernel.ts#L1422) ###### Parameters @@ -1178,7 +1396,7 @@ Defined in: [packages/core/src/kernel.ts:1336](https://github.com/Aicoo-Team/Sha > **invokeTool**(`context`, `call`, `options?`): `Promise`\<\{ `callId`: `string`; `completedAt`: `string`; `metadata?`: [`JsonObject`](sharedos-contracts.md#jsonobject); `output`: [`JsonValue`](sharedos-contracts.md#jsonvalue); `status`: `"succeeded"`; `tool`: `string`; \} \| \{ `callId`: `string`; `completedAt`: `string`; `error`: \{ `code`: `string`; `details?`: [`JsonObject`](sharedos-contracts.md#jsonobject); `message`: `string`; `retryable?`: `boolean`; \}; `metadata?`: [`JsonObject`](sharedos-contracts.md#jsonobject); `status`: `"denied"`; `tool`: `string`; \} \| \{ `callId`: `string`; `completedAt`: `string`; `error`: \{ `code`: `string`; `details?`: [`JsonObject`](sharedos-contracts.md#jsonobject); `message`: `string`; `retryable?`: `boolean`; \}; `metadata?`: [`JsonObject`](sharedos-contracts.md#jsonobject); `status`: `"failed"`; `tool`: `string`; \}\> -Defined in: [packages/core/src/kernel.ts:1011](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/kernel.ts#L1011) +Defined in: [packages/core/src/kernel.ts:1032](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/kernel.ts#L1032) Re-authorize and dispatch one tool call. @@ -1217,7 +1435,7 @@ SharedOS. Both spans exist or neither does. > **listPublishedTools**(`context`, `options`): `Promise`\<\{ `catalogHash`: `string`; `executionId`: `string`; `tools`: `object`[]; `version`: `"1"`; \}\> -Defined in: [packages/core/src/kernel.ts:923](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/kernel.ts#L923) +Defined in: [packages/core/src/kernel.ts:944](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/kernel.ts#L944) The effective catalogue as an external harness receives it. @@ -1256,7 +1474,7 @@ harness would have to interpret. > **listToolNamespaces**(`context`, `options?`): `Promise`\<\{ `namespaces`: `object`[]; `summary`: \{ `disabled`: `number`; `enabled`: `number`; `total`: `number`; \}; \}\> -Defined in: [packages/core/src/kernel.ts:931](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/kernel.ts#L931) +Defined in: [packages/core/src/kernel.ts:952](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/kernel.ts#L952) ###### Parameters @@ -1281,7 +1499,7 @@ Defined in: [packages/core/src/kernel.ts:931](https://github.com/Aicoo-Team/Shar > **listTools**(`context`, `options?`): `Promise`\ -Defined in: [packages/core/src/kernel.ts:816](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/kernel.ts#L816) +Defined in: [packages/core/src/kernel.ts:837](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/kernel.ts#L837) ###### Parameters @@ -1306,7 +1524,7 @@ Defined in: [packages/core/src/kernel.ts:816](https://github.com/Aicoo-Team/Shar > **openTurnAuthority**(`context`, `options?`): `Promise`\<[`TurnAuthorityScope`](#turnauthorityscope)>\> -Defined in: [packages/core/src/kernel.ts:437](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/kernel.ts#L437) +Defined in: [packages/core/src/kernel.ts:458](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/kernel.ts#L458) Resolve the authority one turn will be decided against, and hold it. @@ -1347,7 +1565,7 @@ lease resolves its own authority, which is a turn of one operation. > **reach**(`context`, `options?`): `Promise`\<\{ `reach`: `object`[]; `status`: `"computed"`; \} \| \{ `reasonCode`: `"authority_unavailable"` \| `"usage_store_unavailable"`; `status`: `"unavailable"`; \}\> -Defined in: [packages/core/src/kernel.ts:806](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/kernel.ts#L806) +Defined in: [packages/core/src/kernel.ts:827](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/kernel.ts#L827) Where this actor may operate, with the authority stripped out. @@ -1399,7 +1617,7 @@ recorded by the envelope as the turn's terminal. > **readAgentCard**(`context`, `subject`, `options?`): `Promise`\<[`AgentCardRead`](#agentcardread)>\> -Defined in: [packages/core/src/kernel.ts:679](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/kernel.ts#L679) +Defined in: [packages/core/src/kernel.ts:700](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/kernel.ts#L700) The kernel's description of one agent: identity, computed reach, nothing else. @@ -1458,7 +1676,7 @@ field is useful but whether it is authority. > **recordEscalation**(`context`, `reason`, `options?`): `Promise`\<\{ `reason`: `string`; `requestedAt`: `string`; `requestedAuthority?`: \{ `capabilities`: `object`[]; `constraints?`: \{ `delegationDepth?`: `number`; `expiresAt?`: `string`; `maxUses?`: `number`; `notBefore?`: `string`; `purposes?`: `string`[]; \}; `id`: `string`; `metadata?`: [`JsonObject`](sharedos-contracts.md#jsonobject); `namespaceId`: `string`; `owner`: \{ `kind`: `"human"`; `userId`: `string`; \} \| \{ `agentId`: `string`; `kind`: `"agent"`; \} \| \{ `conversationId`: `string`; `kind`: `"group"`; \} \| \{ `kind`: `"service"`; `serviceId`: `string`; \}; `purpose`: `string`; `requestedAt`: `string`; `requester`: \{ `kind`: `"human"`; `userId`: `string`; \} \| \{ `agentId`: `string`; `kind`: `"agent"`; \} \| \{ `conversationId`: `string`; `kind`: `"group"`; \} \| \{ `kind`: `"service"`; `serviceId`: `string`; \}; \}; `reviewer`: \{ `kind`: `"human"`; `userId`: `string`; \} \| \{ `agentId`: `string`; `kind`: `"agent"`; \} \| \{ `conversationId`: `string`; `kind`: `"group"`; \} \| \{ `kind`: `"service"`; `serviceId`: `string`; \}; `status`: `"pending"`; \}\> -Defined in: [packages/core/src/kernel.ts:519](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/kernel.ts#L519) +Defined in: [packages/core/src/kernel.ts:540](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/kernel.ts#L540) Record that a turn stopped and asked a human to decide. @@ -1496,7 +1714,7 @@ deliberately no path from here back into the running turn. > **recordRefusedCall**(`context`, `call`, `options?`): `Promise`\<`void`>\> -Defined in: [packages/core/src/kernel.ts:628](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/kernel.ts#L628) +Defined in: [packages/core/src/kernel.ts:649](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/kernel.ts#L649) Record a tool call the envelope refused before the kernel was asked. @@ -1533,7 +1751,7 @@ that stops being inferable the moment this method exists (ADR 0023). > **recordTurnEnd**(`context`, `turn`, `options?`): `Promise`\<`void`>\> -Defined in: [packages/core/src/kernel.ts:595](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/kernel.ts#L595) +Defined in: [packages/core/src/kernel.ts:616](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/kernel.ts#L616) Record how a turn ended, from the boundary that ended it. @@ -1580,7 +1798,7 @@ separates a deadline from a defect. > **registerResourceProvider**(`provider`): `void` -Defined in: [packages/core/src/kernel.ts:401](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/kernel.ts#L401) +Defined in: [packages/core/src/kernel.ts:422](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/kernel.ts#L422) ###### Parameters @@ -1596,7 +1814,7 @@ Defined in: [packages/core/src/kernel.ts:401](https://github.com/Aicoo-Team/Shar > **registerTool**(`handler`): `void` -Defined in: [packages/core/src/kernel.ts:405](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/kernel.ts#L405) +Defined in: [packages/core/src/kernel.ts:426](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/kernel.ts#L426) ###### Parameters @@ -1612,7 +1830,7 @@ Defined in: [packages/core/src/kernel.ts:405](https://github.com/Aicoo-Team/Shar > **registerToolProvider**(`provider`): `void` -Defined in: [packages/core/src/kernel.ts:409](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/kernel.ts#L409) +Defined in: [packages/core/src/kernel.ts:430](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/kernel.ts#L430) ###### Parameters @@ -1628,7 +1846,7 @@ Defined in: [packages/core/src/kernel.ts:409](https://github.com/Aicoo-Team/Shar > **sendMessage**(`context`, `envelope`, `options?`): `Promise`\<\{ `messageId`: `string`; `metadata?`: [`JsonObject`](sharedos-contracts.md#jsonobject); `status`: `"accepted"`; `timestamp`: `string`; \} \| \{ `messageId`: `string`; `metadata?`: [`JsonObject`](sharedos-contracts.md#jsonobject); `status`: `"delivered"`; `timestamp`: `string`; \} \| \{ `error`: \{ `code`: `string`; `details?`: [`JsonObject`](sharedos-contracts.md#jsonobject); `message`: `string`; `retryable?`: `boolean`; \}; `messageId`: `string`; `metadata?`: [`JsonObject`](sharedos-contracts.md#jsonobject); `status`: `"denied"`; `timestamp`: `string`; \} \| \{ `error`: \{ `code`: `string`; `details?`: [`JsonObject`](sharedos-contracts.md#jsonobject); `message`: `string`; `retryable?`: `boolean`; \}; `messageId`: `string`; `metadata?`: [`JsonObject`](sharedos-contracts.md#jsonobject); `status`: `"failed"`; `timestamp`: `string`; \}\> -Defined in: [packages/core/src/kernel.ts:1619](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/kernel.ts#L1619) +Defined in: [packages/core/src/kernel.ts:1739](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/kernel.ts#L1739) ###### Parameters @@ -1667,7 +1885,7 @@ Defined in: [packages/core/src/kernel.ts:1619](https://github.com/Aicoo-Team/Sha > **updateToolNamespaces**(`context`, `update`, `options?`): `Promise`\<\{ `namespaces`: `object`[]; `summary`: \{ `disabled`: `number`; `enabled`: `number`; `total`: `number`; \}; \}\> -Defined in: [packages/core/src/kernel.ts:954](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/kernel.ts#L954) +Defined in: [packages/core/src/kernel.ts:975](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/kernel.ts#L975) ###### Parameters @@ -1695,7 +1913,7 @@ Defined in: [packages/core/src/kernel.ts:954](https://github.com/Aicoo-Team/Shar ### ToolRegistry -Defined in: [packages/core/src/tool-registry.ts:43](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/tool-registry.ts#L43) +Defined in: [packages/core/src/tool-registry.ts:50](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/tool-registry.ts#L50) #### Constructors @@ -1713,7 +1931,7 @@ Defined in: [packages/core/src/tool-registry.ts:43](https://github.com/Aicoo-Tea > **copy**(): [`ToolRegistry`](#toolregistry) -Defined in: [packages/core/src/tool-registry.ts:117](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/tool-registry.ts#L117) +Defined in: [packages/core/src/tool-registry.ts:124](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/tool-registry.ts#L124) A registry holding the same registrations as this one. @@ -1738,7 +1956,7 @@ context-supplied tools beside it. > **definitions**(): readonly `object`[] -Defined in: [packages/core/src/tool-registry.ts:133](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/tool-registry.ts#L133) +Defined in: [packages/core/src/tool-registry.ts:140](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/tool-registry.ts#L140) ###### Returns @@ -1748,7 +1966,7 @@ readonly `object`[] > **get**(`name`): [`ToolHandler`](#toolhandler) \| `undefined` -Defined in: [packages/core/src/tool-registry.ts:125](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/tool-registry.ts#L125) +Defined in: [packages/core/src/tool-registry.ts:132](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/tool-registry.ts#L132) ###### Parameters @@ -1764,7 +1982,7 @@ Defined in: [packages/core/src/tool-registry.ts:125](https://github.com/Aicoo-Te > **handlers**(): readonly [`ToolHandler`](#toolhandler)[] -Defined in: [packages/core/src/tool-registry.ts:139](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/tool-registry.ts#L139) +Defined in: [packages/core/src/tool-registry.ts:146](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/tool-registry.ts#L146) ###### Returns @@ -1774,7 +1992,7 @@ readonly [`ToolHandler`](#toolhandler)[] > **has**(`name`): `boolean` -Defined in: [packages/core/src/tool-registry.ts:129](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/tool-registry.ts#L129) +Defined in: [packages/core/src/tool-registry.ts:136](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/tool-registry.ts#L136) ###### Parameters @@ -1790,7 +2008,7 @@ Defined in: [packages/core/src/tool-registry.ts:129](https://github.com/Aicoo-Te > **namespaceCatalog**(`enabledToolNamespaces`): `object` -Defined in: [packages/core/src/tool-registry.ts:145](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/tool-registry.ts#L145) +Defined in: [packages/core/src/tool-registry.ts:152](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/tool-registry.ts#L152) ###### Parameters @@ -1826,7 +2044,7 @@ Defined in: [packages/core/src/tool-registry.ts:145](https://github.com/Aicoo-Te > **register**(`handler`): `void` -Defined in: [packages/core/src/tool-registry.ts:46](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/tool-registry.ts#L46) +Defined in: [packages/core/src/tool-registry.ts:53](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/tool-registry.ts#L53) ###### Parameters @@ -1898,7 +2116,7 @@ Defined in: [packages/core/src/authority.ts:226](https://github.com/Aicoo-Team/S ### AgentCardReadOptions -Defined in: [packages/core/src/kernel.ts:264](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/kernel.ts#L264) +Defined in: [packages/core/src/kernel.ts:276](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/kernel.ts#L276) #### Extends @@ -1906,10 +2124,11 @@ Defined in: [packages/core/src/kernel.ts:264](https://github.com/Aicoo-Team/Shar #### Properties -| Property | Modifier | Type | Description | Inherited from | Defined in | -| -------------------------------------- | ---------- | ------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------- | -| `signal?` | `readonly` | `AbortSignal` | - | [`KernelOperationOptions`](#kerneloperationoptions).[`signal`](#property-signal-2) | [packages/core/src/kernel.ts:189](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/kernel.ts#L189) | -| `view?` | `readonly` | `"reach"` \| `"identity"` \| `"namespaces"` | The view to serve, defaulting to `reach`. `reach` is the default because a directory without reach is not the feature: an agent asking about a colleague's agent is asking what it can be asked for. A reader that holds only a narrower view is refused and told which views it may still ask for, rather than being quietly served a different card than the one it asked for. | - | [packages/core/src/kernel.ts:274](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/kernel.ts#L274) | +| Property | Modifier | Type | Description | Inherited from | Defined in | +| ------------------------------------------------ | ---------- | ------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------- | +| `executionId?` | `readonly` | `string` | Trusted executor scope for tool-call IDs; standalone calls use context.traceId. | [`KernelOperationOptions`](#kerneloperationoptions).[`executionId`](#property-executionid-2) | [packages/core/src/kernel.ts:200](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/kernel.ts#L200) | +| `signal?` | `readonly` | `AbortSignal` | - | [`KernelOperationOptions`](#kerneloperationoptions).[`signal`](#property-signal-2) | [packages/core/src/kernel.ts:201](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/kernel.ts#L201) | +| `view?` | `readonly` | `"reach"` \| `"identity"` \| `"namespaces"` | The view to serve, defaulting to `reach`. `reach` is the default because a directory without reach is not the feature: an agent asking about a colleague's agent is asking what it can be asked for. A reader that holds only a narrower view is refused and told which views it may still ask for, rather than being quietly served a different card than the one it asked for. | - | [packages/core/src/kernel.ts:286](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/kernel.ts#L286) | --- @@ -2249,7 +2468,7 @@ Defined in: [packages/core/src/authorization.ts:118](https://github.com/Aicoo-Te ### ContextToolProvider -Defined in: [packages/core/src/tool-registry.ts:38](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/tool-registry.ts#L38) +Defined in: [packages/core/src/tool-registry.ts:45](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/tool-registry.ts#L45) Supplies tools for exactly one trusted access context. @@ -2268,7 +2487,7 @@ namespace check still runs per operation over it. | Property | Modifier | Type | Defined in | | ----------------------------- | ---------- | -------- | -------------------------------------------------------------------------------------------------------------------------------- | -| `id` | `readonly` | `string` | [packages/core/src/tool-registry.ts:39](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/tool-registry.ts#L39) | +| `id` | `readonly` | `string` | [packages/core/src/tool-registry.ts:46](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/tool-registry.ts#L46) | #### Methods @@ -2276,7 +2495,7 @@ namespace check still runs per operation over it. > **listTools**(`context`, `signal`): `Promise`\ -Defined in: [packages/core/src/tool-registry.ts:40](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/tool-registry.ts#L40) +Defined in: [packages/core/src/tool-registry.ts:47](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/tool-registry.ts#L47) ###### Parameters @@ -2392,7 +2611,7 @@ move and which do not, and ADR 0016 for why. ### EscalationOptions -Defined in: [packages/core/src/kernel.ts:229](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/kernel.ts#L229) +Defined in: [packages/core/src/kernel.ts:241](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/kernel.ts#L241) #### Extends @@ -2400,11 +2619,11 @@ Defined in: [packages/core/src/kernel.ts:229](https://github.com/Aicoo-Team/Shar #### Properties -| Property | Modifier | Type | Description | Inherited from | Defined in | -| -------------------------------------------------------------- | ---------- | ------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------- | -| `executionId?` | `readonly` | `string` | The execution the escalation ended, where the caller has one. Recorded as the event's `operationId`, which is what `turn.ended` carries for the same turn, so a reviewer's queue built from audit joins an escalation to its turn on an id rather than on `traceId` and time order. The execution envelope passes it; a host escalating outside a turn has none and omits it. | - | [packages/core/src/kernel.ts:239](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/kernel.ts#L239) | -| `requestedAuthority?` | `readonly` | [`CapabilityRequestPayload`](#capabilityrequestpayload) | The authority this escalation is asking for. A host escalating a denial passes the `requiredAuthority` that denial described; a model-chosen escalation usually has none, because a sentence is all it produced. Either way nothing here advances the escalation -- resolution stays host-owned work that ends in a grant the next turn loads. The two names are one concept in two roles, and both end in the noun this package uses for what grants confer: a denial says what was _required_, and an escalation _requests_ it. `{ requestedAuthority: denial.requiredAuthority }` is the whole hop. What is recorded is minted, not copied. The ask -- capabilities, purpose, constraints, metadata -- is the caller's; `id`, `namespaceId`, `requester`, `owner`, and `requestedAt` come from the trusted context, whatever the caller wrote, because a request the caller authored would be a caller-chosen correlation for a decision the kernel made. The hop above still round-trips: the denial's description was minted from the same ask, so it comes back under the same identifier (ADR 0019). | - | [packages/core/src/kernel.ts:261](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/kernel.ts#L261) | -| `signal?` | `readonly` | `AbortSignal` | - | [`KernelOperationOptions`](#kerneloperationoptions).[`signal`](#property-signal-2) | [packages/core/src/kernel.ts:189](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/kernel.ts#L189) | +| Property | Modifier | Type | Description | Overrides | Inherited from | Defined in | +| -------------------------------------------------------------- | ---------- | ------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------- | +| `executionId?` | `readonly` | `string` | The execution the escalation ended, where the caller has one. Recorded as the event's `operationId`, which is what `turn.ended` carries for the same turn, so a reviewer's queue built from audit joins an escalation to its turn on an id rather than on `traceId` and time order. The execution envelope passes it; a host escalating outside a turn has none and omits it. | [`KernelOperationOptions`](#kerneloperationoptions).[`executionId`](#property-executionid-2) | - | [packages/core/src/kernel.ts:251](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/kernel.ts#L251) | +| `requestedAuthority?` | `readonly` | [`CapabilityRequestPayload`](#capabilityrequestpayload) | The authority this escalation is asking for. A host escalating a denial passes the `requiredAuthority` that denial described; a model-chosen escalation usually has none, because a sentence is all it produced. Either way nothing here advances the escalation -- resolution stays host-owned work that ends in a grant the next turn loads. The two names are one concept in two roles, and both end in the noun this package uses for what grants confer: a denial says what was _required_, and an escalation _requests_ it. `{ requestedAuthority: denial.requiredAuthority }` is the whole hop. What is recorded is minted, not copied. The ask -- capabilities, purpose, constraints, metadata -- is the caller's; `id`, `namespaceId`, `requester`, `owner`, and `requestedAt` come from the trusted context, whatever the caller wrote, because a request the caller authored would be a caller-chosen correlation for a decision the kernel made. The hop above still round-trips: the denial's description was minted from the same ask, so it comes back under the same identifier (ADR 0019). | - | - | [packages/core/src/kernel.ts:273](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/kernel.ts#L273) | +| `signal?` | `readonly` | `AbortSignal` | - | - | [`KernelOperationOptions`](#kerneloperationoptions).[`signal`](#property-signal-2) | [packages/core/src/kernel.ts:201](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/kernel.ts#L201) | --- @@ -2637,7 +2856,7 @@ to make its own refusal look like an absent grant. Say more in `metadata`. ### KernelOperationOptions -Defined in: [packages/core/src/kernel.ts:188](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/kernel.ts#L188) +Defined in: [packages/core/src/kernel.ts:198](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/kernel.ts#L198) #### Extended by @@ -2646,9 +2865,10 @@ Defined in: [packages/core/src/kernel.ts:188](https://github.com/Aicoo-Team/Shar #### Properties -| Property | Modifier | Type | Defined in | -| ---------------------------------------- | ---------- | ------------- | -------------------------------------------------------------------------------------------------------------------- | -| `signal?` | `readonly` | `AbortSignal` | [packages/core/src/kernel.ts:189](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/kernel.ts#L189) | +| Property | Modifier | Type | Description | Defined in | +| -------------------------------------------------- | ---------- | ------------- | ------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------- | +| `executionId?` | `readonly` | `string` | Trusted executor scope for tool-call IDs; standalone calls use context.traceId. | [packages/core/src/kernel.ts:200](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/kernel.ts#L200) | +| `signal?` | `readonly` | `AbortSignal` | - | [packages/core/src/kernel.ts:201](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/kernel.ts#L201) | --- @@ -2982,8 +3202,8 @@ sentence that drifts. Everything here is a fact the kernel recorded. | Property | Modifier | Type | Description | Defined in | | --------------------------------------------------------- | ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- | -| `cause` | `readonly` | `string` \| `undefined` | Which situation a coarse code was, where it carried one. | [packages/core/src/refusal.ts:123](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/refusal.ts#L123) | -| `code` | `readonly` | `string` | The code the caller was given. | [packages/core/src/refusal.ts:119](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/refusal.ts#L119) | +| `cause` | `readonly` | `string` \| `undefined` | Which situation a coarse code was, where it carried one. | [packages/core/src/refusal.ts:123](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/refusal.ts#L123) | +| `code` | `readonly` | `string` | The code the caller was given. | [packages/core/src/refusal.ts:119](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/refusal.ts#L119) | | `decision` | `readonly` | \{ `action?`: `string`; `actor`: \{ `kind`: `"human"`; `userId`: `string`; \} \| \{ `agentId`: `string`; `kind`: `"agent"`; \} \| \{ `conversationId`: `string`; `kind`: `"group"`; \} \| \{ `kind`: `"service"`; `serviceId`: `string`; \}; `at`: `string`; `authority`: \{ `kind`: `"human"`; `userId`: `string`; \} \| \{ `agentId`: `string`; `kind`: `"agent"`; \} \| \{ `conversationId`: `string`; `kind`: `"group"`; \} \| \{ `kind`: `"service"`; `serviceId`: `string`; \}; `authorityHash?`: `string`; `cause?`: `string`; `consumed?`: `boolean`; `endedBy?`: `"envelope"` \| `"runtime"`; `failClosed?`: `boolean`; `grantId?`: `string`; `id`: `string`; `messageId?`: `string`; `metadata?`: [`JsonObject`](sharedos-contracts.md#jsonobject); `namespaceId`: `string`; `operationId?`: `string`; `outcome`: `"denied"` \| `"failed"` \| `"succeeded"` \| `"allowed"` \| `"escalated"` \| `"interrupted"`; `owner`: \{ `kind`: `"human"`; `userId`: `string`; \} \| \{ `agentId`: `string`; `kind`: `"agent"`; \} \| \{ `conversationId`: `string`; `kind`: `"group"`; \} \| \{ `kind`: `"service"`; `serviceId`: `string`; \}; `purpose`: `string`; `reason?`: `string`; `receiver?`: \{ `kind`: `"human"`; `userId`: `string`; \} \| \{ `agentId`: `string`; `kind`: `"agent"`; \} \| \{ `conversationId`: `string`; `kind`: `"group"`; \} \| \{ `kind`: `"service"`; `serviceId`: `string`; \}; `requestedAuthority?`: \{ `capabilities`: `object`[]; `constraints?`: \{ `delegationDepth?`: `number`; `expiresAt?`: `string`; `maxUses?`: `number`; `notBefore?`: `string`; `purposes?`: `string`[]; \}; `id`: `string`; `metadata?`: [`JsonObject`](sharedos-contracts.md#jsonobject); `namespaceId`: `string`; `owner`: \{ `kind`: `"human"`; `userId`: `string`; \} \| \{ `agentId`: `string`; `kind`: `"agent"`; \} \| \{ `conversationId`: `string`; `kind`: `"group"`; \} \| \{ `kind`: `"service"`; `serviceId`: `string`; \}; `purpose`: `string`; `requestedAt`: `string`; `requester`: \{ `kind`: `"human"`; `userId`: `string`; \} \| \{ `agentId`: `string`; `kind`: `"agent"`; \} \| \{ `conversationId`: `string`; `kind`: `"group"`; \} \| \{ `kind`: `"service"`; `serviceId`: `string`; \}; \}; `resource?`: \{ `namespace`: `string`; `owner?`: \{ `kind`: `"human"`; `userId`: `string`; \} \| \{ `agentId`: `string`; `kind`: `"agent"`; \} \| \{ `conversationId`: `string`; `kind`: `"group"`; \} \| \{ `kind`: `"service"`; `serviceId`: `string`; \}; `path`: `string`[]; \}; `source?`: `"kernel"` \| `"envelope"`; `tool?`: `string`; `traceId`: `string`; `type`: `"authority.resolved"` \| `"authorization.checked"` \| `"escalation.requested"` \| `"escalation.auto_decided"` \| `"resource.invoked"` \| `"tool.catalog.listed"` \| `"tool.namespace.catalog.listed"` \| `"tool.namespace.selection.updated"` \| `"tool.invoked"` \| `"message.sent"` \| `"turn.ended"`; `version`: `"1"`; \} \| `undefined` | The `authorization.checked` record the refusal followed from, where there was a decision. Its metadata carries `rejectedGrants`, `grantsResolved`, and `missingDependency`. Absent when nothing was checked: an unregistered tool, a disabled namespace, an envelope refusal. | [packages/core/src/refusal.ts:132](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/refusal.ts#L132) | | `gate` | `readonly` | [`RefusalGate`](#refusalgate) \| `undefined` | - | [packages/core/src/refusal.ts:117](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/refusal.ts#L117) | | `refusal` | `readonly` | `object` | The `tool.invoked` record of the refusal. | [packages/core/src/refusal.ts:125](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/refusal.ts#L125) | @@ -3037,7 +3257,7 @@ sentence that drifts. Everything here is a fact the kernel recorded. ### RefusedCall -Defined in: [packages/core/src/kernel.ts:207](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/kernel.ts#L207) +Defined in: [packages/core/src/kernel.ts:219](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/kernel.ts#L219) One call an enforcement boundary refused without invoking anything. @@ -3045,10 +3265,69 @@ One call an enforcement boundary refused without invoking anything. | Property | Modifier | Type | Description | Defined in | | ----------------------------------------------- | ---------- | -------- | ----------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------- | -| `callId` | `readonly` | `string` | - | [packages/core/src/kernel.ts:208](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/kernel.ts#L208) | -| `cause?` | `readonly` | `string` | Which situation a coarse code was, where it covers several. | [packages/core/src/kernel.ts:212](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/kernel.ts#L212) | -| `reasonCode` | `readonly` | `string` | - | [packages/core/src/kernel.ts:210](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/kernel.ts#L210) | -| `tool` | `readonly` | `string` | - | [packages/core/src/kernel.ts:209](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/kernel.ts#L209) | +| `callId` | `readonly` | `string` | - | [packages/core/src/kernel.ts:220](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/kernel.ts#L220) | +| `cause?` | `readonly` | `string` | Which situation a coarse code was, where it covers several. | [packages/core/src/kernel.ts:224](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/kernel.ts#L224) | +| `reasonCode` | `readonly` | `string` | - | [packages/core/src/kernel.ts:222](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/kernel.ts#L222) | +| `tool` | `readonly` | `string` | - | [packages/core/src/kernel.ts:221](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/kernel.ts#L221) | + +--- + +### ReplayStore + +Defined in: [packages/core/src/replay.ts:14](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/replay.ts#L14) + +Host-owned durable storage. All changes must be atomic across workers. + +#### Methods + +##### claim() + +> **claim**(`key`, `fingerprint`): `Promise`\<\{ `claimed`: `boolean`; `record`: \{ `fingerprint`: `string`; `key`: \{ `id`: `string`; `kind`: `"tool"` \| `"resource"` \| `"message"` \| `"execution"`; `namespaceId`: `string`; `scope`: `string`; \}; `result?`: [`JsonValue`](sharedos-contracts.md#jsonvalue); `state`: `"failed"` \| `"interrupted"` \| `"completed"` \| `"pending"` \| `"expired"`; `token`: `string`; \}; \}\> + +Defined in: [packages/core/src/replay.ts:16](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/replay.ts#L16) + +Insert pending or return the existing record, including a conflicting fingerprint. + +###### Parameters + +| Parameter | Type | +| ----------------- | ----------------------------------------------------------------------------------------------------------------------------------- | +| `key` | \{ `id`: `string`; `kind`: `"tool"` \| `"resource"` \| `"message"` \| `"execution"`; `namespaceId`: `string`; `scope`: `string`; \} | +| `key.id` | `string` | +| `key.kind` | `"tool"` \| `"resource"` \| `"message"` \| `"execution"` | +| `key.namespaceId` | `string` | +| `key.scope` | `string` | +| `fingerprint` | `string` | + +###### Returns + +`Promise`\<\{ `claimed`: `boolean`; `record`: \{ `fingerprint`: `string`; `key`: \{ `id`: `string`; `kind`: `"tool"` \| `"resource"` \| `"message"` \| `"execution"`; `namespaceId`: `string`; `scope`: `string`; \}; `result?`: [`JsonValue`](sharedos-contracts.md#jsonvalue); `state`: `"failed"` \| `"interrupted"` \| `"completed"` \| `"pending"` \| `"expired"`; `token`: `string`; \}; \}\> + +##### settle() + +> **settle**(`key`, `token`, `outcome`): `Promise`\<`boolean`>>\> + +Defined in: [packages/core/src/replay.ts:24](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/replay.ts#L24) + +CAS pending -> terminal, fenced by token. False means ownership was lost. + +###### Parameters + +| Parameter | Type | +| ----------------- | ----------------------------------------------------------------------------------------------------------------------------------- | +| `key` | \{ `id`: `string`; `kind`: `"tool"` \| `"resource"` \| `"message"` \| `"execution"`; `namespaceId`: `string`; `scope`: `string`; \} | +| `key.id` | `string` | +| `key.kind` | `"tool"` \| `"resource"` \| `"message"` \| `"execution"` | +| `key.namespaceId` | `string` | +| `key.scope` | `string` | +| `token` | `string` | +| `outcome` | \{ `result?`: [`JsonValue`](sharedos-contracts.md#jsonvalue); `state`: `"failed"` \| `"interrupted"` \| `"completed"`; \} | +| `outcome.result?` | [`JsonValue`](sharedos-contracts.md#jsonvalue) | +| `outcome.state` | `"failed"` \| `"interrupted"` \| `"completed"` | + +###### Returns + +`Promise`\<`boolean`\> --- @@ -3151,29 +3430,30 @@ Defined in: [packages/core/src/resource-registry.ts:20](https://github.com/Aicoo ### SharedOSKernelOptions -Defined in: [packages/core/src/kernel.ts:101](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/kernel.ts#L101) +Defined in: [packages/core/src/kernel.ts:109](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/kernel.ts#L109) #### Properties | Property | Modifier | Type | Description | Defined in | | ---------------------------------------------------------------------------- | ---------- | ----------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------- | -| `audit?` | `readonly` | [`AuditSink`](#auditsink) | - | [packages/core/src/kernel.ts:137](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/kernel.ts#L137) | -| `auditWriteTimeoutMs?` | `readonly` | `number` | How long a record written after an effect may keep a caller from its result. A sink that throws on such a record is already handed to `onAuditError`. A sink that does not answer at all is not a throw, and without a limit the result of an effect that has committed waits behind it for as long as the caller does: a turn that reaches its deadline first drops a result whose transfer went through. Past the limit the event is handed to `onAuditError` with an `AuditWriteTimeoutError` and the caller receives its result. The hook is held to the same limit, since it usually writes to the same store. Absent means no limit, which is the behaviour before this option. The records written before an effect are never limited: there a sink that does not answer holds back an operation that has not run, which is the point. | [packages/core/src/kernel.ts:164](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/kernel.ts#L164) | -| `authorizer?` | `readonly` | [`CapabilityAuthorizer`](#capabilityauthorizer) | - | [packages/core/src/kernel.ts:119](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/kernel.ts#L119) | -| `createAuditId?` | `readonly` | () => `string` | How each audit record gets its identity. A random UUID by default. Override for a deterministic host -- a replayed fixture, a conformance run -- and nowhere else, and never with a factory that can repeat: two records with one id are one record to every sink that deduplicates, which is exactly the loss `AuditEvent.id` exists to prevent. | [packages/core/src/kernel.ts:136](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/kernel.ts#L136) | -| `createMessageId?` | `readonly` | (`context`, `call`) => `string` | - | [packages/core/src/kernel.ts:127](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/kernel.ts#L127) | -| `grantSource` | `readonly` | [`GrantSource`](#grantsource) | The trusted boundary that loads authority. It is required: a kernel with no authoritative grant source can only fail closed. | [packages/core/src/kernel.ts:106](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/kernel.ts#L106) | -| `messageCapabilityResolver?` | `readonly` | [`MessageCapabilityResolver`](#messagecapabilityresolver) | - | [packages/core/src/kernel.ts:126](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/kernel.ts#L126) | -| `messageRequestRouter?` | `readonly` | [`MessageRequestRouter`](#messagerequestrouter) | - | [packages/core/src/kernel.ts:125](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/kernel.ts#L125) | -| `messageTransport?` | `readonly` | [`MessageTransport`](#messagetransport) | - | [packages/core/src/kernel.ts:124](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/kernel.ts#L124) | -| `onAuditError?` | `readonly` | (`error`, `event`) => `void` \| `Promise`\<`void`\> | Notification for an audit write that failed after the effect it records. An operation's outcome, a turn's ending and an escalation are written once the answer is final; a sink that throws there is handed here and the caller receives the result it would have received. The writes made _before_ an effect -- an authority load, a decision, a catalogue listing -- are not reported here: a sink that throws on one of those rejects the operation with an `AuditUnavailableError`, and nothing runs. | [packages/core/src/kernel.ts:148](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/kernel.ts#L148) | -| `onProviderError?` | `readonly` | [`ProviderErrorReporter`](#providererrorreporter) | Notification for a throw the kernel contained rather than propagated. A provider, tool handler, transport, or router that throws is answered with a fixed reason code, and until a host installs this the error itself is gone: `tool_execution_failed` says an operation stopped and does not say why. One hook covers every such port, and [ProviderErrorContext.kind](#property-kind) is what a host branches on if it wants to treat them differently. Synchronous, unlike [SharedOSKernelOptions.onAuditError](#property-onauditerror), and see [reportContainedError](#reportcontainederror) for why the two differ. | [packages/core/src/kernel.ts:177](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/kernel.ts#L177) | -| `policySource?` | `readonly` | [`PolicySource`](#policysource)\<`unknown`\> | The trusted boundary that loads host policy, once per turn, beside the grant set. See [PolicySource](#policysource). Optional. Without one the ceiling installed on the authorizer, if any, is handed `undefined` and decides over state it closes over. It is installed here rather than beside the ceiling because the load is a turn-boundary event and the kernel owns the turn boundary; a throw is reported to [SharedOSKernelOptions.onProviderError](#property-onprovidererror-1) as `kind: "policy"`, and the turn's policy fails closed. | [packages/core/src/kernel.ts:118](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/kernel.ts#L118) | -| `resources?` | `readonly` | [`ResourceProviderRegistry`](#resourceproviderregistry) | - | [packages/core/src/kernel.ts:120](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/kernel.ts#L120) | -| `spans?` | `readonly` | [`SpanSink`](#spansink) | Where the cost of enforcement is reported, when a host is measuring it. Absent by default and absent in every production path that does not ask for it, which is what keeps a measured run and an unmeasured one the same run. See [SpanSink](#spansink). | [packages/core/src/kernel.ts:185](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/kernel.ts#L185) | -| `toolNamespaceSettings?` | `readonly` | [`ToolNamespaceSettingsStore`](#toolnamespacesettingsstore) | - | [packages/core/src/kernel.ts:123](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/kernel.ts#L123) | -| `toolProviders?` | `readonly` | readonly [`ContextToolProvider`](#contexttoolprovider)[] | - | [packages/core/src/kernel.ts:122](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/kernel.ts#L122) | -| `tools?` | `readonly` | [`ToolRegistry`](#toolregistry) | - | [packages/core/src/kernel.ts:121](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/kernel.ts#L121) | +| `audit?` | `readonly` | [`AuditSink`](#auditsink) | - | [packages/core/src/kernel.ts:147](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/kernel.ts#L147) | +| `auditWriteTimeoutMs?` | `readonly` | `number` | How long a record written after an effect may keep a caller from its result. A sink that throws on such a record is already handed to `onAuditError`. A sink that does not answer at all is not a throw, and without a limit the result of an effect that has committed waits behind it for as long as the caller does: a turn that reaches its deadline first drops a result whose transfer went through. Past the limit the event is handed to `onAuditError` with an `AuditWriteTimeoutError` and the caller receives its result. The hook is held to the same limit, since it usually writes to the same store. Absent means no limit, which is the behaviour before this option. The records written before an effect are never limited: there a sink that does not answer holds back an operation that has not run, which is the point. | [packages/core/src/kernel.ts:174](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/kernel.ts#L174) | +| `authorizer?` | `readonly` | [`CapabilityAuthorizer`](#capabilityauthorizer) | - | [packages/core/src/kernel.ts:129](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/kernel.ts#L129) | +| `createAuditId?` | `readonly` | () => `string` | How each audit record gets its identity. A random UUID by default. Override for a deterministic host -- a replayed fixture, a conformance run -- and nowhere else, and never with a factory that can repeat: two records with one id are one record to every sink that deduplicates, which is exactly the loss `AuditEvent.id` exists to prevent. | [packages/core/src/kernel.ts:146](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/kernel.ts#L146) | +| `createMessageId?` | `readonly` | (`context`, `call`) => `string` | - | [packages/core/src/kernel.ts:137](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/kernel.ts#L137) | +| `grantSource` | `readonly` | [`GrantSource`](#grantsource) | The trusted boundary that loads authority. It is required: a kernel with no authoritative grant source can only fail closed. | [packages/core/src/kernel.ts:116](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/kernel.ts#L116) | +| `messageCapabilityResolver?` | `readonly` | [`MessageCapabilityResolver`](#messagecapabilityresolver) | - | [packages/core/src/kernel.ts:136](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/kernel.ts#L136) | +| `messageRequestRouter?` | `readonly` | [`MessageRequestRouter`](#messagerequestrouter) | - | [packages/core/src/kernel.ts:135](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/kernel.ts#L135) | +| `messageTransport?` | `readonly` | [`MessageTransport`](#messagetransport) | - | [packages/core/src/kernel.ts:134](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/kernel.ts#L134) | +| `onAuditError?` | `readonly` | (`error`, `event`) => `void` \| `Promise`\<`void`\> | Notification for an audit write that failed after the effect it records. An operation's outcome, a turn's ending and an escalation are written once the answer is final; a sink that throws there is handed here and the caller receives the result it would have received. The writes made _before_ an effect -- an authority load, a decision, a catalogue listing -- are not reported here: a sink that throws on one of those rejects the operation with an `AuditUnavailableError`, and nothing runs. | [packages/core/src/kernel.ts:158](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/kernel.ts#L158) | +| `onProviderError?` | `readonly` | [`ProviderErrorReporter`](#providererrorreporter) | Notification for a throw the kernel contained rather than propagated. A provider, tool handler, transport, or router that throws is answered with a fixed reason code, and until a host installs this the error itself is gone: `tool_execution_failed` says an operation stopped and does not say why. One hook covers every such port, and [ProviderErrorContext.kind](#property-kind) is what a host branches on if it wants to treat them differently. Synchronous, unlike [SharedOSKernelOptions.onAuditError](#property-onauditerror), and see [reportContainedError](#reportcontainederror) for why the two differ. | [packages/core/src/kernel.ts:187](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/kernel.ts#L187) | +| `policySource?` | `readonly` | [`PolicySource`](#policysource)\<`unknown`\> | The trusted boundary that loads host policy, once per turn, beside the grant set. See [PolicySource](#policysource). Optional. Without one the ceiling installed on the authorizer, if any, is handed `undefined` and decides over state it closes over. It is installed here rather than beside the ceiling because the load is a turn-boundary event and the kernel owns the turn boundary; a throw is reported to [SharedOSKernelOptions.onProviderError](#property-onprovidererror-1) as `kind: "policy"`, and the turn's policy fails closed. | [packages/core/src/kernel.ts:128](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/kernel.ts#L128) | +| `replayStore?` | `readonly` | [`ReplayStore`](#replaystore) | Activate durable replay enforcement for all effect and execution paths (ADR 0028). | [packages/core/src/kernel.ts:111](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/kernel.ts#L111) | +| `resources?` | `readonly` | [`ResourceProviderRegistry`](#resourceproviderregistry) | - | [packages/core/src/kernel.ts:130](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/kernel.ts#L130) | +| `spans?` | `readonly` | [`SpanSink`](#spansink) | Where the cost of enforcement is reported, when a host is measuring it. Absent by default and absent in every production path that does not ask for it, which is what keeps a measured run and an unmeasured one the same run. See [SpanSink](#spansink). | [packages/core/src/kernel.ts:195](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/kernel.ts#L195) | +| `toolNamespaceSettings?` | `readonly` | [`ToolNamespaceSettingsStore`](#toolnamespacesettingsstore) | - | [packages/core/src/kernel.ts:133](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/kernel.ts#L133) | +| `toolProviders?` | `readonly` | readonly [`ContextToolProvider`](#contexttoolprovider)[] | - | [packages/core/src/kernel.ts:132](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/kernel.ts#L132) | +| `tools?` | `readonly` | [`ToolRegistry`](#toolregistry) | - | [packages/core/src/kernel.ts:131](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/kernel.ts#L131) | --- @@ -3189,7 +3469,7 @@ One completed span of SharedOS-owned work. | --------------------------------------------- | ---------- | ----------------------------------- | ---------------------------------------------- | ---------------------------------------------------------------------------------------------------------------- | | `attributes` | `readonly` | [`SpanAttributes`](#spanattributes) | - | [packages/core/src/spans.ts:41](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/spans.ts#L41) | | `durationMs` | `readonly` | `number` | Monotonic duration in fractional milliseconds. | [packages/core/src/spans.ts:40](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/spans.ts#L40) | -| `name` | `readonly` | `string` | - | [packages/core/src/spans.ts:38](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/spans.ts#L38) | +| `name` | `readonly` | `string` | - | [packages/core/src/spans.ts:38](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/spans.ts#L38) | --- @@ -3264,19 +3544,19 @@ Defined in: [packages/core/src/published-tool.ts:128](https://github.com/Aicoo-T | Property | Modifier | Type | Defined in | | ------------------------------------------------- | ---------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------ | -| `executionId` | `readonly` | `string` | [packages/core/src/published-tool.ts:129](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/published-tool.ts#L129) | +| `executionId` | `readonly` | `string` | [packages/core/src/published-tool.ts:129](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/published-tool.ts#L129) | --- ### ToolHandler -Defined in: [packages/core/src/tool-registry.ts:15](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/tool-registry.ts#L15) +Defined in: [packages/core/src/tool-registry.ts:16](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/tool-registry.ts#L16) #### Properties | Property | Modifier | Type | Description | Defined in | | -------------------------------------------------------------- | ---------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | -| `definition` | `readonly` | `object` | - | [packages/core/src/tool-registry.ts:16](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/tool-registry.ts#L16) | +| `definition` | `readonly` | `object` | - | [packages/core/src/tool-registry.ts:17](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/tool-registry.ts#L17) | | `definition.annotations?` | `public` | `object` | - | packages/contracts/dist/tool.d.ts:309 | | `definition.annotations.destructive?` | `public` | `boolean` | - | packages/contracts/dist/tool.d.ts:311 | | `definition.annotations.idempotent?` | `public` | `boolean` | - | packages/contracts/dist/tool.d.ts:312 | @@ -3295,16 +3575,18 @@ Defined in: [packages/core/src/tool-registry.ts:15](https://github.com/Aicoo-Tea | `definition.requiredCapability.resource.owner?` | `public` | \{ `kind`: `"human"`; `userId`: `string`; \} \| \{ `agentId`: `string`; `kind`: `"agent"`; \} \| \{ `conversationId`: `string`; `kind`: `"group"`; \} \| \{ `kind`: `"service"`; `serviceId`: `string`; \} | - | packages/contracts/dist/tool.d.ts:291 | | `definition.requiredCapability.resource.path` | `public` | `string`[] | - | packages/contracts/dist/tool.d.ts:289 | | `definition.source` | `public` | `string` | - | packages/contracts/dist/tool.d.ts:284 | -| `parseArguments` | `readonly` | (`arguments_`) => `unknown` | Parse and normalize untrusted arguments before authorization or execution. | [packages/core/src/tool-registry.ts:18](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/tool-registry.ts#L18) | -| `resolveRequirement?` | `readonly` | (`context`, `call`) => `object` | Resolve argument-selected resources immediately before execution. | [packages/core/src/tool-registry.ts:20](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/tool-registry.ts#L20) | +| `parseArguments` | `readonly` | (`arguments_`) => `unknown` | Parse and normalize untrusted arguments before authorization or execution. | [packages/core/src/tool-registry.ts:19](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/tool-registry.ts#L19) | +| `resolveRequirement?` | `readonly` | (`context`, `call`) => `object` | Resolve argument-selected resources immediately before execution. | [packages/core/src/tool-registry.ts:21](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/tool-registry.ts#L21) | #### Methods ##### invoke() -> **invoke**(`context`, `call`, `signal`): `Promise`\<\{ `callId`: `string`; `completedAt`: `string`; `metadata?`: [`JsonObject`](sharedos-contracts.md#jsonobject); `output`: [`JsonValue`](sharedos-contracts.md#jsonvalue); `status`: `"succeeded"`; `tool`: `string`; \} \| \{ `callId`: `string`; `completedAt`: `string`; `error`: \{ `code`: `string`; `details?`: [`JsonObject`](sharedos-contracts.md#jsonobject); `message`: `string`; `retryable?`: `boolean`; \}; `metadata?`: [`JsonObject`](sharedos-contracts.md#jsonobject); `status`: `"denied"`; `tool`: `string`; \} \| \{ `callId`: `string`; `completedAt`: `string`; `error`: \{ `code`: `string`; `details?`: [`JsonObject`](sharedos-contracts.md#jsonobject); `message`: `string`; `retryable?`: `boolean`; \}; `metadata?`: [`JsonObject`](sharedos-contracts.md#jsonobject); `status`: `"failed"`; `tool`: `string`; \}\> +> **invoke**(`context`, `call`, `signal`, `identity?`): `Promise`\<\{ `callId`: `string`; `completedAt`: `string`; `metadata?`: [`JsonObject`](sharedos-contracts.md#jsonobject); `output`: [`JsonValue`](sharedos-contracts.md#jsonvalue); `status`: `"succeeded"`; `tool`: `string`; \} \| \{ `callId`: `string`; `completedAt`: `string`; `error`: \{ `code`: `string`; `details?`: [`JsonObject`](sharedos-contracts.md#jsonobject); `message`: `string`; `retryable?`: `boolean`; \}; `metadata?`: [`JsonObject`](sharedos-contracts.md#jsonobject); `status`: `"denied"`; `tool`: `string`; \} \| \{ `callId`: `string`; `completedAt`: `string`; `error`: \{ `code`: `string`; `details?`: [`JsonObject`](sharedos-contracts.md#jsonobject); `message`: `string`; `retryable?`: `boolean`; \}; `metadata?`: [`JsonObject`](sharedos-contracts.md#jsonobject); `status`: `"failed"`; `tool`: `string`; \}\> -Defined in: [packages/core/src/tool-registry.ts:21](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/tool-registry.ts#L21) +Defined in: [packages/core/src/tool-registry.ts:23](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/tool-registry.ts#L23) + +The optional identity is trusted kernel provenance for stable nested operation IDs. ###### Parameters @@ -3314,18 +3596,23 @@ Defined in: [packages/core/src/tool-registry.ts:21](https://github.com/Aicoo-Tea | `context.actor` | \{ `kind`: `"human"`; `userId`: `string`; \} \| \{ `agentId`: `string`; `kind`: `"agent"`; \} \| \{ `conversationId`: `string`; `kind`: `"group"`; \} \| \{ `kind`: `"service"`; `serviceId`: `string`; \} | | `context.authority` | \{ `kind`: `"human"`; `userId`: `string`; \} \| \{ `agentId`: `string`; `kind`: `"agent"`; \} \| \{ `conversationId`: `string`; `kind`: `"group"`; \} \| \{ `kind`: `"service"`; `serviceId`: `string`; \} | | `context.enabledToolNamespaces` | `string`[] | -| `context.namespaceId` | `string` | -| `context.now` | `string` | -| `context.owner` | \{ `kind`: `"human"`; `userId`: `string`; \} \| \{ `agentId`: `string`; `kind`: `"agent"`; \} \| \{ `conversationId`: `string`; `kind`: `"group"`; \} \| \{ `kind`: `"service"`; `serviceId`: `string`; \} | -| `context.purpose` | `string` | -| `context.traceId` | `string` | -| `call` | \{ `arguments`: [`JsonObject`](sharedos-contracts.md#jsonobject); `id`: `string`; `requestedAt`: `string`; `tool`: `string`; `traceId`: `string`; \} | -| `call.arguments` | [`JsonObject`](sharedos-contracts.md#jsonobject) | -| `call.id` | `string` | -| `call.requestedAt` | `string` | -| `call.tool` | `string` | -| `call.traceId` | `string` | -| `signal` | `AbortSignal` | +| `context.namespaceId?` | `string` | +| `context.now?` | `string` | +| `context.owner?` | \{ `kind`: `"human"`; `userId`: `string`; \} \| \{ `agentId`: `string`; `kind`: `"agent"`; \} \| \{ `conversationId`: `string`; `kind`: `"group"`; \} \| \{ `kind`: `"service"`; `serviceId`: `string`; \} | +| `context.purpose?` | `string` | +| `context.traceId?` | `string` | +| `call?` | \{ `arguments`: [`JsonObject`](sharedos-contracts.md#jsonobject); `id`: `string`; `requestedAt`: `string`; `tool`: `string`; `traceId`: `string`; \} | +| `call.arguments?` | [`JsonObject`](sharedos-contracts.md#jsonobject) | +| `call.id?` | `string` | +| `call.requestedAt?` | `string` | +| `call.tool?` | `string` | +| `call.traceId?` | `string` | +| `signal?` | `AbortSignal` | +| `identity?` | \{ `id`: `string`; `kind`: `"tool"` \| `"resource"` \| `"message"` \| `"execution"`; `namespaceId`: `string`; `scope`: `string`; \} | +| `identity.id?` | `string` | +| `identity.kind?` | `"tool"` \| `"resource"` \| `"message"` \| `"execution"` | +| `identity.namespaceId?` | `string` | +| `identity.scope?` | `string` | ###### Returns @@ -3377,7 +3664,7 @@ Defined in: [packages/core/src/tool-namespace-control.ts:12](https://github.com/ ### TurnEndRecord -Defined in: [packages/core/src/kernel.ts:193](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/kernel.ts#L193) +Defined in: [packages/core/src/kernel.ts:205](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/kernel.ts#L205) How a turn finished, as the boundary that finished it saw it. @@ -3385,10 +3672,10 @@ How a turn finished, as the boundary that finished it saw it. | Property | Modifier | Type | Description | Defined in | | ------------------------------------------------- | ---------- | --------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------- | -| `endedBy?` | `readonly` | `"envelope"` \| `"runtime"` | Who produced a failure: the envelope refusing, or the runtime reporting its own. The same distinction `ExecutionEvent` carries, kept because a record reader crediting enforcement must not credit a plugin's self-reported error. | [packages/core/src/kernel.ts:203](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/kernel.ts#L203) | -| `executionId` | `readonly` | `string` | - | [packages/core/src/kernel.ts:194](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/kernel.ts#L194) | -| `reasonCode?` | `readonly` | `string` | The terminal code, where the ending had one. | [packages/core/src/kernel.ts:197](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/kernel.ts#L197) | -| `status` | `readonly` | `"denied"` \| `"failed"` \| `"succeeded"` \| `"escalated"` \| `"cancelled"` | - | [packages/core/src/kernel.ts:195](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/kernel.ts#L195) | +| `endedBy?` | `readonly` | `"envelope"` \| `"runtime"` | Who produced a failure: the envelope refusing, or the runtime reporting its own. The same distinction `ExecutionEvent` carries, kept because a record reader crediting enforcement must not credit a plugin's self-reported error. | [packages/core/src/kernel.ts:215](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/kernel.ts#L215) | +| `executionId` | `readonly` | `string` | - | [packages/core/src/kernel.ts:206](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/kernel.ts#L206) | +| `reasonCode?` | `readonly` | `string` | The terminal code, where the ending had one. | [packages/core/src/kernel.ts:209](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/kernel.ts#L209) | +| `status` | `readonly` | `"denied"` \| `"failed"` \| `"succeeded"` \| `"escalated"` \| `"cancelled"` | - | [packages/core/src/kernel.ts:207](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/kernel.ts#L207) | ## Type Aliases @@ -3616,7 +3903,7 @@ what audit records -- never the policy. ### PolicyResolution -> **PolicyResolution**\<`Policy`> \> = \{ `policy`: `Policy`; `status`: `"loaded"`; `version`: `string`; \} \| \{ `status`: `"unavailable"`; \} +> **PolicyResolution**\<`Policy`> > \> = \{ `policy`: `Policy`; `status`: `"loaded"`; `version`: `string`; \} \| \{ `status`: `"unavailable"`; \} Defined in: [packages/core/src/authority.ts:135](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/authority.ts#L135) @@ -3813,7 +4100,7 @@ Defined in: [packages/core/src/authority.ts:49](https://github.com/Aicoo-Team/Sh > `const` **AGENT\_INVOKE\_ACTION**: `"invoke"` = `"invoke"` -Defined in: [packages/core/src/kernel.ts:278](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/kernel.ts#L278) +Defined in: [packages/core/src/kernel.ts:290](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/kernel.ts#L290) --- @@ -3900,7 +4187,7 @@ The resource every card hangs beneath, and the one a host grants over. > `const` **EXECUTION\_NAMESPACE**: `"sharedos.execution"` = `"sharedos.execution"` -Defined in: [packages/core/src/kernel.ts:277](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/kernel.ts#L277) +Defined in: [packages/core/src/kernel.ts:289](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/kernel.ts#L289) --- @@ -4154,7 +4441,7 @@ The authorization one card read is decided on. > **agentExecutionCapability**(`agent`, `owner`): `object` -Defined in: [packages/core/src/kernel.ts:280](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/kernel.ts#L280) +Defined in: [packages/core/src/kernel.ts:292](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/kernel.ts#L292) #### Parameters @@ -4469,6 +4756,26 @@ Structural JSON equality for protocol values with unordered object keys. --- +### canonicalReplayJson() + +> **canonicalReplayJson**(`value`): `string` + +Defined in: [packages/core/src/replay.ts:49](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/replay.ts#L49) + +Canonical JSON: code-unit key order, no locale dependence or unsupported values. + +#### Parameters + +| Parameter | Type | +| --------- | --------- | +| `value` | `unknown` | + +#### Returns + +`string` + +--- + ### capabilityIntersectsCeiling() > **capabilityIntersectsCeiling**(`capability`, `ceiling`, `context`): `boolean` @@ -4629,6 +4936,32 @@ tool, and a stale discovery cache alike. --- +### childOperationId() + +> **childOperationId**(`parent`, `kind`, `slot`): `Promise`\<`string`>>\> + +Defined in: [packages/core/src/replay.ts:75](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/replay.ts#L75) + +Stable provider-facing identity for a named child of a claimed operation. + +#### Parameters + +| Parameter | Type | +| -------------------- | ----------------------------------------------------------------------------------------------------------------------------------- | +| `parent` | \{ `id`: `string`; `kind`: `"tool"` \| `"resource"` \| `"message"` \| `"execution"`; `namespaceId`: `string`; `scope`: `string`; \} | +| `parent.id` | `string` | +| `parent.kind` | `"tool"` \| `"resource"` \| `"message"` \| `"execution"` | +| `parent.namespaceId` | `string` | +| `parent.scope` | `string` | +| `kind` | `"tool"` \| `"resource"` \| `"message"` \| `"execution"` | +| `slot` | `string` | + +#### Returns + +`Promise`\<`string`\> + +--- + ### classifyRefusal() > **classifyRefusal**(`event`): [`RefusalGate`](#refusalgate) \| `undefined` @@ -5370,6 +5703,34 @@ readonly `object`[] --- +### replayContext() + +> **replayContext**(`context`): `Omit`\<[`AccessContext`](sharedos-contracts.md#accesscontext), `"now"`>>\> + +Defined in: [packages/core/src/replay.ts:84](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/core/src/replay.ts#L84) + +Bind replayed data to trusted identity and provenance, excluding observation time. + +#### Parameters + +| Parameter | Type | +| ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `context` | \{ `actor`: \{ `kind`: `"human"`; `userId`: `string`; \} \| \{ `agentId`: `string`; `kind`: `"agent"`; \} \| \{ `conversationId`: `string`; `kind`: `"group"`; \} \| \{ `kind`: `"service"`; `serviceId`: `string`; \}; `authority`: \{ `kind`: `"human"`; `userId`: `string`; \} \| \{ `agentId`: `string`; `kind`: `"agent"`; \} \| \{ `conversationId`: `string`; `kind`: `"group"`; \} \| \{ `kind`: `"service"`; `serviceId`: `string`; \}; `enabledToolNamespaces`: `string`[]; `namespaceId`: `string`; `now`: `string`; `owner`: \{ `kind`: `"human"`; `userId`: `string`; \} \| \{ `agentId`: `string`; `kind`: `"agent"`; \} \| \{ `conversationId`: `string`; `kind`: `"group"`; \} \| \{ `kind`: `"service"`; `serviceId`: `string`; \}; `purpose`: `string`; `traceId`: `string`; \} | +| `context.actor` | \{ `kind`: `"human"`; `userId`: `string`; \} \| \{ `agentId`: `string`; `kind`: `"agent"`; \} \| \{ `conversationId`: `string`; `kind`: `"group"`; \} \| \{ `kind`: `"service"`; `serviceId`: `string`; \} | +| `context.authority` | \{ `kind`: `"human"`; `userId`: `string`; \} \| \{ `agentId`: `string`; `kind`: `"agent"`; \} \| \{ `conversationId`: `string`; `kind`: `"group"`; \} \| \{ `kind`: `"service"`; `serviceId`: `string`; \} | +| `context.enabledToolNamespaces` | `string`[] | +| `context.namespaceId` | `string` | +| `context.now` | `string` | +| `context.owner` | \{ `kind`: `"human"`; `userId`: `string`; \} \| \{ `agentId`: `string`; `kind`: `"agent"`; \} \| \{ `conversationId`: `string`; `kind`: `"group"`; \} \| \{ `kind`: `"service"`; `serviceId`: `string`; \} | +| `context.purpose` | `string` | +| `context.traceId` | `string` | + +#### Returns + +`Omit`\<[`AccessContext`](sharedos-contracts.md#accesscontext), `"now"`\> + +--- + ### reportContainedError() > **reportContainedError**\<`Context`>\>(`report`, `error`, `context`): `void` diff --git a/docs/api/sharedos-os.md b/docs/api/sharedos-os.md index ac6cb06d..1dfe20f3 100644 --- a/docs/api/sharedos-os.md +++ b/docs/api/sharedos-os.md @@ -29,14 +29,14 @@ SharedOS is currently a `1.0.0` preview. ### StandardOsProviders -Defined in: [index.ts:211](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/os/src/index.ts#L211) +Defined in: [index.ts:212](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/os/src/index.ts#L212) #### Properties | Property | Modifier | Type | Defined in | | ------------------------------------ | ---------- | ------------------------------------------------------- | ---------------------------------------------------------------------------------------------- | -| `files?` | `readonly` | [`ResourceProvider`](sharedos-core.md#resourceprovider) | [index.ts:212](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/os/src/index.ts#L212) | -| `repo?` | `readonly` | [`ResourceProvider`](sharedos-core.md#resourceprovider) | [index.ts:213](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/os/src/index.ts#L213) | +| `files?` | `readonly` | [`ResourceProvider`](sharedos-core.md#resourceprovider) | [index.ts:213](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/os/src/index.ts#L213) | +| `repo?` | `readonly` | [`ResourceProvider`](sharedos-core.md#resourceprovider) | [index.ts:214](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/os/src/index.ts#L214) | ## Type Aliases @@ -44,7 +44,7 @@ Defined in: [index.ts:211](https://github.com/Aicoo-Team/SharedOS/blob/main/pack > **FilePath** = `z.infer`\<_typeof_ [`FilePathSchema`](#filepathschema)>\> -Defined in: [index.ts:37](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/os/src/index.ts#L37) +Defined in: [index.ts:38](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/os/src/index.ts#L38) --- @@ -52,7 +52,7 @@ Defined in: [index.ts:37](https://github.com/Aicoo-Team/SharedOS/blob/main/packa > **FilesAppendArguments** = `z.infer`\<_typeof_ [`FilesAppendArgumentsSchema`](#filesappendargumentsschema)>\> -Defined in: [index.ts:106](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/os/src/index.ts#L106) +Defined in: [index.ts:107](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/os/src/index.ts#L107) --- @@ -60,7 +60,7 @@ Defined in: [index.ts:106](https://github.com/Aicoo-Team/SharedOS/blob/main/pack > **FilesCreateArguments** = `z.infer`\<_typeof_ [`FilesCreateArgumentsSchema`](#filescreateargumentsschema)>\> -Defined in: [index.ts:70](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/os/src/index.ts#L70) +Defined in: [index.ts:71](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/os/src/index.ts#L71) --- @@ -68,7 +68,7 @@ Defined in: [index.ts:70](https://github.com/Aicoo-Team/SharedOS/blob/main/packa > **FilesDeleteArguments** = `z.infer`\<_typeof_ [`FilesDeleteArgumentsSchema`](#filesdeleteargumentsschema)>\> -Defined in: [index.ts:115](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/os/src/index.ts#L115) +Defined in: [index.ts:116](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/os/src/index.ts#L116) --- @@ -76,7 +76,7 @@ Defined in: [index.ts:115](https://github.com/Aicoo-Team/SharedOS/blob/main/pack > **FilesGrepArguments** = `z.infer`\<_typeof_ [`FilesGrepArgumentsSchema`](#filesgrepargumentsschema)>\> -Defined in: [index.ts:61](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/os/src/index.ts#L61) +Defined in: [index.ts:62](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/os/src/index.ts#L62) --- @@ -84,7 +84,7 @@ Defined in: [index.ts:61](https://github.com/Aicoo-Team/SharedOS/blob/main/packa > **FilesPathArguments** = `z.infer`\<_typeof_ [`FilesPathArgumentsSchema`](#filespathargumentsschema)>\> -Defined in: [index.ts:40](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/os/src/index.ts#L40) +Defined in: [index.ts:41](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/os/src/index.ts#L41) --- @@ -92,7 +92,7 @@ Defined in: [index.ts:40](https://github.com/Aicoo-Team/SharedOS/blob/main/packa > **FilesReplaceArguments** = `z.infer`\<_typeof_ [`FilesReplaceArgumentsSchema`](#filesreplaceargumentsschema)>\> -Defined in: [index.ts:96](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/os/src/index.ts#L96) +Defined in: [index.ts:97](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/os/src/index.ts#L97) --- @@ -100,7 +100,7 @@ Defined in: [index.ts:96](https://github.com/Aicoo-Team/SharedOS/blob/main/packa > **FilesSearchArguments** = `z.infer`\<_typeof_ [`FilesSearchArgumentsSchema`](#filessearchargumentsschema)>\> -Defined in: [index.ts:49](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/os/src/index.ts#L49) +Defined in: [index.ts:50](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/os/src/index.ts#L50) --- @@ -108,7 +108,7 @@ Defined in: [index.ts:49](https://github.com/Aicoo-Team/SharedOS/blob/main/packa > **FilesSnapshotCreateArguments** = `z.infer`\<_typeof_ [`FilesSnapshotCreateArgumentsSchema`](#filessnapshotcreateargumentsschema)>\> -Defined in: [index.ts:123](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/os/src/index.ts#L123) +Defined in: [index.ts:124](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/os/src/index.ts#L124) --- @@ -116,7 +116,7 @@ Defined in: [index.ts:123](https://github.com/Aicoo-Team/SharedOS/blob/main/pack > **FilesSnapshotListArguments** = `z.infer`\<_typeof_ [`FilesSnapshotListArgumentsSchema`](#filessnapshotlistargumentsschema)>\> -Defined in: [index.ts:131](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/os/src/index.ts#L131) +Defined in: [index.ts:132](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/os/src/index.ts#L132) --- @@ -124,7 +124,7 @@ Defined in: [index.ts:131](https://github.com/Aicoo-Team/SharedOS/blob/main/pack > **FilesSnapshotRestoreArguments** = `z.infer`\<_typeof_ [`FilesSnapshotRestoreArgumentsSchema`](#filessnapshotrestoreargumentsschema)>\> -Defined in: [index.ts:140](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/os/src/index.ts#L140) +Defined in: [index.ts:141](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/os/src/index.ts#L141) --- @@ -132,7 +132,7 @@ Defined in: [index.ts:140](https://github.com/Aicoo-Team/SharedOS/blob/main/pack > **RepoCommitArguments** = `z.infer`\<_typeof_ [`RepoCommitArgumentsSchema`](#repocommitargumentsschema)>\> -Defined in: [index.ts:199](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/os/src/index.ts#L199) +Defined in: [index.ts:200](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/os/src/index.ts#L200) --- @@ -140,7 +140,7 @@ Defined in: [index.ts:199](https://github.com/Aicoo-Team/SharedOS/blob/main/pack > **RepoDiffArguments** = `z.infer`\<_typeof_ [`RepoDiffArgumentsSchema`](#repodiffargumentsschema)>\> -Defined in: [index.ts:181](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/os/src/index.ts#L181) +Defined in: [index.ts:182](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/os/src/index.ts#L182) --- @@ -148,7 +148,7 @@ Defined in: [index.ts:181](https://github.com/Aicoo-Team/SharedOS/blob/main/pack > **RepoLogArguments** = `z.infer`\<_typeof_ [`RepoLogArgumentsSchema`](#repologargumentsschema)>\> -Defined in: [index.ts:189](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/os/src/index.ts#L189) +Defined in: [index.ts:190](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/os/src/index.ts#L190) --- @@ -156,7 +156,7 @@ Defined in: [index.ts:189](https://github.com/Aicoo-Team/SharedOS/blob/main/pack > **RepoPathspec** = `z.infer`\<_typeof_ [`RepoPathspecSchema`](#repopathspecschema)>\> -Defined in: [index.ts:153](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/os/src/index.ts#L153) +Defined in: [index.ts:154](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/os/src/index.ts#L154) --- @@ -164,7 +164,7 @@ Defined in: [index.ts:153](https://github.com/Aicoo-Team/SharedOS/blob/main/pack > **RepoStageArguments** = `z.infer`\<_typeof_ [`RepoStageArgumentsSchema`](#repostageargumentsschema)>\> -Defined in: [index.ts:194](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/os/src/index.ts#L194) +Defined in: [index.ts:195](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/os/src/index.ts#L195) --- @@ -172,7 +172,7 @@ Defined in: [index.ts:194](https://github.com/Aicoo-Team/SharedOS/blob/main/pack > **RepoStatusArguments** = `z.infer`\<_typeof_ [`RepoStatusArgumentsSchema`](#repostatusargumentsschema)>\> -Defined in: [index.ts:172](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/os/src/index.ts#L172) +Defined in: [index.ts:173](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/os/src/index.ts#L173) ## Variables @@ -180,7 +180,7 @@ Defined in: [index.ts:172](https://github.com/Aicoo-Team/SharedOS/blob/main/pack > `const` **FilePathSchema**: `ZodArray`\<`ZodString`, `"many"`>\> -Defined in: [index.ts:36](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/os/src/index.ts#L36) +Defined in: [index.ts:37](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/os/src/index.ts#L37) --- @@ -188,7 +188,7 @@ Defined in: [index.ts:36](https://github.com/Aicoo-Team/SharedOS/blob/main/packa > `const` **FILES\_NAMESPACE**: `"files"` = `"files"` -Defined in: [index.ts:20](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/os/src/index.ts#L20) +Defined in: [index.ts:21](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/os/src/index.ts#L21) The canonical SharedOS resource plane. Memory is a role of files, not a second store. @@ -198,7 +198,7 @@ The canonical SharedOS resource plane. Memory is a role of files, not a second s > `const` **FilesAppendArgumentsSchema**: `ZodObject`\<\{ `content`: `ZodType`\<[`JsonValue`](sharedos-contracts.md#jsonvalue), `ZodTypeDef`, [`JsonValue`](sharedos-contracts.md#jsonvalue)>\>; `expectedVersion`: `ZodOptional`\<`ZodEffects`\<`ZodEffects`\<`ZodString`, `string`, `string`>\>, `string`, `string`>>\>\>; `metadata`: `ZodOptional`\<`ZodRecord`\<`ZodString`, `ZodType`\<[`JsonValue`](sharedos-contracts.md#jsonvalue), `ZodTypeDef`, [`JsonValue`](sharedos-contracts.md#jsonvalue)>>>\>\>\>; `path`: `ZodArray`\<`ZodString`, `"many"`>\>; \}, `"strict"`, `ZodTypeAny`, \{ `content`: [`JsonValue`](sharedos-contracts.md#jsonvalue); `expectedVersion?`: `string`; `metadata?`: `Record`\<`string`, [`JsonValue`](sharedos-contracts.md#jsonvalue)>\>; `path`: `string`[]; \}, \{ `content`: [`JsonValue`](sharedos-contracts.md#jsonvalue); `expectedVersion?`: `string`; `metadata?`: `Record`\<`string`, [`JsonValue`](sharedos-contracts.md#jsonvalue)>\>; `path`: `string`[]; \}\> -Defined in: [index.ts:98](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/os/src/index.ts#L98) +Defined in: [index.ts:99](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/os/src/index.ts#L99) --- @@ -206,7 +206,7 @@ Defined in: [index.ts:98](https://github.com/Aicoo-Team/SharedOS/blob/main/packa > `const` **FilesCreateArgumentsSchema**: `ZodObject`\<\{ `content`: `ZodType`\<[`JsonValue`](sharedos-contracts.md#jsonvalue), `ZodTypeDef`, [`JsonValue`](sharedos-contracts.md#jsonvalue)>\>; `metadata`: `ZodOptional`\<`ZodRecord`\<`ZodString`, `ZodType`\<[`JsonValue`](sharedos-contracts.md#jsonvalue), `ZodTypeDef`, [`JsonValue`](sharedos-contracts.md#jsonvalue)>>>\>\>\>; `path`: `ZodArray`\<`ZodString`, `"many"`>\>; \}, `"strict"`, `ZodTypeAny`, \{ `content`: [`JsonValue`](sharedos-contracts.md#jsonvalue); `metadata?`: `Record`\<`string`, [`JsonValue`](sharedos-contracts.md#jsonvalue)>\>; `path`: `string`[]; \}, \{ `content`: [`JsonValue`](sharedos-contracts.md#jsonvalue); `metadata?`: `Record`\<`string`, [`JsonValue`](sharedos-contracts.md#jsonvalue)>\>; `path`: `string`[]; \}\> -Defined in: [index.ts:63](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/os/src/index.ts#L63) +Defined in: [index.ts:64](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/os/src/index.ts#L64) --- @@ -214,7 +214,7 @@ Defined in: [index.ts:63](https://github.com/Aicoo-Team/SharedOS/blob/main/packa > `const` **FilesDeleteArgumentsSchema**: `ZodObject`\<\{ `expectedVersion`: `ZodOptional`\<`ZodEffects`\<`ZodEffects`\<`ZodString`, `string`, `string`>\>, `string`, `string`>>\>\>; `path`: `ZodArray`\<`ZodString`, `"many"`>\>; `recursive`: `ZodDefault`\<`ZodBoolean`>\>; \}, `"strict"`, `ZodTypeAny`, \{ `expectedVersion?`: `string`; `path`: `string`[]; `recursive`: `boolean`; \}, \{ `expectedVersion?`: `string`; `path`: `string`[]; `recursive?`: `boolean`; \}\> -Defined in: [index.ts:108](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/os/src/index.ts#L108) +Defined in: [index.ts:109](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/os/src/index.ts#L109) --- @@ -222,7 +222,7 @@ Defined in: [index.ts:108](https://github.com/Aicoo-Team/SharedOS/blob/main/pack > `const` **FilesGrepArgumentsSchema**: `ZodObject`\<\{ `caseSensitive`: `ZodDefault`\<`ZodBoolean`>\>; `contextAfter`: `ZodDefault`\<`ZodNumber`>\>; `contextBefore`: `ZodDefault`\<`ZodNumber`>\>; `mode`: `ZodDefault`\<`ZodEnum`\<\[`"literal"`, `"regex"`\]\>\>; `path`: `ZodArray`\<`ZodString`, `"many"`>\>; `pattern`: `ZodString`; \}, `"strict"`, `ZodTypeAny`, \{ `caseSensitive`: `boolean`; `contextAfter`: `number`; `contextBefore`: `number`; `mode`: `"literal"` \| `"regex"`; `path`: `string`[]; `pattern`: `string`; \}, \{ `caseSensitive?`: `boolean`; `contextAfter?`: `number`; `contextBefore?`: `number`; `mode?`: `"literal"` \| `"regex"`; `path`: `string`[]; `pattern`: `string`; \}\> -Defined in: [index.ts:51](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/os/src/index.ts#L51) +Defined in: [index.ts:52](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/os/src/index.ts#L52) --- @@ -230,7 +230,7 @@ Defined in: [index.ts:51](https://github.com/Aicoo-Team/SharedOS/blob/main/packa > `const` **FilesPathArgumentsSchema**: `ZodObject`\<\{ `path`: `ZodArray`\<`ZodString`, `"many"`>\>; \}, `"strict"`, `ZodTypeAny`, \{ `path`: `string`[]; \}, \{ `path`: `string`[]; \}\> -Defined in: [index.ts:39](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/os/src/index.ts#L39) +Defined in: [index.ts:40](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/os/src/index.ts#L40) --- @@ -238,7 +238,7 @@ Defined in: [index.ts:39](https://github.com/Aicoo-Team/SharedOS/blob/main/packa > `const` **FilesReplaceArgumentsSchema**: `ZodObject`\<\{ `content`: `ZodType`\<[`JsonValue`](sharedos-contracts.md#jsonvalue), `ZodTypeDef`, [`JsonValue`](sharedos-contracts.md#jsonvalue)>\>; `expectedVersion`: `ZodOptional`\<`ZodEffects`\<`ZodEffects`\<`ZodString`, `string`, `string`>\>, `string`, `string`>>\>\>; `path`: `ZodArray`\<`ZodString`, `"many"`>\>; \}, `"strict"`, `ZodTypeAny`, \{ `content`: [`JsonValue`](sharedos-contracts.md#jsonvalue); `expectedVersion?`: `string`; `path`: `string`[]; \}, \{ `content`: [`JsonValue`](sharedos-contracts.md#jsonvalue); `expectedVersion?`: `string`; `path`: `string`[]; \}\> -Defined in: [index.ts:89](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/os/src/index.ts#L89) +Defined in: [index.ts:90](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/os/src/index.ts#L90) --- @@ -246,7 +246,7 @@ Defined in: [index.ts:89](https://github.com/Aicoo-Team/SharedOS/blob/main/packa > `const` **FilesSearchArgumentsSchema**: `ZodObject`\<\{ `limit`: `ZodOptional`\<`ZodNumber`>\>; `path`: `ZodArray`\<`ZodString`, `"many"`>\>; `query`: `ZodString`; \}, `"strict"`, `ZodTypeAny`, \{ `limit?`: `number`; `path`: `string`[]; `query`: `string`; \}, \{ `limit?`: `number`; `path`: `string`[]; `query`: `string`; \}\> -Defined in: [index.ts:42](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/os/src/index.ts#L42) +Defined in: [index.ts:43](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/os/src/index.ts#L43) --- @@ -254,7 +254,7 @@ Defined in: [index.ts:42](https://github.com/Aicoo-Team/SharedOS/blob/main/packa > `const` **FilesSnapshotCreateArgumentsSchema**: `ZodObject`\<\{ `label`: `ZodOptional`\<`ZodString`>\>; `path`: `ZodArray`\<`ZodString`, `"many"`>\>; \}, `"strict"`, `ZodTypeAny`, \{ `label?`: `string`; `path`: `string`[]; \}, \{ `label?`: `string`; `path`: `string`[]; \}\> -Defined in: [index.ts:117](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/os/src/index.ts#L117) +Defined in: [index.ts:118](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/os/src/index.ts#L118) --- @@ -262,7 +262,7 @@ Defined in: [index.ts:117](https://github.com/Aicoo-Team/SharedOS/blob/main/pack > `const` **FilesSnapshotListArgumentsSchema**: `ZodObject`\<\{ `limit`: `ZodOptional`\<`ZodNumber`>\>; `path`: `ZodArray`\<`ZodString`, `"many"`>\>; \}, `"strict"`, `ZodTypeAny`, \{ `limit?`: `number`; `path`: `string`[]; \}, \{ `limit?`: `number`; `path`: `string`[]; \}\> -Defined in: [index.ts:125](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/os/src/index.ts#L125) +Defined in: [index.ts:126](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/os/src/index.ts#L126) --- @@ -270,7 +270,7 @@ Defined in: [index.ts:125](https://github.com/Aicoo-Team/SharedOS/blob/main/pack > `const` **FilesSnapshotRestoreArgumentsSchema**: `ZodObject`\<\{ `expectedVersion`: `ZodOptional`\<`ZodEffects`\<`ZodEffects`\<`ZodString`, `string`, `string`>\>, `string`, `string`>>\>\>; `path`: `ZodArray`\<`ZodString`, `"many"`>\>; `snapshotId`: `ZodString`; \}, `"strict"`, `ZodTypeAny`, \{ `expectedVersion?`: `string`; `path`: `string`[]; `snapshotId`: `string`; \}, \{ `expectedVersion?`: `string`; `path`: `string`[]; `snapshotId`: `string`; \}\> -Defined in: [index.ts:133](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/os/src/index.ts#L133) +Defined in: [index.ts:134](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/os/src/index.ts#L134) --- @@ -278,7 +278,7 @@ Defined in: [index.ts:133](https://github.com/Aicoo-Team/SharedOS/blob/main/pack > `const` **REPO\_NAMESPACE**: `"repo"` = `"repo"` -Defined in: [index.ts:33](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/os/src/index.ts#L33) +Defined in: [index.ts:34](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/os/src/index.ts#L34) The Git resource plane. @@ -296,7 +296,7 @@ ADR 0024. > `const` **RepoCommitArgumentsSchema**: `ZodObject`\<\{ `message`: `ZodEffects`\<`ZodString`, `string`, `string`>\>; `path`: `ZodArray`\<`ZodString`, `"many"`>\>; \}, `"strict"`, `ZodTypeAny`, \{ `message`: `string`; `path`: `string`[]; \}, \{ `message`: `string`; `path`: `string`[]; \}\> -Defined in: [index.ts:196](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/os/src/index.ts#L196) +Defined in: [index.ts:197](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/os/src/index.ts#L197) --- @@ -304,7 +304,7 @@ Defined in: [index.ts:196](https://github.com/Aicoo-Team/SharedOS/blob/main/pack > `const` **RepoDiffArgumentsSchema**: `ZodObject`\<\{ `path`: `ZodArray`\<`ZodString`, `"many"`>\>; `pathspec`: `ZodOptional`\<`ZodArray`\<`ZodArray`\<`ZodString`, `"many"`>\>, `"many"`>>\>\>; `staged`: `ZodDefault`\<`ZodBoolean`>\>; \}, `"strict"`, `ZodTypeAny`, \{ `path`: `string`[]; `pathspec?`: `string`[][]; `staged`: `boolean`; \}, \{ `path`: `string`[]; `pathspec?`: `string`[][]; `staged?`: `boolean`; \}\> -Defined in: [index.ts:174](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/os/src/index.ts#L174) +Defined in: [index.ts:175](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/os/src/index.ts#L175) --- @@ -312,7 +312,7 @@ Defined in: [index.ts:174](https://github.com/Aicoo-Team/SharedOS/blob/main/pack > `const` **RepoLogArgumentsSchema**: `ZodObject`\<\{ `maxCount`: `ZodOptional`\<`ZodNumber`>\>; `path`: `ZodArray`\<`ZodString`, `"many"`>\>; \}, `"strict"`, `ZodTypeAny`, \{ `maxCount?`: `number`; `path`: `string`[]; \}, \{ `maxCount?`: `number`; `path`: `string`[]; \}\> -Defined in: [index.ts:183](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/os/src/index.ts#L183) +Defined in: [index.ts:184](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/os/src/index.ts#L184) --- @@ -320,7 +320,7 @@ Defined in: [index.ts:183](https://github.com/Aicoo-Team/SharedOS/blob/main/pack > `const` **RepoPathspecSchema**: `ZodArray`\<`ZodArray`\<`ZodString`, `"many"`>\>, `"many"`>\> -Defined in: [index.ts:152](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/os/src/index.ts#L152) +Defined in: [index.ts:153](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/os/src/index.ts#L153) Paths inside a repository, selecting what a diff or a stage covers. @@ -337,7 +337,7 @@ authorization boundary. See ADR 0024. > `const` **RepoStageArgumentsSchema**: `ZodObject`\<\{ `path`: `ZodArray`\<`ZodString`, `"many"`>\>; `pathspec`: `ZodArray`\<`ZodArray`\<`ZodString`, `"many"`>\>, `"many"`>\>; \}, `"strict"`, `ZodTypeAny`, \{ `path`: `string`[]; `pathspec`: `string`[][]; \}, \{ `path`: `string`[]; `pathspec`: `string`[][]; \}\> -Defined in: [index.ts:191](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/os/src/index.ts#L191) +Defined in: [index.ts:192](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/os/src/index.ts#L192) --- @@ -345,7 +345,7 @@ Defined in: [index.ts:191](https://github.com/Aicoo-Team/SharedOS/blob/main/pack > `const` **RepoStatusArgumentsSchema**: `ZodObject`\<\{ `path`: `ZodArray`\<`ZodString`, `"many"`>\>; \}, `"strict"`, `ZodTypeAny`, \{ `path`: `string`[]; \}, \{ `path`: `string`[]; \}\> -Defined in: [index.ts:171](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/os/src/index.ts#L171) +Defined in: [index.ts:172](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/os/src/index.ts#L172) --- @@ -353,7 +353,7 @@ Defined in: [index.ts:171](https://github.com/Aicoo-Team/SharedOS/blob/main/pack > `const` **SHAREDOS\_TOOL\_SOURCE**: `"sharedos"` = `"sharedos"` -Defined in: [index.ts:34](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/os/src/index.ts#L34) +Defined in: [index.ts:35](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/os/src/index.ts#L35) ## Functions @@ -361,7 +361,7 @@ Defined in: [index.ts:34](https://github.com/Aicoo-Team/SharedOS/blob/main/packa > **createFileTools**(`provider`): readonly [`ToolHandler`](sharedos-core.md#toolhandler)[] -Defined in: [index.ts:239](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/os/src/index.ts#L239) +Defined in: [index.ts:240](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/os/src/index.ts#L240) Portable file tools over one host-owned provider. @@ -385,7 +385,7 @@ readonly [`ToolHandler`](sharedos-core.md#toolhandler)[] > **createRepoTools**(`provider`): readonly [`ToolHandler`](sharedos-core.md#toolhandler)[] -Defined in: [index.ts:473](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/os/src/index.ts#L473) +Defined in: [index.ts:474](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/os/src/index.ts#L474) The vetted Git subset over one host-owned provider, beside the file tools. @@ -419,7 +419,7 @@ readonly [`ToolHandler`](sharedos-core.md#toolhandler)[] > **registerStandardOsTools**(`kernel`, `providers`): `void` -Defined in: [index.ts:216](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/os/src/index.ts#L216) +Defined in: [index.ts:217](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/os/src/index.ts#L217) #### Parameters diff --git a/docs/api/sharedos-runtime.md b/docs/api/sharedos-runtime.md index ecde863d..7bb5c8ef 100644 --- a/docs/api/sharedos-runtime.md +++ b/docs/api/sharedos-runtime.md @@ -376,7 +376,7 @@ Defined in: [packages/runtime/src/runtime-plugin.ts:254](https://github.com/Aico ### SharedOSExecutor -Defined in: [packages/runtime/src/executor.ts:143](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/runtime/src/executor.ts#L143) +Defined in: [packages/runtime/src/executor.ts:147](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/runtime/src/executor.ts#L147) The non-replaceable security envelope around one replaceable RuntimePlugin. Scheduling, retries, and network-level stopping remain host responsibilities. @@ -391,7 +391,7 @@ Scheduling, retries, and network-level stopping remain host responsibilities. > **new SharedOSExecutor**(`kernel`, `runtime`, `options?`): [`SharedOSExecutor`](#sharedosexecutor) -Defined in: [packages/runtime/src/executor.ts:156](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/runtime/src/executor.ts#L156) +Defined in: [packages/runtime/src/executor.ts:160](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/runtime/src/executor.ts#L160) ###### Parameters @@ -413,7 +413,7 @@ Defined in: [packages/runtime/src/executor.ts:156](https://github.com/Aicoo-Team > **get** **runtimeManifest**(): `object` -Defined in: [packages/runtime/src/executor.ts:207](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/runtime/src/executor.ts#L207) +Defined in: [packages/runtime/src/executor.ts:211](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/runtime/src/executor.ts#L211) ###### Returns @@ -441,7 +441,7 @@ Defined in: [packages/runtime/src/executor.ts:207](https://github.com/Aicoo-Team > **execute**(`input`, `options?`): `Promise`\<\{ `completedAt`: `string`; `events`: `object`[]; `executionId`: `string`; `metadata?`: [`JsonObject`](sharedos-contracts.md#jsonobject); `output`: [`JsonValue`](sharedos-contracts.md#jsonvalue); `startedAt`: `string`; `status`: `"succeeded"`; `traceId`: `string`; `version`: `"1"`; \} \| \{ `completedAt`: `string`; `error`: \{ `code`: `string`; `details?`: [`JsonObject`](sharedos-contracts.md#jsonobject); `message`: `string`; `retryable?`: `boolean`; \}; `events`: `object`[]; `executionId`: `string`; `metadata?`: [`JsonObject`](sharedos-contracts.md#jsonobject); `startedAt`: `string`; `status`: `"denied"`; `traceId`: `string`; `version`: `"1"`; \} \| \{ `completedAt`: `string`; `error`: \{ `code`: `string`; `details?`: [`JsonObject`](sharedos-contracts.md#jsonobject); `message`: `string`; `retryable?`: `boolean`; \}; `events`: `object`[]; `executionId`: `string`; `metadata?`: [`JsonObject`](sharedos-contracts.md#jsonobject); `startedAt`: `string`; `status`: `"failed"`; `traceId`: `string`; `version`: `"1"`; \} \| \{ `completedAt`: `string`; `error?`: \{ `code`: `string`; `details?`: [`JsonObject`](sharedos-contracts.md#jsonobject); `message`: `string`; `retryable?`: `boolean`; \}; `events`: `object`[]; `executionId`: `string`; `metadata?`: [`JsonObject`](sharedos-contracts.md#jsonobject); `startedAt`: `string`; `status`: `"cancelled"`; `traceId`: `string`; `version`: `"1"`; \} \| \{ `completedAt`: `string`; `escalation`: \{ `reason`: `string`; `requestedAt`: `string`; `requestedAuthority?`: \{ `capabilities`: `object`[]; `constraints?`: \{ `delegationDepth?`: `number`; `expiresAt?`: `string`; `maxUses?`: `number`; `notBefore?`: `string`; `purposes?`: `string`[]; \}; `id`: `string`; `metadata?`: [`JsonObject`](sharedos-contracts.md#jsonobject); `namespaceId`: `string`; `owner`: \{ `kind`: `"human"`; `userId`: `string`; \} \| \{ `agentId`: `string`; `kind`: `"agent"`; \} \| \{ `conversationId`: `string`; `kind`: `"group"`; \} \| \{ `kind`: `"service"`; `serviceId`: `string`; \}; `purpose`: `string`; `requestedAt`: `string`; `requester`: \{ `kind`: `"human"`; `userId`: `string`; \} \| \{ `agentId`: `string`; `kind`: `"agent"`; \} \| \{ `conversationId`: `string`; `kind`: `"group"`; \} \| \{ `kind`: `"service"`; `serviceId`: `string`; \}; \}; `reviewer`: \{ `kind`: `"human"`; `userId`: `string`; \} \| \{ `agentId`: `string`; `kind`: `"agent"`; \} \| \{ `conversationId`: `string`; `kind`: `"group"`; \} \| \{ `kind`: `"service"`; `serviceId`: `string`; \}; `status`: `"pending"`; \}; `events`: `object`[]; `executionId`: `string`; `metadata?`: [`JsonObject`](sharedos-contracts.md#jsonobject); `startedAt`: `string`; `status`: `"escalated"`; `traceId`: `string`; `version`: `"1"`; \}\> -Defined in: [packages/runtime/src/executor.ts:211](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/runtime/src/executor.ts#L211) +Defined in: [packages/runtime/src/executor.ts:215](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/runtime/src/executor.ts#L215) ###### Parameters @@ -605,14 +605,14 @@ Defined in: [packages/runtime/src/reach.ts:3](https://github.com/Aicoo-Team/Shar ### ExecuteTurnOptions -Defined in: [packages/runtime/src/executor.ts:102](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/runtime/src/executor.ts#L102) +Defined in: [packages/runtime/src/executor.ts:105](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/runtime/src/executor.ts#L105) #### Properties | Property | Type | Description | Defined in | | ---------------------------------------- | ------------------- | ----------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ | -| `onEvent?` | (`event`) => `void` | Synchronous observation hook for streaming an immutable event snapshot. | [packages/runtime/src/executor.ts:105](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/runtime/src/executor.ts#L105) | -| `signal?` | `AbortSignal` | - | [packages/runtime/src/executor.ts:103](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/runtime/src/executor.ts#L103) | +| `onEvent?` | (`event`) => `void` | Synchronous observation hook for streaming an immutable event snapshot. | [packages/runtime/src/executor.ts:108](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/runtime/src/executor.ts#L108) | +| `signal?` | `AbortSignal` | - | [packages/runtime/src/executor.ts:106](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/runtime/src/executor.ts#L106) | --- @@ -805,20 +805,20 @@ Defined in: [packages/runtime/src/runtime-plugin.ts:82](https://github.com/Aicoo ### SharedOSExecutorOptions -Defined in: [packages/runtime/src/executor.ts:56](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/runtime/src/executor.ts#L56) +Defined in: [packages/runtime/src/executor.ts:59](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/runtime/src/executor.ts#L59) #### Properties -| Property | Type | Description | Defined in | -| ---------------------------------------------------------------- | ----------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- | -| `clock?` | () => `string` | - | [packages/runtime/src/executor.ts:57](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/runtime/src/executor.ts#L57) | -| `createId?` | () => `string` | - | [packages/runtime/src/executor.ts:58](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/runtime/src/executor.ts#L58) | -| `defaultMaxSteps?` | `number` | - | [packages/runtime/src/executor.ts:59](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/runtime/src/executor.ts#L59) | -| `defaultMaxToolCalls?` | `number` | - | [packages/runtime/src/executor.ts:60](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/runtime/src/executor.ts#L60) | -| `defaultTimeoutMs?` | `number` | - | [packages/runtime/src/executor.ts:61](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/runtime/src/executor.ts#L61) | -| `drainGraceMs?` | `number` | How long before a turn's deadline it stops taking new tool calls, so the calls already inside a handler can answer before the deadline stops them. Inside the limit, never on top of it: with `timeoutMs` 120 000 and a grace of 5 000 a new call is refused from 115 s, a handler running then is left alone, and the abort reaches it at 120 s as it always did. A handler still running at the deadline is recorded `interrupted`. An audit outage drains the same way, for the grace or the time left, whichever is shorter. A host's own cancellation does not: it stops the turn at once, as before. Zero, the default, is the behaviour before this option. It is not capped against a request's `timeoutMs`; a grace no smaller than the limit leaves a turn no time in which a call is taken, and choosing it is the host's. | [packages/runtime/src/executor.ts:77](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/runtime/src/executor.ts#L77) | -| `onTurnError?` | [`TurnErrorReporter`](#turnerrorreporter) | Notification for a throw the turn body did not convert into an outcome. The envelope contains such a throw and ends the turn `failed` with `runtime_failed`; the error itself comes here rather than being discarded. See [TurnErrorReporter](#turnerrorreporter) for what it may and may not be used for. Not only the plugin's. The turn body also calls `openTurnAuthority`, `admitTurn`, `reach`, and `listTools`, and a host port that throws arrives here too under the same terminal code. That conflation is in the wire vocabulary and is not fixed by this hook; the error's own stack is what separates them, which is the reason for handing it over rather than classifying it here. | [packages/runtime/src/executor.ts:99](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/runtime/src/executor.ts#L99) | -| `spans?` | [`SpanSink`](sharedos-core.md#spansink) | Where the envelope reports what it cost, when a host is measuring. A second clock, and deliberately not the one `clock` supplies: that one names instants for a record and a conformance run freezes it. See [SpanSink](sharedos-core.md#spansink). | [packages/runtime/src/executor.ts:85](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/runtime/src/executor.ts#L85) | +| Property | Type | Description | Defined in | +| ---------------------------------------------------------------- | ----------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ | +| `clock?` | () => `string` | - | [packages/runtime/src/executor.ts:60](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/runtime/src/executor.ts#L60) | +| `createId?` | () => `string` | - | [packages/runtime/src/executor.ts:61](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/runtime/src/executor.ts#L61) | +| `defaultMaxSteps?` | `number` | - | [packages/runtime/src/executor.ts:62](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/runtime/src/executor.ts#L62) | +| `defaultMaxToolCalls?` | `number` | - | [packages/runtime/src/executor.ts:63](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/runtime/src/executor.ts#L63) | +| `defaultTimeoutMs?` | `number` | - | [packages/runtime/src/executor.ts:64](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/runtime/src/executor.ts#L64) | +| `drainGraceMs?` | `number` | How long before a turn's deadline it stops taking new tool calls, so the calls already inside a handler can answer before the deadline stops them. Inside the limit, never on top of it: with `timeoutMs` 120 000 and a grace of 5 000 a new call is refused from 115 s, a handler running then is left alone, and the abort reaches it at 120 s as it always did. A handler still running at the deadline is recorded `interrupted`. An audit outage drains the same way, for the grace or the time left, whichever is shorter. A host's own cancellation does not: it stops the turn at once, as before. Zero, the default, is the behaviour before this option. It is not capped against a request's `timeoutMs`; a grace no smaller than the limit leaves a turn no time in which a call is taken, and choosing it is the host's. | [packages/runtime/src/executor.ts:80](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/runtime/src/executor.ts#L80) | +| `onTurnError?` | [`TurnErrorReporter`](#turnerrorreporter) | Notification for a throw the turn body did not convert into an outcome. The envelope contains such a throw and ends the turn `failed` with `runtime_failed`; the error itself comes here rather than being discarded. See [TurnErrorReporter](#turnerrorreporter) for what it may and may not be used for. Not only the plugin's. The turn body also calls `openTurnAuthority`, `admitTurn`, `reach`, and `listTools`, and a host port that throws arrives here too under the same terminal code. That conflation is in the wire vocabulary and is not fixed by this hook; the error's own stack is what separates them, which is the reason for handing it over rather than classifying it here. | [packages/runtime/src/executor.ts:102](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/runtime/src/executor.ts#L102) | +| `spans?` | [`SpanSink`](sharedos-core.md#spansink) | Where the envelope reports what it cost, when a host is measuring. A second clock, and deliberately not the one `clock` supplies: that one names instants for a record and a conformance run freezes it. See [SpanSink](sharedos-core.md#spansink). | [packages/runtime/src/executor.ts:88](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/runtime/src/executor.ts#L88) | --- @@ -853,7 +853,7 @@ Which turn a [TurnErrorReporter](#turnerrorreporter) notification is about. ### TurnExecutionPort -Defined in: [packages/runtime/src/executor.ts:108](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/runtime/src/executor.ts#L108) +Defined in: [packages/runtime/src/executor.ts:111](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/runtime/src/executor.ts#L111) #### Methods @@ -861,7 +861,7 @@ Defined in: [packages/runtime/src/executor.ts:108](https://github.com/Aicoo-Team > **execute**(`input`, `options?`): `Promise`\<\{ `completedAt`: `string`; `events`: `object`[]; `executionId`: `string`; `metadata?`: [`JsonObject`](sharedos-contracts.md#jsonobject); `output`: [`JsonValue`](sharedos-contracts.md#jsonvalue); `startedAt`: `string`; `status`: `"succeeded"`; `traceId`: `string`; `version`: `"1"`; \} \| \{ `completedAt`: `string`; `error`: \{ `code`: `string`; `details?`: [`JsonObject`](sharedos-contracts.md#jsonobject); `message`: `string`; `retryable?`: `boolean`; \}; `events`: `object`[]; `executionId`: `string`; `metadata?`: [`JsonObject`](sharedos-contracts.md#jsonobject); `startedAt`: `string`; `status`: `"denied"`; `traceId`: `string`; `version`: `"1"`; \} \| \{ `completedAt`: `string`; `error`: \{ `code`: `string`; `details?`: [`JsonObject`](sharedos-contracts.md#jsonobject); `message`: `string`; `retryable?`: `boolean`; \}; `events`: `object`[]; `executionId`: `string`; `metadata?`: [`JsonObject`](sharedos-contracts.md#jsonobject); `startedAt`: `string`; `status`: `"failed"`; `traceId`: `string`; `version`: `"1"`; \} \| \{ `completedAt`: `string`; `error?`: \{ `code`: `string`; `details?`: [`JsonObject`](sharedos-contracts.md#jsonobject); `message`: `string`; `retryable?`: `boolean`; \}; `events`: `object`[]; `executionId`: `string`; `metadata?`: [`JsonObject`](sharedos-contracts.md#jsonobject); `startedAt`: `string`; `status`: `"cancelled"`; `traceId`: `string`; `version`: `"1"`; \} \| \{ `completedAt`: `string`; `escalation`: \{ `reason`: `string`; `requestedAt`: `string`; `requestedAuthority?`: \{ `capabilities`: `object`[]; `constraints?`: \{ `delegationDepth?`: `number`; `expiresAt?`: `string`; `maxUses?`: `number`; `notBefore?`: `string`; `purposes?`: `string`[]; \}; `id`: `string`; `metadata?`: [`JsonObject`](sharedos-contracts.md#jsonobject); `namespaceId`: `string`; `owner`: \{ `kind`: `"human"`; `userId`: `string`; \} \| \{ `agentId`: `string`; `kind`: `"agent"`; \} \| \{ `conversationId`: `string`; `kind`: `"group"`; \} \| \{ `kind`: `"service"`; `serviceId`: `string`; \}; `purpose`: `string`; `requestedAt`: `string`; `requester`: \{ `kind`: `"human"`; `userId`: `string`; \} \| \{ `agentId`: `string`; `kind`: `"agent"`; \} \| \{ `conversationId`: `string`; `kind`: `"group"`; \} \| \{ `kind`: `"service"`; `serviceId`: `string`; \}; \}; `reviewer`: \{ `kind`: `"human"`; `userId`: `string`; \} \| \{ `agentId`: `string`; `kind`: `"agent"`; \} \| \{ `conversationId`: `string`; `kind`: `"group"`; \} \| \{ `kind`: `"service"`; `serviceId`: `string`; \}; `status`: `"pending"`; \}; `events`: `object`[]; `executionId`: `string`; `metadata?`: [`JsonObject`](sharedos-contracts.md#jsonobject); `startedAt`: `string`; `status`: `"escalated"`; `traceId`: `string`; `version`: `"1"`; \}\> -Defined in: [packages/runtime/src/executor.ts:109](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/runtime/src/executor.ts#L109) +Defined in: [packages/runtime/src/executor.ts:112](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/runtime/src/executor.ts#L112) ###### Parameters @@ -1006,7 +1006,7 @@ Defined in: [packages/runtime/src/standard-runtime.ts:35](https://github.com/Aic ### RuntimeTurnRequest -> **RuntimeTurnRequest** = `Omit`\<[`ExecutionRequest`](sharedos-contracts.md#executionrequest), `"context"`> \> & `object` +> **RuntimeTurnRequest** = `Omit`\<[`ExecutionRequest`](sharedos-contracts.md#executionrequest), `"context"`> > \> & `object` Defined in: [packages/runtime/src/runtime-plugin.ts:120](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/runtime/src/runtime-plugin.ts#L120) @@ -1066,9 +1066,9 @@ turn's identifiers are not the ones a mediated call has. ### TurnKernel -> **TurnKernel** = `Pick`\<[`SharedOSKernel`](sharedos-core.md#sharedoskernel), `"admitTurn"` \| `"reach"` \| `"listTools"` \| `"invokeTool"` \| `"openTurnAuthority"` \| `"recordEscalation"` \| `"recordTurnEnd"` \| `"recordRefusedCall"`>\> +> **TurnKernel** = `Pick`\<[`SharedOSKernel`](sharedos-core.md#sharedoskernel), `"replayProtection"` \| `"admitTurn"` \| `"reach"` \| `"listTools"` \| `"invokeTool"` \| `"openTurnAuthority"` \| `"recordEscalation"` \| `"recordTurnEnd"` \| `"recordRefusedCall"`>\> -Defined in: [packages/runtime/src/executor.ts:127](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/runtime/src/executor.ts#L127) +Defined in: [packages/runtime/src/executor.ts:130](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/runtime/src/executor.ts#L130) The minimal deny-by-default kernel surface required by a turn executor. @@ -1226,7 +1226,7 @@ Defined in: [packages/runtime/src/standard-runtime.ts:155](https://github.com/Ai > `const` **TURN\_DRAINING**: `"turn_draining"` = `"turn_draining"` -Defined in: [packages/runtime/src/executor.ts:1037](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/runtime/src/executor.ts#L1037) +Defined in: [packages/runtime/src/executor.ts:1099](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/runtime/src/executor.ts#L1099) The code a new tool call is refused under once the turn takes nothing new. @@ -1517,7 +1517,7 @@ offers the same hook. > **terminalSource**(`events`): `"envelope"` \| `"runtime"` \| `undefined` -Defined in: [packages/runtime/src/executor.ts:1100](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/runtime/src/executor.ts#L1100) +Defined in: [packages/runtime/src/executor.ts:1162](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/runtime/src/executor.ts#L1162) Whether the envelope refused the turn or the runtime reported its own failure. diff --git a/docs/api/sharedos-testkit.md b/docs/api/sharedos-testkit.md index e32afc30..0e6f10c1 100644 --- a/docs/api/sharedos-testkit.md +++ b/docs/api/sharedos-testkit.md @@ -25,11 +25,24 @@ host store would, and throw on an id the store does not hold. SharedOS is currently a `1.0.0` preview. +## Replay storage + +`SharedOSKernelOptions.replayStore` enables the kernel-owned replay state machine +for execution, tool, resource and message IDs. Hosts implement `ReplayStore` with +atomic durable claims and fenced settlement. Keep identity tombstones after +result expiry; never reclaim pending operations solely because a timeout passed. +See [ADR 0028](https://github.com/systemind-team/SharedOS/blob/main/docs/adr/0028-durable-operation-replay.md). + +`InMemoryReplayStore` is exported by `@aicoo/sharedos-testkit` for isolated tests. +It is not durable or suitable for production. Protocol v1 without a configured +store preserves legacy unprotected behavior. External effects still require +provider idempotency or reconciliation for crash ambiguity. + ## Classes ### InMemoryAuditSink -Defined in: [testkit/src/index.ts:31](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/testkit/src/index.ts#L31) +Defined in: [testkit/src/index.ts:32](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/testkit/src/index.ts#L32) #### Implements @@ -49,7 +62,7 @@ Defined in: [testkit/src/index.ts:31](https://github.com/Aicoo-Team/SharedOS/blo | Property | Modifier | Type | Default value | Defined in | | ------------------------------------- | ---------- | ---------- | ------------- | ------------------------------------------------------------------------------------------------------------- | -| `events` | `readonly` | `object`[] | `[]` | [testkit/src/index.ts:32](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/testkit/src/index.ts#L32) | +| `events` | `readonly` | `object`[] | `[]` | [testkit/src/index.ts:33](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/testkit/src/index.ts#L33) | #### Methods @@ -57,13 +70,13 @@ Defined in: [testkit/src/index.ts:31](https://github.com/Aicoo-Team/SharedOS/blo > **record**(`event`): `Promise`\<`void`>\> -Defined in: [testkit/src/index.ts:34](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/testkit/src/index.ts#L34) +Defined in: [testkit/src/index.ts:35](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/testkit/src/index.ts#L35) ###### Parameters | Parameter | Type | | ------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `event` | \{ `action?`: `string`; `actor`: \{ `kind`: `"human"`; `userId`: `string`; \} \| \{ `agentId`: `string`; `kind`: `"agent"`; \} \| \{ `conversationId`: `string`; `kind`: `"group"`; \} \| \{ `kind`: `"service"`; `serviceId`: `string`; \}; `at`: `string`; `authority`: \{ `kind`: `"human"`; `userId`: `string`; \} \| \{ `agentId`: `string`; `kind`: `"agent"`; \} \| \{ `conversationId`: `string`; `kind`: `"group"`; \} \| \{ `kind`: `"service"`; `serviceId`: `string`; \}; `authorityHash?`: `string`; `cause?`: `string`; `consumed?`: `boolean`; `endedBy?`: `"envelope"` \| `"runtime"`; `failClosed?`: `boolean`; `grantId?`: `string`; `id`: `string`; `messageId?`: `string`; `metadata?`: [`JsonObject`](sharedos-contracts.md#jsonobject); `namespaceId`: `string`; `operationId?`: `string`; `outcome`: `"denied"` \| `"failed"` \| `"succeeded"` \| `"allowed"` \| `"escalated"` \| `"interrupted"`; `owner`: \{ `kind`: `"human"`; `userId`: `string`; \} \| \{ `agentId`: `string`; `kind`: `"agent"`; \} \| \{ `conversationId`: `string`; `kind`: `"group"`; \} \| \{ `kind`: `"service"`; `serviceId`: `string`; \}; `purpose`: `string`; `reason?`: `string`; `receiver?`: \{ `kind`: `"human"`; `userId`: `string`; \} \| \{ `agentId`: `string`; `kind`: `"agent"`; \} \| \{ `conversationId`: `string`; `kind`: `"group"`; \} \| \{ `kind`: `"service"`; `serviceId`: `string`; \}; `requestedAuthority?`: \{ `capabilities`: `object`[]; `constraints?`: \{ `delegationDepth?`: `number`; `expiresAt?`: `string`; `maxUses?`: `number`; `notBefore?`: `string`; `purposes?`: `string`[]; \}; `id`: `string`; `metadata?`: [`JsonObject`](sharedos-contracts.md#jsonobject); `namespaceId`: `string`; `owner`: \{ `kind`: `"human"`; `userId`: `string`; \} \| \{ `agentId`: `string`; `kind`: `"agent"`; \} \| \{ `conversationId`: `string`; `kind`: `"group"`; \} \| \{ `kind`: `"service"`; `serviceId`: `string`; \}; `purpose`: `string`; `requestedAt`: `string`; `requester`: \{ `kind`: `"human"`; `userId`: `string`; \} \| \{ `agentId`: `string`; `kind`: `"agent"`; \} \| \{ `conversationId`: `string`; `kind`: `"group"`; \} \| \{ `kind`: `"service"`; `serviceId`: `string`; \}; \}; `resource?`: \{ `namespace`: `string`; `owner?`: \{ `kind`: `"human"`; `userId`: `string`; \} \| \{ `agentId`: `string`; `kind`: `"agent"`; \} \| \{ `conversationId`: `string`; `kind`: `"group"`; \} \| \{ `kind`: `"service"`; `serviceId`: `string`; \}; `path`: `string`[]; \}; `source?`: `"envelope"` \| `"kernel"`; `tool?`: `string`; `traceId`: `string`; `type`: `"authorization.checked"` \| `"authority.resolved"` \| `"escalation.requested"` \| `"escalation.auto_decided"` \| `"resource.invoked"` \| `"tool.catalog.listed"` \| `"tool.namespace.catalog.listed"` \| `"tool.namespace.selection.updated"` \| `"tool.invoked"` \| `"message.sent"` \| `"turn.ended"`; `version`: `"1"`; \} | +| `event` | \{ `action?`: `string`; `actor`: \{ `kind`: `"human"`; `userId`: `string`; \} \| \{ `agentId`: `string`; `kind`: `"agent"`; \} \| \{ `conversationId`: `string`; `kind`: `"group"`; \} \| \{ `kind`: `"service"`; `serviceId`: `string`; \}; `at`: `string`; `authority`: \{ `kind`: `"human"`; `userId`: `string`; \} \| \{ `agentId`: `string`; `kind`: `"agent"`; \} \| \{ `conversationId`: `string`; `kind`: `"group"`; \} \| \{ `kind`: `"service"`; `serviceId`: `string`; \}; `authorityHash?`: `string`; `cause?`: `string`; `consumed?`: `boolean`; `endedBy?`: `"envelope"` \| `"runtime"`; `failClosed?`: `boolean`; `grantId?`: `string`; `id`: `string`; `messageId?`: `string`; `metadata?`: [`JsonObject`](sharedos-contracts.md#jsonobject); `namespaceId`: `string`; `operationId?`: `string`; `outcome`: `"failed"` \| `"interrupted"` \| `"denied"` \| `"succeeded"` \| `"allowed"` \| `"escalated"`; `owner`: \{ `kind`: `"human"`; `userId`: `string`; \} \| \{ `agentId`: `string`; `kind`: `"agent"`; \} \| \{ `conversationId`: `string`; `kind`: `"group"`; \} \| \{ `kind`: `"service"`; `serviceId`: `string`; \}; `purpose`: `string`; `reason?`: `string`; `receiver?`: \{ `kind`: `"human"`; `userId`: `string`; \} \| \{ `agentId`: `string`; `kind`: `"agent"`; \} \| \{ `conversationId`: `string`; `kind`: `"group"`; \} \| \{ `kind`: `"service"`; `serviceId`: `string`; \}; `requestedAuthority?`: \{ `capabilities`: `object`[]; `constraints?`: \{ `delegationDepth?`: `number`; `expiresAt?`: `string`; `maxUses?`: `number`; `notBefore?`: `string`; `purposes?`: `string`[]; \}; `id`: `string`; `metadata?`: [`JsonObject`](sharedos-contracts.md#jsonobject); `namespaceId`: `string`; `owner`: \{ `kind`: `"human"`; `userId`: `string`; \} \| \{ `agentId`: `string`; `kind`: `"agent"`; \} \| \{ `conversationId`: `string`; `kind`: `"group"`; \} \| \{ `kind`: `"service"`; `serviceId`: `string`; \}; `purpose`: `string`; `requestedAt`: `string`; `requester`: \{ `kind`: `"human"`; `userId`: `string`; \} \| \{ `agentId`: `string`; `kind`: `"agent"`; \} \| \{ `conversationId`: `string`; `kind`: `"group"`; \} \| \{ `kind`: `"service"`; `serviceId`: `string`; \}; \}; `resource?`: \{ `namespace`: `string`; `owner?`: \{ `kind`: `"human"`; `userId`: `string`; \} \| \{ `agentId`: `string`; `kind`: `"agent"`; \} \| \{ `conversationId`: `string`; `kind`: `"group"`; \} \| \{ `kind`: `"service"`; `serviceId`: `string`; \}; `path`: `string`[]; \}; `source?`: `"envelope"` \| `"kernel"`; `tool?`: `string`; `traceId`: `string`; `type`: `"authorization.checked"` \| `"authority.resolved"` \| `"escalation.requested"` \| `"escalation.auto_decided"` \| `"resource.invoked"` \| `"tool.catalog.listed"` \| `"tool.namespace.catalog.listed"` \| `"tool.namespace.selection.updated"` \| `"tool.invoked"` \| `"message.sent"` \| `"turn.ended"`; `version`: `"1"`; \} | | `event.action?` | `string` | | `event.actor` | \{ `kind`: `"human"`; `userId`: `string`; \} \| \{ `agentId`: `string`; `kind`: `"agent"`; \} \| \{ `conversationId`: `string`; `kind`: `"group"`; \} \| \{ `kind`: `"service"`; `serviceId`: `string`; \} | | `event.at` | `string` | @@ -79,7 +92,7 @@ Defined in: [testkit/src/index.ts:34](https://github.com/Aicoo-Team/SharedOS/blo | `event.metadata?` | [`JsonObject`](sharedos-contracts.md#jsonobject) | | `event.namespaceId` | `string` | | `event.operationId?` | `string` | -| `event.outcome` | `"denied"` \| `"failed"` \| `"succeeded"` \| `"allowed"` \| `"escalated"` \| `"interrupted"` | +| `event.outcome` | `"failed"` \| `"interrupted"` \| `"denied"` \| `"succeeded"` \| `"allowed"` \| `"escalated"` | | `event.owner` | \{ `kind`: `"human"`; `userId`: `string`; \} \| \{ `agentId`: `string`; `kind`: `"agent"`; \} \| \{ `conversationId`: `string`; `kind`: `"group"`; \} \| \{ `kind`: `"service"`; `serviceId`: `string`; \} | | `event.purpose` | `string` | | `event.reason?` | `string` | @@ -121,7 +134,7 @@ Defined in: [testkit/src/index.ts:34](https://github.com/Aicoo-Team/SharedOS/blo ### InMemoryDelegationChainResolver -Defined in: [testkit/src/index.ts:190](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/testkit/src/index.ts#L190) +Defined in: [testkit/src/index.ts:191](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/testkit/src/index.ts#L191) Namespace-scoped ancestor lookup for delegated-grant fixtures. @@ -135,7 +148,7 @@ Namespace-scoped ancestor lookup for delegated-grant fixtures. > **new InMemoryDelegationChainResolver**(`grants?`): [`InMemoryDelegationChainResolver`](#inmemorydelegationchainresolver) -Defined in: [testkit/src/index.ts:193](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/testkit/src/index.ts#L193) +Defined in: [testkit/src/index.ts:194](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/testkit/src/index.ts#L194) ###### Parameters @@ -153,7 +166,7 @@ Defined in: [testkit/src/index.ts:193](https://github.com/Aicoo-Team/SharedOS/bl > **add**(`grant`): `this` -Defined in: [testkit/src/index.ts:199](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/testkit/src/index.ts#L199) +Defined in: [testkit/src/index.ts:200](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/testkit/src/index.ts#L200) ###### Parameters @@ -184,7 +197,7 @@ Defined in: [testkit/src/index.ts:199](https://github.com/Aicoo-Team/SharedOS/bl > **expire**(`namespaceId`, `grantId`, `expiresAt`): `this` -Defined in: [testkit/src/index.ts:220](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/testkit/src/index.ts#L220) +Defined in: [testkit/src/index.ts:221](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/testkit/src/index.ts#L221) Move a grant's expiry in place, as [InMemoryGrantSource.expire](#expire-1) does. @@ -204,7 +217,7 @@ Move a grant's expiry in place, as [InMemoryGrantSource.expire](#expire-1) does. > **resolve**(`namespaceId`, `grantId`): `Promise`\<\{ `capabilities`: `object`[]; `constraints`: \{ `delegationDepth?`: `number`; `expiresAt?`: `string`; `maxUses?`: `number`; `notBefore?`: `string`; `purposes?`: `string`[]; \}; `id`: `string`; `issuedAt`: `string`; `issuer`: \{ `kind`: `"human"`; `userId`: `string`; \} \| \{ `agentId`: `string`; `kind`: `"agent"`; \} \| \{ `conversationId`: `string`; `kind`: `"group"`; \} \| \{ `kind`: `"service"`; `serviceId`: `string`; \}; `metadata?`: [`JsonObject`](sharedos-contracts.md#jsonobject); `namespaceId`: `string`; `parentGrantId?`: `string`; `revokedAt?`: `string`; `subject`: \{ `kind`: `"human"`; `userId`: `string`; \} \| \{ `agentId`: `string`; `kind`: `"agent"`; \} \| \{ `conversationId`: `string`; `kind`: `"group"`; \} \| \{ `kind`: `"service"`; `serviceId`: `string`; \}; \} \| `undefined`>\> -Defined in: [testkit/src/index.ts:231](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/testkit/src/index.ts#L231) +Defined in: [testkit/src/index.ts:232](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/testkit/src/index.ts#L232) ###### Parameters @@ -225,7 +238,7 @@ Defined in: [testkit/src/index.ts:231](https://github.com/Aicoo-Team/SharedOS/bl > **revoke**(`namespaceId`, `grantId`, `revokedAt`): `this` -Defined in: [testkit/src/index.ts:210](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/testkit/src/index.ts#L210) +Defined in: [testkit/src/index.ts:211](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/testkit/src/index.ts#L211) Record a revocation the way a host grant store would, in place. @@ -245,7 +258,7 @@ Record a revocation the way a host grant store would, in place. ### InMemoryGrantSource -Defined in: [testkit/src/index.ts:130](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/testkit/src/index.ts#L130) +Defined in: [testkit/src/index.ts:131](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/testkit/src/index.ts#L131) A host grant store fixture. @@ -263,7 +276,7 @@ authority inside its namespace, which is the contract every production > **new InMemoryGrantSource**(`grants?`): [`InMemoryGrantSource`](#inmemorygrantsource) -Defined in: [testkit/src/index.ts:133](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/testkit/src/index.ts#L133) +Defined in: [testkit/src/index.ts:134](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/testkit/src/index.ts#L134) ###### Parameters @@ -281,7 +294,7 @@ Defined in: [testkit/src/index.ts:133](https://github.com/Aicoo-Team/SharedOS/bl > **add**(...`grants`): `this` -Defined in: [testkit/src/index.ts:139](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/testkit/src/index.ts#L139) +Defined in: [testkit/src/index.ts:140](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/testkit/src/index.ts#L140) ###### Parameters @@ -297,7 +310,7 @@ Defined in: [testkit/src/index.ts:139](https://github.com/Aicoo-Team/SharedOS/bl > **expire**(`grantId`, `expiresAt`): `this` -Defined in: [testkit/src/index.ts:158](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/testkit/src/index.ts#L158) +Defined in: [testkit/src/index.ts:159](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/testkit/src/index.ts#L159) Move a grant's expiry, the way a host store would record a shortened window. @@ -316,7 +329,7 @@ Move a grant's expiry, the way a host store would record a shortened window. > **load**(`context`): `Promise`\ -Defined in: [testkit/src/index.ts:168](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/testkit/src/index.ts#L168) +Defined in: [testkit/src/index.ts:169](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/testkit/src/index.ts#L169) ###### Parameters @@ -344,7 +357,7 @@ Defined in: [testkit/src/index.ts:168](https://github.com/Aicoo-Team/SharedOS/bl > **revoke**(`grantId`, `revokedAt`): `this` -Defined in: [testkit/src/index.ts:147](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/testkit/src/index.ts#L147) +Defined in: [testkit/src/index.ts:148](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/testkit/src/index.ts#L148) Record a revocation the way a host store would, without deleting history. @@ -363,7 +376,7 @@ Record a revocation the way a host store would, without deleting history. ### InMemoryMessageRequestRouter -Defined in: [testkit/src/index.ts:63](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/testkit/src/index.ts#L63) +Defined in: [testkit/src/index.ts:64](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/testkit/src/index.ts#L64) Answers a request from the transport's own log of accepted deliveries. @@ -382,7 +395,7 @@ has no reply to give, and asking for one throws. > **new InMemoryMessageRequestRouter**(`transport`): [`InMemoryMessageRequestRouter`](#inmemorymessagerequestrouter) -Defined in: [testkit/src/index.ts:66](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/testkit/src/index.ts#L66) +Defined in: [testkit/src/index.ts:67](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/testkit/src/index.ts#L67) ###### Parameters @@ -400,7 +413,7 @@ Defined in: [testkit/src/index.ts:66](https://github.com/Aicoo-Team/SharedOS/blo > **resolveReply**(`context`, `request`, `delivery`): `Promise`\<\{ `createdAt`: `string`; `id`: `string`; `payload`: [`JsonValue`](sharedos-contracts.md#jsonvalue); `provenance?`: \{ `metadata?`: [`JsonObject`](sharedos-contracts.md#jsonobject); `parentIds`: `string`[]; `source`: `string`; \}; `purpose`: `string`; `receiver`: \{ `kind`: `"human"`; `userId`: `string`; \} \| \{ `agentId`: `string`; `kind`: `"agent"`; \} \| \{ `conversationId`: `string`; `kind`: `"group"`; \} \| \{ `kind`: `"service"`; `serviceId`: `string`; \}; `replyTo?`: `string`; `sender`: \{ `kind`: `"human"`; `userId`: `string`; \} \| \{ `agentId`: `string`; `kind`: `"agent"`; \} \| \{ `conversationId`: `string`; `kind`: `"group"`; \} \| \{ `kind`: `"service"`; `serviceId`: `string`; \}; `traceId`: `string`; `version`: `"1"`; \}\> -Defined in: [testkit/src/index.ts:70](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/testkit/src/index.ts#L70) +Defined in: [testkit/src/index.ts:71](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/testkit/src/index.ts#L71) ###### Parameters @@ -443,7 +456,7 @@ Defined in: [testkit/src/index.ts:70](https://github.com/Aicoo-Team/SharedOS/blo ### InMemoryMessageTransport -Defined in: [testkit/src/index.ts:39](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/testkit/src/index.ts#L39) +Defined in: [testkit/src/index.ts:40](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/testkit/src/index.ts#L40) #### Implements @@ -463,7 +476,7 @@ Defined in: [testkit/src/index.ts:39](https://github.com/Aicoo-Team/SharedOS/blo | Property | Modifier | Type | Default value | Defined in | | --------------------------------------------- | ---------- | ---------- | ------------- | ------------------------------------------------------------------------------------------------------------- | -| `deliveries` | `readonly` | `object`[] | `[]` | [testkit/src/index.ts:40](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/testkit/src/index.ts#L40) | +| `deliveries` | `readonly` | `object`[] | `[]` | [testkit/src/index.ts:41](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/testkit/src/index.ts#L41) | #### Methods @@ -471,7 +484,7 @@ Defined in: [testkit/src/index.ts:39](https://github.com/Aicoo-Team/SharedOS/blo > **deliver**(`context`, `envelope`): `Promise`\<\{ `messageId`: `string`; `metadata?`: [`JsonObject`](sharedos-contracts.md#jsonobject); `status`: `"accepted"`; `timestamp`: `string`; \} \| \{ `messageId`: `string`; `metadata?`: [`JsonObject`](sharedos-contracts.md#jsonobject); `status`: `"delivered"`; `timestamp`: `string`; \} \| \{ `error`: \{ `code`: `string`; `details?`: [`JsonObject`](sharedos-contracts.md#jsonobject); `message`: `string`; `retryable?`: `boolean`; \}; `messageId`: `string`; `metadata?`: [`JsonObject`](sharedos-contracts.md#jsonobject); `status`: `"denied"`; `timestamp`: `string`; \} \| \{ `error`: \{ `code`: `string`; `details?`: [`JsonObject`](sharedos-contracts.md#jsonobject); `message`: `string`; `retryable?`: `boolean`; \}; `messageId`: `string`; `metadata?`: [`JsonObject`](sharedos-contracts.md#jsonobject); `status`: `"failed"`; `timestamp`: `string`; \}\> -Defined in: [testkit/src/index.ts:42](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/testkit/src/index.ts#L42) +Defined in: [testkit/src/index.ts:43](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/testkit/src/index.ts#L43) ###### Parameters @@ -511,9 +524,112 @@ Defined in: [testkit/src/index.ts:42](https://github.com/Aicoo-Team/SharedOS/blo --- +### InMemoryReplayStore + +Defined in: [testkit/src/replay.ts:6](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/testkit/src/replay.ts#L6) + +Isolated test fixture. Not durable and never suitable for production. + +#### Implements + +- [`ReplayStore`](sharedos-core.md#replaystore) + +#### Constructors + +##### Constructor + +> **new InMemoryReplayStore**(): [`InMemoryReplayStore`](#inmemoryreplaystore) + +###### Returns + +[`InMemoryReplayStore`](#inmemoryreplaystore) + +#### Methods + +##### claim() + +> **claim**(`key`, `fingerprint`): `Promise`\<\{ `claimed`: `boolean`; `record`: \{ `fingerprint`: `string`; `key`: \{ `id`: `string`; `kind`: `"resource"` \| `"message"` \| `"execution"` \| `"tool"`; `namespaceId`: `string`; `scope`: `string`; \}; `result?`: [`JsonValue`](sharedos-contracts.md#jsonvalue); `state`: `"completed"` \| `"failed"` \| `"interrupted"` \| `"pending"` \| `"expired"`; `token`: `string`; \}; \}\> + +Defined in: [testkit/src/replay.ts:9](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/testkit/src/replay.ts#L9) + +Insert pending or return the existing record, including a conflicting fingerprint. + +###### Parameters + +| Parameter | Type | +| ----------------- | ----------------------------------------------------------------------------------------------------------------------------------- | +| `key` | \{ `id`: `string`; `kind`: `"resource"` \| `"message"` \| `"execution"` \| `"tool"`; `namespaceId`: `string`; `scope`: `string`; \} | +| `key.id` | `string` | +| `key.kind` | `"resource"` \| `"message"` \| `"execution"` \| `"tool"` | +| `key.namespaceId` | `string` | +| `key.scope` | `string` | +| `fingerprint` | `string` | + +###### Returns + +`Promise`\<\{ `claimed`: `boolean`; `record`: \{ `fingerprint`: `string`; `key`: \{ `id`: `string`; `kind`: `"resource"` \| `"message"` \| `"execution"` \| `"tool"`; `namespaceId`: `string`; `scope`: `string`; \}; `result?`: [`JsonValue`](sharedos-contracts.md#jsonvalue); `state`: `"completed"` \| `"failed"` \| `"interrupted"` \| `"pending"` \| `"expired"`; `token`: `string`; \}; \}\> + +###### Implementation of + +[`ReplayStore`](sharedos-core.md#replaystore).[`claim`](sharedos-core.md#claim) + +##### expire() + +> **expire**(`key`): `boolean` + +Defined in: [testkit/src/replay.ts:46](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/testkit/src/replay.ts#L46) + +Drop a sensitive result body while preserving its unclaimable identity. + +###### Parameters + +| Parameter | Type | +| ----------------- | ----------------------------------------------------------------------------------------------------------------------------------- | +| `key` | \{ `id`: `string`; `kind`: `"resource"` \| `"message"` \| `"execution"` \| `"tool"`; `namespaceId`: `string`; `scope`: `string`; \} | +| `key.id` | `string` | +| `key.kind` | `"resource"` \| `"message"` \| `"execution"` \| `"tool"` | +| `key.namespaceId` | `string` | +| `key.scope` | `string` | + +###### Returns + +`boolean` + +##### settle() + +> **settle**(`key`, `token`, `outcome`): `Promise`\<`boolean`>\> + +Defined in: [testkit/src/replay.ts:26](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/testkit/src/replay.ts#L26) + +CAS pending -> terminal, fenced by token. False means ownership was lost. + +###### Parameters + +| Parameter | Type | +| ----------------- | ----------------------------------------------------------------------------------------------------------------------------------- | +| `key` | \{ `id`: `string`; `kind`: `"resource"` \| `"message"` \| `"execution"` \| `"tool"`; `namespaceId`: `string`; `scope`: `string`; \} | +| `key.id` | `string` | +| `key.kind` | `"resource"` \| `"message"` \| `"execution"` \| `"tool"` | +| `key.namespaceId` | `string` | +| `key.scope` | `string` | +| `token` | `string` | +| `outcome` | \{ `result?`: [`JsonValue`](sharedos-contracts.md#jsonvalue); `state`: `"completed"` \| `"failed"` \| `"interrupted"`; \} | +| `outcome.result?` | [`JsonValue`](sharedos-contracts.md#jsonvalue) | +| `outcome.state` | `"completed"` \| `"failed"` \| `"interrupted"` | + +###### Returns + +`Promise`\<`boolean`\> + +###### Implementation of + +[`ReplayStore`](sharedos-core.md#replaystore).[`settle`](sharedos-core.md#settle) + +--- + ### InMemoryResourceProvider -Defined in: [testkit/src/index.ts:270](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/testkit/src/index.ts#L270) +Defined in: [testkit/src/index.ts:271](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/testkit/src/index.ts#L271) A host-neutral recording provider for examples, conformance tests, and isolated experiment worlds. @@ -527,7 +643,7 @@ A host-neutral recording provider for examples, conformance tests, and isolated > **new InMemoryResourceProvider**(`namespace`, `handler?`): [`InMemoryResourceProvider`](#inmemoryresourceprovider) -Defined in: [testkit/src/index.ts:275](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/testkit/src/index.ts#L275) +Defined in: [testkit/src/index.ts:276](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/testkit/src/index.ts#L276) ###### Parameters @@ -544,8 +660,8 @@ Defined in: [testkit/src/index.ts:275](https://github.com/Aicoo-Team/SharedOS/bl | Property | Modifier | Type | Default value | Defined in | | --------------------------------------------- | ---------- | ---------- | ------------- | --------------------------------------------------------------------------------------------------------------- | -| `namespace` | `readonly` | `string` | `undefined` | [testkit/src/index.ts:271](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/testkit/src/index.ts#L271) | -| `operations` | `readonly` | `object`[] | `[]` | [testkit/src/index.ts:272](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/testkit/src/index.ts#L272) | +| `namespace` | `readonly` | `string` | `undefined` | [testkit/src/index.ts:272](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/testkit/src/index.ts#L272) | +| `operations` | `readonly` | `object`[] | `[]` | [testkit/src/index.ts:273](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/testkit/src/index.ts#L273) | #### Methods @@ -553,7 +669,7 @@ Defined in: [testkit/src/index.ts:275](https://github.com/Aicoo-Team/SharedOS/bl > **invoke**(`operation`): `Promise`\<\{ `completedAt`: `string`; `metadata?`: [`JsonObject`](sharedos-contracts.md#jsonobject); `operationId`: `string`; `output`: [`JsonValue`](sharedos-contracts.md#jsonvalue); `status`: `"succeeded"`; \} \| \{ `completedAt`: `string`; `error`: \{ `code`: `string`; `details?`: [`JsonObject`](sharedos-contracts.md#jsonobject); `message`: `string`; `retryable?`: `boolean`; \}; `metadata?`: [`JsonObject`](sharedos-contracts.md#jsonobject); `operationId`: `string`; `status`: `"denied"`; \} \| \{ `completedAt`: `string`; `error`: \{ `code`: `string`; `details?`: [`JsonObject`](sharedos-contracts.md#jsonobject); `message`: `string`; `retryable?`: `boolean`; \}; `metadata?`: [`JsonObject`](sharedos-contracts.md#jsonobject); `operationId`: `string`; `status`: `"failed"`; \}\> -Defined in: [testkit/src/index.ts:280](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/testkit/src/index.ts#L280) +Defined in: [testkit/src/index.ts:281](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/testkit/src/index.ts#L281) ###### Parameters @@ -590,7 +706,7 @@ Defined in: [testkit/src/index.ts:280](https://github.com/Aicoo-Team/SharedOS/bl ### InMemoryToolNamespaceSettingsStore -Defined in: [testkit/src/index.ts:97](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/testkit/src/index.ts#L97) +Defined in: [testkit/src/index.ts:98](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/testkit/src/index.ts#L98) Namespace settings fixture keyed by the access-context namespace/world. @@ -604,7 +720,7 @@ Namespace settings fixture keyed by the access-context namespace/world. > **new InMemoryToolNamespaceSettingsStore**(`initial?`): [`InMemoryToolNamespaceSettingsStore`](#inmemorytoolnamespacesettingsstore) -Defined in: [testkit/src/index.ts:100](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/testkit/src/index.ts#L100) +Defined in: [testkit/src/index.ts:101](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/testkit/src/index.ts#L101) ###### Parameters @@ -622,7 +738,7 @@ Defined in: [testkit/src/index.ts:100](https://github.com/Aicoo-Team/SharedOS/bl > **applyUpdate**(`context`, `update`): `Promise`\ -Defined in: [testkit/src/index.ts:106](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/testkit/src/index.ts#L106) +Defined in: [testkit/src/index.ts:107](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/testkit/src/index.ts#L107) ###### Parameters @@ -653,7 +769,7 @@ Defined in: [testkit/src/index.ts:106](https://github.com/Aicoo-Team/SharedOS/bl > **get**(`namespaceId`): readonly `string`[] -Defined in: [testkit/src/index.ts:118](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/testkit/src/index.ts#L118) +Defined in: [testkit/src/index.ts:119](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/testkit/src/index.ts#L119) ###### Parameters @@ -669,7 +785,7 @@ readonly `string`[] ### UnavailableDelegationChainResolver -Defined in: [testkit/src/index.ts:260](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/testkit/src/index.ts#L260) +Defined in: [testkit/src/index.ts:261](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/testkit/src/index.ts#L261) A resolver whose authoritative source is down; every lookup must fail closed. @@ -693,7 +809,7 @@ A resolver whose authoritative source is down; every lookup must fail closed. > **resolve**(): `Promise`\<\{ `capabilities`: `object`[]; `constraints`: \{ `delegationDepth?`: `number`; `expiresAt?`: `string`; `maxUses?`: `number`; `notBefore?`: `string`; `purposes?`: `string`[]; \}; `id`: `string`; `issuedAt`: `string`; `issuer`: \{ `kind`: `"human"`; `userId`: `string`; \} \| \{ `agentId`: `string`; `kind`: `"agent"`; \} \| \{ `conversationId`: `string`; `kind`: `"group"`; \} \| \{ `kind`: `"service"`; `serviceId`: `string`; \}; `metadata?`: [`JsonObject`](sharedos-contracts.md#jsonobject); `namespaceId`: `string`; `parentGrantId?`: `string`; `revokedAt?`: `string`; `subject`: \{ `kind`: `"human"`; `userId`: `string`; \} \| \{ `agentId`: `string`; `kind`: `"agent"`; \} \| \{ `conversationId`: `string`; `kind`: `"group"`; \} \| \{ `kind`: `"service"`; `serviceId`: `string`; \}; \} \| `undefined`>\> -Defined in: [testkit/src/index.ts:261](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/testkit/src/index.ts#L261) +Defined in: [testkit/src/index.ts:262](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/testkit/src/index.ts#L262) ###### Returns @@ -707,7 +823,7 @@ Defined in: [testkit/src/index.ts:261](https://github.com/Aicoo-Team/SharedOS/bl ### UnavailableGrantSource -Defined in: [testkit/src/index.ts:182](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/testkit/src/index.ts#L182) +Defined in: [testkit/src/index.ts:183](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/testkit/src/index.ts#L183) A grant store that is down; every decision made against it must fail closed. @@ -731,7 +847,7 @@ A grant store that is down; every decision made against it must fail closed. > **load**(): `Promise`\ -Defined in: [testkit/src/index.ts:183](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/testkit/src/index.ts#L183) +Defined in: [testkit/src/index.ts:184](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/testkit/src/index.ts#L184) ###### Returns @@ -745,7 +861,7 @@ Defined in: [testkit/src/index.ts:183](https://github.com/Aicoo-Team/SharedOS/bl ### UnavailableGrantUsageStore -Defined in: [testkit/src/index.ts:247](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/testkit/src/index.ts#L247) +Defined in: [testkit/src/index.ts:248](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/testkit/src/index.ts#L248) A usage store that cannot answer. @@ -775,7 +891,7 @@ execution. > **getUsage**(): `Promise`\<`number`>\> -Defined in: [testkit/src/index.ts:248](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/testkit/src/index.ts#L248) +Defined in: [testkit/src/index.ts:249](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/testkit/src/index.ts#L249) ###### Returns @@ -789,7 +905,7 @@ Defined in: [testkit/src/index.ts:248](https://github.com/Aicoo-Team/SharedOS/bl > **tryConsume**(): `Promise`\<`boolean`>\> -Defined in: [testkit/src/index.ts:253](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/testkit/src/index.ts#L253) +Defined in: [testkit/src/index.ts:254](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/testkit/src/index.ts#L254) ###### Returns @@ -803,73 +919,73 @@ Defined in: [testkit/src/index.ts:253](https://github.com/Aicoo-Team/SharedOS/bl ### TestContextOptions -Defined in: [testkit/src/index.ts:325](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/testkit/src/index.ts#L325) +Defined in: [testkit/src/index.ts:327](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/testkit/src/index.ts#L327) #### Properties | Property | Modifier | Type | Defined in | | -------------------------------------------------------------------- | ---------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- | -| `actor?` | `readonly` | \{ `kind`: `"human"`; `userId`: `string`; \} \| \{ `agentId`: `string`; `kind`: `"agent"`; \} \| \{ `conversationId`: `string`; `kind`: `"group"`; \} \| \{ `kind`: `"service"`; `serviceId`: `string`; \} | [testkit/src/index.ts:326](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/testkit/src/index.ts#L326) | -| `authority?` | `readonly` | \{ `kind`: `"human"`; `userId`: `string`; \} \| \{ `agentId`: `string`; `kind`: `"agent"`; \} \| \{ `conversationId`: `string`; `kind`: `"group"`; \} \| \{ `kind`: `"service"`; `serviceId`: `string`; \} | [testkit/src/index.ts:327](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/testkit/src/index.ts#L327) | -| `enabledToolNamespaces?` | `readonly` | readonly `string`[] | [testkit/src/index.ts:330](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/testkit/src/index.ts#L330) | -| `namespaceId?` | `readonly` | `string` | [testkit/src/index.ts:329](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/testkit/src/index.ts#L329) | -| `now?` | `readonly` | `string` | [testkit/src/index.ts:333](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/testkit/src/index.ts#L333) | -| `owner?` | `readonly` | \{ `kind`: `"human"`; `userId`: `string`; \} \| \{ `agentId`: `string`; `kind`: `"agent"`; \} \| \{ `conversationId`: `string`; `kind`: `"group"`; \} \| \{ `kind`: `"service"`; `serviceId`: `string`; \} | [testkit/src/index.ts:328](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/testkit/src/index.ts#L328) | -| `purpose?` | `readonly` | `string` | [testkit/src/index.ts:331](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/testkit/src/index.ts#L331) | -| `traceId?` | `readonly` | `string` | [testkit/src/index.ts:332](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/testkit/src/index.ts#L332) | +| `actor?` | `readonly` | \{ `kind`: `"human"`; `userId`: `string`; \} \| \{ `agentId`: `string`; `kind`: `"agent"`; \} \| \{ `conversationId`: `string`; `kind`: `"group"`; \} \| \{ `kind`: `"service"`; `serviceId`: `string`; \} | [testkit/src/index.ts:328](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/testkit/src/index.ts#L328) | +| `authority?` | `readonly` | \{ `kind`: `"human"`; `userId`: `string`; \} \| \{ `agentId`: `string`; `kind`: `"agent"`; \} \| \{ `conversationId`: `string`; `kind`: `"group"`; \} \| \{ `kind`: `"service"`; `serviceId`: `string`; \} | [testkit/src/index.ts:329](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/testkit/src/index.ts#L329) | +| `enabledToolNamespaces?` | `readonly` | readonly `string`[] | [testkit/src/index.ts:332](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/testkit/src/index.ts#L332) | +| `namespaceId?` | `readonly` | `string` | [testkit/src/index.ts:331](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/testkit/src/index.ts#L331) | +| `now?` | `readonly` | `string` | [testkit/src/index.ts:335](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/testkit/src/index.ts#L335) | +| `owner?` | `readonly` | \{ `kind`: `"human"`; `userId`: `string`; \} \| \{ `agentId`: `string`; `kind`: `"agent"`; \} \| \{ `conversationId`: `string`; `kind`: `"group"`; \} \| \{ `kind`: `"service"`; `serviceId`: `string`; \} | [testkit/src/index.ts:330](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/testkit/src/index.ts#L330) | +| `purpose?` | `readonly` | `string` | [testkit/src/index.ts:333](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/testkit/src/index.ts#L333) | +| `traceId?` | `readonly` | `string` | [testkit/src/index.ts:334](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/testkit/src/index.ts#L334) | --- ### TestGrantOptions -Defined in: [testkit/src/index.ts:350](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/testkit/src/index.ts#L350) +Defined in: [testkit/src/index.ts:352](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/testkit/src/index.ts#L352) #### Properties | Property | Modifier | Type | Defined in | | -------------------------------------------------------- | ---------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- | -| `capabilities` | `readonly` | readonly `object`[] | [testkit/src/index.ts:355](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/testkit/src/index.ts#L355) | -| `delegationDepth?` | `readonly` | `number` | [testkit/src/index.ts:362](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/testkit/src/index.ts#L362) | -| `expiresAt?` | `readonly` | `string` | [testkit/src/index.ts:359](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/testkit/src/index.ts#L359) | -| `id?` | `readonly` | `string` | [testkit/src/index.ts:351](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/testkit/src/index.ts#L351) | -| `issuedAt?` | `readonly` | `string` | [testkit/src/index.ts:357](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/testkit/src/index.ts#L357) | -| `issuer?` | `readonly` | \{ `kind`: `"human"`; `userId`: `string`; \} \| \{ `agentId`: `string`; `kind`: `"agent"`; \} \| \{ `conversationId`: `string`; `kind`: `"group"`; \} \| \{ `kind`: `"service"`; `serviceId`: `string`; \} | [testkit/src/index.ts:354](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/testkit/src/index.ts#L354) | -| `maxUses?` | `readonly` | `number` | [testkit/src/index.ts:361](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/testkit/src/index.ts#L361) | -| `namespaceId?` | `readonly` | `string` | [testkit/src/index.ts:352](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/testkit/src/index.ts#L352) | -| `notBefore?` | `readonly` | `string` | [testkit/src/index.ts:358](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/testkit/src/index.ts#L358) | -| `parentGrantId?` | `readonly` | `string` | [testkit/src/index.ts:363](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/testkit/src/index.ts#L363) | -| `purposes?` | `readonly` | readonly `string`[] | [testkit/src/index.ts:356](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/testkit/src/index.ts#L356) | -| `revokedAt?` | `readonly` | `string` | [testkit/src/index.ts:360](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/testkit/src/index.ts#L360) | -| `subject?` | `readonly` | \{ `kind`: `"human"`; `userId`: `string`; \} \| \{ `agentId`: `string`; `kind`: `"agent"`; \} \| \{ `conversationId`: `string`; `kind`: `"group"`; \} \| \{ `kind`: `"service"`; `serviceId`: `string`; \} | [testkit/src/index.ts:353](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/testkit/src/index.ts#L353) | +| `capabilities` | `readonly` | readonly `object`[] | [testkit/src/index.ts:357](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/testkit/src/index.ts#L357) | +| `delegationDepth?` | `readonly` | `number` | [testkit/src/index.ts:364](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/testkit/src/index.ts#L364) | +| `expiresAt?` | `readonly` | `string` | [testkit/src/index.ts:361](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/testkit/src/index.ts#L361) | +| `id?` | `readonly` | `string` | [testkit/src/index.ts:353](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/testkit/src/index.ts#L353) | +| `issuedAt?` | `readonly` | `string` | [testkit/src/index.ts:359](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/testkit/src/index.ts#L359) | +| `issuer?` | `readonly` | \{ `kind`: `"human"`; `userId`: `string`; \} \| \{ `agentId`: `string`; `kind`: `"agent"`; \} \| \{ `conversationId`: `string`; `kind`: `"group"`; \} \| \{ `kind`: `"service"`; `serviceId`: `string`; \} | [testkit/src/index.ts:356](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/testkit/src/index.ts#L356) | +| `maxUses?` | `readonly` | `number` | [testkit/src/index.ts:363](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/testkit/src/index.ts#L363) | +| `namespaceId?` | `readonly` | `string` | [testkit/src/index.ts:354](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/testkit/src/index.ts#L354) | +| `notBefore?` | `readonly` | `string` | [testkit/src/index.ts:360](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/testkit/src/index.ts#L360) | +| `parentGrantId?` | `readonly` | `string` | [testkit/src/index.ts:365](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/testkit/src/index.ts#L365) | +| `purposes?` | `readonly` | readonly `string`[] | [testkit/src/index.ts:358](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/testkit/src/index.ts#L358) | +| `revokedAt?` | `readonly` | `string` | [testkit/src/index.ts:362](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/testkit/src/index.ts#L362) | +| `subject?` | `readonly` | \{ `kind`: `"human"`; `userId`: `string`; \} \| \{ `agentId`: `string`; `kind`: `"agent"`; \} \| \{ `conversationId`: `string`; `kind`: `"group"`; \} \| \{ `kind`: `"service"`; `serviceId`: `string`; \} | [testkit/src/index.ts:355](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/testkit/src/index.ts#L355) | --- ### TestKernel -Defined in: [testkit/src/index.ts:286](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/testkit/src/index.ts#L286) +Defined in: [testkit/src/index.ts:287](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/testkit/src/index.ts#L287) #### Properties | Property | Modifier | Type | Description | Defined in | | ----------------------------------------- | ---------- | ------------------------------------------------------- | -------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- | -| `audit` | `readonly` | [`InMemoryAuditSink`](#inmemoryauditsink) | - | [testkit/src/index.ts:288](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/testkit/src/index.ts#L288) | -| `grants` | `readonly` | [`InMemoryGrantSource`](#inmemorygrantsource) | The trusted store the kernel loads authority from; mutate it to grant or revoke. | [testkit/src/index.ts:291](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/testkit/src/index.ts#L291) | -| `kernel` | `readonly` | [`SharedOSKernel`](sharedos-core.md#sharedoskernel) | - | [testkit/src/index.ts:287](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/testkit/src/index.ts#L287) | -| `messages` | `readonly` | [`InMemoryMessageTransport`](#inmemorymessagetransport) | - | [testkit/src/index.ts:289](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/testkit/src/index.ts#L289) | +| `audit` | `readonly` | [`InMemoryAuditSink`](#inmemoryauditsink) | - | [testkit/src/index.ts:289](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/testkit/src/index.ts#L289) | +| `grants` | `readonly` | [`InMemoryGrantSource`](#inmemorygrantsource) | The trusted store the kernel loads authority from; mutate it to grant or revoke. | [testkit/src/index.ts:292](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/testkit/src/index.ts#L292) | +| `kernel` | `readonly` | [`SharedOSKernel`](sharedos-core.md#sharedoskernel) | - | [testkit/src/index.ts:288](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/testkit/src/index.ts#L288) | +| `messages` | `readonly` | [`InMemoryMessageTransport`](#inmemorymessagetransport) | - | [testkit/src/index.ts:290](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/testkit/src/index.ts#L290) | --- ### TestKernelOptions -Defined in: [testkit/src/index.ts:294](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/testkit/src/index.ts#L294) +Defined in: [testkit/src/index.ts:295](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/testkit/src/index.ts#L295) #### Properties | Property | Modifier | Type | Description | Defined in | | -------------------------------------------------------------- | ---------- | --------------------------------------------------------------------- | --------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- | -| `delegationResolver?` | `readonly` | [`DelegationChainResolver`](sharedos-core.md#delegationchainresolver) | Installs ancestor validation so delegated grants can be exercised. | [testkit/src/index.ts:300](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/testkit/src/index.ts#L300) | -| `grants?` | `readonly` | readonly `object`[] | Seed authority for the kernel's trusted grant source. | [testkit/src/index.ts:296](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/testkit/src/index.ts#L296) | -| `grantSource?` | `readonly` | [`GrantSource`](sharedos-core.md#grantsource) | Replaces the trusted grant source, for example to exercise an outage. | [testkit/src/index.ts:298](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/testkit/src/index.ts#L298) | +| `delegationResolver?` | `readonly` | [`DelegationChainResolver`](sharedos-core.md#delegationchainresolver) | Installs ancestor validation so delegated grants can be exercised. | [testkit/src/index.ts:301](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/testkit/src/index.ts#L301) | +| `grants?` | `readonly` | readonly `object`[] | Seed authority for the kernel's trusted grant source. | [testkit/src/index.ts:297](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/testkit/src/index.ts#L297) | +| `grantSource?` | `readonly` | [`GrantSource`](sharedos-core.md#grantsource) | Replaces the trusted grant source, for example to exercise an outage. | [testkit/src/index.ts:299](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/testkit/src/index.ts#L299) | ## Type Aliases @@ -877,7 +993,7 @@ Defined in: [testkit/src/index.ts:294](https://github.com/Aicoo-Team/SharedOS/bl > **ResourceHandler** = (`operation`) => `Promise`\<[`ResourceResult`](sharedos-contracts.md#resourceresult)>\> -Defined in: [testkit/src/index.ts:267](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/testkit/src/index.ts#L267) +Defined in: [testkit/src/index.ts:268](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/testkit/src/index.ts#L268) #### Parameters @@ -895,7 +1011,7 @@ Defined in: [testkit/src/index.ts:267](https://github.com/Aicoo-Team/SharedOS/bl > **createTestContext**(`options?`): `object` -Defined in: [testkit/src/index.ts:336](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/testkit/src/index.ts#L336) +Defined in: [testkit/src/index.ts:338](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/testkit/src/index.ts#L338) #### Parameters @@ -945,7 +1061,7 @@ Defined in: [testkit/src/index.ts:336](https://github.com/Aicoo-Team/SharedOS/bl > **createTestGrant**(`options`): `object` -Defined in: [testkit/src/index.ts:366](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/testkit/src/index.ts#L366) +Defined in: [testkit/src/index.ts:368](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/testkit/src/index.ts#L368) #### Parameters @@ -1023,7 +1139,7 @@ Defined in: [testkit/src/index.ts:366](https://github.com/Aicoo-Team/SharedOS/bl > **createTestKernel**(`options?`): [`TestKernel`](#testkernel) -Defined in: [testkit/src/index.ts:303](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/testkit/src/index.ts#L303) +Defined in: [testkit/src/index.ts:304](https://github.com/Aicoo-Team/SharedOS/blob/main/packages/testkit/src/index.ts#L304) #### Parameters diff --git a/docs/errors.md b/docs/errors.md index d9ee58cc..11b9c012 100644 --- a/docs/errors.md +++ b/docs/errors.md @@ -758,3 +758,24 @@ Rejected by the schemas, so they hold identically on both boundaries. Path segments additionally reject separators, traversal markers, and control characters. A filesystem-backed provider must **still** resolve beneath its own root and reject link escapes — the contract cannot see your disk. + +## Durable replay + +Installing `SharedOSKernelOptions.replayStore` activates the replay state machine +in [ADR 0028](adr/0028-durable-operation-replay.md). Production storage belongs to +the host. Keep IDs stable across transport retries; use a new ID only for a new +intent after reconciling any ambiguous effect. + +| Code | Meaning | +| -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | +| `replay_conflict` | The tenant-scoped ID is already bound to different input or trusted context. Denied; do not reuse it. | +| `replay_pending` | Another worker owns the claim. Poll/retry the same ID for its retained result; never dispatch a replacement effect. | +| `replay_interrupted` | The outcome is ambiguous or the operation was interrupted. Reconcile through trusted host recovery. | +| `replay_expired` | The result body expired; the identity tombstone still prevents another effect. | +| `replay_unavailable` | Storage or its response could not be trusted. No effect starts without a claim; failure to persist a result after an effect is ambiguous. | + +All replay refusals are non-retryable as new turns/effects. Pending requests can +be resubmitted with the same ID to retrieve the result. Except for conflict, +refusals use `failed` rather than `denied`, since a previous or concurrent effect +may already exist. Replayed terminal results preserve their original timestamps, +IDs, metadata and events. Replaying an execution does not stream new events. diff --git a/docs/release-readiness.md b/docs/release-readiness.md index 7856f4e5..fde8b44a 100644 --- a/docs/release-readiness.md +++ b/docs/release-readiness.md @@ -23,9 +23,13 @@ vulnerability-reporting contact. ## Production security gates -- Define a durable replay/freshness port for execution IDs, message IDs, tool - call IDs, and resource operation IDs. Implement atomic production and isolated - test adapters and reject same-key/different-input replays. +- Install a production durable `ReplayStore` for the kernel replay gates covering + execution IDs, message IDs, tool-call IDs and resource-operation IDs (ADR 0028). + SharedOS now defines atomic claim, conflict rejection, result replay and fenced + interruption semantics; testkit supplies an isolated in-memory adapter. Verify + host storage against these rules across processes, retain identity tombstones, + and configure host freshness policy. Protocol v1 without a store remains + unprotected; mandatory activation is coordinated with protocol versioning. - Use a trusted grant store or verifier and a durable compare-and-set usage store for bounded grants. The kernel intentionally fails bounded grants closed when no usage store is supplied. @@ -47,6 +51,9 @@ vulnerability-reporting contact. ## Already enforced in this bootstrap +- Durable replay semantics when a host `ReplayStore` is installed, including + concurrent duplicate refusal, conflicting-input rejection and retained results. + - Deny-by-default complete capability matching with segment-safe paths. - Separate recipient-scoped grants for messaging and target-agent execution. - Server-derived remote authority; request bodies cannot supply grants or tools. diff --git a/docs/security/threat-model.md b/docs/security/threat-model.md index bfdfb28c..9ba8ce20 100644 --- a/docs/security/threat-model.md +++ b/docs/security/threat-model.md @@ -76,7 +76,7 @@ Authenticated callers remain untrusted for authorization. | Cross-user MCP mutation | One user's reload replaces another user's dynamic tools | Resolve dynamic catalogs per trusted context; avoid shared mutable registration | | Discovery leakage | Tool list reveals a private connector or account | Permission-filter definitions and metadata before returning the catalog | | Tenant/world escape | Crafted path reaches another run or user | Bind namespace/world and owner in every provider call; use segment-safe paths | -| Replay | Captured request repeats a destructive call | Host-owned durable deduplication and freshness checks; SharedOS ships no replay port | +| Replay | Captured request repeats a destructive call | Kernel replay gate backed by a host durable ReplayStore; stable IDs and host freshness policy | | Revocation race | Grant is revoked while a turn is running | Decided at admission and observed by the next turn (ADR 0010/0016); bound turn length, or issue short-lived grants, whose expiry is refused inside the turn | | Bounded-use race | Two workers consume the last use | Durable atomic compare-and-set; deny when the usage store is unavailable | | SSRF / connector escape | MCP tool fetches metadata endpoints | Destination allowlists, DNS/IP validation, redirects policy, egress controls | @@ -224,15 +224,21 @@ specific deployment policy allows them. ### Idempotency and concurrency -SharedOS does **not** yet implement durable replay or -idempotency enforcement for message IDs, call IDs, operation IDs, or execution -IDs, and it does not impose a created-at freshness window. A production host -must atomically bind each accepted identifier to namespace/world, -authenticated actor, operation, target, and semantic input, and must reject a -replay whose input changes. This is a production gate, not a publication one: the -packages are public, and until this is a tested SharedOS port with production and -isolated adapters it stays a host obligation -([release readiness](../release-readiness.md)). +SharedOS owns replay semantics through `SharedOSKernelOptions.replayStore`. +Installing a host durable `ReplayStore` activates tenant-scoped atomic claims +for execution, message, tool-call and resource-operation identities. Same-key +changed input is rejected; terminal results replay without another effect. +Pending, interrupted and expired identities cannot be automatically re-executed. +See [ADR 0028](../adr/0028-durable-operation-replay.md) for canonical fingerprints, +nested operations, retention and fenced recovery. + +Protocol v1 deployments without a store retain their legacy unprotected behavior. +Production hosts must install durable storage; testkit's isolated in-memory +adapter is not durable. Store outages fail closed once replay protection is +configured. No universal created-at freshness window is imposed. A crash between +an external effect and durable result storage remains ambiguous: replay protection +does not guarantee exactly-once external execution. Grant-use reservation and +transactional audit/effect commits remain separate host obligations. A retry is the commonest replay, and the envelope says when one is unsafe. A turn's ending carries `retryable`, and it is `false` once a call to a `write` @@ -300,7 +306,7 @@ SharedOS does not by itself: - verify that a declared purpose reflects a model's private motivation; - make arbitrary third-party tools trustworthy; - provide user authentication, credential custody, or network sandboxing; -- provide durable replay protection; that remains a host obligation; +- supply production replay storage or guarantee exactly-once external effects; - guarantee storage durability or deletion when a host provider violates its contract; - define host billing policy or the statistical validity of an evaluation. diff --git a/packages/conformance/package.json b/packages/conformance/package.json index 69c40082..13b76584 100644 --- a/packages/conformance/package.json +++ b/packages/conformance/package.json @@ -53,5 +53,8 @@ "@aicoo/sharedos-runtime": "workspace:*", "@aicoo/sharedos-testkit": "workspace:*", "zod": "^3.24.1" + }, + "devDependencies": { + "@aicoo/sharedos-http": "workspace:*" } } diff --git a/packages/conformance/src/replay.test.ts b/packages/conformance/src/replay.test.ts new file mode 100644 index 00000000..247c4af6 --- /dev/null +++ b/packages/conformance/src/replay.test.ts @@ -0,0 +1,694 @@ +import { createFileTools } from "@aicoo/sharedos-os"; +import { createKernelSharedOSApi, createSharedOSHandler } from "@aicoo/sharedos-http"; +import { describe, expect, it, vi } from "vitest"; +import { + PROTOCOL_VERSION, + SHAREDOS_ROUTES, + type ExecutionRequest, + type MessageEnvelope, + type ResourceResult, + type ToolCall, + type ToolDefinition, +} from "@aicoo/sharedos-contracts"; +import { + CapabilityAuthorizer, + InMemoryGrantUsageStore, + ReplayProtection, + ResourceProviderRegistry, + SharedOSKernel, + ToolRegistry, + canonicalReplayJson, + type ReplayStore, +} from "@aicoo/sharedos-core"; +import { SharedOSExecutor, STANDARD_RUNTIME_MANIFEST } from "@aicoo/sharedos-runtime"; +import { + InMemoryGrantSource, + InMemoryMessageTransport, + InMemoryMessageRequestRouter, + InMemoryReplayStore, + createTestContext, + createTestGrant, +} from "@aicoo/sharedos-testkit"; + +const context = createTestContext({ enabledToolNamespaces: ["files", "messages"] }); +const request = { + operationId: "op-1", + resource: { namespace: "files", path: ["note"] }, + action: "write", + input: { b: 2, a: 1 }, +}; +const definition: ToolDefinition = { + name: "files.write", + description: "Write", + namespace: "files", + source: "sharedos", + readWrite: "write", + inputSchema: { type: "object" }, + requiredCapability: { resource: { namespace: "files", path: [] }, action: "write" }, +}; +const call: ToolCall = { + id: "call-1", + tool: definition.name, + arguments: { a: 1 }, + traceId: context.traceId, + requestedAt: context.now, +}; +const envelope: MessageEnvelope = { + version: PROTOCOL_VERSION, + id: "msg-1", + sender: context.actor, + receiver: { kind: "agent", agentId: "recipient" }, + purpose: context.purpose, + traceId: context.traceId, + payload: { a: 1 }, + createdAt: context.now, +}; + +function deferred() { + let resolve!: (value: T) => void; + const promise = new Promise((done) => { + resolve = done; + }); + return { promise, resolve }; +} + +function fixture( + store: ReplayStore | undefined = new InMemoryReplayStore(), + invoke?: (id: string, signal: AbortSignal) => Promise, + maxUses?: number, +) { + let effects = 0; + const resources = new ResourceProviderRegistry(); + resources.register({ + namespace: "files", + invoke: async (op, signal) => { + effects++; + if (invoke) return invoke(op.operationId, signal); + return { + operationId: op.operationId, + completedAt: op.context.now, + status: "succeeded", + output: { effects }, + }; + }, + }); + const tools = new ToolRegistry(); + const toolInvoke = vi.fn(async (_context, checked) => ({ + callId: checked.id, + tool: checked.tool, + completedAt: context.now, + status: "succeeded" as const, + output: { a: 1 }, + })); + tools.register({ definition, parseArguments: (args) => args, invoke: toolInvoke }); + const grants = new InMemoryGrantSource( + ["namespace-1", "namespace-2"].map((namespaceId) => + createTestGrant({ + namespaceId, + ...(maxUses === undefined ? {} : { maxUses }), + capabilities: ["files", "sharedos.messaging", "sharedos.execution"].map((namespace) => ({ + resource: { namespace, path: [] }, + actions: ["write", "create", "send", "invoke"], + scope: "descendants", + })), + }), + ), + ); + const transport = new InMemoryMessageTransport(); + const kernel = new SharedOSKernel({ + grantSource: grants, + resources, + tools, + messageTransport: transport, + messageRequestRouter: new InMemoryMessageRequestRouter(transport), + authorizer: new CapabilityAuthorizer({ usageStore: new InMemoryGrantUsageStore() }), + ...(store === undefined ? {} : { replayStore: store }), + }); + return { kernel, store, transport, toolInvoke, effects: () => effects, resources, grants }; +} + +function execution(): ExecutionRequest { + return { + version: PROTOCOL_VERSION, + executionId: "execution-1", + agent: { kind: "agent", agentId: "agent-1" }, + context, + message: { ...envelope, receiver: context.actor }, + tools: [definition], + }; +} + +function executor( + kernel: SharedOSKernel, + run = vi.fn(async () => ({ type: "complete" as const, output: { a: 1 } })), +) { + return { + turns: new SharedOSExecutor( + kernel, + { manifest: STANDARD_RUNTIME_MANIFEST, run }, + { clock: () => context.now }, + ), + run, + }; +} + +function expectCode( + result: { status: string; error?: { code: string } | undefined }, + code: string, +) { + expect(result.error?.code).toBe(code); +} + +describe("durable replay gates", () => { + it("reproduces the resource probe in the legacy unprotected path", async () => { + const f = fixture(); + const legacy = new SharedOSKernel({ grantSource: f.grants, resources: f.resources }); + await legacy.invokeResource(context, request); + await legacy.invokeResource(context, request); + expect(f.effects()).toBe(2); + }); + + it("replays the exact resource result without consuming a bounded grant twice", async () => { + const f = fixture(new InMemoryReplayStore(), undefined, 1); + const first = await f.kernel.invokeResource(context, request); + expect(first.status).toBe("succeeded"); + expect( + await f.kernel.invokeResource( + { ...context, now: "2026-01-01T00:00:01.000Z" }, + { + ...request, + input: { a: 1, b: 2 }, + resource: { ...request.resource, owner: context.owner }, + }, + ), + ).toEqual(first); + expect(f.effects()).toBe(1); + expectCode( + await f.kernel.invokeResource(context, { ...request, operationId: "op-2" }), + "grant_exhausted", + ); + }); + + it("arbitrates concurrent duplicates across kernel instances", async () => { + const started = deferred(); + const release = deferred(); + const store = new InMemoryReplayStore(); + const invoke = async () => { + started.resolve(); + return release.promise; + }; + const a = fixture(store, invoke), + b = fixture(store, invoke); + const owner = a.kernel.invokeResource(context, request); + await started.promise; + const duplicates = await Promise.all( + Array.from({ length: 8 }, () => b.kernel.invokeResource(context, request)), + ); + duplicates.forEach((result) => expectCode(result, "replay_pending")); + expect(a.effects() + b.effects()).toBe(1); + release.resolve({ + operationId: request.operationId, + status: "succeeded", + completedAt: context.now, + output: "receipt", + }); + const result = await owner; + expect(await b.kernel.invokeResource(context, request)).toEqual(result); + }); + + it("rejects changed input, target, actor, purpose and trace before any second effect", async () => { + const f = fixture(); + await f.kernel.invokeResource(context, request); + expectCode( + await f.kernel.invokeResource(context, { ...request, input: { a: 2 } }), + "replay_conflict", + ); + expectCode( + await f.kernel.invokeResource(context, { + ...request, + resource: { ...request.resource, path: ["other"] }, + }), + "replay_conflict", + ); + for (const change of [ + { actor: envelope.receiver }, + { purpose: "other" }, + { traceId: "other" }, + ]) { + expectCode( + await f.kernel.invokeResource({ ...context, ...change }, request), + "replay_conflict", + ); + } + expect(f.effects()).toBe(1); + }); + + it("isolates identical IDs in different tenants", async () => { + const f = fixture(); + await f.kernel.invokeResource(context, request); + await f.kernel.invokeResource({ ...context, namespaceId: "namespace-2" }, request); + expect(f.effects()).toBe(2); + }); + + it("fails closed on a claim outage", async () => { + const f = fixture({ + claim: async () => { + throw new Error("offline"); + }, + settle: async () => true, + }); + expectCode(await f.kernel.invokeResource(context, request), "replay_unavailable"); + expect(f.effects()).toBe(0); + }); + + it("leaves a blocked claim after an outcome persistence outage", async () => { + const durable = new InMemoryReplayStore(); + const f = fixture({ + claim: (key, fp) => durable.claim(key, fp), + settle: async () => { + throw new Error("offline"); + }, + }); + expectCode(await f.kernel.invokeResource(context, request), "replay_unavailable"); + expectCode(await f.kernel.invokeResource(context, request), "replay_pending"); + expect(f.effects()).toBe(1); + }); + + it("retains failed and denied results instead of trying providers again", async () => { + const f = fixture(new InMemoryReplayStore(), async () => { + throw new Error("effect may have committed"); + }); + const first = await f.kernel.invokeResource(context, request); + expect(first.status).toBe("failed"); + expect(await f.kernel.invokeResource(context, request)).toEqual(first); + expect(f.effects()).toBe(1); + const denied = await f.kernel.invokeResource(context, { + ...request, + operationId: "denied", + action: "delete", + }); + expect(denied.status).toBe("denied"); + expect( + await f.kernel.invokeResource(context, { + ...request, + operationId: "denied", + action: "delete", + }), + ).toEqual(denied); + }); + + it("cancels an owned effect and fences its late result", async () => { + const started = deferred(), + release = deferred(); + const f = fixture(new InMemoryReplayStore(), async () => { + started.resolve(); + return release.promise; + }); + const controller = new AbortController(); + const pending = f.kernel.invokeResource(context, request, { signal: controller.signal }); + const rejection = expect(pending).rejects.toThrow(); + await started.promise; + controller.abort(new Error("cancelled")); + await rejection; + expectCode(await f.kernel.invokeResource(context, request), "replay_interrupted"); + release.resolve({ + operationId: request.operationId, + completedAt: context.now, + status: "succeeded", + output: "late", + }); + await Promise.resolve(); + expectCode(await f.kernel.invokeResource(context, request), "replay_interrupted"); + expect(f.effects()).toBe(1); + }); + + it("settles a claim that committed after its caller cancelled without dispatching", async () => { + const durable = new InMemoryReplayStore(), + committed = deferred(), + release = deferred(); + let delay = true; + const store: ReplayStore = { + claim: async (key, fingerprint) => { + const record = await durable.claim(key, fingerprint); + if (delay) { + committed.resolve(); + await release.promise; + delay = false; + } + return record; + }, + settle: (key, token, outcome) => durable.settle(key, token, outcome), + }; + const f = fixture(store), + controller = new AbortController(); + const pending = f.kernel.invokeResource(context, request, { signal: controller.signal }); + const rejected = expect(pending).rejects.toThrow("cancelled"); + await committed.promise; + controller.abort(new Error("cancelled")); + release.resolve(); + await rejected; + expectCode(await f.kernel.invokeResource(context, request), "replay_interrupted"); + expect(f.effects()).toBe(0); + }); + + it("does not claim cancellation before dispatch", async () => { + const f = fixture(); + await expect( + f.kernel.invokeResource(context, request, { signal: AbortSignal.abort(new Error("stop")) }), + ).rejects.toThrow("stop"); + expect((await f.kernel.invokeResource(context, request)).status).toBe("succeeded"); + expect(f.effects()).toBe(1); + }); + + it("never re-executes after result-body expiration", async () => { + const store = new InMemoryReplayStore(), + f = fixture(store); + await f.kernel.invokeResource(context, request); + expect( + store.expire({ + namespaceId: context.namespaceId, + kind: "resource", + scope: "", + id: request.operationId, + }), + ).toBe(true); + expectCode(await f.kernel.invokeResource(context, request), "replay_expired"); + expect(f.effects()).toBe(1); + }); + + it("scopes tool calls to executions and rejects conflicting arguments", async () => { + const f = fixture(); + const first = await f.kernel.invokeTool(context, call, { executionId: "turn-1" }); + expect(first.status).toBe("succeeded"); + expect(await f.kernel.invokeTool(context, call, { executionId: "turn-1" })).toEqual(first); + expectCode( + await f.kernel.invokeTool( + context, + { ...call, arguments: { a: 2 } }, + { executionId: "turn-1" }, + ), + "replay_conflict", + ); + expect((await f.kernel.invokeTool(context, call, { executionId: "turn-2" })).status).toBe( + "succeeded", + ); + expect(f.toolInvoke).toHaveBeenCalledTimes(2); + }); + + it("arbitrates concurrent tool-call duplicates", async () => { + const f = fixture(), + start = deferred(), + release = deferred(); + f.toolInvoke.mockImplementation(async (_context, checked) => { + start.resolve(); + await release.promise; + return { + callId: checked.id, + tool: checked.tool, + completedAt: context.now, + status: "succeeded", + output: { a: 1 }, + }; + }); + const owner = f.kernel.invokeTool(context, call); + await start.promise; + expectCode(await f.kernel.invokeTool(context, call), "replay_pending"); + release.resolve(); + const first = await owner; + expect(await f.kernel.invokeTool(context, call)).toEqual(first); + expect(f.toolInvoke).toHaveBeenCalledTimes(1); + }); + + it("arbitrates concurrent message sends", async () => { + const f = fixture(), + start = deferred(), + release = deferred(); + const deliver = vi + .spyOn(f.transport, "deliver") + .mockImplementation(async (_context, checked) => { + start.resolve(); + await release.promise; + return { messageId: checked.id, timestamp: context.now, status: "accepted" }; + }); + const owner = f.kernel.sendMessage(context, envelope); + await start.promise; + expectCode(await f.kernel.sendMessage(context, envelope), "replay_pending"); + release.resolve(); + const first = await owner; + expect(await f.kernel.sendMessage(context, envelope)).toEqual(first); + expect(deliver).toHaveBeenCalledTimes(1); + }); + + it("derives globally distinct provider operation IDs for nested resource effects", async () => { + const seen: string[] = [], + resources = new ResourceProviderRegistry(), + tools = new ToolRegistry(); + const provider = { + namespace: "files", + invoke: async (op: import("@aicoo/sharedos-contracts").ResourceOperation) => { + seen.push(op.operationId); + return { + operationId: op.operationId, + completedAt: context.now, + status: "succeeded" as const, + output: "receipt", + }; + }, + }; + resources.register(provider); + createFileTools(provider).forEach((handler) => tools.register(handler)); + const f = fixture(); + const kernel = new SharedOSKernel({ + grantSource: f.grants, + tools, + resources, + replayStore: new InMemoryReplayStore(), + }); + const nested = { + ...call, + tool: "files.create", + arguments: { path: ["note"], content: "value" }, + }; + const first = await kernel.invokeTool(context, nested, { executionId: "one" }); + expect(first.status).toBe("succeeded"); + expect(await kernel.invokeTool(context, nested, { executionId: "one" })).toEqual(first); + expect((await kernel.invokeTool(context, nested, { executionId: "two" })).status).toBe( + "succeeded", + ); + expect(seen).toHaveLength(2); + expect(seen[0]).not.toBe(seen[1]); + expect(seen[0]).toMatch(/^resource-[a-f0-9]{64}$/u); + }); + + it("replays direct message receipts and rejects changed payloads", async () => { + const f = fixture(); + const first = await f.kernel.sendMessage(context, envelope); + expect(first.status).toBe("accepted"); + expect(await f.kernel.sendMessage(context, envelope)).toEqual(first); + expectCode( + await f.kernel.sendMessage(context, { ...envelope, payload: { a: 2 } }), + "replay_conflict", + ); + expect(f.transport.deliveries).toHaveLength(1); + }); + + it("replays the enclosing message tool reply without redelivery", async () => { + const f = fixture(); + const messageCall = { + ...call, + tool: "messages.request", + arguments: { recipient: envelope.receiver, payload: envelope.payload }, + }; + const first = await f.kernel.invokeTool(context, messageCall); + expect(first.status).toBe("succeeded"); + expect(await f.kernel.invokeTool(context, messageCall)).toEqual(first); + expect(f.transport.deliveries).toHaveLength(1); + const delivered = f.transport.deliveries[0]!.envelope; + expect(await f.kernel.sendMessage(context, delivered)).toMatchObject({ status: "accepted" }); + expect(f.transport.deliveries).toHaveLength(1); + }); + + it("replays execution results and events without running or streaming again", async () => { + const f = fixture(), + e = executor(f.kernel), + observed = vi.fn(); + const first = await e.turns.execute(execution()); + expect(first.status).toBe("succeeded"); + expect(await e.turns.execute(execution(), { onEvent: observed })).toEqual(first); + expect(e.run).toHaveBeenCalledTimes(1); + expect(observed).not.toHaveBeenCalled(); + expectCode( + await e.turns.execute({ ...execution(), state: { changed: true } }), + "replay_conflict", + ); + }); + + it("blocks concurrent execution IDs before opening a second runtime", async () => { + const f = fixture(), + start = deferred(), + release = deferred(); + const e = executor( + f.kernel, + vi.fn(async () => { + start.resolve(); + await release.promise; + return { type: "complete", output: { a: 1 } }; + }), + ); + const owner = e.turns.execute(execution()); + await start.promise; + expectCode(await executor(f.kernel).turns.execute(execution()), "replay_pending"); + release.resolve(); + await owner; + expect(e.run).toHaveBeenCalledTimes(1); + }); + + it("keeps crash leftovers blocked and permits fenced recovery to interrupted", async () => { + const store = new InMemoryReplayStore(); + const gate = new ReplayProtection(store), + key = { namespaceId: context.namespaceId, kind: "resource" as const, scope: "", id: "crash" }; + const { sha256Hex } = await import("@aicoo/sharedos-core"); + const fingerprint = await sha256Hex(canonicalReplayJson({ a: 1 })); + const claim = await store.claim(key, fingerprint); + await expect( + gate.run( + key, + { a: 1 }, + async () => "effect", + (value) => value, + ), + ).rejects.toMatchObject({ code: "replay_pending" }); + expect(await store.settle(key, "wrong", { state: "interrupted" })).toBe(false); + expect(await store.settle(key, claim.record.token, { state: "interrupted" })).toBe(true); + expect( + await store.settle(key, claim.record.token, { state: "completed", result: "late" }), + ).toBe(false); + await expect( + gate.run( + key, + { a: 1 }, + async () => "effect", + (value) => value, + ), + ).rejects.toMatchObject({ code: "replay_interrupted" }); + }); + + it("enforces replay through the authenticated HTTP resource route", async () => { + const f = fixture(), + e = executor(f.kernel); + const handler = createSharedOSHandler({ + api: createKernelSharedOSApi({ kernel: f.kernel, turns: e.turns }), + resolveContext: async () => context, + }); + const submit = async (body: unknown) => { + const response = await handler( + new Request(`http://sharedos.test${SHAREDOS_ROUTES.invokeResource.path}`, { + method: "POST", + headers: { "content-type": "application/json" }, + body: JSON.stringify(body), + }), + ); + expect(response.status).toBe(200); + return response.json(); + }; + const first = await submit(request); + expect(first.status).toBe("succeeded"); + expect(await submit(request)).toEqual(first); + expectCode(await submit({ ...request, input: "changed" }), "replay_conflict"); + expect(f.effects()).toBe(1); + }); + + it("rejects malformed store records and mismatched stored results", async () => { + const real = new InMemoryReplayStore(); + const broken: ReplayStore = { + claim: async (key, fp) => { + const claimed = await real.claim(key, fp); + return { ...claimed, record: { ...claimed.record, key: { ...key, namespaceId: "wrong" } } }; + }, + settle: async () => true, + }; + const f = fixture(broken); + expectCode(await f.kernel.invokeResource(context, request), "replay_unavailable"); + expect(f.effects()).toBe(0); + const wrongResult: ReplayStore = { + claim: async (key, fp) => ({ + claimed: false, + record: { + key, + fingerprint: fp, + token: "token", + state: "completed", + result: { + operationId: "wrong", + status: "succeeded", + output: "secret", + completedAt: context.now, + }, + }, + }), + settle: async () => true, + }; + expectCode( + await fixture(wrongResult).kernel.invokeResource(context, request), + "replay_unavailable", + ); + }); + + it("retains a cancelled execution and blocks reopening its runtime", async () => { + const f = fixture(), + start = deferred(), + controller = new AbortController(); + const run = vi.fn(async () => { + start.resolve(); + await new Promise(() => undefined); + return { type: "complete" as const, output: { a: 1 } }; + }); + const e = executor(f.kernel, run); + const pending = e.turns.execute(execution(), { signal: controller.signal }); + await start.promise; + controller.abort(new Error("stop")); + const first = await pending; + expect(first.status).toBe("cancelled"); + expect(await e.turns.execute(execution())).toEqual(first); + expect(run).toHaveBeenCalledTimes(1); + }); + + it("assigns distinct nested message identities to calls in separate executions", async () => { + const f = fixture(); + const messageCall = { + ...call, + tool: "messages.request", + arguments: { recipient: envelope.receiver, payload: envelope.payload }, + }; + expect((await f.kernel.invokeTool(context, messageCall, { executionId: "one" })).status).toBe( + "succeeded", + ); + expect((await f.kernel.invokeTool(context, messageCall, { executionId: "two" })).status).toBe( + "succeeded", + ); + expect(f.transport.deliveries).toHaveLength(2); + expect(f.transport.deliveries[0]!.envelope.id).not.toBe(f.transport.deliveries[1]!.envelope.id); + }); + + it("has canonical JSON independent of key insertion order and rejects unsafe numbers", () => { + expect(canonicalReplayJson({ z: 1, A: [null, true], a: "text" })).toBe( + canonicalReplayJson({ a: "text", A: [null, true], z: 1 }), + ); + expect(canonicalReplayJson({ z: 1, A: 2, a: 3 })).toBe('{"A":2,"a":3,"z":1}'); + expect(() => canonicalReplayJson({ number: Infinity })).toThrow(); + expect(() => canonicalReplayJson([undefined])).toThrow(); + expect(() => canonicalReplayJson(new Array(1))).toThrow(); + }); + + it("isolates test adapter instances and returned result mutation", async () => { + const a = fixture(), + b = fixture(); + const first = await a.kernel.invokeResource(context, request); + if (first.status === "succeeded") first.output = "mutated"; + expect(await a.kernel.invokeResource(context, request)).toMatchObject({ + output: { effects: 1 }, + }); + expect((await b.kernel.invokeResource(context, request)).status).toBe("succeeded"); + expect(b.effects()).toBe(1); + }); +}); diff --git a/packages/conformance/tsconfig.json b/packages/conformance/tsconfig.json index fd194ac1..b347ed3b 100644 --- a/packages/conformance/tsconfig.json +++ b/packages/conformance/tsconfig.json @@ -9,6 +9,7 @@ "references": [ { "path": "../adapters" }, { "path": "../contracts" }, + { "path": "../http" }, { "path": "../core" }, { "path": "../mcp" }, { "path": "../os" }, diff --git a/packages/contracts/src/index.ts b/packages/contracts/src/index.ts index 215f2b5e..955c2273 100644 --- a/packages/contracts/src/index.ts +++ b/packages/contracts/src/index.ts @@ -12,3 +12,4 @@ export * from "./protocol-error.js"; export * from "./resource.js"; export * from "./runtime.js"; export * from "./tool.js"; +export * from "./replay.js"; diff --git a/packages/contracts/src/replay.ts b/packages/contracts/src/replay.ts new file mode 100644 index 00000000..6f44a295 --- /dev/null +++ b/packages/contracts/src/replay.ts @@ -0,0 +1,36 @@ +import { z } from "zod"; + +import { IdentifierSchema } from "./common.js"; +import { JsonValueSchema } from "./json.js"; + +/** A structural tenant-scoped identity; scope is an execution ID for tool calls. */ +export const ReplayKeySchema = z + .object({ + namespaceId: IdentifierSchema, + kind: z.enum(["execution", "tool", "resource", "message"]), + scope: z.string().max(256), + id: IdentifierSchema, + }) + .strict(); +export type ReplayKey = z.infer; + +/** Durable identity survives result-body expiry and never becomes claimable again. */ +export const ReplayRecordSchema = z + .object({ + key: ReplayKeySchema, + fingerprint: z.string().regex(/^[0-9a-f]{64}$/u), + token: IdentifierSchema, + state: z.enum(["pending", "completed", "failed", "interrupted", "expired"]), + result: JsonValueSchema.optional(), + }) + .strict() + .superRefine((record, context) => { + const terminal = record.state === "completed" || record.state === "failed"; + if (terminal !== (record.result !== undefined)) { + context.addIssue({ + code: z.ZodIssueCode.custom, + message: "Only completed/failed records carry a result", + }); + } + }); +export type ReplayRecord = z.infer; diff --git a/packages/core/README.md b/packages/core/README.md index ef48db5b..5b9415d7 100644 --- a/packages/core/README.md +++ b/packages/core/README.md @@ -16,3 +16,16 @@ Static handlers use `ToolRegistry`; user-specific MCP catalogs use atomic, durable namespace updates while keeping its database and product policy. SharedOS is currently a `1.0.0` preview. + +## Replay storage + +`SharedOSKernelOptions.replayStore` enables the kernel-owned replay state machine +for execution, tool, resource and message IDs. Hosts implement `ReplayStore` with +atomic durable claims and fenced settlement. Keep identity tombstones after +result expiry; never reclaim pending operations solely because a timeout passed. +See [ADR 0028](https://github.com/systemind-team/SharedOS/blob/main/docs/adr/0028-durable-operation-replay.md). + +`InMemoryReplayStore` is exported by `@aicoo/sharedos-testkit` for isolated tests. +It is not durable or suitable for production. Protocol v1 without a configured +store preserves legacy unprotected behavior. External effects still require +provider idempotency or reconciliation for crash ambiguity. diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 5fa3e63b..9f9dde55 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -22,3 +22,4 @@ export * from "./resource-registry.js"; export * from "./spans.js"; export * from "./tool-registry.js"; export * from "./tool-namespace-control.js"; +export * from "./replay.js"; diff --git a/packages/core/src/kernel.ts b/packages/core/src/kernel.ts index 21328047..e1a7b969 100644 --- a/packages/core/src/kernel.ts +++ b/packages/core/src/kernel.ts @@ -71,7 +71,7 @@ import { RecipientScopedMessageCapabilityResolver, addressPath, } from "./message-service.js"; -import { createMessageRequestTool } from "./message-tool.js"; +import { createMessageRequestTool, MESSAGE_REQUEST_TOOL_NAME } from "./message-tool.js"; import { type ResourceInvocationRequest, type ResourceProvider, @@ -79,6 +79,14 @@ import { toResourceOperation, } from "./resource-registry.js"; import { buildToolCatalog, catalogHash, publishToolCatalog } from "./published-tool.js"; +import { + ReplayProtection, + ReplayError, + replayContext, + canonicalReplayJson, + type ReplayStore, +} from "./replay.js"; +import { sha256Hex } from "./hashing.js"; import { SPAN, measure, type SpanSink } from "./spans.js"; import { type ContextToolProvider, type ToolHandler, ToolRegistry } from "./tool-registry.js"; import type { ToolNamespaceSettingsStore } from "./tool-namespace-control.js"; @@ -99,6 +107,8 @@ import { } from "./internal.js"; export interface SharedOSKernelOptions { + /** Activate durable replay enforcement for all effect and execution paths (ADR 0028). */ + readonly replayStore?: ReplayStore; /** * The trusted boundary that loads authority. It is required: a kernel with * no authoritative grant source can only fail closed. @@ -186,6 +196,8 @@ export interface SharedOSKernelOptions { } export interface KernelOperationOptions { + /** Trusted executor scope for tool-call IDs; standalone calls use context.traceId. */ + readonly executionId?: string; readonly signal?: AbortSignal; } @@ -333,6 +345,7 @@ interface AuthorityLease { * unverified request body. */ export class SharedOSKernel { + readonly replayProtection: ReplayProtection; readonly #authority: TrustedAuthorityResolver; readonly #policySource: PolicySource | undefined; readonly #leases = new Map(); @@ -357,6 +370,7 @@ export class SharedOSKernel { readonly #messageTransport: MessageTransport | undefined; readonly #messageRequestRouter: MessageRequestRouter | undefined; readonly #messageCapabilityResolver: MessageCapabilityResolver; + readonly #messageIds = new WeakMap(); readonly #createMessageId: (context: AccessContext, call: ToolCall) => string; readonly #createAuditId: () => string; readonly #audit: AuditSink; @@ -366,6 +380,7 @@ export class SharedOSKernel { readonly #spans: SpanSink | undefined; constructor(options: SharedOSKernelOptions) { + this.replayProtection = new ReplayProtection(options.replayStore); this.#authority = new TrustedAuthorityResolver(options?.grantSource); if (options.policySource !== undefined && typeof options.policySource?.load !== "function") { throw new TypeError("SharedOS requires a policy source that provides a load function"); @@ -383,7 +398,13 @@ export class SharedOSKernel { this.#messageRequestRouter = options.messageRequestRouter; this.#messageCapabilityResolver = options.messageCapabilityResolver ?? new RecipientScopedMessageCapabilityResolver(); - this.#createMessageId = options.createMessageId ?? (() => crypto.randomUUID()); + this.#createMessageId = + options.createMessageId ?? + ((_context, call) => { + const id = this.#messageIds.get(call); + if (id === undefined) throw new Error("Message identity was not prepared"); + return id; + }); this.#createAuditId = options.createAuditId ?? (() => crypto.randomUUID()); this.#audit = options.audit ?? new NoopAuditSink(); this.#onAuditError = options.onAuditError; @@ -1019,12 +1040,52 @@ export class SharedOSKernel { (span) => { span.set("callId", call.id); span.set("tool", call.tool); - return this.#invokeTool(context, call, options); + const snapshot = structuredClone({ context, call }); + const { requestedAt: _requestedAt, ...input } = snapshot.call; + return this.#replayTool(snapshot.context, snapshot.call, options, input); }, (result, span) => span.set("outcome", result.status), ); } + async #replayTool( + context: AccessContext, + call: ToolCall, + options: KernelOperationOptions, + input: unknown, + ): Promise { + try { + return await this.replayProtection.run( + { + namespaceId: context.namespaceId, + kind: "tool", + scope: options.executionId ?? context.traceId, + id: call.id, + }, + { context: replayContext(context), call: input }, + () => this.#invokeTool(context, call, options), + (value) => { + const result = ToolResultSchema.parse(value); + if (result.callId !== call.id || result.tool !== call.tool) + throw new Error("Mismatched replay result"); + return result; + }, + options.signal, + ); + } catch (error) { + if (!(error instanceof ReplayError)) throw error; + const result = refusedToolResult( + call, + error.code === "replay_conflict" ? "denied" : "failed", + context.now, + error.code, + "Operation replay refused", + ); + await this.#recordToolResult(context, call, result); + return result; + } + } + async #invokeTool( context: AccessContext, call: ToolCall, @@ -1208,6 +1269,19 @@ export class SharedOSKernel { let requirement: CapabilityRequirement; try { + if (call.tool === MESSAGE_REQUEST_TOOL_NAME) { + this.#messageIds.set( + parsedCall, + await sha256Hex( + canonicalReplayJson([ + context.namespaceId, + options.executionId ?? context.traceId, + context.actor, + call.id, + ]), + ), + ); + } requirement = handler.resolveRequirement?.(context, parsedCall) ?? handler.definition.requiredCapability; } catch (error) { @@ -1292,7 +1366,19 @@ export class SharedOSKernel { measure(this.#spans, SPAN.TOOL_HANDLER, (span) => { span.set("callId", call.id); span.set("tool", call.tool); - return handler.invoke(context, parsedCall, options.signal ?? neverAbortedSignal()); + return handler.invoke( + context, + parsedCall, + options.signal ?? neverAbortedSignal(), + this.replayProtection.store === undefined + ? undefined + : deepFreeze({ + namespaceId: context.namespaceId, + kind: "tool", + scope: options.executionId ?? context.traceId, + id: call.id, + }), + ); }), accept: (candidate) => { const parsed = ToolResultSchema.safeParse(candidate); @@ -1337,6 +1423,44 @@ export class SharedOSKernel { context: AccessContext, request: ResourceInvocationRequest, options: KernelOperationOptions = {}, + ): Promise { + context = structuredClone(context); + request = structuredClone(request); + request = { + ...request, + resource: { ...request.resource, owner: request.resource.owner ?? context.owner }, + }; + try { + return await this.replayProtection.run( + { namespaceId: context.namespaceId, kind: "resource", scope: "", id: request.operationId }, + { context: replayContext(context), request }, + () => this.#invokeResource(context, request, options), + (value) => { + const result = ResourceResultSchema.parse(value); + if (result.operationId !== request.operationId) + throw new Error("Mismatched replay result"); + return result; + }, + options.signal, + ); + } catch (error) { + if (!(error instanceof ReplayError)) throw error; + const result = refusedResourceResult( + request, + error.code === "replay_conflict" ? "denied" : "failed", + context.now, + error.code, + "Operation replay refused", + ); + await this.#recordResourceResult(context, request, result); + return result; + } + } + + async #invokeResource( + context: AccessContext, + request: ResourceInvocationRequest, + options: KernelOperationOptions = {}, ): Promise { options.signal?.throwIfAborted(); context = structuredClone(context); @@ -1563,12 +1687,8 @@ export class SharedOSKernel { reportProviderError: (error, access, operation) => this.#reportProviderError(error, access, operation), deliverAuthorizedMessage: async (access, envelope, operationSignal, call) => { - const delivery = await this.#deliverAuthorizedMessage( - access, - envelope, - operationSignal, - undefined, - call.id, + const delivery = await this.#replayMessage(access, envelope, operationSignal, () => + this.#deliverAuthorizedMessage(access, envelope, operationSignal, undefined, call.id), ); if (delivery.status === "denied" || delivery.status === "failed") { this.#dispatchCauses.set(call, delivery.error.code); @@ -1620,6 +1740,53 @@ export class SharedOSKernel { context: AccessContext, envelope: MessageEnvelope, options: KernelOperationOptions = {}, + ): Promise { + context = structuredClone(context); + const parsed = MessageEnvelopeSchema.safeParse(structuredClone(envelope)); + if (!parsed.success) + throw new TypeError("message envelope does not match the SharedOS contract"); + envelope = parsed.data; + return this.#replayMessage(context, envelope, options.signal, () => + this.#sendMessage(context, envelope, options), + ); + } + + async #replayMessage( + context: AccessContext, + envelope: MessageEnvelope, + signal: AbortSignal | undefined, + invoke: () => Promise, + ): Promise { + try { + return await this.replayProtection.run( + { namespaceId: context.namespaceId, kind: "message", scope: "", id: envelope.id }, + { context: replayContext(context), envelope }, + invoke, + (value) => { + const result = MessageDeliveryResultSchema.parse(value); + if (result.messageId !== envelope.id) throw new Error("Mismatched replay result"); + return result; + }, + signal, + ); + } catch (error) { + if (!(error instanceof ReplayError)) throw error; + const result = refusedMessageResult( + envelope, + error.code === "replay_conflict" ? "denied" : "failed", + context.now, + error.code, + "Operation replay refused", + ); + await this.#recordMessageResult(context, envelope, result); + return result; + } + } + + async #sendMessage( + context: AccessContext, + envelope: MessageEnvelope, + options: KernelOperationOptions = {}, ): Promise { options.signal?.throwIfAborted(); let parsedEnvelope: ReturnType; @@ -2327,7 +2494,9 @@ const OPERATION_ABORTED = "operation_aborted"; /** `failClosed`, present only on a code that is SharedOS failing to establish a fact. */ function failClosedFor(reasonCode: string | undefined): Pick { return reasonCode !== undefined && - (isInfrastructureDenial(reasonCode) || reasonCode === AUDIT_UNAVAILABLE) + (isInfrastructureDenial(reasonCode) || + reasonCode === AUDIT_UNAVAILABLE || + reasonCode === "replay_unavailable") ? { failClosed: true } : {}; } diff --git a/packages/core/src/message-tool.ts b/packages/core/src/message-tool.ts index a387bd9c..a007476f 100644 --- a/packages/core/src/message-tool.ts +++ b/packages/core/src/message-tool.ts @@ -193,10 +193,7 @@ export function createMessageRequestTool(options: MessageRequestToolOptions): To const envelope = deepFreeze( MessageEnvelopeSchema.parse({ version: PROTOCOL_VERSION, - id: options.createMessageId( - structuredClone(trustedContext), - structuredClone(trustedCall), - ), + id: options.createMessageId(structuredClone(trustedContext), call), sender: trustedContext.actor, receiver: arguments_.recipient, purpose: trustedContext.purpose, diff --git a/packages/core/src/replay.ts b/packages/core/src/replay.ts new file mode 100644 index 00000000..f60130ee --- /dev/null +++ b/packages/core/src/replay.ts @@ -0,0 +1,174 @@ +import { + JsonValueSchema, + ReplayRecordSchema, + type AccessContext, + type JsonValue, + type ReplayKey, + type ReplayRecord, +} from "@aicoo/sharedos-contracts"; + +import { sha256Hex } from "./hashing.js"; +import { raceAbort } from "./internal.js"; + +/** Host-owned durable storage. All changes must be atomic across workers. */ +export interface ReplayStore { + /** Insert pending or return the existing record, including a conflicting fingerprint. */ + claim( + key: ReplayKey, + fingerprint: string, + ): Promise<{ + readonly claimed: boolean; + readonly record: ReplayRecord; + }>; + /** CAS pending -> terminal, fenced by token. False means ownership was lost. */ + settle( + key: ReplayKey, + token: string, + outcome: { + readonly state: "completed" | "failed" | "interrupted"; + readonly result?: JsonValue; + }, + ): Promise; +} + +export class ReplayError extends Error { + constructor( + readonly code: + | "replay_conflict" + | "replay_pending" + | "replay_interrupted" + | "replay_expired" + | "replay_unavailable", + ) { + super(code); + this.name = "ReplayError"; + } +} + +/** Canonical JSON: code-unit key order, no locale dependence or unsupported values. */ +export function canonicalReplayJson(value: unknown): string { + if (Array.isArray(value)) return `[${Array.from(value, canonicalReplayJson).join(",")}]`; + if (value !== null && typeof value === "object") { + if ( + Object.getPrototypeOf(value) !== Object.prototype && + Object.getPrototypeOf(value) !== null + ) { + throw new TypeError("Replay input must be JSON-safe"); + } + return `{${Object.entries(value) + .filter(([, child]) => child !== undefined) + .sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0)) + .map(([key, child]) => `${JSON.stringify(key)}:${canonicalReplayJson(child)}`) + .join(",")}}`; + } + if ( + value === null || + typeof value === "string" || + typeof value === "boolean" || + (typeof value === "number" && Number.isFinite(value)) + ) + return JSON.stringify(value); + throw new TypeError("Replay input must be JSON-safe"); +} + +/** Stable provider-facing identity for a named child of a claimed operation. */ +export async function childOperationId( + parent: ReplayKey, + kind: ReplayKey["kind"], + slot: string, +): Promise { + return `${kind}-${await sha256Hex(canonicalReplayJson({ parent, kind, slot }))}`; +} + +/** Bind replayed data to trusted identity and provenance, excluding observation time. */ +export function replayContext(context: AccessContext): Omit { + const { now: _now, ...identity } = context; + return identity; +} + +/** Kernel-owned state machine. An omitted store retains legacy v1 behavior (ADR 0028). */ +export class ReplayProtection { + constructor(readonly store?: ReplayStore) {} + + async run( + key: ReplayKey, + input: unknown, + invoke: () => Promise, + accept: (value: unknown) => Result, + signal?: AbortSignal, + ): Promise { + signal?.throwIfAborted(); + if (this.store === undefined) return invoke(); + const store = this.store; + const fingerprint = await sha256Hex(canonicalReplayJson(input)); + signal?.throwIfAborted(); + let claim: Awaited>; + try { + // Do not abandon claim on cancellation: it may commit in storage later. + claim = await store.claim(structuredClone(key), fingerprint); + if (typeof claim.claimed !== "boolean") throw new Error("Invalid claim status"); + const record = ReplayRecordSchema.parse(claim.record); + if ( + canonicalReplayJson(record.key) !== canonicalReplayJson(key) || + (claim.claimed && (record.state !== "pending" || record.fingerprint !== fingerprint)) + ) { + throw new Error("Invalid replay claim"); + } + claim = { claimed: claim.claimed, record }; + } catch { + throw new ReplayError("replay_unavailable"); + } + const record = claim.record; + if (record.fingerprint !== fingerprint) throw new ReplayError("replay_conflict"); + if (!claim.claimed) { + signal?.throwIfAborted(); + if (record.state === "completed" || record.state === "failed") { + try { + return accept(structuredClone(record.result)); + } catch { + throw new ReplayError("replay_unavailable"); + } + } + throw new ReplayError( + record.state === "pending" + ? "replay_pending" + : record.state === "expired" + ? "replay_expired" + : "replay_interrupted", + ); + } + let result: Result; + try { + signal?.throwIfAborted(); + result = await raceAbort(invoke(), signal); + signal?.throwIfAborted(); + } catch (error) { + try { + if (!(await store.settle(key, record.token, { state: "interrupted" }))) + throw new Error("Lost claim"); + } catch { + throw new ReplayError("replay_unavailable"); + } + throw error; + } + try { + const json = JsonValueSchema.parse(result); + const failed = + typeof json === "object" && + json !== null && + !Array.isArray(json) && + json.status === "failed"; + if ( + !(await store.settle(key, record.token, { + state: failed ? "failed" : "completed", + result: json, + })) + ) { + throw new Error("Lost claim"); + } + } catch { + throw new ReplayError("replay_unavailable"); + } + return result; + } +} diff --git a/packages/core/src/tool-registry.ts b/packages/core/src/tool-registry.ts index 04e492aa..7615aafd 100644 --- a/packages/core/src/tool-registry.ts +++ b/packages/core/src/tool-registry.ts @@ -2,6 +2,7 @@ import type { AccessContext, CapabilityRequirement, JsonObject, + ReplayKey, ToolCall, ToolDefinition, ToolNamespaceCatalog, @@ -18,7 +19,13 @@ export interface ToolHandler { readonly parseArguments: (arguments_: JsonObject) => unknown; /** Resolve argument-selected resources immediately before execution. */ readonly resolveRequirement?: (context: AccessContext, call: ToolCall) => CapabilityRequirement; - invoke(context: AccessContext, call: ToolCall, signal: AbortSignal): Promise; + /** The optional identity is trusted kernel provenance for stable nested operation IDs. */ + invoke( + context: AccessContext, + call: ToolCall, + signal: AbortSignal, + identity?: ReplayKey, + ): Promise; } /** @@ -82,7 +89,7 @@ export class ToolRegistry { const registered: ToolHandler = { definition, parseArguments: (arguments_) => parseArguments(arguments_), - invoke: (context, call, signal) => invoke(context, call, signal), + invoke: (context, call, signal, identity) => invoke(context, call, signal, identity), ...(resolveRequirement === undefined ? {} : { diff --git a/packages/os/src/index.ts b/packages/os/src/index.ts index ebf8cf67..c2848284 100644 --- a/packages/os/src/index.ts +++ b/packages/os/src/index.ts @@ -13,6 +13,7 @@ import { type ToolDefinition, type ToolResult, } from "@aicoo/sharedos-contracts"; +import { childOperationId } from "@aicoo/sharedos-core"; import type { ResourceProvider, SharedOSKernel, ToolHandler } from "@aicoo/sharedos-core"; import { compactObject } from "@aicoo/sharedos-core/internal"; @@ -630,16 +631,18 @@ function resourceTool(provider: ResourceProvider, spec: ResourceToolSpec): ToolH }, action: spec.definition.requiredCapability.action, }), - invoke: async (context, call, signal) => { + invoke: async (context, call, signal, identity) => { const parsed = parsedFor(call); + const operationId = + identity === undefined ? call.id : await childOperationId(identity, "resource", "provider"); const operation: ResourceOperation = { - operationId: call.id, + operationId, context, resource: { namespace: provider.namespace, path: parsed.path, owner: context.owner }, action: spec.definition.requiredCapability.action, ...(parsed.input === undefined ? {} : { input: parsed.input }), }; - return toToolResult(call, context, await provider.invoke(operation, signal)); + return toToolResult(call, context, await provider.invoke(operation, signal), operationId); }, }; } @@ -648,9 +651,10 @@ function toToolResult( call: ToolCall, context: AccessContext, candidate: ResourceResult, + operationId = call.id, ): ToolResult { const parsed = ResourceResultSchema.safeParse(candidate); - if (!parsed.success || parsed.data.operationId !== call.id) { + if (!parsed.success || parsed.data.operationId !== operationId) { return { callId: call.id, tool: call.tool, diff --git a/packages/runtime/src/executor.ts b/packages/runtime/src/executor.ts index 49f3ee03..6a11061a 100644 --- a/packages/runtime/src/executor.ts +++ b/packages/runtime/src/executor.ts @@ -1,5 +1,6 @@ import { ExecutionRequestSchema, + ExecutionResultSchema, JsonValueSchema, MAX_EXECUTION_TOOL_CALLS, PROTOCOL_VERSION, @@ -25,6 +26,8 @@ import { type ToolResult, } from "@aicoo/sharedos-contracts"; import { + ReplayError, + replayContext, AUDIT_UNAVAILABLE, AuditUnavailableError, SPAN, @@ -126,6 +129,7 @@ export interface TurnExecutionPort { */ export type TurnKernel = Pick< SharedOSKernel, + | "replayProtection" | "admitTurn" | "reach" | "listTools" @@ -215,7 +219,7 @@ export class SharedOSExecutor implements TurnExecutionPort { return measure( this.#spans, SPAN.TURN, - () => this.#execute(input, options), + () => this.#replayExecute(input, options), (result, span) => { span.set("executionId", result.executionId); span.set("status", result.status); @@ -223,6 +227,63 @@ export class SharedOSExecutor implements TurnExecutionPort { ); } + async #replayExecute( + input: ExecutionRequest, + options: ExecuteTurnOptions, + ): Promise { + const parsed = ExecutionRequestSchema.safeParse(structuredClone(input)); + if (!parsed.success) + throw new TypeError("ExecutionRequest does not match the SharedOS v1 contract"); + const request = parsed.data; + if (options.signal?.aborted === true) return this.#execute(request, options); + try { + return await this.#kernel.replayProtection.run( + { + namespaceId: request.context.namespaceId, + kind: "execution", + scope: "", + id: request.executionId, + }, + { + ...request, + context: replayContext(request.context), + runtime: this.#manifest, + defaults: { + maxSteps: this.#defaultMaxSteps, + maxToolCalls: this.#defaultMaxToolCalls, + timeoutMs: this.#defaultTimeoutMs, + drainGraceMs: this.#drainGraceMs, + }, + }, + () => this.#execute(request, options), + (value) => { + const result = ExecutionResultSchema.parse(value); + if ( + result.executionId !== request.executionId || + result.traceId !== request.context.traceId + ) + throw new Error("Mismatched replay result"); + return result; + }, + ); + } catch (error) { + if (!(error instanceof ReplayError)) throw error; + const now = this.#clock(); + const refused: ExecutionResult = { + version: PROTOCOL_VERSION, + executionId: request.executionId, + traceId: request.context.traceId, + events: [], + startedAt: now, + completedAt: now, + status: error.code === "replay_conflict" ? "denied" : "failed", + error: protocolError(error.code, "Operation replay refused"), + }; + await this.#recordTurnEnd(request, refused); + return refused; + } + } + async #execute( input: ExecutionRequest, options: ExecuteTurnOptions = {}, @@ -544,6 +605,7 @@ export class SharedOSExecutor implements TurnExecutionPort { const withKernel = watchAudit( this.#kernel.invokeTool(contextAt(executionContext, this.#clock()), parsedCall.data, { signal: abort.signal, + executionId: request.executionId, }), ).then( (settled) => { diff --git a/packages/runtime/src/runtime-plugin.test.ts b/packages/runtime/src/runtime-plugin.test.ts index 8901a799..585cae20 100644 --- a/packages/runtime/src/runtime-plugin.test.ts +++ b/packages/runtime/src/runtime-plugin.test.ts @@ -1,3 +1,4 @@ +import { ReplayProtection } from "@aicoo/sharedos-core"; import { describe, expect, it, vi } from "vitest"; import type { @@ -89,6 +90,7 @@ function request(options: { readonly escalation?: boolean } = {}): ExecutionRequ function kernel(result?: ToolResult, options: { readonly escalation?: boolean } = {}): TurnKernel { const catalogue = options.escalation === true ? [tool, ESCALATION_TOOL_DEFINITION] : [tool]; return { + replayProtection: new ReplayProtection(), admitTurn: vi.fn(async () => ({ allowed: true as const, reasonCode: "allowed" as const, diff --git a/packages/runtime/src/runtime.test.ts b/packages/runtime/src/runtime.test.ts index bb3d16d4..a95d1d9f 100644 --- a/packages/runtime/src/runtime.test.ts +++ b/packages/runtime/src/runtime.test.ts @@ -1,3 +1,4 @@ +import { ReplayProtection } from "@aicoo/sharedos-core"; import { describe, expect, it, vi } from "vitest"; import type { @@ -85,6 +86,7 @@ function request(options: { readonly escalation?: boolean } = {}): ExecutionRequ function kernel(result?: ToolResult, options: { readonly escalation?: boolean } = {}): TurnKernel { const catalogue = options.escalation === true ? [tool, ESCALATION_TOOL_DEFINITION] : [tool]; return { + replayProtection: new ReplayProtection(), admitTurn: vi.fn(async () => ({ allowed: true as const, reasonCode: "allowed" as const, diff --git a/packages/testkit/README.md b/packages/testkit/README.md index 6141a53c..d2f803ad 100644 --- a/packages/testkit/README.md +++ b/packages/testkit/README.md @@ -18,3 +18,16 @@ call, so a test can show a decision failing closed. host store would, and throw on an id the store does not hold. SharedOS is currently a `1.0.0` preview. + +## Replay storage + +`SharedOSKernelOptions.replayStore` enables the kernel-owned replay state machine +for execution, tool, resource and message IDs. Hosts implement `ReplayStore` with +atomic durable claims and fenced settlement. Keep identity tombstones after +result expiry; never reclaim pending operations solely because a timeout passed. +See [ADR 0028](https://github.com/systemind-team/SharedOS/blob/main/docs/adr/0028-durable-operation-replay.md). + +`InMemoryReplayStore` is exported by `@aicoo/sharedos-testkit` for isolated tests. +It is not durable or suitable for production. Protocol v1 without a configured +store preserves legacy unprotected behavior. External effects still require +provider idempotency or reconciliation for crash ambiguity. diff --git a/packages/testkit/src/index.ts b/packages/testkit/src/index.ts index 62d41b85..bc1327c0 100644 --- a/packages/testkit/src/index.ts +++ b/packages/testkit/src/index.ts @@ -26,6 +26,7 @@ import { type ResourceProvider, type ToolNamespaceSettingsStore, } from "@aicoo/sharedos-core"; +import { InMemoryReplayStore } from "./replay.js"; import { addressesEqual } from "@aicoo/sharedos-core/internal"; export class InMemoryAuditSink implements AuditSink { @@ -307,6 +308,7 @@ export function createTestKernel(options: TestKernelOptions = {}): TestKernel { return { kernel: new SharedOSKernel({ grantSource: options.grantSource ?? grants, + replayStore: new InMemoryReplayStore(), audit, authorizer: new CapabilityAuthorizer({ usageStore: new InMemoryGrantUsageStore(), @@ -402,3 +404,4 @@ async function echoResourceOperation(operation: ResourceOperation): Promise(); + + async claim( + key: ReplayKey, + fingerprint: string, + ): Promise<{ claimed: boolean; record: ReplayRecord }> { + const identity = canonicalReplayJson(key); + const existing = this.#records.get(identity); + if (existing !== undefined) return { claimed: false, record: structuredClone(existing) }; + const record = ReplayRecordSchema.parse({ + key: structuredClone(key), + fingerprint, + token: crypto.randomUUID(), + state: "pending", + }); + this.#records.set(identity, record); + return { claimed: true, record: structuredClone(record) }; + } + + async settle( + key: ReplayKey, + token: string, + outcome: { + readonly state: "completed" | "failed" | "interrupted"; + readonly result?: JsonValue; + }, + ): Promise { + const identity = canonicalReplayJson(key); + const existing = this.#records.get(identity); + if (existing === undefined || existing.token !== token || existing.state !== "pending") + return false; + this.#records.set( + identity, + ReplayRecordSchema.parse({ ...existing, ...structuredClone(outcome) }), + ); + return true; + } + + /** Drop a sensitive result body while preserving its unclaimable identity. */ + expire(key: ReplayKey): boolean { + const identity = canonicalReplayJson(key); + const existing = this.#records.get(identity); + if (existing === undefined || existing.state === "pending") return false; + const { result: _result, ...record } = existing; + this.#records.set(identity, { ...record, state: "expired" }); + return true; + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 56f3c601..5757b375 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -149,6 +149,10 @@ importers: zod: specifier: ^3.24.1 version: 3.25.76 + devDependencies: + '@aicoo/sharedos-http': + specifier: workspace:* + version: link:../http packages/contracts: dependencies: