@openwop/openwop-conformance 1.6.0 → 1.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +18 -0
- package/README.md +2 -2
- package/api/asyncapi.yaml +74 -1
- package/api/openapi.yaml +316 -0
- package/coverage.md +16 -0
- package/fixtures/conformance-run-duration-breach.json +33 -0
- package/fixtures.md +19 -0
- package/package.json +1 -1
- package/schemas/README.md +12 -0
- package/schemas/agent-inventory-response.schema.json +90 -0
- package/schemas/ai-envelope.schema.json +28 -0
- package/schemas/annotation-create.schema.json +37 -0
- package/schemas/annotation.schema.json +56 -0
- package/schemas/artifact-type-pack-manifest.schema.json +160 -0
- package/schemas/capabilities.schema.json +195 -4
- package/schemas/chat-card-pack-manifest.schema.json +158 -0
- package/schemas/envelopes/media.audio.schema.json +38 -0
- package/schemas/envelopes/media.file.schema.json +37 -0
- package/schemas/envelopes/media.image.schema.json +33 -0
- package/schemas/heartbeat-evaluated.schema.json +14 -0
- package/schemas/heartbeat-state-changed.schema.json +14 -0
- package/schemas/node-pack-manifest.schema.json +16 -1
- package/schemas/run-event-payloads.schema.json +96 -5
- package/schemas/run-event.schema.json +4 -0
- package/schemas/workflow-definition.schema.json +5 -0
- package/schemas/workspace-file-create.schema.json +20 -0
- package/schemas/workspace-file.schema.json +39 -0
- package/src/lib/agentLoop.ts +44 -0
- package/src/lib/agentRuntime.ts +45 -0
- package/src/lib/artifactTypes.ts +96 -0
- package/src/lib/cardPacks.ts +52 -0
- package/src/lib/discovery-capabilities.ts +50 -0
- package/src/lib/distillation.ts +38 -0
- package/src/lib/feedback.ts +31 -0
- package/src/lib/heartbeat.ts +31 -0
- package/src/lib/memoryAttribution.ts +48 -0
- package/src/lib/subRunAttestation.ts +35 -0
- package/src/lib/toolHooks.ts +33 -0
- package/src/scenarios/agent-loop-iteration-monotonic.test.ts +33 -0
- package/src/scenarios/agent-loop-stateful-resume.test.ts +28 -0
- package/src/scenarios/agent-loop-version5-shape.test.ts +41 -0
- package/src/scenarios/agent-loop-workspace-snapshot.test.ts +33 -0
- package/src/scenarios/agent-manifest-runtime.test.ts +85 -0
- package/src/scenarios/ai-envelope-shape.test.ts +14 -18
- package/src/scenarios/aiEnvelope.capBreached.test.ts +2 -1
- package/src/scenarios/aiEnvelope.schemaDrift.test.ts +2 -1
- package/src/scenarios/aiEnvelope.universalKinds.test.ts +2 -1
- package/src/scenarios/approval-gate-flow.test.ts +4 -6
- package/src/scenarios/artifact-schema-compile-bounded.test.ts +126 -0
- package/src/scenarios/artifact-type-pack-install.test.ts +78 -0
- package/src/scenarios/artifact-type-pack-manifest-validation.test.ts +140 -0
- package/src/scenarios/artifact-type-store-without-render.test.ts +54 -0
- package/src/scenarios/audit-log-integrity.test.ts +3 -2
- package/src/scenarios/auth-api-key-rotation.test.ts +2 -1
- package/src/scenarios/auth-mtls.test.ts +2 -1
- package/src/scenarios/auth-oauth2-client-credentials.test.ts +2 -1
- package/src/scenarios/auth-oidc-user-bearer.test.ts +2 -1
- package/src/scenarios/auth-saml-profile.test.ts +2 -1
- package/src/scenarios/auth-scim-profile.test.ts +2 -1
- package/src/scenarios/authorization-fail-closed.test.ts +2 -1
- package/src/scenarios/authorization-roles-shape.test.ts +2 -1
- package/src/scenarios/byok-auth-modes.test.ts +141 -0
- package/src/scenarios/chat-card-pack-execution.test.ts +56 -0
- package/src/scenarios/chat-card-pack-manifest-validation.test.ts +128 -0
- package/src/scenarios/commitment-fired.test.ts +83 -0
- package/src/scenarios/credential-payload-redaction.test.ts +2 -1
- package/src/scenarios/credentials-capability-shape.test.ts +2 -1
- package/src/scenarios/cross-engine-append-ordering.test.ts +2 -1
- package/src/scenarios/cross-host-ancestry-endpoint.test.ts +3 -2
- package/src/scenarios/cross-host-causation-shape.test.ts +3 -2
- package/src/scenarios/deadletter-capability-shape.test.ts +2 -1
- package/src/scenarios/deadletter-retry-exhaustion.test.ts +2 -1
- package/src/scenarios/distillation-index-roundtrip.test.ts +35 -0
- package/src/scenarios/distillation-secret-carryforward.test.ts +35 -0
- package/src/scenarios/distillation-shape.test.ts +41 -0
- package/src/scenarios/distillation-stable-archive.test.ts +37 -0
- package/src/scenarios/distillation-token-budget.test.ts +45 -0
- package/src/scenarios/envelope-completion-distinguishes-truncation.test.ts +4 -3
- package/src/scenarios/envelope-reasoning-secret-redaction.test.ts +5 -4
- package/src/scenarios/envelope-reasoning-shape.test.ts +3 -2
- package/src/scenarios/envelope-refusal-shape.test.ts +3 -2
- package/src/scenarios/envelope-rendering-hint.test.ts +95 -0
- package/src/scenarios/envelope-retry-attempted.test.ts +2 -1
- package/src/scenarios/envelope-tier-one-subset-static.test.ts +3 -2
- package/src/scenarios/exec-not-protocol-tier.test.ts +137 -0
- package/src/scenarios/experimental-tier-shape.test.ts +5 -4
- package/src/scenarios/feedback-capability-shape.test.ts +35 -0
- package/src/scenarios/feedback-correction-redaction.test.ts +35 -0
- package/src/scenarios/feedback-cross-tenant-isolation.test.ts +37 -0
- package/src/scenarios/feedback-fork-not-copied.test.ts +40 -0
- package/src/scenarios/feedback-on-terminal-run.test.ts +32 -0
- package/src/scenarios/feedback-record-and-list.test.ts +32 -0
- package/src/scenarios/feedback-unsupported-501.test.ts +32 -0
- package/src/scenarios/fs-path-traversal.test.ts +2 -1
- package/src/scenarios/heartbeat-capability-shape.test.ts +35 -0
- package/src/scenarios/heartbeat-fires-once-per-tick.test.ts +28 -0
- package/src/scenarios/heartbeat-idempotent-no-spam.test.ts +43 -0
- package/src/scenarios/heartbeat-runtime-bound.test.ts +30 -0
- package/src/scenarios/http-client-ssrf.test.ts +10 -13
- package/src/scenarios/mcp-toolcall-redaction.test.ts +3 -2
- package/src/scenarios/media-url-inline-cap.test.ts +167 -0
- package/src/scenarios/memory-attribution-emits-on-write.test.ts +54 -0
- package/src/scenarios/memory-attribution-no-content.test.ts +45 -0
- package/src/scenarios/memory-attribution-replay-stable.test.ts +60 -0
- package/src/scenarios/memory-attribution-shape.test.ts +28 -0
- package/src/scenarios/memory-attribution-tenant-scoped.test.ts +44 -0
- package/src/scenarios/memory-compaction-event-emitted.test.ts +2 -1
- package/src/scenarios/memory-compaction-provenance-tag.test.ts +2 -1
- package/src/scenarios/memory-compaction-sr1-carry-forward.test.ts +2 -1
- package/src/scenarios/memory-consolidation-idempotent.test.ts +77 -0
- package/src/scenarios/memory-consolidation-shape.test.ts +90 -0
- package/src/scenarios/model-capability-substituted.test.ts +2 -1
- package/src/scenarios/multi-agent-confidence-escalation.test.ts +5 -4
- package/src/scenarios/multi-agent-handoff-state-machine.test.ts +6 -5
- package/src/scenarios/multi-agent-memory-lifecycle.test.ts +4 -3
- package/src/scenarios/multi-region-idempotency.test.ts +10 -10
- package/src/scenarios/oauth-capability-shape.test.ts +2 -1
- package/src/scenarios/oauth-connector-redaction.test.ts +2 -1
- package/src/scenarios/pause-resume.test.ts +3 -3
- package/src/scenarios/production-backpressure.test.ts +2 -2
- package/src/scenarios/production-retention-expiry.test.ts +2 -2
- package/src/scenarios/prompt-all-four-kinds-events.test.ts +2 -1
- package/src/scenarios/prompt-composed-secret-redaction.test.ts +2 -1
- package/src/scenarios/prompt-composed-trust-marker.test.ts +2 -1
- package/src/scenarios/prompt-end-to-end-events.test.ts +2 -1
- package/src/scenarios/prompt-list-and-fetch.test.ts +2 -1
- package/src/scenarios/prompt-mutable-lifecycle.test.ts +2 -1
- package/src/scenarios/prompt-mutation-workspace-membership-enforced.test.ts +2 -1
- package/src/scenarios/prompt-pack-install.test.ts +2 -1
- package/src/scenarios/prompt-read-workspace-membership-enforced.test.ts +2 -1
- package/src/scenarios/prompt-render-deterministic.test.ts +2 -1
- package/src/scenarios/prompt-resolution-chain-agent-intrinsic.test.ts +2 -1
- package/src/scenarios/prompt-resolution-chain-fallback-cascade.test.ts +2 -1
- package/src/scenarios/prompt-resolution-chain-node-wins.test.ts +2 -1
- package/src/scenarios/prompt-template-shape.test.ts +2 -1
- package/src/scenarios/provider-usage.test.ts +2 -1
- package/src/scenarios/redaction.test.ts +4 -1
- package/src/scenarios/replay-divergence-at-refusal.test.ts +4 -3
- package/src/scenarios/replay-fork-arbitrary.test.ts +3 -1
- package/src/scenarios/replay-llm-cache-key-portable.test.ts +2 -1
- package/src/scenarios/replayDeterminism.test.ts +3 -1
- package/src/scenarios/run-execution-bounds-shape.test.ts +133 -0
- package/src/scenarios/sandbox-memory-cap.test.ts +2 -1
- package/src/scenarios/sandbox-mvp-behavior.test.ts +2 -1
- package/src/scenarios/sandbox-no-host-fs-escape.test.ts +2 -1
- package/src/scenarios/sandbox-timeout-cap.test.ts +2 -1
- package/src/scenarios/scheduling-capability-shape.test.ts +2 -1
- package/src/scenarios/scheduling-cron-fires-once.test.ts +2 -1
- package/src/scenarios/secret-leakage-otel-attribute.test.ts +7 -6
- package/src/scenarios/spec-corpus-validity.test.ts +4 -1
- package/src/scenarios/subrun-approval-fail-closed.test.ts +33 -0
- package/src/scenarios/subrun-approval-gate.test.ts +35 -0
- package/src/scenarios/subrun-attestation-shape.test.ts +30 -0
- package/src/scenarios/subrun-checksum-stable.test.ts +43 -0
- package/src/scenarios/tool-hooks-authorization-fail-closed.test.ts +39 -0
- package/src/scenarios/tool-hooks-content-free.test.ts +40 -0
- package/src/scenarios/tool-hooks-rate-limit.test.ts +32 -0
- package/src/scenarios/tool-hooks-secret-redaction.test.ts +34 -0
- package/src/scenarios/tool-hooks-shape.test.ts +34 -0
- package/src/scenarios/wasm-pack-abi-version-rejection.test.ts +3 -10
- package/src/scenarios/wasm-pack-invoke-completed.test.ts +2 -2
- package/src/scenarios/wasm-pack-invoke-suspended.test.ts +2 -2
- package/src/scenarios/wasm-pack-load.test.ts +2 -2
- package/src/scenarios/wasm-pack-memory-cap.test.ts +3 -6
- package/src/scenarios/wasm-pack-replay-determinism.test.ts +2 -2
- package/src/scenarios/workflow-primary-output-annotation.test.ts +142 -0
- package/src/scenarios/workspace-behavior.test.ts +134 -0
- package/src/scenarios/workspace-capability-shape.test.ts +73 -0
- package/src/scenarios/workspace-cross-tenant-isolation.test.ts +84 -0
|
@@ -56,7 +56,22 @@
|
|
|
56
56
|
"peerDependencies": {
|
|
57
57
|
"type": "object",
|
|
58
58
|
"additionalProperties": { "type": "string" },
|
|
59
|
-
"description": "Engine-supplied capabilities the pack consumes (e.g., a particular AI provider extension). Resolved against `Capabilities` at register time."
|
|
59
|
+
"description": "Engine-supplied capabilities the pack consumes (e.g., a particular AI provider extension). Resolved against `Capabilities` at register time. Each entry is REQUIRED by default — an unmet entry fails install with `pack_peer_dependency_missing` — unless marked optional in `peerDependenciesMeta` (RFC 0072 §C)."
|
|
60
|
+
},
|
|
61
|
+
"peerDependenciesMeta": {
|
|
62
|
+
"type": "object",
|
|
63
|
+
"description": "Per-peer-dependency metadata (RFC 0072 §C). Keys mirror `peerDependencies` keys. Marking an entry `optional: true` makes it degrade-if-unmet instead of refuse-if-unmet: a host that does not satisfy it MUST install the pack with that capability inert AND surface it in the affected agents' inventory `degraded[]` (RFC 0072 §A); it MUST NOT silently ignore the declared dependency. Default (absent entry) is required. Packs published before RFC 0072 validate unchanged.",
|
|
64
|
+
"additionalProperties": {
|
|
65
|
+
"type": "object",
|
|
66
|
+
"additionalProperties": false,
|
|
67
|
+
"properties": {
|
|
68
|
+
"optional": {
|
|
69
|
+
"type": "boolean",
|
|
70
|
+
"default": false,
|
|
71
|
+
"description": "When true, an unmet peer dependency degrades (inert + surfaced in `degraded[]`) rather than failing install."
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
60
75
|
},
|
|
61
76
|
"nodes": {
|
|
62
77
|
"type": "array",
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
3
|
"$id": "https://openwop.dev/spec/v1/run-event-payloads.schema.json",
|
|
4
4
|
"title": "RunEventPayloads",
|
|
5
|
-
"description": "Per-RunEventType payload schemas. The base RunEventDoc shape (run-event.schema.json) leaves `payload` permissive for forward-compat. This schema defines the canonical payload contract for each known RunEventType. Consumers MAY pin strict payload validation via `$defs.<typeId>` and `ajv.validate(schema.$defs[event.type], event.payload)`. Unknown event types MUST be tolerated (no $defs match → fold best-effort).\n\
|
|
5
|
+
"description": "Per-RunEventType payload schemas. The base RunEventDoc shape (run-event.schema.json) leaves `payload` permissive for forward-compat. This schema defines the canonical payload contract for each known RunEventType. Consumers MAY pin strict payload validation via `$defs.<typeId>` and `ajv.validate(schema.$defs[event.type], event.payload)`. Unknown event types MUST be tolerated (no $defs match → fold best-effort).\n\n75 variants from `run-event.schema.json#$defs.RunEventType` are covered, grouped into ~20 shape families with shared $defs. Naming convention: camelCase keys mirror dotted RunEventType names (e.g., `run.started` → `runStarted`).",
|
|
6
6
|
"type": "object",
|
|
7
7
|
"$defs": {
|
|
8
8
|
"_typeIndex": {
|
|
@@ -76,6 +76,10 @@
|
|
|
76
76
|
"conversation.exchanged": { "$ref": "#/$defs/conversationExchanged" },
|
|
77
77
|
"conversation.closed": { "$ref": "#/$defs/conversationClosed" },
|
|
78
78
|
"memory.compacted": { "$ref": "#/$defs/memoryCompacted" },
|
|
79
|
+
"memory.written": { "$ref": "#/$defs/memoryWritten" },
|
|
80
|
+
"agent.memory.consolidated": { "$ref": "#/$defs/agentMemoryConsolidated" },
|
|
81
|
+
"commitment.fired": { "$ref": "#/$defs/commitmentFired" },
|
|
82
|
+
"workspace.updated": { "$ref": "#/$defs/workspaceUpdated" },
|
|
79
83
|
"core.workflowChain.event": { "$ref": "#/$defs/coreWorkflowChainEvent" },
|
|
80
84
|
"core.workflowChain.confidence-escalated": { "$ref": "#/$defs/coreWorkflowChainConfidenceEscalated" },
|
|
81
85
|
"connector.authorized": { "$ref": "#/$defs/connectorAuthorized" },
|
|
@@ -161,6 +165,24 @@
|
|
|
161
165
|
"items": { "type": "string" },
|
|
162
166
|
"description": "On phase `output.harvested`: which parent-variable keys were populated by the dispatch config's outputMapping per RFC 0022 §A. SHOULD be present; conformance asserts presence when outputMapping is non-empty."
|
|
163
167
|
},
|
|
168
|
+
"attestation": {
|
|
169
|
+
"type": "object",
|
|
170
|
+
"description": "RFC 0063 (when `capabilities.agents.subRunAttestation: true` AND the `core.subWorkflow` node sets `outputAttestation.checksum: true`). A content checksum over the child's harvested output object, computed BEFORE `outputMapping` is applied so the parent (or a downstream node) can verify integrity — host-independent because the recipe is RFC 8785 JCS canonicalization + SHA-256 (the `replay.md` recipe, RFC 0041), enabling verification of a child that ran on a different host (RFC 0040). Advisory: the host does NOT itself reject on checksum mismatch; that is parent policy.",
|
|
171
|
+
"additionalProperties": false,
|
|
172
|
+
"required": ["checksum", "algorithm"],
|
|
173
|
+
"properties": {
|
|
174
|
+
"checksum": {
|
|
175
|
+
"type": "string",
|
|
176
|
+
"minLength": 1,
|
|
177
|
+
"description": "Lowercase hex digest of the canonicalized harvested-output object. Content-free: a hash, never the outputs themselves."
|
|
178
|
+
},
|
|
179
|
+
"algorithm": {
|
|
180
|
+
"type": "string",
|
|
181
|
+
"enum": ["sha256"],
|
|
182
|
+
"description": "Hash algorithm. `sha256` is the only v1 value; the enum reserves the axis for a future agility migration without a wire-shape break."
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
},
|
|
164
186
|
"error": {
|
|
165
187
|
"$ref": "#/$defs/_errorObject",
|
|
166
188
|
"description": "On phases `dispatch.failed` / `child.failed` / `child.cancelled`: the canonical error envelope."
|
|
@@ -585,7 +607,9 @@
|
|
|
585
607
|
"artifactType": { "type": "string", "minLength": 1 },
|
|
586
608
|
"nodeId": { "type": "string" },
|
|
587
609
|
"version": { "type": "string" },
|
|
588
|
-
"summary": { "type": "string" }
|
|
610
|
+
"summary": { "type": "string" },
|
|
611
|
+
"registered": { "type": "boolean", "description": "RFC 0071. True when `artifactType` resolves to a registered artifact type (pack- or host-registered) and the payload was validated against its schema; false for unregistered/host-local types accepted without schema validation. Optional; absent ⇒ treat as true, preserving pre-RFC-0071 semantics. See artifact-type-packs.md §\"Binding the existing artifact surfaces\"." },
|
|
612
|
+
"registrationSource": { "type": "string", "enum": ["pack", "host"], "description": "RFC 0075. Provenance of a registered type: `pack` (matches an installed artifact-type pack) or `host` (a host-native type the host validates against a host-known schema, independent of any pack — e.g. an AI host's built-in artifact types). Optional; meaningful only when `registered: true`. Absent ⇒ unspecified provenance (existing semantics)." }
|
|
589
613
|
},
|
|
590
614
|
"additionalProperties": true
|
|
591
615
|
},
|
|
@@ -717,10 +741,10 @@
|
|
|
717
741
|
|
|
718
742
|
"capBreached": {
|
|
719
743
|
"type": "object",
|
|
720
|
-
"description": "Emitted when a CapabilityLimit is exceeded. Protocol-level limits use the
|
|
744
|
+
"description": "Emitted when a CapabilityLimit is exceeded. Protocol-level limits use the engine kinds (clarificationRounds / schemaRounds / envelopesPerTurn / maxNodeExecutions). RFC 0008 §K WASM-runtime caps use the wasm-* kinds (memory ceiling, fuel exhaustion, execution-time wall-clock). RFC 0058 run-execution bounds use the run-scoped run-duration (wall-clock timeout; limit=resolvedMs, observed=elapsedMs) and loop-iterations (agent-loop ceiling; limit=resolvedMax, observed=iterationCount) kinds.",
|
|
721
745
|
"required": ["kind", "limit", "observed"],
|
|
722
746
|
"properties": {
|
|
723
|
-
"kind": { "type": "string", "enum": ["clarification", "schema", "envelopes", "node-executions", "wasm-memory", "wasm-fuel", "wasm-execution-time"] },
|
|
747
|
+
"kind": { "type": "string", "enum": ["clarification", "schema", "envelopes", "node-executions", "wasm-memory", "wasm-fuel", "wasm-execution-time", "run-duration", "loop-iterations"] },
|
|
724
748
|
"limit": { "type": "integer", "minimum": 0 },
|
|
725
749
|
"observed": { "type": "integer", "minimum": 0 },
|
|
726
750
|
"nodeId": { "type": "string" }
|
|
@@ -1160,6 +1184,9 @@
|
|
|
1160
1184
|
"toolName": { "type": "string", "minLength": 1, "description": "Tool identifier (typically a node-pack typeId or function name)." },
|
|
1161
1185
|
"callId": { "type": "string", "minLength": 1, "description": "Unique correlation id linking this call to the subsequent `agent.toolReturned`. Hosts SHOULD use UUIDs or content-addressable hashes." },
|
|
1162
1186
|
"inputs": { "description": "Tool inputs. Shape is tool-specific; consumers MUST NOT assume an object." },
|
|
1187
|
+
"argsHash": { "type": "string", "description": "RFC 0064 (when `capabilities.toolHooks.prePostEvents`). SHA-256 over RFC 8785 JCS-canonicalized args with resolved secrets already redacted (SR-1) — the SIEM-safe, content-free alternative to `inputs`. Raw key material MUST NOT enter the hash input." },
|
|
1188
|
+
"principal": { "type": "string", "description": "RFC 0064. RFC 0048 principal id the call is attributed to (`core.system` for non-agent host egress)." },
|
|
1189
|
+
"transport": { "type": "string", "enum": ["mcp", "http", "native"], "description": "RFC 0064. How the tool was reached." },
|
|
1163
1190
|
"causationHostId": { "type": "string", "minLength": 1, "description": "RFC 0040 §A — optional cross-host causation pointer. Present + non-empty when this event's `causationId` points at an event on a DIFFERENT host; absent when same-host. MUST equal the originating host's `crossHostCausation.hostId`." }
|
|
1164
1191
|
},
|
|
1165
1192
|
"additionalProperties": true
|
|
@@ -1175,6 +1202,8 @@
|
|
|
1175
1202
|
"callId": { "type": "string", "minLength": 1 },
|
|
1176
1203
|
"outcome": { "description": "Tool result. Discriminated by host on success vs error; payload shape is tool-specific." },
|
|
1177
1204
|
"error": { "$ref": "#/$defs/_errorObject", "description": "Set on tool-execution failure. Mutually exclusive with `outcome`." },
|
|
1205
|
+
"status": { "type": "string", "enum": ["ok", "error", "forbidden", "rate_limited"], "description": "RFC 0064 (when `capabilities.toolHooks.prePostEvents`). Tool-hooks outcome. `forbidden`/`rate_limited` mean the tool was never invoked (per-tool authz / rate-limit gate)." },
|
|
1206
|
+
"durationMs": { "type": "integer", "minimum": 0, "description": "RFC 0064. Wall-clock tool duration; recorded in the event and re-emitted verbatim on replay/`:fork` (MUST NOT be recomputed from a clock, per `replay.md`). Absent when the call never started (`forbidden`/`rate_limited`)." },
|
|
1178
1207
|
"causationHostId": { "type": "string", "minLength": 1, "description": "RFC 0040 §A — optional cross-host causation pointer. Present + non-empty when this event's `causationId` points at an event on a DIFFERENT host; absent when same-host. MUST equal the originating host's `crossHostCausation.hostId`." }
|
|
1179
1208
|
},
|
|
1180
1209
|
"additionalProperties": true
|
|
@@ -1213,6 +1242,7 @@
|
|
|
1213
1242
|
"properties": {
|
|
1214
1243
|
"agentId": { "type": "string", "minLength": 3, "maxLength": 256, "description": "AgentRef.agentId of the orchestrator. MUST equal `RunSnapshot.runOrchestrator.agentId` for the run's lifetime — orchestrator identity is set at first decision and does not change." },
|
|
1215
1244
|
"decision": { "$ref": "orchestrator-decision.schema.json" },
|
|
1245
|
+
"iteration": { "type": "integer", "minimum": 1, "description": "RFC 0061 (`multiAgent.executionModel.version >= 5`). 1-based, monotonic count of orchestrator turns in this run — the observable loop-iteration counter that `maxLoopIterations` (RFC 0058) bounds; a breach emits `cap.breached { kind: 'loop-iterations', observed: <this+1> }`. A `version >= 5` host MUST set it on every `runOrchestrator.decided`, incrementing by exactly 1 per turn and surviving a stateful HITL resume unchanged. Recorded in the event and re-emitted verbatim on replay/`:fork`, never recomputed (`replay.md`). Omitted by hosts on `version < 5`." },
|
|
1216
1246
|
"causationHostId": { "type": "string", "minLength": 1, "description": "RFC 0040 §A — optional cross-host causation pointer. Present + non-empty when this event's `causationId` points at an event on a DIFFERENT host; absent when same-host. MUST equal the originating host's `crossHostCausation.hostId`." }
|
|
1217
1247
|
},
|
|
1218
1248
|
"additionalProperties": false
|
|
@@ -1275,9 +1305,70 @@
|
|
|
1275
1305
|
"sourceIds": { "type": "array", "items": { "type": "string", "minLength": 1 }, "description": "Ids of entries collapsed into outputId. Hosts MAY omit when sourceCount > 100; the `compacted-from:<runId>` tag convention (RFC 0012 §C) becomes the authoritative provenance signal. When present, MUST be exhaustive within the array (no 'and N more' semantics)." },
|
|
1276
1306
|
"sourceCount": { "type": "integer", "minimum": 1, "description": "Total source entries collapsed, including any not enumerated in `sourceIds`." },
|
|
1277
1307
|
"trigger": { "type": "string", "enum": ["host-managed", "client-requested", "both"], "description": "Matches `capabilities.memory.compaction.trigger`; indicates which trigger path drove this run. v1.x normates only `host-managed`." },
|
|
1278
|
-
"byteSize": { "type": "integer", "minimum": 0, "description": "Byte size of the resulting MemoryEntry.content." }
|
|
1308
|
+
"byteSize": { "type": "integer", "minimum": 0, "description": "Byte size of the resulting MemoryEntry.content." },
|
|
1309
|
+
"distillation": {
|
|
1310
|
+
"type": "object",
|
|
1311
|
+
"description": "RFC 0062 (when `capabilities.memory.distillation.supported: true`). Present when this compaction is part of a budgeted distillation run (a scheduled or on-demand 'dream'). Absent for a plain RFC 0012 compaction. Carries the token-budget accounting + whether the memory index was refreshed — not a parallel `memory.distilled` event.",
|
|
1312
|
+
"additionalProperties": false,
|
|
1313
|
+
"required": ["tokenBudget", "tokensUsed"],
|
|
1314
|
+
"properties": {
|
|
1315
|
+
"tokenBudget": { "type": "integer", "minimum": 1, "description": "Effective per-run budget honored (≤ advertised `maxTokenBudget`), counted against the advertised `tokenizerName`." },
|
|
1316
|
+
"tokensUsed": { "type": "integer", "minimum": 0, "description": "Total input+output tokens the distillation consumed, best-effort-honest per the advertised tokenizer (±10% tolerance). MUST be ≤ `tokenBudget` on a successful run." },
|
|
1317
|
+
"indexUpdated": { "type": "boolean", "description": "Whether this run refreshed the memory-index workspace file (`MEMORY-INDEX.json`, RFC 0059). When `true`, a `workspace.updated` event was also emitted for that path." }
|
|
1318
|
+
}
|
|
1319
|
+
}
|
|
1320
|
+
},
|
|
1321
|
+
"additionalProperties": true
|
|
1322
|
+
},
|
|
1323
|
+
"memoryWritten": {
|
|
1324
|
+
"type": "object",
|
|
1325
|
+
"description": "RFC 0057. Emitted by a host advertising `capabilities.memory.attribution.emitsWriteEvents: true` for each memory write a run makes, attributing it to the node (and agent, when known) that caused it. Content-free: identifiers + non-secret tags only — the entry content is served by the read-side (`MemoryAdapter.get`), already SR-1-redacted. On replay the event is re-read from the log, never regenerated (the host MUST NOT mint a new `memoryId` or timestamp at replay time). SECURITY invariants `memory-attribution-no-content` + `memory-attribution-tenant-scoped` apply.",
|
|
1326
|
+
"required": ["memoryRef", "memoryId"],
|
|
1327
|
+
"properties": {
|
|
1328
|
+
"memoryRef": { "type": "string", "minLength": 1, "description": "Opaque MemoryAdapter handle (RFC 0004 §C) the entry was written under; resolvable via the read-side." },
|
|
1329
|
+
"memoryId": { "type": "string", "minLength": 1, "description": "Host-issued, stable entry id; correlates to `MemoryAdapter.get(memoryRef, memoryId)`." },
|
|
1330
|
+
"nodeId": { "type": "string", "minLength": 1, "description": "SHOULD — the node whose execution caused the write. Omitted only for writes with no node attribution (e.g. host session-end auto-memory)." },
|
|
1331
|
+
"agentId": { "type": "string", "minLength": 1, "description": "MAY — the agent identity (AgentRef per RFC 0002) behind the write, when known." },
|
|
1332
|
+
"tags": { "type": "array", "items": { "type": "string" }, "description": "MAY — the entry's non-secret labels. MUST NOT carry secret material (the no-content invariant covers payload bodies)." }
|
|
1279
1333
|
},
|
|
1280
1334
|
"additionalProperties": true
|
|
1335
|
+
},
|
|
1336
|
+
"agentMemoryConsolidated": {
|
|
1337
|
+
"type": "object",
|
|
1338
|
+
"description": "RFC 0068 (`Draft`). Emitted by a host advertising `capabilities.agents.memoryConsolidation.supported: true` after a background consolidation pass over LONG-TERM memory (merge/dedup/supersede/strengthen). Content-free: identifiers + counts only — entry content is served by the SR-1-redacted read-side (`MemoryAdapter.get`). On replay the event is re-read from the log, never regenerated. Distinct from RFC 0062 `memory.compacted` (token-budgeted distillation of transactional memory). SR-1 carry-forward (RFC 0004) + CTI-1 apply to the consolidated entries.",
|
|
1339
|
+
"required": ["memoryRef", "inputCount", "outputCount"],
|
|
1340
|
+
"properties": {
|
|
1341
|
+
"memoryRef": { "type": "string", "minLength": 1, "description": "Opaque MemoryAdapter handle (RFC 0004 §C) the consolidation pass operated over. Bounds the pass to a single memory scope (CTI-1)." },
|
|
1342
|
+
"inputCount": { "type": "integer", "minimum": 0, "description": "Long-term entries considered by the pass." },
|
|
1343
|
+
"outputCount": { "type": "integer", "minimum": 0, "description": "Long-term entries remaining after the pass. MUST be <= inputCount for a pass that only merges/dedups; a strengthen-only pass MAY leave outputCount == inputCount." },
|
|
1344
|
+
"mergedIds": { "type": "array", "items": { "type": "string", "minLength": 1 }, "description": "MAY — ids of entries collapsed/superseded by the pass. Hosts MAY omit when inputCount is large; consumers MUST tolerate absence." },
|
|
1345
|
+
"trigger": { "type": "string", "enum": ["host-managed", "scheduled", "on-demand"], "description": "MAY — which initiation path drove this pass (mirrors `agents.memoryConsolidation.schedule`)." }
|
|
1346
|
+
},
|
|
1347
|
+
"additionalProperties": true
|
|
1348
|
+
},
|
|
1349
|
+
"commitmentFired": {
|
|
1350
|
+
"type": "object",
|
|
1351
|
+
"description": "RFC 0068 (`Draft`). Emitted by a host advertising `capabilities.agents.commitments.supported: true` when an inferred standing commitment fires. Content-free: identifiers + condition kind only — the intention text lives in SR-1-redacted memory (`MemoryAdapter.get(memoryRef, memoryId)`). The fired arm composes RFC 0052 (time) or RFC 0060 (predicate). On replay the event is re-read from the log, never regenerated (the host MUST NOT re-infer or re-fire a commitment at replay time). CTI-1: the commitment, its memory provenance, and any run it enqueues MUST share the source memory's tenant.",
|
|
1352
|
+
"required": ["commitmentId", "memoryRef", "condition"],
|
|
1353
|
+
"properties": {
|
|
1354
|
+
"commitmentId": { "type": "string", "minLength": 1, "description": "Host-issued, stable id for the inferred commitment. Correlates repeated observability across re-evaluations of the same commitment." },
|
|
1355
|
+
"memoryRef": { "type": "string", "minLength": 1, "description": "Opaque MemoryAdapter handle of the memory scope the commitment was inferred from (provenance + CTI-1 tenant binding)." },
|
|
1356
|
+
"memoryId": { "type": "string", "minLength": 1, "description": "MAY — id of the source `MemoryEntry` the commitment was inferred from, when a single entry is attributable. Resolvable via `MemoryAdapter.get`; content is read SR-1-redacted from the read-side." },
|
|
1357
|
+
"condition": { "type": "string", "enum": ["time", "predicate"], "description": "Which fire-condition kind triggered. `time` composes RFC 0052; `predicate` composes RFC 0060." },
|
|
1358
|
+
"enqueuedRunId": { "type": "string", "minLength": 1, "description": "MAY — id of the run the fired commitment enqueued, when the host initiates one. Absent when the commitment fires as a notification without a run." }
|
|
1359
|
+
},
|
|
1360
|
+
"additionalProperties": true
|
|
1361
|
+
},
|
|
1362
|
+
|
|
1363
|
+
"workspaceUpdated": {
|
|
1364
|
+
"description": "RFC 0059. Emitted by a host advertising `capabilities.workspace.supported: true` on each successful `PUT`/`DELETE` of a workspace file, attributing the change to the file `path` + resulting `version`. Content-free: carries the path + version only — the file body is served by the read-side (`GET /v1/host/workspace/files/{path}`), already SR-1-redacted (RFC 0059 §E WSR-1). On replay the event is re-read from the log, never regenerated. MUST NOT be emitted unless `capabilities.workspace.supported: true`. The proposed SECURITY invariant `workspace-cross-tenant-isolation` (WCT-1) applies to the read-side that resolves these paths.",
|
|
1365
|
+
"type": "object",
|
|
1366
|
+
"additionalProperties": false,
|
|
1367
|
+
"required": ["path", "version"],
|
|
1368
|
+
"properties": {
|
|
1369
|
+
"path": { "type": "string", "minLength": 1, "description": "Workspace-relative path of the file that changed (matches `workspace-file.schema.json#path`)." },
|
|
1370
|
+
"version": { "type": "integer", "minimum": 1, "description": "The file's resulting monotonic version after the write. On delete, the tombstone version when `versioned: true`." }
|
|
1371
|
+
}
|
|
1281
1372
|
}
|
|
1282
1373
|
}
|
|
1283
1374
|
}
|
|
@@ -129,6 +129,10 @@
|
|
|
129
129
|
"conversation.exchanged",
|
|
130
130
|
"conversation.closed",
|
|
131
131
|
"memory.compacted",
|
|
132
|
+
"memory.written",
|
|
133
|
+
"agent.memory.consolidated",
|
|
134
|
+
"commitment.fired",
|
|
135
|
+
"workspace.updated",
|
|
132
136
|
"core.workflowChain.event",
|
|
133
137
|
"core.workflowChain.confidence-escalated",
|
|
134
138
|
"connector.authorized",
|
|
@@ -138,6 +138,11 @@
|
|
|
138
138
|
},
|
|
139
139
|
"credentialsRef": { "type": "string" },
|
|
140
140
|
"settings": { "type": "object" },
|
|
141
|
+
"outputRole": {
|
|
142
|
+
"type": "string",
|
|
143
|
+
"enum": ["primary", "secondary"],
|
|
144
|
+
"description": "RFC 0065 — author hint that this terminal node's output is the workflow's primary deliverable (`primary`) or an auxiliary output (`secondary`). Advisory: hosts MUST execute the workflow identically regardless of value. Tooling MAY use the hint to pick which of N terminal nodes' outputs to surface as the run's canonical artifact. Unknown values + absent values fall back to default behavior (show all terminal outputs)."
|
|
145
|
+
},
|
|
141
146
|
"disabled": { "type": "boolean", "default": false },
|
|
142
147
|
"notes": { "type": "string" },
|
|
143
148
|
"groupId": { "type": "string" },
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://openwop.dev/spec/v1/workspace-file-create.schema.json",
|
|
4
|
+
"title": "WorkspaceFileCreate",
|
|
5
|
+
"description": "RFC 0059. Request body for `PUT /v1/host/workspace/files/{path}` (atomic create/replace). The `path` is taken from the URL and MUST NOT be supplied here; the host assigns `version`, `etag`, and `updatedAt`. The client supplies only `content` and an optional `contentType`. Optimistic concurrency is expressed via the `If-Match` request header (the file's current `etag`), NOT a body field — a stale `If-Match` returns `409 workspace_conflict`. A `content` exceeding `capabilities.workspace.maxFileBytes` returns `workspace_too_large`. The response is a full `workspace-file.schema.json`.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"additionalProperties": false,
|
|
8
|
+
"required": ["content"],
|
|
9
|
+
"properties": {
|
|
10
|
+
"content": {
|
|
11
|
+
"type": "string",
|
|
12
|
+
"description": "UTF-8 file body to persist. SR-1-redacted on write per RFC 0059 §E WSR-1."
|
|
13
|
+
},
|
|
14
|
+
"contentType": {
|
|
15
|
+
"type": "string",
|
|
16
|
+
"default": "text/markdown",
|
|
17
|
+
"description": "Advisory MIME type of `content`. Defaults to `text/markdown`."
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://openwop.dev/spec/v1/workspace-file.schema.json",
|
|
4
|
+
"title": "WorkspaceFile",
|
|
5
|
+
"description": "RFC 0059. A versioned, tenant·workspace-scoped ground-truth file in the `host.workspace` store. Returned by `GET /v1/host/workspace/files/{path}` and `PUT …/{path}`; the `list` endpoint returns the same shape minus `content` (metadata only). `path` is a flat namespace with `/`-in-names (NOT nested directories) — no `..`, no leading `/` (the pattern enforces this). Writes are atomic and optimistically concurrent via the `etag` token (`If-Match`). When `capabilities.workspace.versioned: true`, prior versions are retrievable via `?version=N`. Content is SR-1-redacted on write (RFC 0059 §E WSR-1) — never the BYOK plaintext.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"additionalProperties": false,
|
|
8
|
+
"required": ["path", "content", "version", "updatedAt"],
|
|
9
|
+
"properties": {
|
|
10
|
+
"path": {
|
|
11
|
+
"type": "string",
|
|
12
|
+
"pattern": "^[A-Za-z0-9][A-Za-z0-9._/-]{0,255}$",
|
|
13
|
+
"description": "Workspace-relative path. Flat namespace with `/`-in-names; MUST NOT contain `..` or a leading `/` (the pattern forbids both). Stable identity of the file within the `{tenant, workspace}` scope."
|
|
14
|
+
},
|
|
15
|
+
"content": {
|
|
16
|
+
"type": "string",
|
|
17
|
+
"description": "UTF-8 file body. SR-1-redacted on write per RFC 0059 §E WSR-1 — a value the run's BYOK vault resolved is persisted as `[REDACTED:<secretId>]`, never the plaintext."
|
|
18
|
+
},
|
|
19
|
+
"contentType": {
|
|
20
|
+
"type": "string",
|
|
21
|
+
"default": "text/markdown",
|
|
22
|
+
"description": "Advisory MIME type of `content`. Defaults to `text/markdown` (the ground-truth-file convention)."
|
|
23
|
+
},
|
|
24
|
+
"version": {
|
|
25
|
+
"type": "integer",
|
|
26
|
+
"minimum": 1,
|
|
27
|
+
"description": "Monotonic version. 1 on first create; each successful `PUT` bumps it by one. The latest version is always retrievable; historical versions are best-effort up to `capabilities.workspace.maxVersions`."
|
|
28
|
+
},
|
|
29
|
+
"etag": {
|
|
30
|
+
"type": "string",
|
|
31
|
+
"description": "Opaque version token for `If-Match` optimistic concurrency. A `PUT` carrying a stale `If-Match` MUST be refused with `409 workspace_conflict`."
|
|
32
|
+
},
|
|
33
|
+
"updatedAt": {
|
|
34
|
+
"type": "string",
|
|
35
|
+
"format": "date-time",
|
|
36
|
+
"description": "ISO 8601 timestamp of the write that produced this version."
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared helper for the RFC 0061 stateful agent-loop-lifecycle
|
|
3
|
+
* (`multiAgent.executionModel.version: 5`) conformance scenarios. Lives in lib/
|
|
4
|
+
* (not a *.test.ts) so scenarios import it via `../lib/agentLoop.js`.
|
|
5
|
+
*/
|
|
6
|
+
import { driver } from './driver.js';
|
|
7
|
+
import { readCapabilityFamily } from './discovery-capabilities.js';
|
|
8
|
+
|
|
9
|
+
/** Reads `multiAgent.executionModel` from discovery (root-first per RFC 0073);
|
|
10
|
+
* null when the host advertises no execution model (treated as no support). */
|
|
11
|
+
export async function readExecutionModelCap(): Promise<Record<string, unknown> | null> {
|
|
12
|
+
const ma = await readCapabilityFamily<{ executionModel?: unknown }>('multiAgent');
|
|
13
|
+
const em = ma?.executionModel;
|
|
14
|
+
return em && typeof em === 'object' ? (em as Record<string, unknown>) : null;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** True when the host advertises `executionModel.version >= 5`. */
|
|
18
|
+
export function isVersion5(em: Record<string, unknown> | null): boolean {
|
|
19
|
+
return typeof em?.version === 'number' && (em.version as number) >= 5;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** True when the host advertises `workspace.supported: true` (root-first per RFC 0073). */
|
|
23
|
+
export async function hasWorkspace(): Promise<boolean> {
|
|
24
|
+
const ws = await readCapabilityFamily<{ supported?: unknown }>('workspace');
|
|
25
|
+
return ws?.supported === true;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
interface LoopResult {
|
|
29
|
+
decisions?: Array<{ iteration?: unknown }>;
|
|
30
|
+
workspaceVisible?: { atWriteTurn?: unknown; atNextTurn?: unknown };
|
|
31
|
+
resumedIteration?: unknown;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Drives one bounded stateful loop via the host-sample seam, or null
|
|
35
|
+
* (soft-skip) when the host doesn't expose it. The seam is host-extension
|
|
36
|
+
* surface specified in host-sample-test-seams.md §"Open seams":
|
|
37
|
+
* `POST /v1/host/sample/agentloop/run` returns the ordered
|
|
38
|
+
* `runOrchestrator.decided` payloads (each carrying `iteration`) the host
|
|
39
|
+
* would emit, plus optional workspace-visibility + resume reports. */
|
|
40
|
+
export async function invokeAgentLoop(body: Record<string, unknown>): Promise<LoopResult | null> {
|
|
41
|
+
const res = await driver.post('/v1/host/sample/agentloop/run', body);
|
|
42
|
+
if (res.status === 404 || res.status === 405) return null; // seam absent — soft-skip
|
|
43
|
+
return (res.json as LoopResult | undefined) ?? {};
|
|
44
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared helper for the RFC 0070 agents.manifestRuntime conformance scenario.
|
|
3
|
+
* Lives in lib/ (not a *.test.ts) so scenarios import it via `../lib/agentRuntime.js`.
|
|
4
|
+
*/
|
|
5
|
+
import { driver } from './driver.js';
|
|
6
|
+
import { readCapabilityFamily } from './discovery-capabilities.js';
|
|
7
|
+
|
|
8
|
+
/** Reads `agents.manifestRuntime` from discovery (root-first per RFC 0073);
|
|
9
|
+
* null when unadvertised. */
|
|
10
|
+
export async function readManifestRuntimeCap(): Promise<Record<string, unknown> | null> {
|
|
11
|
+
const agents = await readCapabilityFamily<{ manifestRuntime?: unknown }>('agents');
|
|
12
|
+
const mr = agents?.manifestRuntime;
|
|
13
|
+
return mr && typeof mr === 'object' ? (mr as Record<string, unknown>) : null;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
interface AgentInventory {
|
|
17
|
+
agents?: Array<{ agentId?: string; [k: string]: unknown }>;
|
|
18
|
+
total?: number;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** GET the NORMATIVE manifest-agent inventory (RFC 0072 §A `GET /v1/agents`);
|
|
22
|
+
* null when the host doesn't advertise the capability (404/405/501). */
|
|
23
|
+
export async function listManifestAgents(): Promise<AgentInventory | null> {
|
|
24
|
+
const res = await driver.get('/v1/agents');
|
|
25
|
+
if (res.status === 404 || res.status === 405 || res.status === 501) return null;
|
|
26
|
+
return (res.json as AgentInventory | undefined) ?? {};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface DispatchResult {
|
|
30
|
+
agentId?: string;
|
|
31
|
+
status?: string;
|
|
32
|
+
toolSurface?: string[];
|
|
33
|
+
confidence?: number;
|
|
34
|
+
threshold?: number;
|
|
35
|
+
events?: Array<{ type?: string; agentId?: string; decision?: string; [k: string]: unknown }>;
|
|
36
|
+
result?: unknown;
|
|
37
|
+
error?: { code?: string; message?: string };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Dispatch one manifest-agent turn via the host-sample seam; null when absent. */
|
|
41
|
+
export async function dispatchAgent(agentId: string, body: Record<string, unknown>): Promise<DispatchResult | null> {
|
|
42
|
+
const res = await driver.post(`/v1/host/sample/agents/${encodeURIComponent(agentId)}/dispatch`, body);
|
|
43
|
+
if (res.status === 404 || res.status === 405) return null;
|
|
44
|
+
return (res.json as DispatchResult | undefined) ?? {};
|
|
45
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared helpers for the RFC 0071 host.artifactTypes conformance scenarios.
|
|
3
|
+
* Lives in lib/ (not a *.test.ts) so scenarios import it via `../lib/artifactTypes.js`.
|
|
4
|
+
*
|
|
5
|
+
* The behavioral scenarios drive a documented host-extension seam — hosts
|
|
6
|
+
* wiring RFC 0071 Phase 1 (artifact-type packs) expose:
|
|
7
|
+
*
|
|
8
|
+
* POST /v1/host/sample/artifacttypes/install
|
|
9
|
+
* body: { manifest: <artifact-type-pack manifest>,
|
|
10
|
+
* schemas: { "<artifactTypeId>": <JSON Schema> } }
|
|
11
|
+
* → 2xx { installed: true, artifactTypeIds: string[] }
|
|
12
|
+
*
|
|
13
|
+
* POST /v1/host/sample/artifacttypes/produce
|
|
14
|
+
* body: { artifactTypeId: string, payload: unknown }
|
|
15
|
+
* → 2xx {
|
|
16
|
+
* artifactId: string,
|
|
17
|
+
* registered: boolean, // matched an installed artifact-type pack
|
|
18
|
+
* validated: boolean, // payload checked against the pack schema
|
|
19
|
+
* stored: boolean, // persisted (host.artifactTypes.store)
|
|
20
|
+
* rendered: boolean, // a renderer ran (host.artifactTypes.render)
|
|
21
|
+
* runStatus: 'completed' | 'failed',
|
|
22
|
+
* artifactCreated?: { registered?: boolean, artifactType?: string } // the emitted event
|
|
23
|
+
* }
|
|
24
|
+
*
|
|
25
|
+
* Either seam returning 404/405 means the host hasn't wired it → soft-skip.
|
|
26
|
+
*/
|
|
27
|
+
import { driver } from './driver.js';
|
|
28
|
+
|
|
29
|
+
interface DiscoveryDoc {
|
|
30
|
+
capabilities?: Record<string, unknown>;
|
|
31
|
+
// host.* capabilities may also appear top-level per host-capabilities.md §"The contract pattern".
|
|
32
|
+
[k: string]: unknown;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Reads `host.artifactTypes` from discovery (capabilities block or top-level); null when unadvertised. */
|
|
36
|
+
export async function readArtifactTypesCap(): Promise<Record<string, unknown> | null> {
|
|
37
|
+
const res = await driver.get('/.well-known/openwop');
|
|
38
|
+
const doc = res.json as DiscoveryDoc | undefined;
|
|
39
|
+
const fromCaps = doc?.capabilities && typeof doc.capabilities === 'object'
|
|
40
|
+
? (doc.capabilities as Record<string, unknown>)['host.artifactTypes']
|
|
41
|
+
: undefined;
|
|
42
|
+
const cap = fromCaps ?? doc?.['host.artifactTypes'];
|
|
43
|
+
return cap && typeof cap === 'object' ? (cap as Record<string, unknown>) : null;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function artifactTypesSupported(cap: Record<string, unknown> | null): boolean {
|
|
47
|
+
return cap?.['supported'] === true;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Installs an artifact-type pack via the host-sample seam, or null (soft-skip) when absent. */
|
|
51
|
+
export async function installArtifactTypePack(
|
|
52
|
+
manifest: unknown,
|
|
53
|
+
schemas: Record<string, unknown>,
|
|
54
|
+
): Promise<{ status: number; json: unknown } | null> {
|
|
55
|
+
const res = await driver.post('/v1/host/sample/artifacttypes/install', { manifest, schemas });
|
|
56
|
+
if (res.status === 404 || res.status === 405) return null;
|
|
57
|
+
return { status: res.status, json: res.json };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Produces an artifact of `artifactTypeId` via the host-sample seam, or null (soft-skip) when absent. */
|
|
61
|
+
export async function produceArtifact(
|
|
62
|
+
artifactTypeId: string,
|
|
63
|
+
payload: unknown,
|
|
64
|
+
): Promise<{ status: number; json: Record<string, unknown> } | null> {
|
|
65
|
+
const res = await driver.post('/v1/host/sample/artifacttypes/produce', { artifactTypeId, payload });
|
|
66
|
+
if (res.status === 404 || res.status === 405) return null;
|
|
67
|
+
return { status: res.status, json: (res.json ?? {}) as Record<string, unknown> };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** A minimal valid artifact-type pack + schema pair the scenarios install. */
|
|
71
|
+
export function sampleArtifactTypePack() {
|
|
72
|
+
const artifactTypeId = 'vendor.conformance.note';
|
|
73
|
+
const manifest = {
|
|
74
|
+
kind: 'artifact-type',
|
|
75
|
+
name: 'vendor.conformance.notes',
|
|
76
|
+
version: '1.0.0',
|
|
77
|
+
engines: { openwop: '>=1.1' },
|
|
78
|
+
artifactTypes: [
|
|
79
|
+
{
|
|
80
|
+
artifactTypeId,
|
|
81
|
+
schemaVersion: 1,
|
|
82
|
+
schemaRef: 'schemas/note.schema.json',
|
|
83
|
+
rendering: { display: 'markdown' },
|
|
84
|
+
},
|
|
85
|
+
],
|
|
86
|
+
};
|
|
87
|
+
const schema = {
|
|
88
|
+
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
89
|
+
$id: 'https://h.example/schemas/artifacts/vendor.conformance.note.schema.json',
|
|
90
|
+
type: 'object',
|
|
91
|
+
additionalProperties: false,
|
|
92
|
+
required: ['title', 'body'],
|
|
93
|
+
properties: { title: { type: 'string' }, body: { type: 'string' } },
|
|
94
|
+
};
|
|
95
|
+
return { artifactTypeId, manifest, schema };
|
|
96
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared helpers for the RFC 0071 Phase 2 host.chat.cardPacks conformance scenarios.
|
|
3
|
+
* Lives in lib/ so scenarios import it via `../lib/cardPacks.js`.
|
|
4
|
+
*
|
|
5
|
+
* Hosts wiring chat card packs expose a documented host-extension seam:
|
|
6
|
+
*
|
|
7
|
+
* POST /v1/host/sample/cardpacks/execute
|
|
8
|
+
* body: { cardTypeId: string, inputs: Record<string, unknown> }
|
|
9
|
+
* → 2xx {
|
|
10
|
+
* artifactId?: string,
|
|
11
|
+
* registered?: boolean, // output validated against the linked outputArtifactType
|
|
12
|
+
* validated?: boolean,
|
|
13
|
+
* contentTrust?: 'trusted' | 'untrusted', // the composed-envelope trust tag (R2)
|
|
14
|
+
* runStatus?: 'completed' | 'failed',
|
|
15
|
+
* artifactCreated?: { registered?: boolean, artifactType?: string }
|
|
16
|
+
* }
|
|
17
|
+
*
|
|
18
|
+
* A 404/405 means the host hasn't wired the seam → soft-skip.
|
|
19
|
+
*/
|
|
20
|
+
import { driver } from './driver.js';
|
|
21
|
+
|
|
22
|
+
interface DiscoveryDoc {
|
|
23
|
+
capabilities?: Record<string, unknown>;
|
|
24
|
+
[k: string]: unknown;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Reads `host.chat.cardPacks` (or the `host.chat` block's `cardPacks` facet) from discovery; null when unadvertised. */
|
|
28
|
+
export async function readCardPacksCap(): Promise<Record<string, unknown> | null> {
|
|
29
|
+
const res = await driver.get('/.well-known/openwop');
|
|
30
|
+
const doc = res.json as DiscoveryDoc | undefined;
|
|
31
|
+
const caps = doc?.capabilities && typeof doc.capabilities === 'object' ? (doc.capabilities as Record<string, unknown>) : undefined;
|
|
32
|
+
// Accept either a discrete `host.chat.cardPacks` key or a `cardPacks` facet under `host.chat`.
|
|
33
|
+
const direct = caps?.['host.chat.cardPacks'] ?? doc?.['host.chat.cardPacks'];
|
|
34
|
+
if (direct && typeof direct === 'object') return direct as Record<string, unknown>;
|
|
35
|
+
const chat = caps?.['host.chat'] ?? doc?.['host.chat'];
|
|
36
|
+
const facet = chat && typeof chat === 'object' ? (chat as Record<string, unknown>)['cardPacks'] : undefined;
|
|
37
|
+
return facet && typeof facet === 'object' ? (facet as Record<string, unknown>) : null;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function cardPacksSupported(cap: Record<string, unknown> | null): boolean {
|
|
41
|
+
return cap?.['supported'] === true;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Executes a registered card via the host-sample seam, or null (soft-skip) when absent. */
|
|
45
|
+
export async function executeCard(
|
|
46
|
+
cardTypeId: string,
|
|
47
|
+
inputs: Record<string, unknown>,
|
|
48
|
+
): Promise<{ status: number; json: Record<string, unknown> } | null> {
|
|
49
|
+
const res = await driver.post('/v1/host/sample/cardpacks/execute', { cardTypeId, inputs });
|
|
50
|
+
if (res.status === 404 || res.status === 405) return null;
|
|
51
|
+
return { status: res.status, json: (res.json ?? {}) as Record<string, unknown> };
|
|
52
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared root-first reader for `/.well-known/openwop` capability families.
|
|
3
|
+
*
|
|
4
|
+
* Per `spec/v1/capabilities.md` §"Document-root layout" (RFC 0073), every
|
|
5
|
+
* capability family (`agents`, `secrets`, `aiProviders`, `auth`, `memory`,
|
|
6
|
+
* `multiAgent`, `authorization`, …) is a property of the discovery document
|
|
7
|
+
* ROOT — `capabilities.schema.json` defines no `capabilities` wrapper property.
|
|
8
|
+
* A top-level `capabilities` object is a deprecated legacy shape; the suite
|
|
9
|
+
* reads the root first and falls back to a `capabilities.*` wrapper only
|
|
10
|
+
* through the v1.x migration window. Standardizes the ad-hoc dual-read that
|
|
11
|
+
* already existed (e.g. `aiEnvelope.capBreached.test.ts`,
|
|
12
|
+
* `ai-envelope-shape.test.ts`) into one accessor so every helper reads the
|
|
13
|
+
* same way.
|
|
14
|
+
*
|
|
15
|
+
* @see spec/v1/capabilities.md §"Document-root layout"
|
|
16
|
+
* @see RFCS/0073-capability-document-root-layout.md
|
|
17
|
+
*/
|
|
18
|
+
import { driver } from './driver.js';
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Read one capability family from an already-fetched discovery doc: document
|
|
22
|
+
* root first, deprecated `capabilities.*` wrapper as a fallback. Returns
|
|
23
|
+
* `undefined` when the family is advertised in neither location.
|
|
24
|
+
*/
|
|
25
|
+
export function capabilityFamily<T = Record<string, unknown>>(
|
|
26
|
+
doc: unknown,
|
|
27
|
+
name: string,
|
|
28
|
+
): T | undefined {
|
|
29
|
+
if (!doc || typeof doc !== 'object') return undefined;
|
|
30
|
+
const root = (doc as Record<string, unknown>)[name];
|
|
31
|
+
if (root !== undefined) return root as T;
|
|
32
|
+
const wrapper = (doc as { capabilities?: unknown }).capabilities;
|
|
33
|
+
if (wrapper && typeof wrapper === 'object') {
|
|
34
|
+
return (wrapper as Record<string, unknown>)[name] as T | undefined;
|
|
35
|
+
}
|
|
36
|
+
return undefined;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Fetch `/.well-known/openwop` and return one capability family (root-first,
|
|
41
|
+
* wrapper-fallback). `undefined` when the host returns non-200 or doesn't
|
|
42
|
+
* advertise the family.
|
|
43
|
+
*/
|
|
44
|
+
export async function readCapabilityFamily<T = Record<string, unknown>>(
|
|
45
|
+
name: string,
|
|
46
|
+
): Promise<T | undefined> {
|
|
47
|
+
const res = await driver.get('/.well-known/openwop');
|
|
48
|
+
if (res.status !== 200) return undefined;
|
|
49
|
+
return capabilityFamily<T>(res.json, name);
|
|
50
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared helper for the RFC 0062 scheduled-memory-distillation ("dreams")
|
|
3
|
+
* conformance scenarios. Lives in lib/ (not a *.test.ts) so scenarios import it
|
|
4
|
+
* via `../lib/distillation.js`.
|
|
5
|
+
*/
|
|
6
|
+
import { driver } from './driver.js';
|
|
7
|
+
import { readCapabilityFamily } from './discovery-capabilities.js';
|
|
8
|
+
|
|
9
|
+
/** Reads `memory.distillation` from discovery (root-first per RFC 0073); null
|
|
10
|
+
* when the host advertises no distillation sub-block (treated as no support). */
|
|
11
|
+
export async function readDistillationCap(): Promise<Record<string, unknown> | null> {
|
|
12
|
+
const memory = await readCapabilityFamily<{ distillation?: unknown }>('memory');
|
|
13
|
+
const d = memory?.distillation;
|
|
14
|
+
return d && typeof d === 'object' ? (d as Record<string, unknown>) : null;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
interface DistillResult {
|
|
18
|
+
event?: { distillation?: { tokenBudget?: unknown; tokensUsed?: unknown; indexUpdated?: unknown } };
|
|
19
|
+
error?: unknown;
|
|
20
|
+
archiveChecksum?: unknown;
|
|
21
|
+
indexUpdated?: unknown;
|
|
22
|
+
indexFile?: unknown;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Drives one budgeted distillation run via the host-sample seam, or null
|
|
26
|
+
* (soft-skip) when the host doesn't expose it. The seam is host-extension
|
|
27
|
+
* surface specified in host-sample-test-seams.md §"Open seams":
|
|
28
|
+
* `POST /v1/host/sample/memory/distill` returns the `memory.compacted` event
|
|
29
|
+
* (with the additive RFC 0062 `distillation` sub-object) the host would emit,
|
|
30
|
+
* plus the stable archive's checksum. Returns the raw status alongside the
|
|
31
|
+
* body so scenarios can assert the `token_budget_exceeded` failure path. */
|
|
32
|
+
export async function invokeDistill(
|
|
33
|
+
body: Record<string, unknown>,
|
|
34
|
+
): Promise<{ status: number; body: DistillResult } | null> {
|
|
35
|
+
const res = await driver.post('/v1/host/sample/memory/distill', body);
|
|
36
|
+
if (res.status === 404 || res.status === 405) return null; // seam absent — soft-skip
|
|
37
|
+
return { status: res.status, body: (res.json as DistillResult | undefined) ?? {} };
|
|
38
|
+
}
|