@openwop/openwop-conformance 1.44.0 → 1.46.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 +12 -0
- package/README.md +2 -2
- package/api/asyncapi.yaml +16 -0
- package/coverage.md +1 -1
- package/fixtures/connection-packs/connection-pack-apihosts-valid.json +26 -0
- package/fixtures.md +1 -0
- package/package.json +1 -1
- package/schemas/capabilities.schema.json +8 -4
- package/schemas/connection-pack-manifest.schema.json +15 -1
- package/schemas/dispatch-config.schema.json +42 -2
- package/schemas/frontend-plugin-manifest.schema.json +1 -1
- package/schemas/run-event-payloads.schema.json +30 -1
- package/schemas/run-event.schema.json +2 -0
- package/schemas/ui-plugin-message.schema.json +1 -1
- package/src/scenarios/connection-pack-apihosts.test.ts +196 -0
- package/src/scenarios/dispatch-fanout-parallel.test.ts +163 -0
- package/src/scenarios/frontend-plugin-packs.test.ts +13 -5
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# `@openwop/openwop-conformance` Changelog
|
|
2
2
|
|
|
3
|
+
## [1.46.0] — 2026-06-29 — RFC 0120 connection-pack `apiHosts` credential-egress allow-list
|
|
4
|
+
|
|
5
|
+
Adds `connection-pack-apihosts.test.ts` (suite 379 → 380) for **RFC 0120 — Connection-pack provider `apiHosts`** (the optional `provider.apiHosts` credential-egress allow-list on the RFC 0095 connection-pack manifest + the `reach: openapi ⇒ apiHosts` conditional MUST). The public test for the two protocol-tier invariants `connection-pack-api-host-shape` + `connection-pack-egress-host-bound`.
|
|
6
|
+
|
|
7
|
+
Two always-on, server-free layers assert the wire shape + the matching rule. The **schema legs** (`connection-pack-manifest.schema.json`) validate the canonical `connection-pack-apihosts-valid` fixture (an `openapi`-reach `meta-ads` provider declaring `apiHosts: ["facebook.com"]`), reject an `openapi`-reach provider that omits `apiHosts` (the provider-level conditional-MUST `allOf`), and reject IP-literal / wildcard / port-bearing / single-label / uppercase entries (the `connection_pack_invalid_api_host` shape). The **matching-rule legs** assert the §Manifest item-10 dot-anchored eTLD+1 suffix-containment rule: an entry admits itself and its subdomains, a pack MAY declare a tighter exact host that does not admit siblings, and a non-match fails closed with **no substring/suffix/prefix escape** (`notexample.com` / `evil.com` / `example.com.evil.com` MUST NOT match `example.com`). The capability-gated behavioral leg drives the new `POST /v1/host/sample/connection-packs/egress-check` seam (`host-sample-test-seams.md` §10): a credential-bearing egress to an `apiHosts` match is PERMITTED, a non-match is FAIL-CLOSED. Cast-free. Soft-skips on absent `capabilities.connections.packsSupported` or `404`/`403` on the seam (RFC 0120 stays `Active` pending the openwop-app witness re-measure against this published suite).
|
|
8
|
+
|
|
9
|
+
## [1.45.0] — 2026-06-28 — RFC 0118 parallel sub-workflow fan-out and join
|
|
10
|
+
|
|
11
|
+
Adds `dispatch-fanout-parallel.test.ts` (suite 378 → 379) for **RFC 0118 — Parallel sub-workflow fan-out and join** (the additive `fanOutPolicy: 'parallel'` enum value plus the optional `joinPolicy` object and `maxConcurrency` on `DispatchConfig`, the `core.dispatch.fanOut` / `core.dispatch.join` run-events, and the `capabilities.dispatch.{fanOutPolicies,joinModes,maxFanOut}` descriptors — closing RFC 0007 §K3).
|
|
12
|
+
|
|
13
|
+
Two always-on, server-free schema layers assert the wire shape: `dispatch-config.schema.json` accepts a valid `parallel` config (each `joinPolicy.mode` of wait-all/quorum/first/race, `maxConcurrency`), rejects an unknown `fanOutPolicy`/`mode`/`onChildFailure`, rejects `additionalProperties` on `joinPolicy` and a non-positive `maxConcurrency`, and keeps a pre-RFC-0118 config valid (additive); the `dispatchFanOut` / `dispatchJoin` event `$defs` (`run-event-payloads.schema.json`) validate well-formed payloads, enforce `fanOutPolicy` const `"parallel"` + `childCount ≥ 2`, and require the replay-deterministic `mergeOrder` on the join. The capability-gated behavioral legs drive the `POST /v1/host/sample/dispatch/fanout` seam (a wait-all/collect join over all-completing children → `joinOutcome: 'satisfied'` + a full `children[]` + a recorded `mergeOrder`) and the un-gated registration-rejection leg (a host advertising `dispatch.fanOutSupported: false` MUST reject `fanOutPolicy: 'parallel'` with a 4xx `validation_error`). Cast-free. Soft-skips on absent `capabilities.dispatch.fanOutSupported` / `"parallel"` not in `fanOutPolicies`, or `404`/`403` on the seam (RFC 0118 stays `Active` pending host witnesses).
|
|
14
|
+
|
|
3
15
|
## [1.44.0] — 2026-06-27 — RFC 0117 front-end plugin packs
|
|
4
16
|
|
|
5
17
|
Adds `frontend-plugin-packs.test.ts` (suite 377 → 378) for **RFC 0117 — Front-End Plugin Packs** (the `kind: "frontend-plugin"` registry pack + the OPTIONAL `capabilities.uiPlugins` capability for signed, SANDBOXED UI extensions loaded in a cross-origin isolated iframe over the closed `ui-plugin/1` host-RPC boundary).
|
package/README.md
CHANGED
|
@@ -92,7 +92,7 @@ Exit code is non-zero on any failed assertion.
|
|
|
92
92
|
|
|
93
93
|
## What's Covered
|
|
94
94
|
|
|
95
|
-
The current suite has 378 scenario files under `src/scenarios/`. 2026-06-27 (RFC 0117 — front-end plugin packs, suite `1.43.0 → 1.44.0`) added `frontend-plugin-packs.test.ts` (always-on schema legs for `frontend-plugin-manifest.schema.json` + the `ui-plugin/1` envelope `ui-plugin-message.schema.json`: manifest validity, `runtime`-forbidden, path-traversal-guarded `entry`, the closed `hostApi` allowlist, the version-token `artifact_conflict` + `currentVersion` concurrency shape, and no credential-bearing envelope field (`additionalProperties:false`); the capability-shape leg asserts `isolation` pins to the `cross-origin-iframe` const; capability-gated behavioral legs over `POST /v1/host/sample/ui-plugin/rpc` assert an undeclared method → `method_not_allowed` and a stale `artifact.write` → `artifact_conflict` with no persist; soft-skips on absent `capabilities.uiPlugins.supported` or `404`/`403` on the seam (RFC 0117 stays Active pending host witnesses); the public test for the protocol-tier `frontend-plugin-{isolation,egress,rpc-allowlist,no-byok}` invariants). 2026-06-26 (RFC 0116 — portable prompt-prefix cache, suite `1.42.0 → 1.43.0`) added `prompt-prefix-cache.test.ts` (advertisement-shape on `aiProviders.promptPrefixCache` + behavioral legs over the OPTIONAL `POST /v1/host/sample/ai/generate` seam (`host-sample-test-seams.md` §16): (a) outcome-invariance — a generate with `cachePrefixId` and a control without produce the same accepted envelope + identical `provider.usage.inputTokens`/`outputTokens` (cost-hint-only, replay-invariant); (b) cache-hit observable — a repeat generate shows `cacheReadTokens > 0`; (c) cross-tenant isolation — tenant B’s first use of tenant A’s `cachePrefixId` shows `cacheReadTokens == 0`, the public test for the protocol-tier `prompt-prefix-cache-cross-tenant-isolation` invariant (the host MUST key its provider cache by `(tenant, cachePrefixId)`, never global); (d) secret-free — the cost-only cache-token fields carry no prompt substrings (SR-1); soft-skips on absent `aiProviders.promptPrefixCache.supported` or `404`/`405` on the seam, since RFC 0116 defers reference-host impl as provider-gated). 2026-06-26 (RFC 0114 — A2UI surface deltas, suite `1.41.0 → 1.42.0`) added `a2ui-surface-delta-transport.test.ts` (always-on legs use a cast-free, dependency-free client-side RFC 6902 applier + Ajv2020 to assert: the new `a2ui-surface-delta-frame.schema.json` compiles, the op enum excludes `test`, a full surface + delta frames reconstruct the expected tree AND equal the full a non-negotiating subscriber materializes and re-validate against the closed catalog, an out-of-catalog/script-bearing delta fails closed-catalog validation post-patch — the `a2ui-surface-no-code-exec` boundary holds — and the recorded envelope is always the full surface; the HTTP leg is capability-gated on `a2uiSurface.deltaTransport` and soft-skips on absent host/capability or `404`/`405` on the OPTIONAL `GET /v1/host/sample/a2ui/surface/materialized` seam). 2026-06-26 (RFC 0111 — context economy, suite `1.40.0 → 1.41.0`) added `context-budget-transcript-bound.test.ts` (capability-gated on `multiAgent.executionModel.contextBudget.transcriptTokenBudget` being present via `behaviorGate(openwop-context-budget, …)` — drives the `conformance-context-budget-multiturn` orchestrator fixture, reads the host’s per-iteration accounting via the OPTIONAL seam `GET /v1/host/sample/agent/transcript-window?runId=…&iteration=N` (`host-sample-test-seams.md` §14), and asserts `tokenCount ≤ transcriptTokenBudget` in the advertised `tokenCounter` unit, that every `eventId` in the seam accounting is a real persisted run event (internal-consistency cross-check against `/v1/host/sample/test/runs/:runId/events`), recent-tail + no-double-count, every `summarizedRanges.summaryRef` has a matching `context.summarized` event, and `keepLastTurns` recent turns are fed verbatim — never inside a summarized range; soft-skips on `404`/`405` since the RFC defers reference-host impl) + `context-summarization-replay.test.ts` (capability-gated on `multiAgent.executionModel.contextBudget.summarization.supported` via `behaviorGate(openwop-context-summarization, …)` — replays the run via `:fork mode:replay` and asserts the recorded `context.summarized` → `summaryRef` records are REUSED, never re-summarized, the direct analogue of RFC 0041 envelope-refusal recovery; soft-skips when the event-log seam is unwired, no `replay` mode is advertised, or the run produced no summarization). 2026-06-26 (RFC 0113 — memory injection budget, suite `1.39.0 → 1.40.0`) added `memory-injection-budget.test.ts` (capability-gated on `memory.injectionBudget.supported === true` via `behaviorGate(openwop-memory-injection-budget, …)` — drives the `conformance-agent-memory-injection-budget` fixture through the host-sample memory seam and asserts the budgeted injection read's cumulative tokens ≤ the requested `tokenBudget` in the advertised `tokenCounter` unit (`o200k_base`/`cl100k_base`/`chars`/`host-defined`), an over-budget single entry is omitted not truncated, and re-asserts SR-1 (`[REDACTED:<secretId>]` content) + CTI-1 (cross-tenant probe returns empty) on the budgeted path; the `rank:'relevance'` leg DELEGATES to `memory.search` semantic (RFC 0080) and asserts relevance ordering differs from recency ONLY when the host ALSO advertises `memory.search` semantic, else it soft-skips — there is NO ranking primitive in `injectionBudget`; soft-skips on hosts that do not advertise `memory.injectionBudget`). 2026-06-26 (RFC 0112 — compact tool projection, suite `1.38.0 → 1.39.0`) added `tool-catalog-compact-projection.test.ts` (capability-gated on `toolCatalog.compactView === true` via `behaviorGate(openwop-tool-catalog-compact, …)` — drives `GET /v1/tools?view=compact` and asserts the host returns the `{ tools: CompactToolDescriptor[] }` envelope where every descriptor validates against `compact-tool-descriptor.schema.json` with the heavy `ToolDescriptor` fields dropped, every present `inputSchema` satisfies the self-contained compact structural subset (top-level `type:"object"`+`properties`; no `$ref`/`oneOf`/`allOf`/`anyOf`/`not`/`patternProperties`/`dependentSchemas`), and the compact `tools[]` `toolId` set EQUALS the standard view set for the same principal (projection completeness); soft-skips on hosts that do not advertise `toolCatalog.compactView`). 2026-06-26 (RFC 0115 — run transport economy, suite `1.37.0 → 1.38.0`) added `run-transport-economy.test.ts` (capability-gated on `restTransport.conditionalRunGet === true` and a non-empty `restTransport.contentEncodings` — drives `GET /v1/runs/{runId}` and asserts a sequence-derived strong `ETag` on the `200`, `If-None-Match` → `304` with an empty body while the run is unchanged, the `ETag` ROTATES after the `conformance-approval` fixture advances `waiting-approval → completed` (proving it derives from the latest persisted event-log sequence, not a coarser signal that would leave a `304` stale), and each advertised `Content-Encoding` (`gzip` baseline; `br`/`zstd` optional) round-trips byte-identically to the identity body; soft-skips on hosts that do not advertise `restTransport`). 2026-06-24 (RFC 0110 — channel presence, behavioral leg) added `channel-presence-behavioral.test.ts` (capability-gated on `channelPresence.supported` via `behaviorGate('openwop-channel-presence', …)` — drives the `POST /v1/host/sample/channel-presence/snapshot` seam and asserts a live `channel.presence` snapshot is the CLOSED `{conversationId,present,typing}` shape (no PII), every ref is an opaque RFC 0041 `user:`/`agent:` subject, `typing ⊆ present`, and the snapshotting member is non-vacuously present; soft-skips on 404/405 — a host whose presence is bound to a product flow witnesses via its own route test + an INTEROP-MATRIX row, the RFC 0086 dual-staging). 2026-06-24 (RFC 0110 — channel presence, Accept cycle) added `channel-presence-shape.test.ts` (always-on server-free — the OPTIONAL `channel.presence` ephemeral payload validates a conforming snapshot, REQUIRES `conversationId`+`present`, is CLOSED `additionalProperties:false` (the no-PII guard), `typing` optional; `channel.presence` is in the `RunEventType` enum; the `channelPresence` capability block is declared + closed). 2026-06-24 (RFC 0109 — conversation-turn model provenance, Accept cycle) added `conversation-turn-model-provenance-shape.test.ts` (always-on server-free — `agent.model` `{provider, model}` validates conforming / both-required / closed-SR-1 / optional-back-compat, + the `conversationTurnModelProvenance` capability declared + closed). 2026-06-24 (RFC 0108 — self-hosted / OpenAI-compatible provider class, Accept cycle, suite `1.36.0 → 1.37.0`) added the gated behavioral honesty leg `aiproviders-selfhosted-honesty.test.ts` (gated on `aiProviders.selfHosted.length > 0` via `behaviorGate('openwop-selfhosted-providers', …)` — drives `POST /v1/host/sample/ai/call` against an advertised `selfHosted` id and asserts **§A.2** the dispatch reaches a real configured endpoint (success or transport error — never `capability_not_provided` / `provider_not_supported`, which would prove a dishonest advertisement with no endpoint behind it) and **§D** the endpoint location — supplied out-of-band via `OPENWOP_TEST_COMPAT_ENDPOINT`, checked as the raw string + bare `host`/`host:port` — never appears in the seam response or error payload; soft-skips on 404 / when the env var is unset for the §D leg). 2026-06-24 (RFC 0108, suite `1.35.0 → 1.36.0`) added the always-on server-free shape leg `aiproviders-selfhosted-shape.test.ts` (the additive `aiProviders.selfHosted` advertisement: a `string[]` with `uniqueItems`/`minLength:1` items, absent from `aiProviders.required`, validates a conforming array via Ajv2020 while rejecting non-array / duplicate / empty-string forms, and enforces the two §A rules JSON Schema cannot express — §A.1 every `selfHosted` entry is a subset of `supported`, and §A.3 / `self-hosted-endpoint-no-disclosure` no entry is URL-shaped (`://`, bare `host:port`, or leading `/` path) — exercised against good/bad example docs). 2026-06-23 (RFC 0106 — real-time voice session profile, Accept cycle) added six gated behavioral scenarios driving the openwop-app live host (rev `00293-89w`): `voice-transcription-streaming` + `voice-transcription-unadvertised` (the `call-transcriber` seam; the latter gated-by-absence), `voice-synthesis-streaming` (the `stream:true` synthesis arm → metadata-only `voice.synthesis_chunk` events), `voice-bargein-no-partial-leak` (the `/voice/barge-in` seam — `voice.barge_in`→`voice.cancelled` with no chunk after; **graduated `voice-bargein-no-partial-leak` reference-impl→protocol** on the live proof), and `voice-interim-not-durable` + `voice-streamref-tenant-bound` (§F INV-1/INV-4; soft-skip on the §E `transcription_unsupported` / no-live-streamRef path — stay reference-impl until a host with a scripted-interim / live-streamRef arm proves them). 2026-06-23 (RFC 0107 — publishable declarative pack kinds) added one: `registry-declarative-kinds.test.ts` (always-on server-free — the registry version-manifest carries the `kind` discriminator + per-kind declarative payload, `runtime` is conditional on `kind` via `allOf if/then/else`, published artifact-type and connection manifests validate, and a declarative-with-`runtime` or node-without-`runtime` manifest is rejected). 2026-06-23 (RFC 0107 correction, suite `1.34.0 → 1.35.0`) extended that scenario with two cases — a published **chat-card** pack manifest (`kind: "card"`, RFC 0071) validates, and the never-canonical `kind: "chat-card"` is **rejected** — fixing a kind-string mismatch where RFC 0107's published schema enumerated `chat-card` while RFC 0071 (authoritative) defines `card` (no scenario-file count change; cases added to the existing file). 2026-06-23 (RFC 0106 — real-time voice session profile, Phase 2) added one: `voice-event-payloads-shape.test.ts` (always-on server-free — all seven `voice.*` types are in the `RunEventType` enum, each has a payload `$def` via `typeIndex`, `voice.transcript` REQUIRES `contentTrust:"untrusted"` (the schema-enforced half of `voice-transcript-untrusted`), `voice.synthesis_chunk` is metadata-shaped (seq+mimeType required; bytes by `url`/`streamRef`), and the content-free events reject unknown properties). Phase 2 also lands the `voice.*` payload `$defs` in `run-event-payloads.schema.json` + the `asyncapi.yaml` messages + the four §F `SECURITY/invariants.yaml` rows (1 protocol-tier `voice-transcript-untrusted` + 3 reference-impl behavioral, graduating at `Active → Accepted`). The gated behavioral legs (`voice-transcription-streaming`/`-unadvertised`, `voice-synthesis-streaming` + the three behavioral invariant scenarios) land with the reference host at `Active → Accepted`. 2026-06-23 (RFC 0106 — real-time voice session profile, Phase 1) added one: `aiproviders-realtimevoice-shape.test.ts` (always-on server-free — the `aiProviders.realtimeVoice` object advertisement: the four sub-flags `transcription`/`synthesis`/`turnDetection`/`bargeIn` are declared, `realtimeVoice` is absent from `aiProviders.required`, the `turnDetection`/`bargeIn ⇒ transcription` `dependentRequired` closure and the `synthesis ⇒ speechSynthesis` if/then closure hold via Ajv2020, and out-of-enum / boolean sub-flag values are rejected). The gated behavioral legs (`voice-transcription-streaming` / `-unadvertised`, `voice-synthesis-streaming`) and the four §F live-ingress invariant scenarios land in Phase 2 at the `Active → Accepted` cycle. 2026-06-22 (RFC 0101 — multi-party group conversation) added two — `multi-party-conversation-shape.test.ts` (always-on server-free — asserts the three additive RFC 0101 wire facts: a 3-agent council `conversation.opened` participant roster (instance ids) + a user opening turn validates, every `role: 'agent'` turn carrying a roster-instance `speakerId` validates while one OMITTING `speakerId` MUST FAIL schema validation (the `allOf`/`if role==='agent' then required:['speakerId']` conditional), a non-AgentRef participant item is rejected, the `capabilities.multiPartyConversation { supported, maxParticipants? }` block is declared + closed and rejects extras / `maxParticipants:1` / a missing `supported`, and the non-participant membership predicate is asserted server-free) and `multi-party-conversation-behavioral.test.ts` (capability-gated on `multiPartyConversation.supported` via `behaviorGate('openwop-multi-party-conversation', …)` — drives the conformance-only seam `POST /v1/host/sample/conversation/multi-party/{open,exchange}` (`host-sample-test-seams.md`, since RFC 0101 mints no normative client trigger to open a council): a 3-agent council opens and a roster-valid attributed agent turn is accepted, while a `role:'agent'` turn missing `speakerId`, a non-participant `speakerId`, and an over-`maxParticipants` open are each rejected with `error.code:'validation_error'` (status-tolerant 400/422 per RFC 0005 §E); soft-skips on 404/405 — reference impl: the postgres example host). 2026-06-20 (RFC 0105 — speech synthesis adapter) added three: `aiproviders-speechsynth-shape.test.ts` (always-on server-free — the `aiProviders.speechSynthesis` `const "supported"` flag is declared, absent from `aiProviders.required`, and a string-const advertisement validates while the object form `{supported:true}` is rejected), `speech-synthesis-roundtrip.test.ts` (gated on `aiProviders.speechSynthesis === "supported"` via `behaviorGate('openwop-speech-synthesis', …)` — an advertising host's `callSpeechSynthesizer` round-trip via the `POST /v1/host/sample/ai/call-speech-synthesizer` seam returns `audio` with EXACTLY ONE of `url`/`base64`, a non-empty `mimeType`, and the echoed `voiceId`; soft-skips on 404), and `speech-synthesis-unadvertised.test.ts` (gated-by-absence via `behaviorGate('openwop-speech-synthesis-unadvertised', …)` — a host NOT advertising `speechSynthesis` MUST reject the call with `speech_synthesis_unsupported`, never a silent no-op). 2026-06-19 (RFC 0104 — portable HITL approver routing) added one: `interrupt-approver-routing.test.ts` (server-free Ajv2020 — the `interrupt.approverRouting` capability block shape, the additive optionality of `approverGroupRefs` / `approverRoleRefs` / `audience` on the `ApprovalData` schema, the closed `audience` object, and the §"Portable approver routing" `notifyTargets` reference rule that `audience` DEFAULTS to the resolved eligibility union when omitted and OVERRIDES it when present; the capability-gated leg asserts an advertising host's `interrupt.approverRouting` is honest — `refKinds` ⊆ {group, role}, `audience` boolean — and soft-skips when the host does not advertise the capability). 2026-06-17 (RFC 0103 — localized content surface) added one: `localized-content-delivery.test.ts` (server-free Ajv2020 — the four content schemas + the §C `resolveSection` merge + §A capability coherence; the public test for `content-published-cache-no-draft` / `content-response-tenant-scoped` / `content-no-cross-tenant-enumeration`; the live legs gate on `capabilities.content.supported` and soft-skip without a `GET /v1/content/pages/{slug}` target). 2026-06-15 (RFC 0102 — A2UI agent-authored interface surfaces) added five: `a2ui-surface-shape.test.ts` (server-free Ajv2020 — the closed core `ui.a2ui-surface` payload validates, while an out-of-catalog component / extra script-bearing property / unenumerated `catalogVersion` / `action.target` outside `enum["resume","exchange"]` each fail; the structural half of `a2ui-action-confinement` and the enabling precondition for the render-side `a2ui-surface-no-code-exec` / `a2ui-surface-no-network-egress` reference-app probes), `a2ui-surface-degrades.test.ts` (the kind is optional/advertised, not a MUST-recognize universal kind, and an unadvertised kind is gated — N6 — never crashing the run), `a2ui-surface-version-refusal.test.ts` (the enumerated `catalogVersion` rejects an unadvertised version → `unknown_schema_version`, and the surface schema carries no external `$ref`), `a2ui-surface-replay.test.ts` (all `$ref`s internal so a stored surface `:fork`/replays deterministically; same-`correlationId` + divergent `type` → `envelope_correlation_conflict`), and `a2ui-untrusted-blocks-approval.test.ts` (a `meta.contentTrust:'untrusted'` surface is trust-gated and MUST NOT advance an approval interrupt — composition of `untrusted_content_blocks_approval`). The four behavioral legs soft-skip on 404 (host-pending; the `openwop-app` reference renderer is the render-side probe). 2026-06-14 (RFCs 0099/0100 — external-event trigger ingestion + async/durable A2A tasks) added one new scenario (`trigger-ingestion.test.ts`) and extended `a2a-task-roundtrip.test.ts` with the RFC 0100 async subtests: `trigger-ingestion.test.ts` (RFC 0099 — always-on `TriggerEvent` / `TriggerSubscriptionRegistration` schema legs incl. the §F.1 per-source one-of, the `AttachmentRef.ref`-only rule + raw-URL rejection backing `trigger-ingestion-ssrf`, and the content-free `trigger.delivery.attempted` shape backing `trigger-ingestion-content-redaction`; plus a capability-gated behavioral leg on `triggerBridge.ingestion` driving the `POST /v1/host/sample/trigger-bridge/ingest` seam for SSRF refusal + header-redaction), and the `a2a-task-roundtrip.test.ts` additions (RFC 0100 — always-on `A2ATaskState` + `capabilities.a2a` shape legs incl. the lowercase-hyphen state enum, the `PushConfig` `url`-required + truncated-`tokenFingerprint` rule backing `a2a-push-egress-ssrf`, the no-inline-inputs `additionalProperties:false` SR-1 check; plus capability-gated durable-`tasks/get`-after-disconnect and push-SSRF behavioral legs on `a2a.durableTasks` / `a2a.pushNotifications` via the `/v1/host/sample/a2a/tasks/*` seam). 2026-06-13 (RFCs 0096/0097/0098 — reviewable learning, standing goals, agent-platform portability) added three always-on-plus-gated scenarios: `proposal-reviewable-learning.test.ts` (RFC 0096 — the `agents.proposals` shape + the `Proposal` round-trip incl. the dropped `rule` kind + the content-free `proposal.{created,activated}` events, plus a gated apply-without-scope→403 leg; backs `proposal-inert-until-applied` + `proposal-no-resynthesis`), `goal-standing-continuation.test.ts` (RFC 0097 — the `agents.goals` shape + the `Goal` round-trip + the content-free `goal.{evaluated,closed}` events, plus gated bounded-termination→422 + judge-only-completion legs; backs `goal-continuation-bounded` + `goal-completion-judge-only`), and `export-bundle-portability.test.ts` (RFC 0098 — the `portability` shape incl. the `import⇒dryRun` if/then + the `ExportBundle` round-trip rejecting every credential-named field + the content-free `import.applied` event, plus a gated literal-credential-import→422 leg; backs `export-bundle-no-credential-material`). 2026-06-11 (RFCs 0093/0094 — protocol hardening + wire-shape reconciliation) added five: `version-fold.test.ts` (the `version-negotiation.md` §`X-Force-Engine-Version` cross-version matrix through the previously-orphaned `conformance-version-fold` fixture — closes catalog gap F5; soft-skips when `Capabilities.testing.forceEngineVersionRange` is unadvertised), `stream-text-fixture.test.ts` (the `stream-modes.md` §`messages` fold through the deterministic `stream-text` mock provider + the previously-orphaned `conformance-stream-text` fixture — closes catalog gap F1), `i18n-negotiation.test.ts` (gated on `capabilities.i18n` via `behaviorGate('openwop-i18n', …)` — an unsupported or malformed `Accept-Language` never 400s, `Content-Language` reflects the locale actually used, and error `code` strings stay the canonical English tokens), `grpc-transport.test.ts` (gated on `capabilities.grpc` via `behaviorGate('openwop-grpc-transport', …)` — advertisement-shape only per `grpc-transport.md` §Field semantics: `service` MUST be `openwop.v1.Engine`, the `tls` enum, `grpcs?://` endpoint URIs, `supportedTransports` includes `grpc` when exposed, production claimants require `tls: "required"`; no gRPC dialing), and `webhook-tenant-isolation.test.ts` (RFC 0093 §A.3 — backs the new protocol-tier `webhook-cross-tenant-isolation` invariant; a two-tenant proof through the `/v1/host/sample/test/surface` seam plus black-box registration-surface scoping). `spec-corpus-validity.test.ts` also gained the RFC 0094 §A satisfiability probe: canonical `createRun` bodies MUST pass the composed request schema (closed via `unevaluatedProperties: false` at the composition site, never inside an `allOf` branch) and an undeclared property MUST fail. 2026-06-07 (RFCs 0090/0091/0092 — verifier turn + convergence, multimodal perception input, agent capability requirements) added six: the always-on, server-free shape probes `agent-verifier-shape.test.ts`, `aiproviders-input-shape.test.ts`, `agent-requires-capabilities-shape.test.ts`, plus the capability-gated **behavioral** legs `agent-capability-degraded-projection.test.ts` (RFC 0092 §B — the `degraded[]` projection on `GET /v1/agents`, black-box, non-vacuous via `OPENWOP_DEGRADED_CAPABILITY_AGENT_ID`), `callai-multimodal.test.ts` (RFC 0091 §A/§B — advertised modality accepted / unadvertised → `unsupported_modality`, via the `POST /v1/host/sample/ai/call` seam), and `verifier-gating.test.ts` (RFC 0090 §B — a `fail` verdict blocks commit, via the `POST /v1/host/sample/agents/verify-run` seam). The three behavioral legs soft-skip by default and hard-fail under `OPENWOP_REQUIRE_BEHAVIOR=true` — the Active→Accepted reference-host proof for each RFC. 2026-06-02 (RFC 0082 §B — deployment channel resolve-and-pin, production-path coverage) added `agent-channel-dispatch.test.ts` (capability-gated on `agents.deployment.supported` + the seeded `conformance-agent-channel-dispatch` fixture + advertised `replay` mode via `behaviorGate('openwop-deployment-channel-dispatch', …)` — proves the §B pin from a REAL run graph, complementing `agent-deployment-lifecycle.test.ts` Leg 4's host-sample seam: a canonical `POST /v1/runs` of a node binding `agent.channel:"stable"` MUST record `resolvedChannel` + `resolvedAgentVersion` on `agent.invocation.started` (RFC 0077), a `:fork{mode:"replay"}` MUST re-read that recorded version, and the seam-guarded Leg 3 MOVES the channel then asserts a replay STILL carries the original pin — never re-resolving a moved channel; soft-skips by default, hard-fails under `OPENWOP_REQUIRE_BEHAVIOR=true` — the production-path proof of the §B contract). 2026-06-01 (RFC 0085 — `openwop-agent-platform` meta-profile, the Active→Accepted behavioral gate) added `agent-platform-aggregate-evidence.test.ts` (capability-gated on a host CLAIMING `openwop-agent-platform` in its live discovery `profiles[]` via `behaviorGate('openwop-agent-platform', …)` — the §C/§D honest-advertisement rule on the live `/.well-known/openwop`: the claim MUST satisfy the §B floor predicate (`isAgentPlatformPartial` → `partial`/`full`, never `none`), backed by the per-capability evidence not the profile string; `OPENWOP_AGENT_PLATFORM_TIER=full` forces the non-vacuous full bar — all governance terms + tenant installScope + all 16 §D terms; server-requiring, the always-on §B/§D derivation legs stay in `agent-platform-profile.test.ts` — the RFC 0085 → Accepted bar). 2026-06-01 (RFC 0084 — budget, quota + cost policy, the Active→Accepted behavioral gate) added `budget-enforcement.test.ts` (capability-gated on `budget.supported` via `behaviorGate('openwop-budget-enforcement', …)` — the §C/§D enforcement via the new `POST /v1/host/sample/budget/run` seam + the test event-log seam: a `hard-cost-exhaust` run emits the strict-ordered `budget.reserved → budget.consumed → budget.threshold.crossed{percent} → budget.exhausted → cap.breached{kind:"budget-cost"} → run.failed{error:"budget_exhausted"}` chain; a `model-denied` run is refused `budget_model_denied` BEFORE the provider call (fail-closed); an `advisory` host emits the `budget.*` events without stopping; every `budget.*` payload content-free backing `budget-no-pricing-leak`; new lib helper `src/lib/budgetPolicy.ts`; soft-skips on 404 — the RFC 0084 → Accepted bar). 2026-06-01 (RFC 0080 — agent memory capability reconciliation, the Active→Accepted behavioral gate) added `memory-degraded-projection.test.ts` (capability-gated on `agents.manifestRuntime.supported` + `memory.supported` via `behaviorGate('openwop-memory-degraded', …)` — the §C degraded-projection iff-contract on the NORMATIVE `GET /v1/agents`: a degraded inventory entry MUST carry `memoryDegraded:true` + a non-empty, unique `degradedMemoryDimensions[]` from the closed §A-name enum, a non-degraded entry MUST NOT, the inventory is non-empty, and the degraded branch runs non-vacuously when `OPENWOP_DEGRADED_AGENT_ID` names a known-degraded agent; black-box, no POST seam — the RFC 0080 → Accepted bar). This batch also documents the two RFC 0068 conformance seams (`POST /v1/host/sample/memory/consolidate` + `.../commitment/fire`) in `host-sample-test-seams.md` (the 0068 gated scenarios shipped in 1.14.0). 2026-06-01 (RFC 0034 — collector-side BYOK-canary inspection) added `otel-collector-canary-inspection.test.ts` (always-on server-free: stands up a real `OtelCollector`, POSTs synthetic OTLP/HTTP-JSON traces + metrics through its actual ingest path, and proves the new `findCanaryLeakage()` inspector catches a canary embedded in a span attribute / resource attribute / span name / metric data-point attribute while reporting ZERO hits on a redacted payload and never matching an empty canary — the non-vacuous proof that the conformance collector now inspects what the host's OTLP exporter ACTUALLY shipped over the wire, closing the `secret-leakage-otel-attribute` / `-debug-bundle-otel` collector-seam gap; the live capability-gated complement is the new collector-export describe block in `secret-leakage-otel-attribute.test.ts`). 2026-06-01 (RFC 0035 — sandbox wall-clock timeout, the 7th-of-8 graduation) added `sandbox-wasm-timeout.test.ts` (worker-driven server-free: `probeTimeout` in `wasm-sandbox-probe.ts` spawns a worker thread running the committed `misbehaving-timeout.wasm` + a main-thread kill-timer — the thread preemption a same-thread probe can't do — asserting `sandbox_timeout` with a well-behaved positive control; graduates `node-pack-sandbox-timeout` reference-impl→protocol, so 7 of 8 `node-pack-sandbox-*` invariants are now protocol-tier, only the JS-specific `no-eval` permanently exempt). 2026-05-31 (audit-response black-box / graduation batch) added three more: `sandbox-wasm-isolation.test.ts` (RFC 0035 — drives the committed `fixtures/wasm-sandbox/*.wasm` through `wasm-sandbox-probe.ts`: escape/capability-gate via static `WebAssembly.Module.imports()`, an OOB-store memory trap, double-instantiate isolation; 10/10; graduates 6 `node-pack-sandbox-*` invariants reference-impl→protocol), `workspace-cross-tenant-isolation-blackbox.test.ts` (RFC 0059 — two-credential black-box on the normative §C `/v1/host/workspace/files` endpoints: owner A writes, a second-tenant credential fails closed; no seam), and `prompt-resolution-chain-event.test.ts` (RFC 0029 — reads the durable `agent.promptResolved.chain[]` precedence record via the normative `GET /v1/runs/{runId}/events/poll`; no seam) — each the production-path proof that graduates its surface into the `openwop-core-standard` floor. 2026-05-31 (RFC 0088 — the `openwop-core-standard` Core Standard Profile, the audit-response Core Candidate target) added `core-standard-profile.test.ts` (always-on server-free derivation probe: `isCoreStandard` derives the §B floor — `openwop-core` ∧ `openwop-interrupts` ∧ (`openwop-stream-sse` ∨ `openwop-stream-poll`) — a bare `openwop-core` host without interrupts is excluded, a host with no event transport fails, and the annex is absent from `deriveProfiles` because it composes rather than redefines). 2026-05-31 (RFC 0082 — agent deployment lifecycle, the Active→Accepted behavioral gate) added `agent-deployment-lifecycle.test.ts` (capability-gated on `agents.deployment.supported` via `behaviorGate('openwop-deployment-lifecycle', …)` — the §E promotion contract via the new `POST /v1/host/sample/agents/deployment-transition` seam + the test event-log seam across four legs: `promote` (authorize RFC 0049 → approvalGate RFC 0051 → eval-verify RFC 0081 → content-free `deployment.promoted` with a seven-state `toState` + `toVersion`, the record validating `agent-deployment.schema.json`), `unauthorized` (fail-closed — `allowed:false`, no `deployment.promoted`, the behavioral leg of `deployment-promotion-fail-closed`), `eval-gate-unmet` (`eval_gate_unmet` denial, §E-3), and `channel-pin` (the §B `resolvedAgentVersion` recorded-fact on `agent.invocation.started`); new lib helper `src/lib/agentDeployment.ts`; soft-skips on 404 — the RFC 0082 → Accepted bar). 2026-05-31 (RFC 0081 — agent evaluation, the Active→Accepted behavioral gate) added `agent-eval-run.test.ts` (capability-gated on `agents.evalSuite.supported` via `behaviorGate('openwop-eval-run', …)` — the §B `mode:"eval"` projection via the new `POST /v1/host/sample/agents/eval-run` seam + the test event-log seam: `eval.started`-first → one `eval.scored` per task → `eval.completed`-once ordering (count == `eval.completed.taskCount`), the content-free `eval.scored` legs (`score` ∈ 0..1) backing `eval-summary-no-content-leak`, and the NORMATIVE `GET /v1/runs/{runId}/eval-summary` schema-valid `EvalSummary` round-trip with `passedCount <= taskCount`; new lib helper `src/lib/agentEval.ts`; soft-skips on 404 — the RFC 0081 → Accepted bar). 2026-05-31 (RFC 0083 — durable trigger bridge, the Active→Accepted behavioral gate) added `trigger-bridge-delivery.test.ts` (profile-gated on `openwop-trigger-bridge` derived from the live discovery doc — the §C delivery model via the `POST /v1/host/sample/trigger-bridge/deliver` seam + the test event-log seam: dedup→effectively-once `trigger.delivery.attempted{delivered}` (§C-1), retry-exhaustion→`{dead-lettered}` + `trigger.subscription.state.changed{toState:dead-lettered}` (§C-2 + RFC 0053), and the delivered run's `run.started.causationId` == the delivery id (§C / RFC 0040); both `trigger.*` events content-free; the always-on shape stays in `trigger-bridge-shape.test.ts`; new lib helper `src/lib/triggerBridge.ts`). 2026-05-31 (RFC 0087 — agent org-chart, the Active→Accepted behavioral gate) added two capability-gated behavioral scenarios (both gated on `agents.orgChart.supported`, black-box on the normative `/v1/agents/org-chart` surface — no new POST seam): `agent-org-chart-scoping.test.ts` (the `GET /v1/agents/org-chart` tree-shape — departments form an acyclic `parentDepartmentId` tree, members reference `host:<id>` roster entries — + the §D responsibility roll-up via `GET /v1/agents/org-chart/{departmentId}` with a deduped `responsibilities[]` union + the RFC 0074 cross-tenant 404 via `OPENWOP_CROSS_TENANT_ORG_CHART_DEPARTMENT_ID`) and `org-position-no-authority-escalation.test.ts` (the behavioral leg of the protocol-tier invariant — the live org-chart wire carries NO authority-bearing field on any member/department/responsibility-view object; the structural leg stays always-on in `agent-org-chart-shape.test.ts`, and the deeper RFC 0049/0051 authority-invariance legs stay reference-impl tier per the `agent-manifest-runtime` no-host-hook precedent). 2026-05-31 (RFCs 0086 + 0077 — the Active→Accepted behavioral gate) added four capability-gated behavioral scenarios so a non-steward host can be mechanically certified non-vacuously under `OPENWOP_REQUIRE_BEHAVIOR=true`: `agent-roster-attribution.test.ts` (RFC 0086 §B/§C; gated on `agents.roster.supported` — the normative `GET /v1/agents/roster` read shape + `total==roster.length`, the §C `roster.run.initiated`-before-`agent.invocation.started` ordering, the content-free payload backing `roster-attribution-no-content`, the durable work-item `triggerSubscriptionId`, and the RFC 0074 cross-tenant 404 via `OPENWOP_CROSS_TENANT_ROSTER_ID`), `agent-live-invocation-bracket.test.ts` (RFC 0077 §E; gated on `agents.liveRuntime.supported` — `agent.invocation.started`-first / `agent.invocation.completed`-last bracket, matching `invocationId`, `source`/`outcome` closed enums, content-free), `agent-live-structured-output.test.ts` (RFC 0077 §B step 6; gated on `agents.liveRuntime.structuredOutput` — a result violating `handoff.returnSchemaRef` fails the invocation `outcome:"failed"` rather than shipping as completed), and `agent-live-allowlist-enforced.test.ts` (RFC 0077 §F-1 / RFC 0002 §A14; gated on `agents.liveRuntime.supported` — a tool outside `toolAllowlist` is not callable); all four drive the documented `POST /v1/host/sample/roster/fire` + `POST /v1/host/sample/agents/live-invoke` seams plus the test event-log seam and soft-skip on 404 (these are the RFC 0086 / 0077 Active→Accepted bars). 2026-05-30 (RFC 0087 — agent org-chart, Draft -> Active) added `agent-org-chart-shape.test.ts` (always-on server-free: the `capabilities.agents.orgChart` shape + the `AgentOrgChart` round-trip + the non-`host:` member negative + the **§B structural non-authority guarantee** — the schema rejects a `scopes`/`canDispatch`/`permissions`/`authority` field on a member (`additionalProperties:false`), and a member's key set is exactly `{rosterId, departmentId, roleId, reportsTo}` — backing the protocol-tier `org-position-no-authority-escalation` invariant; no new RunEventType). 2026-05-30 (RFC 0086 — standing agent roster, Draft -> Active) added `agent-roster-shape.test.ts` (always-on server-free: the `capabilities.agents.roster` shape + the `AgentRosterEntry` round-trip + the `host:` `rosterId` + `agentRef` version-XOR-channel negatives + the content-free `roster.run.initiated` negatives backing the protocol-tier `roster-attribution-no-content` invariant + the additive `roster` inventory projection + RunEventType-enum membership). 2026-05-30 (RFC 0082 — agent deployment lifecycle, Draft -> Active) added `agent-deployment-shape.test.ts` (always-on server-free: the `capabilities.agents.deployment` shape + the `AgentDeployment` record round-trip + the `AgentRef` `channel` XOR `version` `not`-clause + the four `deployment.*` payloads + the content-free negatives backing the protocol-tier `deployment-event-no-content-leak` invariant). 2026-05-30 (RFC 0085 — `openwop-agent-platform` meta-profile, Draft -> Active) added `agent-platform-profile.test.ts` (always-on server-free derivation of the operational-annex `none`/`partial`/`full` status: all-floor ⇒ partial, missing-flag ⇒ none, the replay-OR-`nondeterminismPolicy.declared` term, floor+governance ⇒ full, missing-tenant-scope ⇒ partial-not-full per the honest-advertisement rule, eval/deploy/budget-are-advisory-not-hard-terms, + the `capabilities.nondeterminismPolicy.declared` shape). 2026-05-30 (RFC 0084 — budget, quota + cost policy, Draft -> Active) added `budget-policy-shape.test.ts` (always-on server-free: `budget-policy.schema.json` round-trip + the §A orthogonality guard — a wall-time field is rejected (it's RFC 0058's `runTimeoutMs`) — + threshold/onExhaustion negatives + the four content-free `budget.{reserved,consumed,threshold.crossed,exhausted}` payloads + the four `cap.breached{budget-*}` kinds + RunEventType-enum membership + the no-pricing-property structural check backing the protocol-tier `budget-no-pricing-leak` invariant + the `capabilities.budget`/`limits.maxBudget*` shape). 2026-05-30 (RFC 0083 — durable trigger + channel bridge, Draft -> Active) added `trigger-bridge-shape.test.ts` (always-on server-free: `trigger-subscription.schema.json` round-trip + missing-`state`/out-of-enum-`source`/unknown-property negatives + the four-state vocab + the two content-free `trigger.{subscription.state.changed,delivery.attempted}` payloads incl. closed `state`/`outcome` enums + RunEventType-enum membership + the `triggerBridge`/`webhooks.durable` capability shape + the `openwop-trigger-bridge` profile derivation incl. the no-dead-letter-sink negative). 2026-05-30 (RFC 0079 — credential provenance + egress policy, Draft -> Active) added `egress-provenance-shape.test.ts` (always-on server-free: `credential-provenance.schema.json` round-trip + `audiences:[]`/missing-`credentialId`/unknown-property negatives + the no-secret-property structural check backing the protocol-tier `egress-decision-no-secret-leak` invariant + the content-free `egress.decided` record incl. the `decision` enum + RunEventType-enum membership + the `httpClient.egressPolicy` shape; the behavioral `egress-credential-audience-bound` confused-deputy MUST is reference-impl tier, deferred to a host). 2026-05-30 (RFC 0078 — portable tool catalog, Draft -> Active) added `tool-descriptor-shape.test.ts` (always-on server-free: `tool-descriptor.schema.json` round-trip + the §C-1 `exec` ⇒ `host-extension` cross-field MUST (RFC 0069) + the `safetyTier`-required negative + `additionalProperties:false`, the `capabilities.toolCatalog` `supported`/`sources`/`sessionLifecycle` shape, and the two content-free `tool.session.{opened,closed}` payload $defs incl. the closed `outcome` enum + RunEventType-enum membership). 2026-05-30 (RFC 0080 — agent memory capability reconciliation, Draft -> Active) added `memory-capability-model-shape.test.ts` (always-on server-free: the additive `capabilities.memory.{writable,search,retention}` dimension shapes + malformed-instance negatives — `retention.ttl` non-boolean, out-of-enum `search.modes`, unknown property under `additionalProperties:false` — the `agent-inventory-response` `memoryDegraded`/`degradedMemoryDimensions` closed-enum fields, and the `openwop-memory` derivation surfacing for read/write + long-term hosts while withholding from `writable:false`). 2026-05-30 (RFC 0081 — agent evaluation, Draft -> Active) added `agent-eval-suite-shape.test.ts` (always-on server-free: the `capabilities.agents.evalSuite` shape + the `AgentEvalSuite`/`EvalSummary` schema round-trips + the three `eval.{started,scored,completed}` payloads + the content-free negatives — a task entry with a `taskOutput` body, a `safetyFinding` with an `excerpt` — backing the new `eval-summary-no-content-leak` SECURITY invariant). 2026-05-29 (RFC 0076 §B — `ctx.http.safeFetch` live-run audit) added `safefetch-live-audit.test.ts` (`behaviorGate('openwop-safefetch-live-audit', …)`, gated on `httpClient.safeFetch` + `toolHooks.prePostEvents`) — asserts the audit-when-both MUST against the **durable run event log** via the new `POST /v1/host/sample/http/safe-fetch-run` open seam + the test event-log seam, closing the seam-vs-production gap (a production `createSafeFetch()` with no audit hooks passes the inline `safefetch-behavior.test.ts` but FAILS this under `OPENWOP_REQUIRE_BEHAVIOR=true`); this is the RFC 0076 §B → Accepted bar; run seam soft-skips on 404 (host-pending). 2026-05-29 (RFC 0066 — `x-openwop-form` picker UX hints, Draft → Active) added `x-openwop-form-pack-manifest.test.ts` (always-on server-free: an annotated `configSchema` stays a valid 2020-12 schema + the advisory hints don't change what it accepts, each §A annotation matches the shape, an unknown `kind` validates for forward-compat, 3 negatives — missing/non-string `kind`, non-string `dependsOn`). 2026-05-29 (RFC 0076 §B — `ctx.http.safeFetch`) added `safefetch-behavior.test.ts` (seam-gated: SSRF block / DNS-rebinding / `Connection: upgrade` refusal / tool-hooks audit-when-both, via `POST /v1/host/sample/http/safe-fetch`; advertisement contract stays in `http-client-ssrf.test.ts`). 2026-05-29 (RFC 0076 §A — pack `runtime.requires[]` install gate) added two: `runtime-requires-shape.test.ts` (server-free closed-vocabulary validation — the 8 tokens validate, a raw builtin name is rejected, empty-array≡omission, `uniqueItems`) + `runtime-requires-install-gate.test.ts` (seam-gated install-grant / install-refuse → `pack_runtime_requirement_unmet` / non-sandbox SHOULD-projection, soft-skip on 404 via `POST /v1/host/sample/packs/install-gate`). 2026-05-29 (RFC 0047 — `host.oauth` authorization-code roundtrip) added `oauth-authorization-code-roundtrip.test.ts` — capability-gated on `capabilities.oauth.supported` + `grants` including `authorization_code`; drives the `POST /v1/host/sample/oauth/authorize-code-roundtrip` seam against the one canonical synthetic provider in `fixtures/oauth-providers/synthetic.json` (soft-skip on 404, Tier-2 host-pending), asserting a successful grant returns a credential REFERENCE (token persisted as a `host.credentials` entry) and that the authorization code / state / PKCE verifier / acquired access+refresh tokens never appear on any run-visible surface (RFC 0047 §C + §C.2 / `credential-payload-redaction`). Closes the RFC 0047 Tier-2 gap (capability-shape + redaction scenarios existed; the actual authorization-code dance was unexercised). 2026-05-26 (RFC 0070 — agent-manifest runtime) added `agent-manifest-runtime.test.ts`; 2026-05-26 (RFC 0071 — artifact-type + chat card packs) added six: `artifact-type-pack-manifest-validation.test.ts` + `artifact-schema-compile-bounded.test.ts` (server-free) + `artifact-type-pack-install.test.ts` + `artifact-type-store-without-render.test.ts` + `chat-card-pack-manifest-validation.test.ts` (server-free) + `chat-card-pack-execution.test.ts` (capability-gated, host-pending). 2026-05-26 (RFCs 0067 / 0068 / 0069 — spec-gap Draft cohort) added five scenarios: `byok-auth-modes.test.ts` (RFC 0067; always-on schema-shape of `aiProviders.authModes` + a discovery-gated §B auth-mode-contract cross-field check), `memory-consolidation-shape.test.ts` (RFC 0068; always-on shape of `agents.memoryConsolidation`/`agents.commitments` + the `agent.memory.consolidated`/`commitment.fired` payload $defs), `memory-consolidation-idempotent.test.ts` + `commitment-fired.test.ts` (RFC 0068; capability-gated behavioral, soft-skip on the documented `/v1/host/sample/memory/consolidate` + `/commitment/fire` seams), and `exec-not-protocol-tier.test.ts` (RFC 0069; always-on server-free structural assertion that the protocol corpus defines no `core.*`/`openwop.*` exec-class primitive — backs the `exec-must-not-be-protocol-tier` SECURITY invariant). 2026-05-25 (RFC 0061 — stateful agent-loop lifecycle, executionModel.version 5) added four `agent-loop-*.test.ts` scenarios: `-version5-shape` (always-on; validates `executionModel.statefulResume`/`transcriptWindow` + the 1–5 version ceiling) plus `-iteration-monotonic` (gated on `version >= 5`; `runOrchestrator.decided.iteration` increments 1,2,3… exactly once per turn), `-workspace-snapshot` (gated additionally on `host.workspace.supported`; a turn-i workspace write is invisible to turn i, visible to turn i+1), and `-stateful-resume` (gated on `statefulResume`; a mid-loop suspend resumes at the same iteration without resetting the counter) — the three behavioral scenarios drive the documented agent-loop seam (`POST /v1/host/sample/agentloop/run`) and soft-skip until a host wires it. 2026-05-25 (RFC 0059 — host.workspace M2, reference-host enforcement) added two `workspace-*.test.ts` scenarios: `-behavior` (capability-gated CRUD round-trip / `If-Match` 409 `workspace_conflict` / `workspace_too_large` / §D run-start snapshot, all via the real `/v1/host/workspace/files` §C endpoints) and `-cross-tenant-isolation` (WCT-1 — drives the documented `POST /v1/host/sample/workspace/op` seam to assert a file owned by one `{tenant, workspace}` is unreadable, on both `get` and `list`, under a different owner; backs the new `workspace-cross-tenant-isolation` SECURITY invariant). The in-memory reference host now advertises `capabilities.workspace.supported` and honors §C/§D/§E end-to-end. 2026-05-25 (RFC 0062 — memory.distillation "dreams") added five `distillation-*.test.ts` scenarios: `-shape` (always-on; validates the `capabilities.memory.distillation` block + the additive `distillation` sub-object on `memory.compacted`) plus `-token-budget` (within budget `tokensUsed ≤ tokenBudget`; an un-meetable budget → `token_budget_exceeded` with no partial archive), `-stable-archive` (same sources + budget ⇒ byte-stable archive checksum), `-index-roundtrip` (gated additionally on `indexEmitted`; the `MEMORY-INDEX.json` workspace file is retrievable + `workspace.updated` fired), and `-secret-carryforward` (SR-1: a redacted source secret never appears in the archive) — the four behavioral scenarios drive the documented memory-distillation seam (`POST /v1/host/sample/memory/distill`) and soft-skip until a host wires it. 2026-05-25 (RFC 0063 — core.subWorkflow.outputAttestation) added four `subrun-*.test.ts` scenarios: `-attestation-shape` (always-on; validates the `capabilities.agents.subRunAttestation` flag) plus `-checksum-stable` (the child output checksum is the byte-stable, key-order-invariant RFC 8785 JCS + SHA-256 digest), `-approval-gate` (`requireApproval` → `accept` merges, `reject` does not), and `-approval-fail-closed` (no `accept`/`edit-accept` → no merge; backs the deferred `subrun-merge-approval-fail-closed` invariant) — the three behavioral scenarios drive the documented sub-run attestation seam (`POST /v1/host/sample/subrun/attest`) and soft-skip until a host wires it. 2026-05-25 (RFC 0064 — host.toolHooks) added five `tool-hooks-*.test.ts` scenarios: `-shape` (always-on; validates the `capabilities.toolHooks` block + the optional content-free fields on `agentToolCalled` / `agentToolReturned`) plus `-content-free` (gated on `prePostEvents`), `-authorization-fail-closed` (gated on `perToolAuthorization`), `-rate-limit` (gated on `perToolRateLimit`), and `-secret-redaction` (gated on `prePostEvents` + the SR-1 `argsHash` redaction rule) — the four behavioral scenarios drive the documented tool-hooks invoke seam (`POST /v1/host/sample/toolhooks/invoke`) and soft-skip until a host wires it. 2026-05-25 (RFC 0060 — host.heartbeat) added four `heartbeat-*.test.ts` scenarios: `-capability-shape` (always-on; validates the `capabilities.heartbeat` block) plus `-fires-once-per-tick`, `-idempotent-no-spam`, and `-runtime-bound` (gated on `capabilities.heartbeat.supported` + the host heartbeat tick seam; soft-skip until a host wires it). 2026-05-25 (RFC 0057 — memory write-attribution) added five `memory-attribution-*.test.ts` scenarios: `-shape` (always-on advertisement check on `capabilities.memory.attribution`), plus `-no-content`, `-tenant-scoped`, `-emits-on-write`, and `-replay-stable` (gated on `capabilities.memory.attribution.emitsWriteEvents`) verifying the content-free `memory.written` RunEvent, its two SECURITY invariants (`memory-attribution-no-content` + `memory-attribution-tenant-scoped`), and the §D replay rule that a `replay`-mode fork MUST NOT regenerate `memoryId`. 2026-05-25 (RFC 0025 §C point 1 — test-catalog isolation invariant; pairs with the 25 publish-error scenarios in `pack-registry-publish.test.ts`) added `pack-registry-isolation.test.ts` — capability-gated on `capabilities.packs.testMode.{supported, isolated}: true`; PUTs a disposable pack into `/v1/packs-test/{name}` and asserts the same `(name, version)` does NOT appear via `GET /v1/packs/{name}` — anchors the test-catalog isolation MUST in RFC 0025 §C. 2026-05-25 (RFC 0028 Tier-2 post-promotion T2 — read-side sister scenario for workspace-membership enforcement) added `prompt-read-workspace-membership-enforced.test.ts` — gates on `capabilities.prompts.supported: true` (broader than `mutableLibrary` so read-only hosts that expose `?workspaceId=` are also probed); drives `GET /v1/prompts?workspaceId=<random-non-member>` and interprets the response: 4xx PASS (canonical envelope check on 403); 200 with empty `templates[]` PASS (correct null result for a nonexistent workspace); 200 with non-empty `templates[]` FAIL (cross-tenant leak); 200 without `templates[]` field SKIP (host doesn't expose workspace-scoped reads). Verifies SECURITY invariant `prompt-read-workspace-membership-enforced`. Same-day T1 strengthened `prompt-mutation-workspace-membership-enforced.test.ts` to pin `error === "workspace_membership_required"` when the host's refusal status is 403 (other refusal codes unconstrained). 2026-05-25 (RFC 0028 Tier-2 follow-up — workspace-membership enforcement on mutating prompt endpoints, filed in response to a self-disclosed adopter vulnerability) added `prompt-mutation-workspace-membership-enforced.test.ts` — capability-gated on `capabilities.prompts.mutableLibrary: true`; drives `POST /v1/prompts` with a cryptographically-random non-member `workspaceId` and asserts the host refuses (NOT a 2xx; any 4xx/5xx is acceptable — silent success is the failure mode). Verifies SECURITY invariant `prompt-mutation-workspace-membership-enforced`. 2026-05-22 (RFC 0034 §B follow-up — secret-leakage harness against the OTel + debug-bundle seams) added `secret-leakage-otel-attribute.test.ts` — gates on `capabilities.secrets.supported` + `capabilities.observability.testSeams.{otelScrape,debugBundleExport}` AND the `OPENWOP_CANARY_SECRET_VALUE` env (host operator + conformance runner agree on the canary). Drives the existing `openwop-smoke-byok-roundtrip` fixture end-to-end; scrapes both seams after run completion; hard-fails if the canary plaintext appears in any OTel span attribute or debug-bundle field. Verifies SECURITY invariants `secret-leakage-otel-attribute` + `secret-leakage-debug-bundle-otel`. 2026-05-22 (RFC 0041 Phase 4 — replay determinism under nondeterministic models) added three scenarios: `replay-divergence-at-refusal.test.ts` (advertisement-shape probe on `replayDeterminism.refusalDivergenceEmission` + 2 `it.todo` for the dual-direction refusal-divergence case), `replay-observable-sequence-determinism.test.ts` (capability-gated; behavioral assertion soft-skipped until a `conformance-phase4-nondet-tool` fixture ships), `replay-llm-cache-key-portable.test.ts` (intra-host reproducibility + non-recipe-field invariance + Phase 4 advertisement alignment — reuses the existing `POST /v1/host/sample/test/llm-cache-key` seam from the sibling `replay-llm-cache-key.test.ts`). 2026-05-20 (RFC 0027 §A templateKinds-coverage follow-up — paired with `prompt-end-to-end-events.test.ts`) added `prompt-all-four-kinds-events.test.ts` exercising all four `PromptKind` values (`system`, `user`, `schema-hint`, `few-shot`) end-to-end through the reference workflow-engine sample's `local.sample.demo.mock-ai` dispatch path; capability-gated via `behaviorGate('prompts-supported', ...)`. Closes the credibility gap where the host advertised `templateKinds: ["system", "user", "few-shot", "schema-hint"]` but only the system+user pair was actually wired into dispatch. 2026-05-20 (RFCs 0030–0033 — envelope LLM-contract-hardening track) added 15 scenarios across four `Active` RFCs: `envelope-reasoning-shape.test.ts` (RFC 0030, always-on; asserts the OPTIONAL `reasoning` property on the three universal-kind schemas + the `schema.response` deliberate omission), `envelope-reasoning-secret-redaction.test.ts` (RFC 0030, capability-gated on `capabilities.envelopes.reasoning.supported` + `secrets.supported`; 5 `it.todo()` placeholders for SECURITY invariant `envelope-reasoning-secret-redaction`), `envelope-tier-one-subset-static.test.ts` (RFC 0030, always-on for load-bearing rules — no `oneOf` / `allOf` / `not` / `prefixItems` / `propertyNames` anywhere; gated on `tierOneSubsetCompliance: "strict"` for OpenAI-strict-only constraints), `envelope-variant-discriminator-static.test.ts` (RFC 0031, always-on; asserts no `oneOf` + every `anyOf` branch declares a single-string-enum discriminator in `required` on every `schemas/envelopes/*.schema.json`), `model-capability-substituted.test.ts` (RFC 0031, advertisement-shape probe on `capabilities.modelCapabilities.advertised[]` identifier pattern + 5 `it.todo()` placeholders for SECURITY invariant `model-capability-substituted-no-credential-disclosure`), `model-capability-insufficient.test.ts` (RFC 0031, 6 `it.todo()` placeholders for refusal + no-recursive-fallback), `node-module-required-capabilities-shape.test.ts` (RFC 0031 SHOULD-tier authoring-convention; 4 `it.todo()` placeholders), and the six envelope-reliability events from RFC 0032 (`envelope-retry-attempted` carrying the shared advertisement-shape probe enforcing both MUST-tier events in `events[]` per RFC 0032 §C, plus `envelope-retry-exhausted`, `envelope-refusal-shape`, `envelope-truncated`, `envelope-nl-to-format-engaged`, `envelope-recovery-applied` — collectively 39 `it.todo()` placeholders covering retry/refusal/truncation/recovery + SECURITY invariants `envelope-refusal-no-prompt-leak` and `envelope-recovery-no-content-leak`), plus RFC 0033's two scenarios (`envelope-completion-distinguishes-truncation.test.ts` + `envelope-truncation-cap-exhaustion.test.ts` — 12 `it.todo()` placeholders covering the truncation-vs-schema-violation retry-routing distinction + the DoS-bound assertion). Reference workflow-engine sample advertises `capabilities.envelopes.reasoning: { supported: true, promptDirective: "off" }` + `tierOneSubsetCompliance: "warn"` honestly (schemas accept the field; host doesn't yet inject the directive); the other three RFCs' capability blocks defer to reference-host emission code per the staged RFC 0027 §G precedent. 2026-05-20 (RFC 0028 §B Phase B — prompt-pack boot-time install) added `prompt-pack-install.test.ts` (capability-gated on `capabilities.prompts.endpointsSupported: true`; asserts a host that ran the boot-time pack loader surfaces ≥ 1 pack-source template under `GET /v1/prompts?source=pack` carrying the canonical `meta.source: "pack"` + `meta.packName` + `meta.packVersion` stamps; positively identifies the in-tree `vendor.openwop.prompt-sample` reference pack's `writer-system` template when present). Pairs with the new `host/promptPackLoader.ts` boot-time entry on the reference workflow-engine sample, which scans `examples/packs/*` plus `OPENWOP_PROMPT_PACKS_DIR` and calls `installPackTemplates()` for each `kind: "prompt"` pack found. 2026-05-20 (RFC 0029 Phase C — prompt resolution chain wire shape) added three more scenarios: `prompt-resolution-chain-node-wins.test.ts` (capability-gated on `capabilities.prompts.supported: true`; asserts layer-1 node-config supersedes lower layers per `spec/v1/prompts.md` §"Resolution chain (normative)"), `prompt-resolution-chain-agent-intrinsic.test.ts` (additionally gated on `capabilities.prompts.agentBindings: true`; asserts agent intrinsic `systemPromptRef` wins over `promptOverrides` AND lower layers when the node has no layer-1 ref), `prompt-resolution-chain-fallback-cascade.test.ts` (asserts layer 3 workflow-defaults wins over layer 4 host-defaults; layer 4 host-defaults wins when 1-3 yield null; resolved is null when all four yield null but chain[] still lists every attempted layer). The scenarios drive the host's `POST /v1/host/sample/prompt/resolve` test seam (reference-host implementation deferred to follow-up slice per RFC 0021 staging precedent). 2026-05-20 (RFC 0027 Phase A — prompt templates wire shape) added three scenarios: `prompt-template-shape.test.ts` (always-on; Ajv compileability + positive/negative round-trip for PromptTemplate + PromptRef + PromptKind), `prompt-composed-secret-redaction.test.ts` (capability-gated on `capabilities.prompts.supported: true` + `observability: "full"`; asserts `[REDACTED:<secretId>]` markers in `prompt.composed` payloads for `source: "secret"` variable bindings per SECURITY/threat-model-secret-leakage.md §SR-1), `prompt-composed-trust-marker.test.ts` (same capability gates; asserts `<UNTRUSTED>...</UNTRUSTED>` wrapping + `contentTrust: "untrusted"` propagation per RFC 0020 §D). Paired with new `fixtures/prompt-templates/` sub-directory + per-fixture schema-validity describe block + future SECURITY invariants `prompt-composed-secret-redaction` and `prompt-composed-trust-marker` (lands alongside reference-host emission per RFC 0021 staging precedent). 2026-05-18 (RFC 0022 `Draft` — runtime variable mapping) added four `it.todo()` placeholder scenarios covering the new mapping surfaces on `core.dispatch` (§A — `dispatch-input-mapping.test.ts`, `dispatch-output-mapping.test.ts`, `dispatch-cross-worker-handoff.test.ts`) and `core.subWorkflow` (§B — `subworkflow-input-mapping.test.ts`). Gated on `capabilities.agents.dispatchMapping` (dispatch trio) and `capabilities.subWorkflow.inputMapping` (subWorkflow). Promote to live assertions when RFC 0022 reaches `Active` + a reference host advertises the matching flags. 2026-05-17 (RFC 0003 §D handoff-schema enforcement, HV-1) added `agentPackHandoffSchemaValidation.test.ts` — verifies the host validates dispatch payloads against `handoff.taskSchemaRef` AND return payloads against `handoff.returnSchemaRef` per RFC 0003 §D. Paired with the new `agent-pack-handoff-schema-enforcement` row in `SECURITY/invariants.yaml`. 2026-05-17 (AI Envelope gap-closure, DRAFT v1.x — `spec/v1/ai-envelope.md`) added 7 advertisement-shape scenarios with `it.todo()` behavioral placeholders gated on `capabilities.envelopeContracts.advertised: true`: `aiEnvelope.universalKinds.test.ts`, `aiEnvelope.schemaDrift.test.ts`, `aiEnvelope.correlationReplay.test.ts`, `aiEnvelope.contractRefusal.test.ts`, `aiEnvelope.trustBoundaryPropagation.test.ts`, `aiEnvelope.redaction.test.ts`, `aiEnvelope.capBreached.test.ts`. Paired with the new `envelope-redaction-sr-1-carry-forward` row in `SECURITY/invariants.yaml`. 2026-05-17 (post-publish hardening, deep audit of `core.openwop.agents`) added `agents-run-tool-allowlist.test.ts` — server-free scenario locking in the `core.openwop.agents@1.0.1` safety-fix that closes `OPENWOP-AUDIT-2026-003` (function-typed `tool.handler` properties rejected at `validateTools()` with `INVALID_TOOL_DECLARATION`; tool-driven runs require `ctx.agentRuntime`; tool-less safe fallback preserved). Paired with the new `agents-run-no-raw-handler` row in `SECURITY/invariants.yaml`. Same-day post-publish hardening added `idempotency-key-determinism.test.ts` — server-free scenario locking in the `core.openwop.http@1.1.2` determinism safety-fix (default `composite` mode produces deterministic keys in `(runId, nodeId, payload)`; removed `uuid` mode rejects with `CONFIG_INVALID`; cross-impl vector test lets third-party reimplementations verify wire agreement). Paired with the new `idempotency-key-deterministic` row in `SECURITY/invariants.yaml`. 2026-05-17 (Phase 3 of RFC 0013) added three server-free scenarios exercising the reference workflow-chain expansion library (`conformance/src/lib/workflow-chain-expansion.ts`): `workflow-chain-expansion.test.ts` (parameter substitution + node id collision avoidance + edge rewriting + capability propagation + runtime-invariance contract), `workflow-chain-unresolvable-typeid.test.ts` (rejection with `chain_unresolvable_typeid` when a chain references an unknown typeId), and `workflow-chain-pack-signature-verification.test.ts` (Ed25519 verification recipe reuse from `node-packs.md §Signing`). Earlier that day (Phase 1) added `workflow-chain-pack-manifest-validation.test.ts` — server-free schema-validation scenario covering the new `workflow-chain-pack-manifest.schema.json` (positive sample + two negatives: kind/contents mismatch and invalid `chainId`). Closes RFC 0013 (`Workflow-chain packs`, `Draft`) Phases 1 + 3 alongside the new `spec/v1/workflow-chain-packs.md`, the `Capabilities.workflowChainPacks` block, and the registry build-index/conformance-check `kind` routing from Phase 2. Earlier that day, the suite added 27 `it.todo()` placeholder scenarios paired with RFCs 0014-0020 (host capability surfaces — fs, kvStorage, tableStorage, queueBus, sql/vector/search, blob/cache, mcp.serverMount). These promote to live assertions when each RFC reaches `Active` + the matching capability block lands in `schemas/capabilities.schema.json` + a reference host advertises the capability. Earlier additions include 18 Multi-Agent Shift scenarios (Phases 1-5) added 2026-05-10, the `registry-public.test.ts` public-registry healthcheck added 2026-05-11 (opt-in via `OPENWOP_TEST_PUBLIC_REGISTRY=true`), the `replay-llm-cache-key.test.ts` placeholder added 2026-05-11 (three `it.todo()` cases for the cross-host LLM cache-key recipe per `replay.md` §"LLM cache-key recipe"), the two `production-*.test.ts` scenarios added 2026-05-11 for the `openwop-production` profile per RFC 0009 (`production-backpressure.test.ts`, `production-retention-expiry.test.ts`), the four `auth-*.test.ts` scenarios added 2026-05-11/12 for the production-auth profiles per RFC 0010 (`auth-api-key-rotation.test.ts`, `auth-oauth2-client-credentials.test.ts`, `auth-oidc-user-bearer.test.ts`, `auth-mtls.test.ts` (opt-in via `OPENWOP_TEST_MTLS=1`)), `replay-retention-expiry.test.ts` added 2026-05-12 (capability shape + 410/422 envelope per `replay.md` §"Retention and garbage collection"), `bulk-cancel.test.ts` added 2026-05-12 (Phase B close-out of R1 — `POST /v1/runs:bulk-cancel`), the two Phase H launch-blocker advertisement-contract scenarios added 2026-05-12 (`mcp-toolcall-redaction.test.ts` for the MCP-1 invariant per `host-capabilities.md §host.mcp` + `threat-model-prompt-injection.md §UNTRUSTED`, and `http-client-ssrf.test.ts` for the SSRF + body-size cap advertisement contract on `capabilities.httpClient`), the `wasm-pack-abi-version-rejection.test.ts` Track 7 scenario added 2026-05-12 for the ABI-mismatch positive path via the `vendor.openwop.misbehaving-abi` pack per RFC 0008 §H, the `otel-trace-propagation-subworkflow.test.ts` Track 11 close-out added 2026-05-13 (parent + child run spans share the inbound traceparent's traceId across the `core.subWorkflow` dispatch boundary), and the three RFC 0012 (Memory Compaction Profile, `Active`) scenarios added 2026-05-13/14: `memory-compaction-sr1-carry-forward.test.ts` (load-bearing SR-1 §D), `memory-compaction-event-emitted.test.ts` (canonical §B payload shape), and `memory-compaction-provenance-tag.test.ts` (soft assertion on §C `compacted-from:<id>` convention). All three gate on `capabilities.memory.compaction.supported` + the host's test seam at `/v1/test/memory/{seed,compact}` (Postgres reference host enables both via `OPENWOP_MEMORY_COMPACTION=true OPENWOP_TEST_TRIGGER_COMPACTION=true`). 2026-05-15 (gap-closure CF-3) added `interrupt-token-matrix.test.ts` (malformed / unknown / replay / cross-run-id paths on `GET|POST /v1/interrupts/{token}`). 2026-05-31 (RFC 0078 portable tool catalog + RFC 0079 credential provenance / egress policy — the Active→Accepted behavioral gate) added four: `tool-catalog-projection.test.ts` (capability-gated on `toolCatalog.supported` via `behaviorGate('openwop-tool-catalog', …)` — the NORMATIVE `GET /v1/tools` list with each `ToolDescriptor` schema-valid + `source`/`safetyTier` in the closed vocab + content-free, `GET /v1/tools/{toolId}` round-trip + unknown-id 404, 401-unauthenticated, and the §F-2 cross-principal non-disclosure; black-box, no POST seam), `tool-session-lifecycle.test.ts` (gated on `toolCatalog.sessionLifecycle` — the §D `tool.session.opened`-before / `tool.session.closed`-after bracket over the RFC 0064 call events via the `POST /v1/host/sample/tools/session-run` seam, one shared `sessionId`, content-free), `egress-audience-binding.test.ts` (KEYSTONE — gated on `httpClient.egressPolicy.supported`; the §C confused-deputy MUST via `POST /v1/host/sample/egress/decide`: an out-of-audience egress is denied/downgraded with the credential NOT attached, a provenance-unevaluable egress fails closed — the behavioral leg of `egress-credential-audience-bound`), and `egress-decision-content-free.test.ts` (the SR-1 canary — the credential value never surfaces in `egress.decided` and `reason` stays in the CLOSED vocabulary). The maintained scenario-to-spec map lives in [`coverage.md`](./coverage.md); this README keeps the operator quickstart and the historical scenario notes below.
|
|
95
|
+
The current suite has 380 scenario files under `src/scenarios/`. 2026-06-29 (RFC 0120 — connection-pack `apiHosts` credential-egress allow-list, suite `1.45.0 → 1.46.0`) added `connection-pack-apihosts.test.ts` (always-on schema legs: the `connection-pack-apihosts-valid` fixture validates, an `openapi`-reach provider omitting `apiHosts` is rejected by the conditional-MUST `allOf`, and IP/wildcard/port/single-label/uppercase entries are each rejected; always-on matching-rule legs: the item-10 dot-anchored eTLD+1 suffix-containment rule permits subdomains + a tighter exact host and fails closed with no substring/suffix/prefix escape — the public tests for the protocol-tier `connection-pack-api-host-shape` + `connection-pack-egress-host-bound` invariants; plus a capability-gated behavioral egress allow-list leg over the new `POST /v1/host/sample/connection-packs/egress-check` seam, soft-skipping when unadvertised/unwired). 2026-06-28 (RFC 0118 — parallel sub-workflow fan-out and join, suite `1.44.0 → 1.45.0`) added `dispatch-fanout-parallel.test.ts` (always-on schema legs for `dispatch-config.schema.json` — accepts a valid `parallel` config + each `joinPolicy.mode` + `maxConcurrency`, rejects unknown enum values / `additionalProperties` on `joinPolicy` / non-positive `maxConcurrency`, keeps a pre-RFC-0118 config valid — and the `dispatchFanOut`/`dispatchJoin` event `$defs` of `run-event-payloads.schema.json` — `fanOutPolicy` const `parallel`, `childCount ≥ 2`, the required replay-deterministic `mergeOrder`; capability-gated behavioral legs over `POST /v1/host/sample/dispatch/fanout` (wait-all/collect → `joinOutcome: satisfied` + full `children[]` + `mergeOrder`) plus the un-gated registration-rejection leg (`fanOutSupported:false` host MUST reject `parallel` with a 4xx); soft-skips on absent `dispatch.fanOutSupported` / `parallel` ∉ `fanOutPolicies`). 2026-06-27 (RFC 0117 — front-end plugin packs, suite `1.43.0 → 1.44.0`) added `frontend-plugin-packs.test.ts` (always-on schema legs for `frontend-plugin-manifest.schema.json` + the `ui-plugin/1` envelope `ui-plugin-message.schema.json`: manifest validity, `runtime`-forbidden, path-traversal-guarded `entry`, the closed `hostApi` allowlist, the version-token `artifact_conflict` + `currentVersion` concurrency shape, and no credential-bearing envelope field (`additionalProperties:false`); the capability-shape leg asserts `isolation` pins to the `cross-origin-iframe` const; capability-gated behavioral legs over `POST /v1/host/sample/ui-plugin/rpc` assert an undeclared method → `method_not_allowed` and a stale `artifact.write` → `artifact_conflict` with no persist; soft-skips on absent `capabilities.uiPlugins.supported` or `404`/`403` on the seam (RFC 0117 stays Active pending host witnesses); the public test for the protocol-tier `frontend-plugin-{isolation,egress,rpc-allowlist,no-byok}` invariants). 2026-06-26 (RFC 0116 — portable prompt-prefix cache, suite `1.42.0 → 1.43.0`) added `prompt-prefix-cache.test.ts` (advertisement-shape on `aiProviders.promptPrefixCache` + behavioral legs over the OPTIONAL `POST /v1/host/sample/ai/generate` seam (`host-sample-test-seams.md` §16): (a) outcome-invariance — a generate with `cachePrefixId` and a control without produce the same accepted envelope + identical `provider.usage.inputTokens`/`outputTokens` (cost-hint-only, replay-invariant); (b) cache-hit observable — a repeat generate shows `cacheReadTokens > 0`; (c) cross-tenant isolation — tenant B’s first use of tenant A’s `cachePrefixId` shows `cacheReadTokens == 0`, the public test for the protocol-tier `prompt-prefix-cache-cross-tenant-isolation` invariant (the host MUST key its provider cache by `(tenant, cachePrefixId)`, never global); (d) secret-free — the cost-only cache-token fields carry no prompt substrings (SR-1); soft-skips on absent `aiProviders.promptPrefixCache.supported` or `404`/`405` on the seam, since RFC 0116 defers reference-host impl as provider-gated). 2026-06-26 (RFC 0114 — A2UI surface deltas, suite `1.41.0 → 1.42.0`) added `a2ui-surface-delta-transport.test.ts` (always-on legs use a cast-free, dependency-free client-side RFC 6902 applier + Ajv2020 to assert: the new `a2ui-surface-delta-frame.schema.json` compiles, the op enum excludes `test`, a full surface + delta frames reconstruct the expected tree AND equal the full a non-negotiating subscriber materializes and re-validate against the closed catalog, an out-of-catalog/script-bearing delta fails closed-catalog validation post-patch — the `a2ui-surface-no-code-exec` boundary holds — and the recorded envelope is always the full surface; the HTTP leg is capability-gated on `a2uiSurface.deltaTransport` and soft-skips on absent host/capability or `404`/`405` on the OPTIONAL `GET /v1/host/sample/a2ui/surface/materialized` seam). 2026-06-26 (RFC 0111 — context economy, suite `1.40.0 → 1.41.0`) added `context-budget-transcript-bound.test.ts` (capability-gated on `multiAgent.executionModel.contextBudget.transcriptTokenBudget` being present via `behaviorGate(openwop-context-budget, …)` — drives the `conformance-context-budget-multiturn` orchestrator fixture, reads the host’s per-iteration accounting via the OPTIONAL seam `GET /v1/host/sample/agent/transcript-window?runId=…&iteration=N` (`host-sample-test-seams.md` §14), and asserts `tokenCount ≤ transcriptTokenBudget` in the advertised `tokenCounter` unit, that every `eventId` in the seam accounting is a real persisted run event (internal-consistency cross-check against `/v1/host/sample/test/runs/:runId/events`), recent-tail + no-double-count, every `summarizedRanges.summaryRef` has a matching `context.summarized` event, and `keepLastTurns` recent turns are fed verbatim — never inside a summarized range; soft-skips on `404`/`405` since the RFC defers reference-host impl) + `context-summarization-replay.test.ts` (capability-gated on `multiAgent.executionModel.contextBudget.summarization.supported` via `behaviorGate(openwop-context-summarization, …)` — replays the run via `:fork mode:replay` and asserts the recorded `context.summarized` → `summaryRef` records are REUSED, never re-summarized, the direct analogue of RFC 0041 envelope-refusal recovery; soft-skips when the event-log seam is unwired, no `replay` mode is advertised, or the run produced no summarization). 2026-06-26 (RFC 0113 — memory injection budget, suite `1.39.0 → 1.40.0`) added `memory-injection-budget.test.ts` (capability-gated on `memory.injectionBudget.supported === true` via `behaviorGate(openwop-memory-injection-budget, …)` — drives the `conformance-agent-memory-injection-budget` fixture through the host-sample memory seam and asserts the budgeted injection read's cumulative tokens ≤ the requested `tokenBudget` in the advertised `tokenCounter` unit (`o200k_base`/`cl100k_base`/`chars`/`host-defined`), an over-budget single entry is omitted not truncated, and re-asserts SR-1 (`[REDACTED:<secretId>]` content) + CTI-1 (cross-tenant probe returns empty) on the budgeted path; the `rank:'relevance'` leg DELEGATES to `memory.search` semantic (RFC 0080) and asserts relevance ordering differs from recency ONLY when the host ALSO advertises `memory.search` semantic, else it soft-skips — there is NO ranking primitive in `injectionBudget`; soft-skips on hosts that do not advertise `memory.injectionBudget`). 2026-06-26 (RFC 0112 — compact tool projection, suite `1.38.0 → 1.39.0`) added `tool-catalog-compact-projection.test.ts` (capability-gated on `toolCatalog.compactView === true` via `behaviorGate(openwop-tool-catalog-compact, …)` — drives `GET /v1/tools?view=compact` and asserts the host returns the `{ tools: CompactToolDescriptor[] }` envelope where every descriptor validates against `compact-tool-descriptor.schema.json` with the heavy `ToolDescriptor` fields dropped, every present `inputSchema` satisfies the self-contained compact structural subset (top-level `type:"object"`+`properties`; no `$ref`/`oneOf`/`allOf`/`anyOf`/`not`/`patternProperties`/`dependentSchemas`), and the compact `tools[]` `toolId` set EQUALS the standard view set for the same principal (projection completeness); soft-skips on hosts that do not advertise `toolCatalog.compactView`). 2026-06-26 (RFC 0115 — run transport economy, suite `1.37.0 → 1.38.0`) added `run-transport-economy.test.ts` (capability-gated on `restTransport.conditionalRunGet === true` and a non-empty `restTransport.contentEncodings` — drives `GET /v1/runs/{runId}` and asserts a sequence-derived strong `ETag` on the `200`, `If-None-Match` → `304` with an empty body while the run is unchanged, the `ETag` ROTATES after the `conformance-approval` fixture advances `waiting-approval → completed` (proving it derives from the latest persisted event-log sequence, not a coarser signal that would leave a `304` stale), and each advertised `Content-Encoding` (`gzip` baseline; `br`/`zstd` optional) round-trips byte-identically to the identity body; soft-skips on hosts that do not advertise `restTransport`). 2026-06-24 (RFC 0110 — channel presence, behavioral leg) added `channel-presence-behavioral.test.ts` (capability-gated on `channelPresence.supported` via `behaviorGate('openwop-channel-presence', …)` — drives the `POST /v1/host/sample/channel-presence/snapshot` seam and asserts a live `channel.presence` snapshot is the CLOSED `{conversationId,present,typing}` shape (no PII), every ref is an opaque RFC 0041 `user:`/`agent:` subject, `typing ⊆ present`, and the snapshotting member is non-vacuously present; soft-skips on 404/405 — a host whose presence is bound to a product flow witnesses via its own route test + an INTEROP-MATRIX row, the RFC 0086 dual-staging). 2026-06-24 (RFC 0110 — channel presence, Accept cycle) added `channel-presence-shape.test.ts` (always-on server-free — the OPTIONAL `channel.presence` ephemeral payload validates a conforming snapshot, REQUIRES `conversationId`+`present`, is CLOSED `additionalProperties:false` (the no-PII guard), `typing` optional; `channel.presence` is in the `RunEventType` enum; the `channelPresence` capability block is declared + closed). 2026-06-24 (RFC 0109 — conversation-turn model provenance, Accept cycle) added `conversation-turn-model-provenance-shape.test.ts` (always-on server-free — `agent.model` `{provider, model}` validates conforming / both-required / closed-SR-1 / optional-back-compat, + the `conversationTurnModelProvenance` capability declared + closed). 2026-06-24 (RFC 0108 — self-hosted / OpenAI-compatible provider class, Accept cycle, suite `1.36.0 → 1.37.0`) added the gated behavioral honesty leg `aiproviders-selfhosted-honesty.test.ts` (gated on `aiProviders.selfHosted.length > 0` via `behaviorGate('openwop-selfhosted-providers', …)` — drives `POST /v1/host/sample/ai/call` against an advertised `selfHosted` id and asserts **§A.2** the dispatch reaches a real configured endpoint (success or transport error — never `capability_not_provided` / `provider_not_supported`, which would prove a dishonest advertisement with no endpoint behind it) and **§D** the endpoint location — supplied out-of-band via `OPENWOP_TEST_COMPAT_ENDPOINT`, checked as the raw string + bare `host`/`host:port` — never appears in the seam response or error payload; soft-skips on 404 / when the env var is unset for the §D leg). 2026-06-24 (RFC 0108, suite `1.35.0 → 1.36.0`) added the always-on server-free shape leg `aiproviders-selfhosted-shape.test.ts` (the additive `aiProviders.selfHosted` advertisement: a `string[]` with `uniqueItems`/`minLength:1` items, absent from `aiProviders.required`, validates a conforming array via Ajv2020 while rejecting non-array / duplicate / empty-string forms, and enforces the two §A rules JSON Schema cannot express — §A.1 every `selfHosted` entry is a subset of `supported`, and §A.3 / `self-hosted-endpoint-no-disclosure` no entry is URL-shaped (`://`, bare `host:port`, or leading `/` path) — exercised against good/bad example docs). 2026-06-23 (RFC 0106 — real-time voice session profile, Accept cycle) added six gated behavioral scenarios driving the openwop-app live host (rev `00293-89w`): `voice-transcription-streaming` + `voice-transcription-unadvertised` (the `call-transcriber` seam; the latter gated-by-absence), `voice-synthesis-streaming` (the `stream:true` synthesis arm → metadata-only `voice.synthesis_chunk` events), `voice-bargein-no-partial-leak` (the `/voice/barge-in` seam — `voice.barge_in`→`voice.cancelled` with no chunk after; **graduated `voice-bargein-no-partial-leak` reference-impl→protocol** on the live proof), and `voice-interim-not-durable` + `voice-streamref-tenant-bound` (§F INV-1/INV-4; soft-skip on the §E `transcription_unsupported` / no-live-streamRef path — stay reference-impl until a host with a scripted-interim / live-streamRef arm proves them). 2026-06-23 (RFC 0107 — publishable declarative pack kinds) added one: `registry-declarative-kinds.test.ts` (always-on server-free — the registry version-manifest carries the `kind` discriminator + per-kind declarative payload, `runtime` is conditional on `kind` via `allOf if/then/else`, published artifact-type and connection manifests validate, and a declarative-with-`runtime` or node-without-`runtime` manifest is rejected). 2026-06-23 (RFC 0107 correction, suite `1.34.0 → 1.35.0`) extended that scenario with two cases — a published **chat-card** pack manifest (`kind: "card"`, RFC 0071) validates, and the never-canonical `kind: "chat-card"` is **rejected** — fixing a kind-string mismatch where RFC 0107's published schema enumerated `chat-card` while RFC 0071 (authoritative) defines `card` (no scenario-file count change; cases added to the existing file). 2026-06-23 (RFC 0106 — real-time voice session profile, Phase 2) added one: `voice-event-payloads-shape.test.ts` (always-on server-free — all seven `voice.*` types are in the `RunEventType` enum, each has a payload `$def` via `typeIndex`, `voice.transcript` REQUIRES `contentTrust:"untrusted"` (the schema-enforced half of `voice-transcript-untrusted`), `voice.synthesis_chunk` is metadata-shaped (seq+mimeType required; bytes by `url`/`streamRef`), and the content-free events reject unknown properties). Phase 2 also lands the `voice.*` payload `$defs` in `run-event-payloads.schema.json` + the `asyncapi.yaml` messages + the four §F `SECURITY/invariants.yaml` rows (1 protocol-tier `voice-transcript-untrusted` + 3 reference-impl behavioral, graduating at `Active → Accepted`). The gated behavioral legs (`voice-transcription-streaming`/`-unadvertised`, `voice-synthesis-streaming` + the three behavioral invariant scenarios) land with the reference host at `Active → Accepted`. 2026-06-23 (RFC 0106 — real-time voice session profile, Phase 1) added one: `aiproviders-realtimevoice-shape.test.ts` (always-on server-free — the `aiProviders.realtimeVoice` object advertisement: the four sub-flags `transcription`/`synthesis`/`turnDetection`/`bargeIn` are declared, `realtimeVoice` is absent from `aiProviders.required`, the `turnDetection`/`bargeIn ⇒ transcription` `dependentRequired` closure and the `synthesis ⇒ speechSynthesis` if/then closure hold via Ajv2020, and out-of-enum / boolean sub-flag values are rejected). The gated behavioral legs (`voice-transcription-streaming` / `-unadvertised`, `voice-synthesis-streaming`) and the four §F live-ingress invariant scenarios land in Phase 2 at the `Active → Accepted` cycle. 2026-06-22 (RFC 0101 — multi-party group conversation) added two — `multi-party-conversation-shape.test.ts` (always-on server-free — asserts the three additive RFC 0101 wire facts: a 3-agent council `conversation.opened` participant roster (instance ids) + a user opening turn validates, every `role: 'agent'` turn carrying a roster-instance `speakerId` validates while one OMITTING `speakerId` MUST FAIL schema validation (the `allOf`/`if role==='agent' then required:['speakerId']` conditional), a non-AgentRef participant item is rejected, the `capabilities.multiPartyConversation { supported, maxParticipants? }` block is declared + closed and rejects extras / `maxParticipants:1` / a missing `supported`, and the non-participant membership predicate is asserted server-free) and `multi-party-conversation-behavioral.test.ts` (capability-gated on `multiPartyConversation.supported` via `behaviorGate('openwop-multi-party-conversation', …)` — drives the conformance-only seam `POST /v1/host/sample/conversation/multi-party/{open,exchange}` (`host-sample-test-seams.md`, since RFC 0101 mints no normative client trigger to open a council): a 3-agent council opens and a roster-valid attributed agent turn is accepted, while a `role:'agent'` turn missing `speakerId`, a non-participant `speakerId`, and an over-`maxParticipants` open are each rejected with `error.code:'validation_error'` (status-tolerant 400/422 per RFC 0005 §E); soft-skips on 404/405 — reference impl: the postgres example host). 2026-06-20 (RFC 0105 — speech synthesis adapter) added three: `aiproviders-speechsynth-shape.test.ts` (always-on server-free — the `aiProviders.speechSynthesis` `const "supported"` flag is declared, absent from `aiProviders.required`, and a string-const advertisement validates while the object form `{supported:true}` is rejected), `speech-synthesis-roundtrip.test.ts` (gated on `aiProviders.speechSynthesis === "supported"` via `behaviorGate('openwop-speech-synthesis', …)` — an advertising host's `callSpeechSynthesizer` round-trip via the `POST /v1/host/sample/ai/call-speech-synthesizer` seam returns `audio` with EXACTLY ONE of `url`/`base64`, a non-empty `mimeType`, and the echoed `voiceId`; soft-skips on 404), and `speech-synthesis-unadvertised.test.ts` (gated-by-absence via `behaviorGate('openwop-speech-synthesis-unadvertised', …)` — a host NOT advertising `speechSynthesis` MUST reject the call with `speech_synthesis_unsupported`, never a silent no-op). 2026-06-19 (RFC 0104 — portable HITL approver routing) added one: `interrupt-approver-routing.test.ts` (server-free Ajv2020 — the `interrupt.approverRouting` capability block shape, the additive optionality of `approverGroupRefs` / `approverRoleRefs` / `audience` on the `ApprovalData` schema, the closed `audience` object, and the §"Portable approver routing" `notifyTargets` reference rule that `audience` DEFAULTS to the resolved eligibility union when omitted and OVERRIDES it when present; the capability-gated leg asserts an advertising host's `interrupt.approverRouting` is honest — `refKinds` ⊆ {group, role}, `audience` boolean — and soft-skips when the host does not advertise the capability). 2026-06-17 (RFC 0103 — localized content surface) added one: `localized-content-delivery.test.ts` (server-free Ajv2020 — the four content schemas + the §C `resolveSection` merge + §A capability coherence; the public test for `content-published-cache-no-draft` / `content-response-tenant-scoped` / `content-no-cross-tenant-enumeration`; the live legs gate on `capabilities.content.supported` and soft-skip without a `GET /v1/content/pages/{slug}` target). 2026-06-15 (RFC 0102 — A2UI agent-authored interface surfaces) added five: `a2ui-surface-shape.test.ts` (server-free Ajv2020 — the closed core `ui.a2ui-surface` payload validates, while an out-of-catalog component / extra script-bearing property / unenumerated `catalogVersion` / `action.target` outside `enum["resume","exchange"]` each fail; the structural half of `a2ui-action-confinement` and the enabling precondition for the render-side `a2ui-surface-no-code-exec` / `a2ui-surface-no-network-egress` reference-app probes), `a2ui-surface-degrades.test.ts` (the kind is optional/advertised, not a MUST-recognize universal kind, and an unadvertised kind is gated — N6 — never crashing the run), `a2ui-surface-version-refusal.test.ts` (the enumerated `catalogVersion` rejects an unadvertised version → `unknown_schema_version`, and the surface schema carries no external `$ref`), `a2ui-surface-replay.test.ts` (all `$ref`s internal so a stored surface `:fork`/replays deterministically; same-`correlationId` + divergent `type` → `envelope_correlation_conflict`), and `a2ui-untrusted-blocks-approval.test.ts` (a `meta.contentTrust:'untrusted'` surface is trust-gated and MUST NOT advance an approval interrupt — composition of `untrusted_content_blocks_approval`). The four behavioral legs soft-skip on 404 (host-pending; the `openwop-app` reference renderer is the render-side probe). 2026-06-14 (RFCs 0099/0100 — external-event trigger ingestion + async/durable A2A tasks) added one new scenario (`trigger-ingestion.test.ts`) and extended `a2a-task-roundtrip.test.ts` with the RFC 0100 async subtests: `trigger-ingestion.test.ts` (RFC 0099 — always-on `TriggerEvent` / `TriggerSubscriptionRegistration` schema legs incl. the §F.1 per-source one-of, the `AttachmentRef.ref`-only rule + raw-URL rejection backing `trigger-ingestion-ssrf`, and the content-free `trigger.delivery.attempted` shape backing `trigger-ingestion-content-redaction`; plus a capability-gated behavioral leg on `triggerBridge.ingestion` driving the `POST /v1/host/sample/trigger-bridge/ingest` seam for SSRF refusal + header-redaction), and the `a2a-task-roundtrip.test.ts` additions (RFC 0100 — always-on `A2ATaskState` + `capabilities.a2a` shape legs incl. the lowercase-hyphen state enum, the `PushConfig` `url`-required + truncated-`tokenFingerprint` rule backing `a2a-push-egress-ssrf`, the no-inline-inputs `additionalProperties:false` SR-1 check; plus capability-gated durable-`tasks/get`-after-disconnect and push-SSRF behavioral legs on `a2a.durableTasks` / `a2a.pushNotifications` via the `/v1/host/sample/a2a/tasks/*` seam). 2026-06-13 (RFCs 0096/0097/0098 — reviewable learning, standing goals, agent-platform portability) added three always-on-plus-gated scenarios: `proposal-reviewable-learning.test.ts` (RFC 0096 — the `agents.proposals` shape + the `Proposal` round-trip incl. the dropped `rule` kind + the content-free `proposal.{created,activated}` events, plus a gated apply-without-scope→403 leg; backs `proposal-inert-until-applied` + `proposal-no-resynthesis`), `goal-standing-continuation.test.ts` (RFC 0097 — the `agents.goals` shape + the `Goal` round-trip + the content-free `goal.{evaluated,closed}` events, plus gated bounded-termination→422 + judge-only-completion legs; backs `goal-continuation-bounded` + `goal-completion-judge-only`), and `export-bundle-portability.test.ts` (RFC 0098 — the `portability` shape incl. the `import⇒dryRun` if/then + the `ExportBundle` round-trip rejecting every credential-named field + the content-free `import.applied` event, plus a gated literal-credential-import→422 leg; backs `export-bundle-no-credential-material`). 2026-06-11 (RFCs 0093/0094 — protocol hardening + wire-shape reconciliation) added five: `version-fold.test.ts` (the `version-negotiation.md` §`X-Force-Engine-Version` cross-version matrix through the previously-orphaned `conformance-version-fold` fixture — closes catalog gap F5; soft-skips when `Capabilities.testing.forceEngineVersionRange` is unadvertised), `stream-text-fixture.test.ts` (the `stream-modes.md` §`messages` fold through the deterministic `stream-text` mock provider + the previously-orphaned `conformance-stream-text` fixture — closes catalog gap F1), `i18n-negotiation.test.ts` (gated on `capabilities.i18n` via `behaviorGate('openwop-i18n', …)` — an unsupported or malformed `Accept-Language` never 400s, `Content-Language` reflects the locale actually used, and error `code` strings stay the canonical English tokens), `grpc-transport.test.ts` (gated on `capabilities.grpc` via `behaviorGate('openwop-grpc-transport', …)` — advertisement-shape only per `grpc-transport.md` §Field semantics: `service` MUST be `openwop.v1.Engine`, the `tls` enum, `grpcs?://` endpoint URIs, `supportedTransports` includes `grpc` when exposed, production claimants require `tls: "required"`; no gRPC dialing), and `webhook-tenant-isolation.test.ts` (RFC 0093 §A.3 — backs the new protocol-tier `webhook-cross-tenant-isolation` invariant; a two-tenant proof through the `/v1/host/sample/test/surface` seam plus black-box registration-surface scoping). `spec-corpus-validity.test.ts` also gained the RFC 0094 §A satisfiability probe: canonical `createRun` bodies MUST pass the composed request schema (closed via `unevaluatedProperties: false` at the composition site, never inside an `allOf` branch) and an undeclared property MUST fail. 2026-06-07 (RFCs 0090/0091/0092 — verifier turn + convergence, multimodal perception input, agent capability requirements) added six: the always-on, server-free shape probes `agent-verifier-shape.test.ts`, `aiproviders-input-shape.test.ts`, `agent-requires-capabilities-shape.test.ts`, plus the capability-gated **behavioral** legs `agent-capability-degraded-projection.test.ts` (RFC 0092 §B — the `degraded[]` projection on `GET /v1/agents`, black-box, non-vacuous via `OPENWOP_DEGRADED_CAPABILITY_AGENT_ID`), `callai-multimodal.test.ts` (RFC 0091 §A/§B — advertised modality accepted / unadvertised → `unsupported_modality`, via the `POST /v1/host/sample/ai/call` seam), and `verifier-gating.test.ts` (RFC 0090 §B — a `fail` verdict blocks commit, via the `POST /v1/host/sample/agents/verify-run` seam). The three behavioral legs soft-skip by default and hard-fail under `OPENWOP_REQUIRE_BEHAVIOR=true` — the Active→Accepted reference-host proof for each RFC. 2026-06-02 (RFC 0082 §B — deployment channel resolve-and-pin, production-path coverage) added `agent-channel-dispatch.test.ts` (capability-gated on `agents.deployment.supported` + the seeded `conformance-agent-channel-dispatch` fixture + advertised `replay` mode via `behaviorGate('openwop-deployment-channel-dispatch', …)` — proves the §B pin from a REAL run graph, complementing `agent-deployment-lifecycle.test.ts` Leg 4's host-sample seam: a canonical `POST /v1/runs` of a node binding `agent.channel:"stable"` MUST record `resolvedChannel` + `resolvedAgentVersion` on `agent.invocation.started` (RFC 0077), a `:fork{mode:"replay"}` MUST re-read that recorded version, and the seam-guarded Leg 3 MOVES the channel then asserts a replay STILL carries the original pin — never re-resolving a moved channel; soft-skips by default, hard-fails under `OPENWOP_REQUIRE_BEHAVIOR=true` — the production-path proof of the §B contract). 2026-06-01 (RFC 0085 — `openwop-agent-platform` meta-profile, the Active→Accepted behavioral gate) added `agent-platform-aggregate-evidence.test.ts` (capability-gated on a host CLAIMING `openwop-agent-platform` in its live discovery `profiles[]` via `behaviorGate('openwop-agent-platform', …)` — the §C/§D honest-advertisement rule on the live `/.well-known/openwop`: the claim MUST satisfy the §B floor predicate (`isAgentPlatformPartial` → `partial`/`full`, never `none`), backed by the per-capability evidence not the profile string; `OPENWOP_AGENT_PLATFORM_TIER=full` forces the non-vacuous full bar — all governance terms + tenant installScope + all 16 §D terms; server-requiring, the always-on §B/§D derivation legs stay in `agent-platform-profile.test.ts` — the RFC 0085 → Accepted bar). 2026-06-01 (RFC 0084 — budget, quota + cost policy, the Active→Accepted behavioral gate) added `budget-enforcement.test.ts` (capability-gated on `budget.supported` via `behaviorGate('openwop-budget-enforcement', …)` — the §C/§D enforcement via the new `POST /v1/host/sample/budget/run` seam + the test event-log seam: a `hard-cost-exhaust` run emits the strict-ordered `budget.reserved → budget.consumed → budget.threshold.crossed{percent} → budget.exhausted → cap.breached{kind:"budget-cost"} → run.failed{error:"budget_exhausted"}` chain; a `model-denied` run is refused `budget_model_denied` BEFORE the provider call (fail-closed); an `advisory` host emits the `budget.*` events without stopping; every `budget.*` payload content-free backing `budget-no-pricing-leak`; new lib helper `src/lib/budgetPolicy.ts`; soft-skips on 404 — the RFC 0084 → Accepted bar). 2026-06-01 (RFC 0080 — agent memory capability reconciliation, the Active→Accepted behavioral gate) added `memory-degraded-projection.test.ts` (capability-gated on `agents.manifestRuntime.supported` + `memory.supported` via `behaviorGate('openwop-memory-degraded', …)` — the §C degraded-projection iff-contract on the NORMATIVE `GET /v1/agents`: a degraded inventory entry MUST carry `memoryDegraded:true` + a non-empty, unique `degradedMemoryDimensions[]` from the closed §A-name enum, a non-degraded entry MUST NOT, the inventory is non-empty, and the degraded branch runs non-vacuously when `OPENWOP_DEGRADED_AGENT_ID` names a known-degraded agent; black-box, no POST seam — the RFC 0080 → Accepted bar). This batch also documents the two RFC 0068 conformance seams (`POST /v1/host/sample/memory/consolidate` + `.../commitment/fire`) in `host-sample-test-seams.md` (the 0068 gated scenarios shipped in 1.14.0). 2026-06-01 (RFC 0034 — collector-side BYOK-canary inspection) added `otel-collector-canary-inspection.test.ts` (always-on server-free: stands up a real `OtelCollector`, POSTs synthetic OTLP/HTTP-JSON traces + metrics through its actual ingest path, and proves the new `findCanaryLeakage()` inspector catches a canary embedded in a span attribute / resource attribute / span name / metric data-point attribute while reporting ZERO hits on a redacted payload and never matching an empty canary — the non-vacuous proof that the conformance collector now inspects what the host's OTLP exporter ACTUALLY shipped over the wire, closing the `secret-leakage-otel-attribute` / `-debug-bundle-otel` collector-seam gap; the live capability-gated complement is the new collector-export describe block in `secret-leakage-otel-attribute.test.ts`). 2026-06-01 (RFC 0035 — sandbox wall-clock timeout, the 7th-of-8 graduation) added `sandbox-wasm-timeout.test.ts` (worker-driven server-free: `probeTimeout` in `wasm-sandbox-probe.ts` spawns a worker thread running the committed `misbehaving-timeout.wasm` + a main-thread kill-timer — the thread preemption a same-thread probe can't do — asserting `sandbox_timeout` with a well-behaved positive control; graduates `node-pack-sandbox-timeout` reference-impl→protocol, so 7 of 8 `node-pack-sandbox-*` invariants are now protocol-tier, only the JS-specific `no-eval` permanently exempt). 2026-05-31 (audit-response black-box / graduation batch) added three more: `sandbox-wasm-isolation.test.ts` (RFC 0035 — drives the committed `fixtures/wasm-sandbox/*.wasm` through `wasm-sandbox-probe.ts`: escape/capability-gate via static `WebAssembly.Module.imports()`, an OOB-store memory trap, double-instantiate isolation; 10/10; graduates 6 `node-pack-sandbox-*` invariants reference-impl→protocol), `workspace-cross-tenant-isolation-blackbox.test.ts` (RFC 0059 — two-credential black-box on the normative §C `/v1/host/workspace/files` endpoints: owner A writes, a second-tenant credential fails closed; no seam), and `prompt-resolution-chain-event.test.ts` (RFC 0029 — reads the durable `agent.promptResolved.chain[]` precedence record via the normative `GET /v1/runs/{runId}/events/poll`; no seam) — each the production-path proof that graduates its surface into the `openwop-core-standard` floor. 2026-05-31 (RFC 0088 — the `openwop-core-standard` Core Standard Profile, the audit-response Core Candidate target) added `core-standard-profile.test.ts` (always-on server-free derivation probe: `isCoreStandard` derives the §B floor — `openwop-core` ∧ `openwop-interrupts` ∧ (`openwop-stream-sse` ∨ `openwop-stream-poll`) — a bare `openwop-core` host without interrupts is excluded, a host with no event transport fails, and the annex is absent from `deriveProfiles` because it composes rather than redefines). 2026-05-31 (RFC 0082 — agent deployment lifecycle, the Active→Accepted behavioral gate) added `agent-deployment-lifecycle.test.ts` (capability-gated on `agents.deployment.supported` via `behaviorGate('openwop-deployment-lifecycle', …)` — the §E promotion contract via the new `POST /v1/host/sample/agents/deployment-transition` seam + the test event-log seam across four legs: `promote` (authorize RFC 0049 → approvalGate RFC 0051 → eval-verify RFC 0081 → content-free `deployment.promoted` with a seven-state `toState` + `toVersion`, the record validating `agent-deployment.schema.json`), `unauthorized` (fail-closed — `allowed:false`, no `deployment.promoted`, the behavioral leg of `deployment-promotion-fail-closed`), `eval-gate-unmet` (`eval_gate_unmet` denial, §E-3), and `channel-pin` (the §B `resolvedAgentVersion` recorded-fact on `agent.invocation.started`); new lib helper `src/lib/agentDeployment.ts`; soft-skips on 404 — the RFC 0082 → Accepted bar). 2026-05-31 (RFC 0081 — agent evaluation, the Active→Accepted behavioral gate) added `agent-eval-run.test.ts` (capability-gated on `agents.evalSuite.supported` via `behaviorGate('openwop-eval-run', …)` — the §B `mode:"eval"` projection via the new `POST /v1/host/sample/agents/eval-run` seam + the test event-log seam: `eval.started`-first → one `eval.scored` per task → `eval.completed`-once ordering (count == `eval.completed.taskCount`), the content-free `eval.scored` legs (`score` ∈ 0..1) backing `eval-summary-no-content-leak`, and the NORMATIVE `GET /v1/runs/{runId}/eval-summary` schema-valid `EvalSummary` round-trip with `passedCount <= taskCount`; new lib helper `src/lib/agentEval.ts`; soft-skips on 404 — the RFC 0081 → Accepted bar). 2026-05-31 (RFC 0083 — durable trigger bridge, the Active→Accepted behavioral gate) added `trigger-bridge-delivery.test.ts` (profile-gated on `openwop-trigger-bridge` derived from the live discovery doc — the §C delivery model via the `POST /v1/host/sample/trigger-bridge/deliver` seam + the test event-log seam: dedup→effectively-once `trigger.delivery.attempted{delivered}` (§C-1), retry-exhaustion→`{dead-lettered}` + `trigger.subscription.state.changed{toState:dead-lettered}` (§C-2 + RFC 0053), and the delivered run's `run.started.causationId` == the delivery id (§C / RFC 0040); both `trigger.*` events content-free; the always-on shape stays in `trigger-bridge-shape.test.ts`; new lib helper `src/lib/triggerBridge.ts`). 2026-05-31 (RFC 0087 — agent org-chart, the Active→Accepted behavioral gate) added two capability-gated behavioral scenarios (both gated on `agents.orgChart.supported`, black-box on the normative `/v1/agents/org-chart` surface — no new POST seam): `agent-org-chart-scoping.test.ts` (the `GET /v1/agents/org-chart` tree-shape — departments form an acyclic `parentDepartmentId` tree, members reference `host:<id>` roster entries — + the §D responsibility roll-up via `GET /v1/agents/org-chart/{departmentId}` with a deduped `responsibilities[]` union + the RFC 0074 cross-tenant 404 via `OPENWOP_CROSS_TENANT_ORG_CHART_DEPARTMENT_ID`) and `org-position-no-authority-escalation.test.ts` (the behavioral leg of the protocol-tier invariant — the live org-chart wire carries NO authority-bearing field on any member/department/responsibility-view object; the structural leg stays always-on in `agent-org-chart-shape.test.ts`, and the deeper RFC 0049/0051 authority-invariance legs stay reference-impl tier per the `agent-manifest-runtime` no-host-hook precedent). 2026-05-31 (RFCs 0086 + 0077 — the Active→Accepted behavioral gate) added four capability-gated behavioral scenarios so a non-steward host can be mechanically certified non-vacuously under `OPENWOP_REQUIRE_BEHAVIOR=true`: `agent-roster-attribution.test.ts` (RFC 0086 §B/§C; gated on `agents.roster.supported` — the normative `GET /v1/agents/roster` read shape + `total==roster.length`, the §C `roster.run.initiated`-before-`agent.invocation.started` ordering, the content-free payload backing `roster-attribution-no-content`, the durable work-item `triggerSubscriptionId`, and the RFC 0074 cross-tenant 404 via `OPENWOP_CROSS_TENANT_ROSTER_ID`), `agent-live-invocation-bracket.test.ts` (RFC 0077 §E; gated on `agents.liveRuntime.supported` — `agent.invocation.started`-first / `agent.invocation.completed`-last bracket, matching `invocationId`, `source`/`outcome` closed enums, content-free), `agent-live-structured-output.test.ts` (RFC 0077 §B step 6; gated on `agents.liveRuntime.structuredOutput` — a result violating `handoff.returnSchemaRef` fails the invocation `outcome:"failed"` rather than shipping as completed), and `agent-live-allowlist-enforced.test.ts` (RFC 0077 §F-1 / RFC 0002 §A14; gated on `agents.liveRuntime.supported` — a tool outside `toolAllowlist` is not callable); all four drive the documented `POST /v1/host/sample/roster/fire` + `POST /v1/host/sample/agents/live-invoke` seams plus the test event-log seam and soft-skip on 404 (these are the RFC 0086 / 0077 Active→Accepted bars). 2026-05-30 (RFC 0087 — agent org-chart, Draft -> Active) added `agent-org-chart-shape.test.ts` (always-on server-free: the `capabilities.agents.orgChart` shape + the `AgentOrgChart` round-trip + the non-`host:` member negative + the **§B structural non-authority guarantee** — the schema rejects a `scopes`/`canDispatch`/`permissions`/`authority` field on a member (`additionalProperties:false`), and a member's key set is exactly `{rosterId, departmentId, roleId, reportsTo}` — backing the protocol-tier `org-position-no-authority-escalation` invariant; no new RunEventType). 2026-05-30 (RFC 0086 — standing agent roster, Draft -> Active) added `agent-roster-shape.test.ts` (always-on server-free: the `capabilities.agents.roster` shape + the `AgentRosterEntry` round-trip + the `host:` `rosterId` + `agentRef` version-XOR-channel negatives + the content-free `roster.run.initiated` negatives backing the protocol-tier `roster-attribution-no-content` invariant + the additive `roster` inventory projection + RunEventType-enum membership). 2026-05-30 (RFC 0082 — agent deployment lifecycle, Draft -> Active) added `agent-deployment-shape.test.ts` (always-on server-free: the `capabilities.agents.deployment` shape + the `AgentDeployment` record round-trip + the `AgentRef` `channel` XOR `version` `not`-clause + the four `deployment.*` payloads + the content-free negatives backing the protocol-tier `deployment-event-no-content-leak` invariant). 2026-05-30 (RFC 0085 — `openwop-agent-platform` meta-profile, Draft -> Active) added `agent-platform-profile.test.ts` (always-on server-free derivation of the operational-annex `none`/`partial`/`full` status: all-floor ⇒ partial, missing-flag ⇒ none, the replay-OR-`nondeterminismPolicy.declared` term, floor+governance ⇒ full, missing-tenant-scope ⇒ partial-not-full per the honest-advertisement rule, eval/deploy/budget-are-advisory-not-hard-terms, + the `capabilities.nondeterminismPolicy.declared` shape). 2026-05-30 (RFC 0084 — budget, quota + cost policy, Draft -> Active) added `budget-policy-shape.test.ts` (always-on server-free: `budget-policy.schema.json` round-trip + the §A orthogonality guard — a wall-time field is rejected (it's RFC 0058's `runTimeoutMs`) — + threshold/onExhaustion negatives + the four content-free `budget.{reserved,consumed,threshold.crossed,exhausted}` payloads + the four `cap.breached{budget-*}` kinds + RunEventType-enum membership + the no-pricing-property structural check backing the protocol-tier `budget-no-pricing-leak` invariant + the `capabilities.budget`/`limits.maxBudget*` shape). 2026-05-30 (RFC 0083 — durable trigger + channel bridge, Draft -> Active) added `trigger-bridge-shape.test.ts` (always-on server-free: `trigger-subscription.schema.json` round-trip + missing-`state`/out-of-enum-`source`/unknown-property negatives + the four-state vocab + the two content-free `trigger.{subscription.state.changed,delivery.attempted}` payloads incl. closed `state`/`outcome` enums + RunEventType-enum membership + the `triggerBridge`/`webhooks.durable` capability shape + the `openwop-trigger-bridge` profile derivation incl. the no-dead-letter-sink negative). 2026-05-30 (RFC 0079 — credential provenance + egress policy, Draft -> Active) added `egress-provenance-shape.test.ts` (always-on server-free: `credential-provenance.schema.json` round-trip + `audiences:[]`/missing-`credentialId`/unknown-property negatives + the no-secret-property structural check backing the protocol-tier `egress-decision-no-secret-leak` invariant + the content-free `egress.decided` record incl. the `decision` enum + RunEventType-enum membership + the `httpClient.egressPolicy` shape; the behavioral `egress-credential-audience-bound` confused-deputy MUST is reference-impl tier, deferred to a host). 2026-05-30 (RFC 0078 — portable tool catalog, Draft -> Active) added `tool-descriptor-shape.test.ts` (always-on server-free: `tool-descriptor.schema.json` round-trip + the §C-1 `exec` ⇒ `host-extension` cross-field MUST (RFC 0069) + the `safetyTier`-required negative + `additionalProperties:false`, the `capabilities.toolCatalog` `supported`/`sources`/`sessionLifecycle` shape, and the two content-free `tool.session.{opened,closed}` payload $defs incl. the closed `outcome` enum + RunEventType-enum membership). 2026-05-30 (RFC 0080 — agent memory capability reconciliation, Draft -> Active) added `memory-capability-model-shape.test.ts` (always-on server-free: the additive `capabilities.memory.{writable,search,retention}` dimension shapes + malformed-instance negatives — `retention.ttl` non-boolean, out-of-enum `search.modes`, unknown property under `additionalProperties:false` — the `agent-inventory-response` `memoryDegraded`/`degradedMemoryDimensions` closed-enum fields, and the `openwop-memory` derivation surfacing for read/write + long-term hosts while withholding from `writable:false`). 2026-05-30 (RFC 0081 — agent evaluation, Draft -> Active) added `agent-eval-suite-shape.test.ts` (always-on server-free: the `capabilities.agents.evalSuite` shape + the `AgentEvalSuite`/`EvalSummary` schema round-trips + the three `eval.{started,scored,completed}` payloads + the content-free negatives — a task entry with a `taskOutput` body, a `safetyFinding` with an `excerpt` — backing the new `eval-summary-no-content-leak` SECURITY invariant). 2026-05-29 (RFC 0076 §B — `ctx.http.safeFetch` live-run audit) added `safefetch-live-audit.test.ts` (`behaviorGate('openwop-safefetch-live-audit', …)`, gated on `httpClient.safeFetch` + `toolHooks.prePostEvents`) — asserts the audit-when-both MUST against the **durable run event log** via the new `POST /v1/host/sample/http/safe-fetch-run` open seam + the test event-log seam, closing the seam-vs-production gap (a production `createSafeFetch()` with no audit hooks passes the inline `safefetch-behavior.test.ts` but FAILS this under `OPENWOP_REQUIRE_BEHAVIOR=true`); this is the RFC 0076 §B → Accepted bar; run seam soft-skips on 404 (host-pending). 2026-05-29 (RFC 0066 — `x-openwop-form` picker UX hints, Draft → Active) added `x-openwop-form-pack-manifest.test.ts` (always-on server-free: an annotated `configSchema` stays a valid 2020-12 schema + the advisory hints don't change what it accepts, each §A annotation matches the shape, an unknown `kind` validates for forward-compat, 3 negatives — missing/non-string `kind`, non-string `dependsOn`). 2026-05-29 (RFC 0076 §B — `ctx.http.safeFetch`) added `safefetch-behavior.test.ts` (seam-gated: SSRF block / DNS-rebinding / `Connection: upgrade` refusal / tool-hooks audit-when-both, via `POST /v1/host/sample/http/safe-fetch`; advertisement contract stays in `http-client-ssrf.test.ts`). 2026-05-29 (RFC 0076 §A — pack `runtime.requires[]` install gate) added two: `runtime-requires-shape.test.ts` (server-free closed-vocabulary validation — the 8 tokens validate, a raw builtin name is rejected, empty-array≡omission, `uniqueItems`) + `runtime-requires-install-gate.test.ts` (seam-gated install-grant / install-refuse → `pack_runtime_requirement_unmet` / non-sandbox SHOULD-projection, soft-skip on 404 via `POST /v1/host/sample/packs/install-gate`). 2026-05-29 (RFC 0047 — `host.oauth` authorization-code roundtrip) added `oauth-authorization-code-roundtrip.test.ts` — capability-gated on `capabilities.oauth.supported` + `grants` including `authorization_code`; drives the `POST /v1/host/sample/oauth/authorize-code-roundtrip` seam against the one canonical synthetic provider in `fixtures/oauth-providers/synthetic.json` (soft-skip on 404, Tier-2 host-pending), asserting a successful grant returns a credential REFERENCE (token persisted as a `host.credentials` entry) and that the authorization code / state / PKCE verifier / acquired access+refresh tokens never appear on any run-visible surface (RFC 0047 §C + §C.2 / `credential-payload-redaction`). Closes the RFC 0047 Tier-2 gap (capability-shape + redaction scenarios existed; the actual authorization-code dance was unexercised). 2026-05-26 (RFC 0070 — agent-manifest runtime) added `agent-manifest-runtime.test.ts`; 2026-05-26 (RFC 0071 — artifact-type + chat card packs) added six: `artifact-type-pack-manifest-validation.test.ts` + `artifact-schema-compile-bounded.test.ts` (server-free) + `artifact-type-pack-install.test.ts` + `artifact-type-store-without-render.test.ts` + `chat-card-pack-manifest-validation.test.ts` (server-free) + `chat-card-pack-execution.test.ts` (capability-gated, host-pending). 2026-05-26 (RFCs 0067 / 0068 / 0069 — spec-gap Draft cohort) added five scenarios: `byok-auth-modes.test.ts` (RFC 0067; always-on schema-shape of `aiProviders.authModes` + a discovery-gated §B auth-mode-contract cross-field check), `memory-consolidation-shape.test.ts` (RFC 0068; always-on shape of `agents.memoryConsolidation`/`agents.commitments` + the `agent.memory.consolidated`/`commitment.fired` payload $defs), `memory-consolidation-idempotent.test.ts` + `commitment-fired.test.ts` (RFC 0068; capability-gated behavioral, soft-skip on the documented `/v1/host/sample/memory/consolidate` + `/commitment/fire` seams), and `exec-not-protocol-tier.test.ts` (RFC 0069; always-on server-free structural assertion that the protocol corpus defines no `core.*`/`openwop.*` exec-class primitive — backs the `exec-must-not-be-protocol-tier` SECURITY invariant). 2026-05-25 (RFC 0061 — stateful agent-loop lifecycle, executionModel.version 5) added four `agent-loop-*.test.ts` scenarios: `-version5-shape` (always-on; validates `executionModel.statefulResume`/`transcriptWindow` + the 1–5 version ceiling) plus `-iteration-monotonic` (gated on `version >= 5`; `runOrchestrator.decided.iteration` increments 1,2,3… exactly once per turn), `-workspace-snapshot` (gated additionally on `host.workspace.supported`; a turn-i workspace write is invisible to turn i, visible to turn i+1), and `-stateful-resume` (gated on `statefulResume`; a mid-loop suspend resumes at the same iteration without resetting the counter) — the three behavioral scenarios drive the documented agent-loop seam (`POST /v1/host/sample/agentloop/run`) and soft-skip until a host wires it. 2026-05-25 (RFC 0059 — host.workspace M2, reference-host enforcement) added two `workspace-*.test.ts` scenarios: `-behavior` (capability-gated CRUD round-trip / `If-Match` 409 `workspace_conflict` / `workspace_too_large` / §D run-start snapshot, all via the real `/v1/host/workspace/files` §C endpoints) and `-cross-tenant-isolation` (WCT-1 — drives the documented `POST /v1/host/sample/workspace/op` seam to assert a file owned by one `{tenant, workspace}` is unreadable, on both `get` and `list`, under a different owner; backs the new `workspace-cross-tenant-isolation` SECURITY invariant). The in-memory reference host now advertises `capabilities.workspace.supported` and honors §C/§D/§E end-to-end. 2026-05-25 (RFC 0062 — memory.distillation "dreams") added five `distillation-*.test.ts` scenarios: `-shape` (always-on; validates the `capabilities.memory.distillation` block + the additive `distillation` sub-object on `memory.compacted`) plus `-token-budget` (within budget `tokensUsed ≤ tokenBudget`; an un-meetable budget → `token_budget_exceeded` with no partial archive), `-stable-archive` (same sources + budget ⇒ byte-stable archive checksum), `-index-roundtrip` (gated additionally on `indexEmitted`; the `MEMORY-INDEX.json` workspace file is retrievable + `workspace.updated` fired), and `-secret-carryforward` (SR-1: a redacted source secret never appears in the archive) — the four behavioral scenarios drive the documented memory-distillation seam (`POST /v1/host/sample/memory/distill`) and soft-skip until a host wires it. 2026-05-25 (RFC 0063 — core.subWorkflow.outputAttestation) added four `subrun-*.test.ts` scenarios: `-attestation-shape` (always-on; validates the `capabilities.agents.subRunAttestation` flag) plus `-checksum-stable` (the child output checksum is the byte-stable, key-order-invariant RFC 8785 JCS + SHA-256 digest), `-approval-gate` (`requireApproval` → `accept` merges, `reject` does not), and `-approval-fail-closed` (no `accept`/`edit-accept` → no merge; backs the deferred `subrun-merge-approval-fail-closed` invariant) — the three behavioral scenarios drive the documented sub-run attestation seam (`POST /v1/host/sample/subrun/attest`) and soft-skip until a host wires it. 2026-05-25 (RFC 0064 — host.toolHooks) added five `tool-hooks-*.test.ts` scenarios: `-shape` (always-on; validates the `capabilities.toolHooks` block + the optional content-free fields on `agentToolCalled` / `agentToolReturned`) plus `-content-free` (gated on `prePostEvents`), `-authorization-fail-closed` (gated on `perToolAuthorization`), `-rate-limit` (gated on `perToolRateLimit`), and `-secret-redaction` (gated on `prePostEvents` + the SR-1 `argsHash` redaction rule) — the four behavioral scenarios drive the documented tool-hooks invoke seam (`POST /v1/host/sample/toolhooks/invoke`) and soft-skip until a host wires it. 2026-05-25 (RFC 0060 — host.heartbeat) added four `heartbeat-*.test.ts` scenarios: `-capability-shape` (always-on; validates the `capabilities.heartbeat` block) plus `-fires-once-per-tick`, `-idempotent-no-spam`, and `-runtime-bound` (gated on `capabilities.heartbeat.supported` + the host heartbeat tick seam; soft-skip until a host wires it). 2026-05-25 (RFC 0057 — memory write-attribution) added five `memory-attribution-*.test.ts` scenarios: `-shape` (always-on advertisement check on `capabilities.memory.attribution`), plus `-no-content`, `-tenant-scoped`, `-emits-on-write`, and `-replay-stable` (gated on `capabilities.memory.attribution.emitsWriteEvents`) verifying the content-free `memory.written` RunEvent, its two SECURITY invariants (`memory-attribution-no-content` + `memory-attribution-tenant-scoped`), and the §D replay rule that a `replay`-mode fork MUST NOT regenerate `memoryId`. 2026-05-25 (RFC 0025 §C point 1 — test-catalog isolation invariant; pairs with the 25 publish-error scenarios in `pack-registry-publish.test.ts`) added `pack-registry-isolation.test.ts` — capability-gated on `capabilities.packs.testMode.{supported, isolated}: true`; PUTs a disposable pack into `/v1/packs-test/{name}` and asserts the same `(name, version)` does NOT appear via `GET /v1/packs/{name}` — anchors the test-catalog isolation MUST in RFC 0025 §C. 2026-05-25 (RFC 0028 Tier-2 post-promotion T2 — read-side sister scenario for workspace-membership enforcement) added `prompt-read-workspace-membership-enforced.test.ts` — gates on `capabilities.prompts.supported: true` (broader than `mutableLibrary` so read-only hosts that expose `?workspaceId=` are also probed); drives `GET /v1/prompts?workspaceId=<random-non-member>` and interprets the response: 4xx PASS (canonical envelope check on 403); 200 with empty `templates[]` PASS (correct null result for a nonexistent workspace); 200 with non-empty `templates[]` FAIL (cross-tenant leak); 200 without `templates[]` field SKIP (host doesn't expose workspace-scoped reads). Verifies SECURITY invariant `prompt-read-workspace-membership-enforced`. Same-day T1 strengthened `prompt-mutation-workspace-membership-enforced.test.ts` to pin `error === "workspace_membership_required"` when the host's refusal status is 403 (other refusal codes unconstrained). 2026-05-25 (RFC 0028 Tier-2 follow-up — workspace-membership enforcement on mutating prompt endpoints, filed in response to a self-disclosed adopter vulnerability) added `prompt-mutation-workspace-membership-enforced.test.ts` — capability-gated on `capabilities.prompts.mutableLibrary: true`; drives `POST /v1/prompts` with a cryptographically-random non-member `workspaceId` and asserts the host refuses (NOT a 2xx; any 4xx/5xx is acceptable — silent success is the failure mode). Verifies SECURITY invariant `prompt-mutation-workspace-membership-enforced`. 2026-05-22 (RFC 0034 §B follow-up — secret-leakage harness against the OTel + debug-bundle seams) added `secret-leakage-otel-attribute.test.ts` — gates on `capabilities.secrets.supported` + `capabilities.observability.testSeams.{otelScrape,debugBundleExport}` AND the `OPENWOP_CANARY_SECRET_VALUE` env (host operator + conformance runner agree on the canary). Drives the existing `openwop-smoke-byok-roundtrip` fixture end-to-end; scrapes both seams after run completion; hard-fails if the canary plaintext appears in any OTel span attribute or debug-bundle field. Verifies SECURITY invariants `secret-leakage-otel-attribute` + `secret-leakage-debug-bundle-otel`. 2026-05-22 (RFC 0041 Phase 4 — replay determinism under nondeterministic models) added three scenarios: `replay-divergence-at-refusal.test.ts` (advertisement-shape probe on `replayDeterminism.refusalDivergenceEmission` + 2 `it.todo` for the dual-direction refusal-divergence case), `replay-observable-sequence-determinism.test.ts` (capability-gated; behavioral assertion soft-skipped until a `conformance-phase4-nondet-tool` fixture ships), `replay-llm-cache-key-portable.test.ts` (intra-host reproducibility + non-recipe-field invariance + Phase 4 advertisement alignment — reuses the existing `POST /v1/host/sample/test/llm-cache-key` seam from the sibling `replay-llm-cache-key.test.ts`). 2026-05-20 (RFC 0027 §A templateKinds-coverage follow-up — paired with `prompt-end-to-end-events.test.ts`) added `prompt-all-four-kinds-events.test.ts` exercising all four `PromptKind` values (`system`, `user`, `schema-hint`, `few-shot`) end-to-end through the reference workflow-engine sample's `local.sample.demo.mock-ai` dispatch path; capability-gated via `behaviorGate('prompts-supported', ...)`. Closes the credibility gap where the host advertised `templateKinds: ["system", "user", "few-shot", "schema-hint"]` but only the system+user pair was actually wired into dispatch. 2026-05-20 (RFCs 0030–0033 — envelope LLM-contract-hardening track) added 15 scenarios across four `Active` RFCs: `envelope-reasoning-shape.test.ts` (RFC 0030, always-on; asserts the OPTIONAL `reasoning` property on the three universal-kind schemas + the `schema.response` deliberate omission), `envelope-reasoning-secret-redaction.test.ts` (RFC 0030, capability-gated on `capabilities.envelopes.reasoning.supported` + `secrets.supported`; 5 `it.todo()` placeholders for SECURITY invariant `envelope-reasoning-secret-redaction`), `envelope-tier-one-subset-static.test.ts` (RFC 0030, always-on for load-bearing rules — no `oneOf` / `allOf` / `not` / `prefixItems` / `propertyNames` anywhere; gated on `tierOneSubsetCompliance: "strict"` for OpenAI-strict-only constraints), `envelope-variant-discriminator-static.test.ts` (RFC 0031, always-on; asserts no `oneOf` + every `anyOf` branch declares a single-string-enum discriminator in `required` on every `schemas/envelopes/*.schema.json`), `model-capability-substituted.test.ts` (RFC 0031, advertisement-shape probe on `capabilities.modelCapabilities.advertised[]` identifier pattern + 5 `it.todo()` placeholders for SECURITY invariant `model-capability-substituted-no-credential-disclosure`), `model-capability-insufficient.test.ts` (RFC 0031, 6 `it.todo()` placeholders for refusal + no-recursive-fallback), `node-module-required-capabilities-shape.test.ts` (RFC 0031 SHOULD-tier authoring-convention; 4 `it.todo()` placeholders), and the six envelope-reliability events from RFC 0032 (`envelope-retry-attempted` carrying the shared advertisement-shape probe enforcing both MUST-tier events in `events[]` per RFC 0032 §C, plus `envelope-retry-exhausted`, `envelope-refusal-shape`, `envelope-truncated`, `envelope-nl-to-format-engaged`, `envelope-recovery-applied` — collectively 39 `it.todo()` placeholders covering retry/refusal/truncation/recovery + SECURITY invariants `envelope-refusal-no-prompt-leak` and `envelope-recovery-no-content-leak`), plus RFC 0033's two scenarios (`envelope-completion-distinguishes-truncation.test.ts` + `envelope-truncation-cap-exhaustion.test.ts` — 12 `it.todo()` placeholders covering the truncation-vs-schema-violation retry-routing distinction + the DoS-bound assertion). Reference workflow-engine sample advertises `capabilities.envelopes.reasoning: { supported: true, promptDirective: "off" }` + `tierOneSubsetCompliance: "warn"` honestly (schemas accept the field; host doesn't yet inject the directive); the other three RFCs' capability blocks defer to reference-host emission code per the staged RFC 0027 §G precedent. 2026-05-20 (RFC 0028 §B Phase B — prompt-pack boot-time install) added `prompt-pack-install.test.ts` (capability-gated on `capabilities.prompts.endpointsSupported: true`; asserts a host that ran the boot-time pack loader surfaces ≥ 1 pack-source template under `GET /v1/prompts?source=pack` carrying the canonical `meta.source: "pack"` + `meta.packName` + `meta.packVersion` stamps; positively identifies the in-tree `vendor.openwop.prompt-sample` reference pack's `writer-system` template when present). Pairs with the new `host/promptPackLoader.ts` boot-time entry on the reference workflow-engine sample, which scans `examples/packs/*` plus `OPENWOP_PROMPT_PACKS_DIR` and calls `installPackTemplates()` for each `kind: "prompt"` pack found. 2026-05-20 (RFC 0029 Phase C — prompt resolution chain wire shape) added three more scenarios: `prompt-resolution-chain-node-wins.test.ts` (capability-gated on `capabilities.prompts.supported: true`; asserts layer-1 node-config supersedes lower layers per `spec/v1/prompts.md` §"Resolution chain (normative)"), `prompt-resolution-chain-agent-intrinsic.test.ts` (additionally gated on `capabilities.prompts.agentBindings: true`; asserts agent intrinsic `systemPromptRef` wins over `promptOverrides` AND lower layers when the node has no layer-1 ref), `prompt-resolution-chain-fallback-cascade.test.ts` (asserts layer 3 workflow-defaults wins over layer 4 host-defaults; layer 4 host-defaults wins when 1-3 yield null; resolved is null when all four yield null but chain[] still lists every attempted layer). The scenarios drive the host's `POST /v1/host/sample/prompt/resolve` test seam (reference-host implementation deferred to follow-up slice per RFC 0021 staging precedent). 2026-05-20 (RFC 0027 Phase A — prompt templates wire shape) added three scenarios: `prompt-template-shape.test.ts` (always-on; Ajv compileability + positive/negative round-trip for PromptTemplate + PromptRef + PromptKind), `prompt-composed-secret-redaction.test.ts` (capability-gated on `capabilities.prompts.supported: true` + `observability: "full"`; asserts `[REDACTED:<secretId>]` markers in `prompt.composed` payloads for `source: "secret"` variable bindings per SECURITY/threat-model-secret-leakage.md §SR-1), `prompt-composed-trust-marker.test.ts` (same capability gates; asserts `<UNTRUSTED>...</UNTRUSTED>` wrapping + `contentTrust: "untrusted"` propagation per RFC 0020 §D). Paired with new `fixtures/prompt-templates/` sub-directory + per-fixture schema-validity describe block + future SECURITY invariants `prompt-composed-secret-redaction` and `prompt-composed-trust-marker` (lands alongside reference-host emission per RFC 0021 staging precedent). 2026-05-18 (RFC 0022 `Draft` — runtime variable mapping) added four `it.todo()` placeholder scenarios covering the new mapping surfaces on `core.dispatch` (§A — `dispatch-input-mapping.test.ts`, `dispatch-output-mapping.test.ts`, `dispatch-cross-worker-handoff.test.ts`) and `core.subWorkflow` (§B — `subworkflow-input-mapping.test.ts`). Gated on `capabilities.agents.dispatchMapping` (dispatch trio) and `capabilities.subWorkflow.inputMapping` (subWorkflow). Promote to live assertions when RFC 0022 reaches `Active` + a reference host advertises the matching flags. 2026-05-17 (RFC 0003 §D handoff-schema enforcement, HV-1) added `agentPackHandoffSchemaValidation.test.ts` — verifies the host validates dispatch payloads against `handoff.taskSchemaRef` AND return payloads against `handoff.returnSchemaRef` per RFC 0003 §D. Paired with the new `agent-pack-handoff-schema-enforcement` row in `SECURITY/invariants.yaml`. 2026-05-17 (AI Envelope gap-closure, DRAFT v1.x — `spec/v1/ai-envelope.md`) added 7 advertisement-shape scenarios with `it.todo()` behavioral placeholders gated on `capabilities.envelopeContracts.advertised: true`: `aiEnvelope.universalKinds.test.ts`, `aiEnvelope.schemaDrift.test.ts`, `aiEnvelope.correlationReplay.test.ts`, `aiEnvelope.contractRefusal.test.ts`, `aiEnvelope.trustBoundaryPropagation.test.ts`, `aiEnvelope.redaction.test.ts`, `aiEnvelope.capBreached.test.ts`. Paired with the new `envelope-redaction-sr-1-carry-forward` row in `SECURITY/invariants.yaml`. 2026-05-17 (post-publish hardening, deep audit of `core.openwop.agents`) added `agents-run-tool-allowlist.test.ts` — server-free scenario locking in the `core.openwop.agents@1.0.1` safety-fix that closes `OPENWOP-AUDIT-2026-003` (function-typed `tool.handler` properties rejected at `validateTools()` with `INVALID_TOOL_DECLARATION`; tool-driven runs require `ctx.agentRuntime`; tool-less safe fallback preserved). Paired with the new `agents-run-no-raw-handler` row in `SECURITY/invariants.yaml`. Same-day post-publish hardening added `idempotency-key-determinism.test.ts` — server-free scenario locking in the `core.openwop.http@1.1.2` determinism safety-fix (default `composite` mode produces deterministic keys in `(runId, nodeId, payload)`; removed `uuid` mode rejects with `CONFIG_INVALID`; cross-impl vector test lets third-party reimplementations verify wire agreement). Paired with the new `idempotency-key-deterministic` row in `SECURITY/invariants.yaml`. 2026-05-17 (Phase 3 of RFC 0013) added three server-free scenarios exercising the reference workflow-chain expansion library (`conformance/src/lib/workflow-chain-expansion.ts`): `workflow-chain-expansion.test.ts` (parameter substitution + node id collision avoidance + edge rewriting + capability propagation + runtime-invariance contract), `workflow-chain-unresolvable-typeid.test.ts` (rejection with `chain_unresolvable_typeid` when a chain references an unknown typeId), and `workflow-chain-pack-signature-verification.test.ts` (Ed25519 verification recipe reuse from `node-packs.md §Signing`). Earlier that day (Phase 1) added `workflow-chain-pack-manifest-validation.test.ts` — server-free schema-validation scenario covering the new `workflow-chain-pack-manifest.schema.json` (positive sample + two negatives: kind/contents mismatch and invalid `chainId`). Closes RFC 0013 (`Workflow-chain packs`, `Draft`) Phases 1 + 3 alongside the new `spec/v1/workflow-chain-packs.md`, the `Capabilities.workflowChainPacks` block, and the registry build-index/conformance-check `kind` routing from Phase 2. Earlier that day, the suite added 27 `it.todo()` placeholder scenarios paired with RFCs 0014-0020 (host capability surfaces — fs, kvStorage, tableStorage, queueBus, sql/vector/search, blob/cache, mcp.serverMount). These promote to live assertions when each RFC reaches `Active` + the matching capability block lands in `schemas/capabilities.schema.json` + a reference host advertises the capability. Earlier additions include 18 Multi-Agent Shift scenarios (Phases 1-5) added 2026-05-10, the `registry-public.test.ts` public-registry healthcheck added 2026-05-11 (opt-in via `OPENWOP_TEST_PUBLIC_REGISTRY=true`), the `replay-llm-cache-key.test.ts` placeholder added 2026-05-11 (three `it.todo()` cases for the cross-host LLM cache-key recipe per `replay.md` §"LLM cache-key recipe"), the two `production-*.test.ts` scenarios added 2026-05-11 for the `openwop-production` profile per RFC 0009 (`production-backpressure.test.ts`, `production-retention-expiry.test.ts`), the four `auth-*.test.ts` scenarios added 2026-05-11/12 for the production-auth profiles per RFC 0010 (`auth-api-key-rotation.test.ts`, `auth-oauth2-client-credentials.test.ts`, `auth-oidc-user-bearer.test.ts`, `auth-mtls.test.ts` (opt-in via `OPENWOP_TEST_MTLS=1`)), `replay-retention-expiry.test.ts` added 2026-05-12 (capability shape + 410/422 envelope per `replay.md` §"Retention and garbage collection"), `bulk-cancel.test.ts` added 2026-05-12 (Phase B close-out of R1 — `POST /v1/runs:bulk-cancel`), the two Phase H launch-blocker advertisement-contract scenarios added 2026-05-12 (`mcp-toolcall-redaction.test.ts` for the MCP-1 invariant per `host-capabilities.md §host.mcp` + `threat-model-prompt-injection.md §UNTRUSTED`, and `http-client-ssrf.test.ts` for the SSRF + body-size cap advertisement contract on `capabilities.httpClient`), the `wasm-pack-abi-version-rejection.test.ts` Track 7 scenario added 2026-05-12 for the ABI-mismatch positive path via the `vendor.openwop.misbehaving-abi` pack per RFC 0008 §H, the `otel-trace-propagation-subworkflow.test.ts` Track 11 close-out added 2026-05-13 (parent + child run spans share the inbound traceparent's traceId across the `core.subWorkflow` dispatch boundary), and the three RFC 0012 (Memory Compaction Profile, `Active`) scenarios added 2026-05-13/14: `memory-compaction-sr1-carry-forward.test.ts` (load-bearing SR-1 §D), `memory-compaction-event-emitted.test.ts` (canonical §B payload shape), and `memory-compaction-provenance-tag.test.ts` (soft assertion on §C `compacted-from:<id>` convention). All three gate on `capabilities.memory.compaction.supported` + the host's test seam at `/v1/test/memory/{seed,compact}` (Postgres reference host enables both via `OPENWOP_MEMORY_COMPACTION=true OPENWOP_TEST_TRIGGER_COMPACTION=true`). 2026-05-15 (gap-closure CF-3) added `interrupt-token-matrix.test.ts` (malformed / unknown / replay / cross-run-id paths on `GET|POST /v1/interrupts/{token}`). 2026-05-31 (RFC 0078 portable tool catalog + RFC 0079 credential provenance / egress policy — the Active→Accepted behavioral gate) added four: `tool-catalog-projection.test.ts` (capability-gated on `toolCatalog.supported` via `behaviorGate('openwop-tool-catalog', …)` — the NORMATIVE `GET /v1/tools` list with each `ToolDescriptor` schema-valid + `source`/`safetyTier` in the closed vocab + content-free, `GET /v1/tools/{toolId}` round-trip + unknown-id 404, 401-unauthenticated, and the §F-2 cross-principal non-disclosure; black-box, no POST seam), `tool-session-lifecycle.test.ts` (gated on `toolCatalog.sessionLifecycle` — the §D `tool.session.opened`-before / `tool.session.closed`-after bracket over the RFC 0064 call events via the `POST /v1/host/sample/tools/session-run` seam, one shared `sessionId`, content-free), `egress-audience-binding.test.ts` (KEYSTONE — gated on `httpClient.egressPolicy.supported`; the §C confused-deputy MUST via `POST /v1/host/sample/egress/decide`: an out-of-audience egress is denied/downgraded with the credential NOT attached, a provenance-unevaluable egress fails closed — the behavioral leg of `egress-credential-audience-bound`), and `egress-decision-content-free.test.ts` (the SR-1 canary — the credential value never surfaces in `egress.decided` and `reason` stays in the CLOSED vocabulary). The maintained scenario-to-spec map lives in [`coverage.md`](./coverage.md); this README keeps the operator quickstart and the historical scenario notes below.
|
|
96
96
|
|
|
97
97
|
High-level coverage includes:
|
|
98
98
|
|
|
@@ -171,7 +171,7 @@ Server-required (added in 1.7.0):
|
|
|
171
171
|
| ------------- | ----------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
172
172
|
| **Redaction** | [`capabilities.md`](../spec/v1/capabilities.md) §"Secrets" + NFR-7 + §"aiProviders" | Vendor-neutral assertions that the server doesn't leak secret material. Three scenario groups: (a) discovery shape contract — `secrets` + `aiProviders` advertisements are well-formed regardless of `secrets.supported`; when `supported === true`, scopes MUST be non-empty + `resolution === 'host-managed'`; `byok ⊆ supported`. (b) bearer-token redaction — invalid Bearer canary in `Authorization` header is not echoed in the 401 response body. (c) credentialRef echo control — gated on `secrets.supported === true`; canary planted in `configurable.ai.credentialRef` MUST NOT appear in any RunEvent payload (poll-based capture; transport-agnostic). Uses runtime-built canary fixtures (`lib/canaries.ts`) that defeat static secret scanners. 6 scenarios. |
|
|
173
173
|
|
|
174
|
-
Current source tree:
|
|
174
|
+
Current source tree: 380 scenario files. Use [`coverage.md`](./coverage.md) for current grade/gap tracking.
|
|
175
175
|
|
|
176
176
|
## Remaining Gaps
|
|
177
177
|
|
package/api/asyncapi.yaml
CHANGED
|
@@ -112,6 +112,8 @@ channels:
|
|
|
112
112
|
nodeSkipped: { $ref: '#/components/messages/NodeSkipped' }
|
|
113
113
|
nodeSuspended: { $ref: '#/components/messages/NodeSuspended' }
|
|
114
114
|
nodeDispatched: { $ref: '#/components/messages/NodeDispatched' }
|
|
115
|
+
dispatchFanOut: { $ref: '#/components/messages/DispatchFanOut' }
|
|
116
|
+
dispatchJoin: { $ref: '#/components/messages/DispatchJoin' }
|
|
115
117
|
approvalRequested: { $ref: '#/components/messages/ApprovalRequested' }
|
|
116
118
|
approvalReceived: { $ref: '#/components/messages/ApprovalReceived' }
|
|
117
119
|
clarificationRequested: { $ref: '#/components/messages/ClarificationRequested' }
|
|
@@ -539,6 +541,20 @@ components:
|
|
|
539
541
|
contentType: application/json
|
|
540
542
|
payload:
|
|
541
543
|
$ref: '#/components/schemas/RunEventDoc'
|
|
544
|
+
DispatchFanOut:
|
|
545
|
+
name: core.dispatch.fanOut
|
|
546
|
+
title: core.dispatch began a parallel fan-out wave (RFC 0118)
|
|
547
|
+
summary: Emitted when a fanOutPolicy=parallel wave begins; payload $defs.dispatchFanOut carries childCount/maxConcurrency/joinMode. Parallel path only.
|
|
548
|
+
contentType: application/json
|
|
549
|
+
payload:
|
|
550
|
+
$ref: '#/components/schemas/RunEventDoc'
|
|
551
|
+
DispatchJoin:
|
|
552
|
+
name: core.dispatch.join
|
|
553
|
+
title: core.dispatch parallel join satisfied (RFC 0118)
|
|
554
|
+
summary: Emitted when a fanOutPolicy=parallel join is satisfied/failed; payload $defs.dispatchJoin carries joinOutcome + the replay-deterministic mergeOrder.
|
|
555
|
+
contentType: application/json
|
|
556
|
+
payload:
|
|
557
|
+
$ref: '#/components/schemas/RunEventDoc'
|
|
542
558
|
|
|
543
559
|
# ── HITL ─────────────────────────────────────────────────────────────
|
|
544
560
|
ApprovalRequested:
|
package/coverage.md
CHANGED
|
@@ -67,7 +67,7 @@
|
|
|
67
67
|
| `openwop-agent-platform` meta-profile (RFC 0085 — `spec/v1/agent-platform-profile.md`, operational annex) | `agent-platform-profile.test.ts` | A (always-on, server-free derivation probe) | RFC 0085 promoted Draft → Active 2026-05-30 (5 UQs resolved via MyndHyve review). Operational annex (NOT a closed `profiles.md` catalog predicate). Always-on derivation probe asserts `isAgentPlatformPartial`/`isAgentPlatformFull`/`agentPlatformStatus` derive `none`/`partial`/`full` correctly: all-floor ⇒ partial, a missing floor flag ⇒ none, the replay-OR-`nondeterminismPolicy.declared` term, floor+governance ⇒ full, a missing governance term (tenant `installScope`) ⇒ partial-not-full (the honest-advertisement rule), and that the eval/deploy/budget platform-plus tier is advisory (a full host without them is still full); plus `capabilities.nondeterminismPolicy.declared` is declared. **Live aggregate-evidence assertion authored** (2026-06-01; see §"Capability-gated scenarios"): `agent-platform-aggregate-evidence.test.ts` (gated on a host CLAIMING `openwop-agent-platform` in live `profiles[]` via `behaviorGate('openwop-agent-platform', …)`) reads the live discovery and asserts the §C/§D honest-advertisement rule — the claim MUST satisfy the §B floor predicate (`partial`/`full`, never `none`), backed by the per-capability evidence; `OPENWOP_AGENT_PLATFORM_TIER=full` forces the non-vacuous full-predicate bar. Path to `Accepted`: a host advertises `openwop-agent-platform` backed by the §B floor + passes the aggregate scenario claiming `full` (MyndHyve, after the memory batch surfaces the floor's `memory.supported`). |
|
|
68
68
|
| `openwop-core-standard` profile (RFC 0088 — `spec/v1/core-standard-profile.md`, operational annex) | `core-standard-profile.test.ts` | A (always-on, server-free derivation probe) | RFC 0088 (`Active` 2026-05-31). Operational annex (NOT a closed `profiles.md` catalog predicate) naming the stable **Core Standard Profile** — the floor of normative MUSTs with black-box production-path conformance. Always-on derivation probe asserts `isCoreStandard` derives the §B floor (`openwop-core` ∧ `openwop-interrupts` ∧ (`openwop-stream-sse` ∨ `openwop-stream-poll`)): core+interrupts+default-transport ⇒ core-standard; a bare `openwop-core` host without `clarification.request` ⇒ NOT core-standard (the floor is stricter than the v1 minimum); a host with no event transport (`supportedTransports:[]`) ⇒ fails; a non-1.x host ⇒ fails; and `openwop-core-standard` is absent from `deriveProfiles` (it composes, it does not redefine). The §C floor scenarios (runs-lifecycle / discovery / auth-401 / event-ordering / failure-path / idempotency / interrupts / webhook-negative / audit-log-verify) are all already black-box production-path. **Live aggregate-evidence assertion deferred** per RFC 0088 §C: a host claiming `openwop-core-standard` must pass every §C floor scenario black-box — already satisfied by MyndHyve + all four reference hosts. Path to `Accepted`: ≥1 host advertises the claim in its `conformance.md`/`INTEROP-MATRIX.md` row backed by the floor scenarios. |
|
|
69
69
|
| Agent deployment lifecycle (RFC 0082 — `capabilities.agents.deployment`) | `agent-deployment-shape.test.ts` | A (always-on, server-free shape probe; doubles as the public test for `deployment-event-no-content-leak`) | RFC 0082 promoted Draft → Active 2026-05-30. Always-on shape probe asserts `capabilities.agents.deployment` (+ `supported`/`channels`/`canary`/`rollback`/`states` sub-flags); the `AgentDeployment` record compiles + round-trips and rejects malformed ones (out-of-enum `state`; `canaryPercent` out of 0..100); the **`AgentRef` `channel` XOR `version`** rule (each alone + neither validate; both rejected by the `not` clause, §A); the four `deployment.*` payloads validate content-free records + reject malformed ones; `agent.invocation.started` carries the additive recorded-fact `resolvedAgentVersion`/`resolvedChannel` (§B channel pin); and all four event names appear in the RunEventType enum. The **content-free negatives** (a `deployment.promoted` carrying a `manifestBody`; a `deployment.state.changed` carrying a `prompt`) are the public test for protocol-tier SECURITY invariant `deployment-event-no-content-leak`. The behavioral `deployment-promotion-fail-closed` invariant is `reference-impl` tier until the behavioral scenario lands (then graduates to protocol; RFC 0035 precedent). **Behavioral scenario authored + gated** (2026-05-31; see §"Capability-gated scenarios"): `agent-deployment-lifecycle.test.ts` (the §E authz → approvalGate → eval-verify → `deployment.promoted` promotion + the record round-trip, the fail-closed denial, the `eval_gate_unmet` denial, and the §B `resolvedAgentVersion` channel pin) gates on `capabilities.agents.deployment.supported` + the `POST /v1/host/sample/agents/deployment-transition` seam (`behaviorGate('openwop-deployment-lifecycle', …)`) and soft-skips until a host wires it. When it passes against a host, the `deployment-promotion-fail-closed` invariant graduates reference-impl → protocol tier. Path to `Accepted`: a host implements the deployment store + canary router + the `POST /v1/agents/{agentId}/deployments` promotion contract (the endpoint + SDK helper already landed). |
|
|
70
|
-
| Connection packs (RFC 0095 — `capabilities.connections.packsSupported`) | `connection-pack-manifest-valid.test.ts`, `connection-pack-no-credential-material.test.ts`, `connection-pack-reach-exclusive.test.ts`, `connection-provider-resolution.test.ts`, `connection-pack-write-reconsent.test.ts` | A (3 always-on server-free schema probes) / B (2 capability-gated behavioral) | RFC 0095 promoted Draft → Active 2026-06-12. The three schema probes are always-on: the `connection-pack-github` fixture validates; every name on the §B.2 normative blocklist is schema-rejected at root/provider/auth depth with the sole `provider.auth.endpoints.token` exemption valid (the public test for the protocol-tier `connection-pack-no-credential-material` invariant); `reach` is exactly-one-of mcp/openapi/integration. The behavioral pair gates on `capabilities.connections.packsSupported` and drives the `POST /v1/host/sample/connection-packs/{install,resolve,consent-plan}` seams: §B.6 resolution (installed pack wins; unknown provider → `connection_provider_unresolved`; SemVer §11 prerelease vs built-in release → `connection_provider_conflict`) + §B.8 rejection isolation (one rejected pack never takes down the install path) + §B.4 write-re-consent (write scope groups are a separate consent step). Soft-skips when unadvertised / seam unwired; hard-fails under `OPENWOP_REQUIRE_BEHAVIOR=true`. |
|
|
70
|
+
| Connection packs (RFC 0095 — `capabilities.connections.packsSupported`) | `connection-pack-manifest-valid.test.ts`, `connection-pack-no-credential-material.test.ts`, `connection-pack-reach-exclusive.test.ts`, `connection-provider-resolution.test.ts`, `connection-pack-write-reconsent.test.ts`, `connection-pack-apihosts.test.ts` | A (3 always-on server-free schema probes + the RFC 0120 apiHosts shape/matching legs) / B (2 capability-gated behavioral + the RFC 0120 egress allow-list leg) | RFC 0095 promoted Draft → Active 2026-06-12. The three schema probes are always-on: the `connection-pack-github` fixture validates; every name on the §B.2 normative blocklist is schema-rejected at root/provider/auth depth with the sole `provider.auth.endpoints.token` exemption valid (the public test for the protocol-tier `connection-pack-no-credential-material` invariant); `reach` is exactly-one-of mcp/openapi/integration. The behavioral pair gates on `capabilities.connections.packsSupported` and drives the `POST /v1/host/sample/connection-packs/{install,resolve,consent-plan}` seams: §B.6 resolution (installed pack wins; unknown provider → `connection_provider_unresolved`; SemVer §11 prerelease vs built-in release → `connection_provider_conflict`) + §B.8 rejection isolation (one rejected pack never takes down the install path) + §B.4 write-re-consent (write scope groups are a separate consent step). **RFC 0120 (`apiHosts`, suite-added 2026-06-29):** `connection-pack-apihosts.test.ts` adds always-on legs (the `connection-pack-apihosts-valid` fixture validates; an `openapi`-reach provider omitting `apiHosts` is rejected by the conditional-MUST `allOf`; IP/wildcard/port/single-label/uppercase entries rejected; the item-10 dot-anchored suffix-containment matcher permits eTLD+1 subdomains + a tighter exact host and fails closed with no substring/suffix escape — the public tests for the protocol-tier `connection-pack-api-host-shape` + `connection-pack-egress-host-bound` invariants) and a capability-gated behavioral egress allow-list leg over the new `POST /v1/host/sample/connection-packs/egress-check` seam. Soft-skips when unadvertised / seam unwired; hard-fails under `OPENWOP_REQUIRE_BEHAVIOR=true`. |
|
|
71
71
|
| Standing agent roster (RFC 0086 — `capabilities.agents.roster`) | `agent-roster-shape.test.ts` | A (always-on, server-free shape probe; doubles as the public test for `roster-attribution-no-content`) | RFC 0086 promoted Draft → Active 2026-05-30. Always-on shape probe asserts `capabilities.agents.roster` (+ `supported`/`installScope`/`portfolioTriggerSources` sub-flags); the `AgentRosterEntry` record compiles + round-trips and rejects malformed ones (a non-`host:` `rosterId`; an `agentRef` carrying BOTH `version` and `channel` — the RFC 0082 §A XOR rule; a missing `rosterId`); the `roster.run.initiated` payload validates a content-free attribution record + requires its ids/persona/triggerSource; the `AgentInventoryEntry` carries the additive optional `roster` portfolio projection (§B); and `roster.run.initiated` appears in the RunEventType enum. The **content-free negatives** (a `roster.run.initiated` carrying a `body`; one carrying a `prompt`) are the public test for protocol-tier SECURITY invariant `roster-attribution-no-content`. **Behavioral scenarios deferred** per RFC 0086 §Conformance (reference host): a scheduled portfolio fire emitting `roster.run.initiated` before `agent.invocation.started`; the RFC 0083 work-item causation chain; the replay re-read; the cross-tenant `GET /v1/agents/roster/{id}` 404 — gate on `capabilities.agents.roster.supported` + the roster-store seam and soft-skip until a host wires it (the host-extension at `/v1/host/sample/roster` + board attribution, `openwop-app` #368 — formerly `apps/workflow-engine` — is the reference demonstration). Path to `Accepted`: a non-steward host advertises `agents.roster` + emits `roster.run.initiated`. |
|
|
72
72
|
| Agent org-chart (RFC 0087 — `capabilities.agents.orgChart`) | `agent-org-chart-shape.test.ts` | A (always-on, server-free shape probe; doubles as the public test for `org-position-no-authority-escalation`) | RFC 0087 promoted Draft → Active 2026-05-30. Always-on shape probe asserts `capabilities.agents.orgChart` (+ `supported`/`installScope`/`departmentNesting`/`responsibilityView` sub-flags); the `AgentOrgChart` record compiles + round-trips and rejects malformed ones (a non-`host:` member `rosterId`; a chart missing `members`). The **§B structural non-authority guarantee**: the schema **rejects** an authority-bearing field on a member (`scopes`/`canDispatch`/`permissions`/`authority` — every object is `additionalProperties:false`), and a conforming member's key set is exactly `{rosterId, departmentId, roleId, reportsTo}`. These are the public test for protocol-tier SECURITY invariant `org-position-no-authority-escalation` (an org edge confers no authority — position describes, never authorizes). NO new RunEventType (the org-chart is structure + a read, not an event surface). **Behavioral scenarios deferred** per RFC 0087 §Conformance (reference host): the live-dispatch refusal of a manager's tool over-reach; an RFC 0049 decision invariant to org position; the cross-tenant `GET /v1/agents/org-chart` 404; the §D responsibility roll-up over live roster portfolios — gate on `capabilities.agents.orgChart.supported` + the org-store seam and soft-skip until a host wires it (the host-extension at `/v1/host/sample/org-chart`, `openwop-app` #371 — formerly `apps/workflow-engine` — is the reference demonstration). Path to `Accepted`: a non-steward host advertises `agents.orgChart` + passes the behavioral non-authority scenario. |
|
|
73
73
|
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "core.openwop.connections.meta-ads",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"kind": "connection",
|
|
5
|
+
"engines": { "openwop": ">=1.0.0" },
|
|
6
|
+
"provider": {
|
|
7
|
+
"id": "meta-ads",
|
|
8
|
+
"displayName": "Meta Ads",
|
|
9
|
+
"category": "marketing",
|
|
10
|
+
"auth": {
|
|
11
|
+
"kind": "oauth2",
|
|
12
|
+
"authFlow": "pkce",
|
|
13
|
+
"scopeModel": "groups",
|
|
14
|
+
"endpoints": {
|
|
15
|
+
"authorize": "https://www.facebook.com/v19.0/dialog/oauth",
|
|
16
|
+
"token": "https://graph.facebook.com/v19.0/oauth/access_token"
|
|
17
|
+
},
|
|
18
|
+
"scopes": {
|
|
19
|
+
"read": [{ "key": "ads.read", "label": "Read ads insights", "scopes": ["ads_read"] }],
|
|
20
|
+
"write": [{ "key": "ads.manage", "label": "Manage ad campaigns", "scopes": ["ads_management"] }]
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"reach": { "openapi": { "ref": "https://developers.facebook.com/docs/marketing-api" } },
|
|
24
|
+
"apiHosts": ["facebook.com"]
|
|
25
|
+
}
|
|
26
|
+
}
|
package/fixtures.md
CHANGED
|
@@ -499,6 +499,7 @@ The `fixtures/connection-packs/` sub-directory holds canonical connection-pack m
|
|
|
499
499
|
| Fixture | `provider.id` | Purpose |
|
|
500
500
|
| ------------------------ | ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
501
501
|
| `connection-pack-github` | `github` | Canonical positive manifest (oauth2 + pkce, read/write scope groups, MCP reach, the exempt `provider.auth.endpoints.token` endpoint URL). Drives manifest-valid, no-credential-material, reach-exclusive, provider-resolution, and write-reconsent scenarios. |
|
|
502
|
+
| `connection-pack-apihosts-valid` | `meta-ads` | Canonical positive `openapi`-reach manifest declaring a `provider.apiHosts` credential-egress allow-list (`["facebook.com"]`) — RFC 0120. Drives `connection-pack-apihosts` (the `apiHosts` schema + conditional-MUST + egress allow-list scenario). Negatives (IP/wildcard/port/single-label/uppercase entries, openapi-without-apiHosts) are inline mutations per suite convention. |
|
|
502
503
|
|
|
503
504
|
Negative manifests (credential material, mixed kinds, dual reach) are inline test data in the scenario files per suite convention — a deliberately-invalid fixture file would fail the automatic `fixtures-valid.test.ts` sweep.
|
|
504
505
|
|
package/package.json
CHANGED
|
@@ -1744,14 +1744,18 @@
|
|
|
1744
1744
|
},
|
|
1745
1745
|
"uiPlugins": {
|
|
1746
1746
|
"type": "object",
|
|
1747
|
-
"description": "RFC 0117 (`Active
|
|
1747
|
+
"description": "RFC 0117 (`Active`; amended by RFC 0119). Host loads SIGNED, SANDBOXED front-end plugin packs (`kind: \"frontend-plugin\"`) — canvas editors, custom artifact viewers, settings panels — in an ORIGIN/EXECUTION-ISOLATED sandbox (mechanism named by `isolation`: cross-origin iframe by default, or wasm/process/container/vm) and talks to them over the closed `ui-plugin/1` host-RPC boundary. The wire owns the boundary (isolation + RPC allowlist + manifest), NOT a renderer. Gated on `supported: true`; a host that does not advertise it rejects `kind: \"frontend-plugin\"` packs at registration and renders no plugin surface (graceful degradation to RFC 0071 host rendering). SECURITY invariants `frontend-plugin-isolation` / `frontend-plugin-egress` / `frontend-plugin-rpc-allowlist` / `frontend-plugin-no-byok` (RFC 0117 §Security).",
|
|
1748
1748
|
"required": ["supported"],
|
|
1749
1749
|
"properties": {
|
|
1750
|
-
"supported": { "type": "boolean", "description": "Host loads `kind: \"frontend-plugin\"` packs in
|
|
1750
|
+
"supported": { "type": "boolean", "description": "Host loads `kind: \"frontend-plugin\"` packs in an origin/execution-isolated sandbox (mechanism per `isolation`) and serves the `ui-plugin/1` host-RPC boundary." },
|
|
1751
1751
|
"isolation": {
|
|
1752
1752
|
"type": "string",
|
|
1753
|
-
"
|
|
1754
|
-
|
|
1753
|
+
"anyOf": [
|
|
1754
|
+
{ "enum": ["cross-origin-iframe", "wasm", "process", "container", "vm"] },
|
|
1755
|
+
{ "pattern": "^x-host-[a-z0-9-]+-[a-z0-9-]+$" }
|
|
1756
|
+
],
|
|
1757
|
+
"default": "cross-origin-iframe",
|
|
1758
|
+
"description": "RFC 0117 (amended by RFC 0119). The categorical isolation model the host enforces for plugin bytes. `cross-origin-iframe` = a distinct-origin sandboxed browser frame (the browser default; §Isolation). `wasm`/`process`/`container`/`vm` mirror `sandbox.isolationModel` (RFC 0035) for non-browser hosts that enforce the SAME isolation property. Vendor-specific models advertise `^x-host-<host>-<key>$` per `host-extensions.md`. ALL values denote the SAME mandatory property (`frontend-plugin-isolation`): plugin bytes execute in a boundary with no access to the host's execution context / DOM / origin-storage / credentials, deny-egress by default, and all host interaction mediated by the closed `ui-plugin/1` RPC. The field names the MECHANISM, never relaxes the property. In-process / same-origin / module-federation loading is a protocol-tier MUST NOT regardless of the advertised value."
|
|
1755
1759
|
},
|
|
1756
1760
|
"surfaces": {
|
|
1757
1761
|
"type": "array",
|
|
@@ -47,6 +47,13 @@
|
|
|
47
47
|
"description": "The portable provider definition. Exactly one per connection pack.",
|
|
48
48
|
"required": ["id", "displayName", "category", "auth", "reach"],
|
|
49
49
|
"additionalProperties": false,
|
|
50
|
+
"allOf": [
|
|
51
|
+
{
|
|
52
|
+
"$comment": "RFC 0120 UQ1: apiHosts is REQUIRED when reach is openapi (a credentialed REST/OpenAPI egress surface). A pure metadata / mcp / integration provider MAY omit it. The runtime binding-site validator (spec §Manifest item 13) is the complementary check for credentialed-egress paths the manifest cannot see.",
|
|
53
|
+
"if": { "required": ["reach"], "properties": { "reach": { "required": ["openapi"] } } },
|
|
54
|
+
"then": { "required": ["apiHosts"] }
|
|
55
|
+
}
|
|
56
|
+
],
|
|
50
57
|
"properties": {
|
|
51
58
|
"id": {
|
|
52
59
|
"type": "string",
|
|
@@ -142,7 +149,14 @@
|
|
|
142
149
|
"items": { "type": "string" },
|
|
143
150
|
"description": "The core node typeIds that consume this provider's credential (e.g. core.openwop.mcp.invoke-tool)."
|
|
144
151
|
},
|
|
145
|
-
"docsUrl": { "type": "string", "format": "uri" }
|
|
152
|
+
"docsUrl": { "type": "string", "format": "uri" },
|
|
153
|
+
"apiHosts": {
|
|
154
|
+
"type": "array",
|
|
155
|
+
"description": "RFC 0120. API host(s) a host MAY send this provider's resolved credential to for connector egress (RFC 0045). Each entry is a bare registrable hostname — lowercase ASCII, >=2 labels, TLD label beginning with a letter; no scheme, port, path, wildcard, or IP literal. Matched by dot-anchored suffix containment (request host == entry OR ends with '.'+entry); eTLD+1 is the floor and a pack MAY list a tighter exact host (e.g. googleads.googleapis.com). Omitted => no credential-bearing connector egress is reachable (fails closed). REQUIRED when reach is openapi (see the provider-level allOf).",
|
|
156
|
+
"items": { "type": "string", "format": "hostname", "pattern": "^(?!-)[a-z0-9-]{1,63}(\\.[a-z0-9-]{1,63})*\\.[a-z][a-z0-9-]*$" },
|
|
157
|
+
"uniqueItems": true,
|
|
158
|
+
"minItems": 1
|
|
159
|
+
}
|
|
146
160
|
}
|
|
147
161
|
}
|
|
148
162
|
},
|
|
@@ -20,9 +20,39 @@
|
|
|
20
20
|
},
|
|
21
21
|
"fanOutPolicy": {
|
|
22
22
|
"type": "string",
|
|
23
|
-
"enum": ["sequential", "reject"],
|
|
23
|
+
"enum": ["sequential", "reject", "parallel"],
|
|
24
24
|
"default": "sequential",
|
|
25
|
-
"description": "Behavior when `decision.kind === 'next-worker'` and `nextWorkerIds.length > 1`. `'sequential'` dispatches each in array order, blocking on each child terminal before starting the next; the dispatch node's output reports the LAST child's `childRunId`/`childStatus` (intermediate child-run identifiers surface in the run's event log via standard `node.*` events). `'reject'` fails the dispatch with a structured `'fan_out_unsupported'` error envelope per `error-envelope.md`.
|
|
25
|
+
"description": "Behavior when `decision.kind === 'next-worker'` and `nextWorkerIds.length > 1`. `'sequential'` (default) dispatches each in array order, blocking on each child terminal before starting the next; the dispatch node's output reports the LAST child's `childRunId`/`childStatus` (intermediate child-run identifiers surface in the run's event log via standard `node.*` events). `'reject'` fails the dispatch with a structured `'fan_out_unsupported'` error envelope per `error-envelope.md`. `'parallel'` (RFC 0118) dispatches ALL `nextWorkerIds[i]` as child runs concurrently (bounded by `maxConcurrency`) and joins on their terminals per `joinPolicy`; it REQUIRES the host to advertise `capabilities.dispatch.fanOutSupported: true` — a host that does not MUST surface a `validation_error` at `POST /v1/workflows`."
|
|
26
|
+
},
|
|
27
|
+
"joinPolicy": {
|
|
28
|
+
"type": "object",
|
|
29
|
+
"description": "RFC 0118. Completion + error-aggregation semantics for `fanOutPolicy: 'parallel'`. Meaningful ONLY when `fanOutPolicy === 'parallel'`; a host MUST surface a `validation_error` at registration if `joinPolicy` is present while `fanOutPolicy !== 'parallel'`. `mode` and `onChildFailure` are orthogonal axes (completion condition vs error aggregation).",
|
|
30
|
+
"properties": {
|
|
31
|
+
"mode": {
|
|
32
|
+
"type": "string",
|
|
33
|
+
"enum": ["wait-all", "quorum", "first", "race"],
|
|
34
|
+
"default": "wait-all",
|
|
35
|
+
"description": "When the join is satisfied. `'wait-all'` (default) — the dispatch node stays suspended until EVERY dispatched child reaches a terminal state (`completed`/`failed`/`cancelled`); the only mode that never discards a child's outcome. `'quorum'` — satisfied at `quorum` `completed` children (REQUIRES `quorum`, `1 ≤ quorum ≤ nextWorkerIds.length`; a missing/out-of-range `quorum` is a registration `validation_error`); in-flight children are cancelled per the `interrupt-profiles.md` parent-child cascade. `'first'` — satisfied at the first `completed`; remaining cancelled. `'race'` — satisfied at the first terminal of ANY kind (`completed` OR `failed`); remaining cancelled."
|
|
36
|
+
},
|
|
37
|
+
"quorum": {
|
|
38
|
+
"type": "integer",
|
|
39
|
+
"minimum": 1,
|
|
40
|
+
"description": "Required iff `mode === 'quorum'`. The number of `completed` children that satisfies the join. MUST be `1 ≤ quorum ≤ nextWorkerIds.length`."
|
|
41
|
+
},
|
|
42
|
+
"onChildFailure": {
|
|
43
|
+
"type": "string",
|
|
44
|
+
"enum": ["collect", "fail-fast", "absorb"],
|
|
45
|
+
"default": "collect",
|
|
46
|
+
"description": "How a non-`completed` child terminal affects the dispatch node. `'collect'` (default) — no individual failure short-circuits; the node waits per `mode`, then reports per-child outcomes in its output (§D). `'fail-fast'` — the first child to reach `failed`/`cancelled` immediately fails the dispatch node and cancels the rest (equivalent to single-child `fail-parent` applied at first failure). `'absorb'` — failed children are recorded in the log + node output but never fail the parent; the join still completes per `mode`. The default deliberately DIVERGES from single-child `core.subWorkflow.onChildFailure: 'fail-parent'` because independent fan-out siblings are the common case (RFC 0118 §Resolved Q1)."
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
"required": [],
|
|
50
|
+
"additionalProperties": false
|
|
51
|
+
},
|
|
52
|
+
"maxConcurrency": {
|
|
53
|
+
"type": "integer",
|
|
54
|
+
"minimum": 1,
|
|
55
|
+
"description": "RFC 0118. Maximum number of children dispatched concurrently under `fanOutPolicy: 'parallel'`. When `nextWorkerIds.length` exceeds this, the host dispatches in waves of at most `maxConcurrency`, starting a queued child as each in-flight child terminates (the host MUST NOT silently drop children above the ceiling). Absent → host-defined effective concurrency (a host MAY cap to protect itself and MUST advertise that cap as `capabilities.dispatch.maxFanOut`). The EFFECTIVE concurrency is `min(maxConcurrency ?? ∞, capabilities.dispatch.maxFanOut ?? ∞)`. Does not change join semantics — `joinPolicy` evaluates over the full `nextWorkerIds` set. Meaningful only under `fanOutPolicy: 'parallel'`."
|
|
26
56
|
},
|
|
27
57
|
"iterationCap": {
|
|
28
58
|
"type": "integer",
|
|
@@ -67,6 +97,16 @@
|
|
|
67
97
|
"askUserRouting": "clarification",
|
|
68
98
|
"fanOutPolicy": "reject",
|
|
69
99
|
"iterationCap": 25
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
"workerDispatchModel": "child-run",
|
|
103
|
+
"fanOutPolicy": "parallel",
|
|
104
|
+
"maxConcurrency": 5,
|
|
105
|
+
"joinPolicy": { "mode": "wait-all", "onChildFailure": "collect" }
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
"fanOutPolicy": "parallel",
|
|
109
|
+
"joinPolicy": { "mode": "quorum", "quorum": 3, "onChildFailure": "fail-fast" }
|
|
70
110
|
}
|
|
71
111
|
]
|
|
72
112
|
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
3
|
"$id": "https://openwop.dev/spec/v1/frontend-plugin-manifest.schema.json",
|
|
4
4
|
"title": "FrontendPluginManifest",
|
|
5
|
-
"description": "RFC 0117. Manifest for a published OpenWOP front-end plugin pack — `pack.json` at the pack root with `kind: \"frontend-plugin\"`. Peer to and disjoint from `node-pack-manifest.schema.json` (RFC 0003), `connection-pack-manifest.schema.json` (RFC 0095), and the other pack kinds via the `kind` discriminator. A front-end plugin pack distributes a SIGNED, SANDBOXED user-interface extension (a canvas editor, a custom artifact viewer, a settings panel) that a host loads at runtime in a
|
|
5
|
+
"description": "RFC 0117. Manifest for a published OpenWOP front-end plugin pack — `pack.json` at the pack root with `kind: \"frontend-plugin\"`. Peer to and disjoint from `node-pack-manifest.schema.json` (RFC 0003), `connection-pack-manifest.schema.json` (RFC 0095), and the other pack kinds via the `kind` discriminator. A front-end plugin pack distributes a SIGNED, SANDBOXED user-interface extension (a canvas editor, a custom artifact viewer, a settings panel) that a host loads at runtime in an ORIGIN/EXECUTION-ISOLATED sandbox (a cross-origin iframe by default, or an equivalent WASM/process/container/VM sandbox per `capabilities.uiPlugins.isolation`, RFC 0119) and enables through its own admin surface. The wire owns only the manifest shape, the isolation model, and the `ui-plugin/1` host-RPC boundary — NOT a rendering runtime: the plugin `entry` bundle is opaque bytes the host runs in a sandbox. Carries NO secret and NO backend `runtime` (a `runtime` member is rejected — a plugin is sandboxed UI, not a node entry). Requires the host to advertise `capabilities.uiPlugins.supported: true`; degrades to nothing on hosts without it. See `spec/v1/frontend-plugin-packs.md`.",
|
|
6
6
|
"type": "object",
|
|
7
7
|
"required": ["name", "version", "kind", "engines", "uiPlugins"],
|
|
8
8
|
"additionalProperties": false,
|
|
@@ -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\n111 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": {
|
|
@@ -75,6 +75,8 @@
|
|
|
75
75
|
"agent.verified": { "$ref": "#/$defs/agentVerified" },
|
|
76
76
|
"runOrchestrator.decided": { "$ref": "#/$defs/runOrchestratorDecided" },
|
|
77
77
|
"node.dispatched": { "$ref": "#/$defs/nodeDispatched" },
|
|
78
|
+
"core.dispatch.fanOut": { "$ref": "#/$defs/dispatchFanOut" },
|
|
79
|
+
"core.dispatch.join": { "$ref": "#/$defs/dispatchJoin" },
|
|
78
80
|
"conversation.opened": { "$ref": "#/$defs/conversationOpened" },
|
|
79
81
|
"conversation.exchanged": { "$ref": "#/$defs/conversationExchanged" },
|
|
80
82
|
"conversation.closed": { "$ref": "#/$defs/conversationClosed" },
|
|
@@ -1482,6 +1484,33 @@
|
|
|
1482
1484
|
"additionalProperties": false
|
|
1483
1485
|
},
|
|
1484
1486
|
|
|
1487
|
+
"dispatchFanOut": {
|
|
1488
|
+
"type": "object",
|
|
1489
|
+
"description": "RFC 0118. Emitted by `core.dispatch` when a `fanOutPolicy: 'parallel'` wave BEGINS, so the parent stays observable while children run concurrently. The envelope's `nodeId` carries the dispatching `core.dispatch` node id; `causationId` is set to the consumed `runOrchestrator.decided` event so replay reconstructs decision → fan-out. Emitted ONLY on the parallel path.",
|
|
1490
|
+
"required": ["fanOutPolicy", "childCount"],
|
|
1491
|
+
"properties": {
|
|
1492
|
+
"fanOutPolicy": { "const": "parallel", "description": "Always `\"parallel\"` — this event is emitted only on the parallel fan-out path." },
|
|
1493
|
+
"childCount": { "type": "integer", "minimum": 2, "description": "Number of children dispatched in this fan-out (`nextWorkerIds.length`; > 1 by construction)." },
|
|
1494
|
+
"maxConcurrency": { "type": "integer", "minimum": 1, "description": "Effective concurrency ceiling for the wave (`min(config.maxConcurrency ?? ∞, capabilities.dispatch.maxFanOut ?? ∞)`), when bounded." },
|
|
1495
|
+
"joinMode": { "type": "string", "enum": ["wait-all", "quorum", "first", "race"], "description": "The `joinPolicy.mode` governing this fan-out." }
|
|
1496
|
+
},
|
|
1497
|
+
"additionalProperties": false
|
|
1498
|
+
},
|
|
1499
|
+
|
|
1500
|
+
"dispatchJoin": {
|
|
1501
|
+
"type": "object",
|
|
1502
|
+
"description": "RFC 0118. Emitted by `core.dispatch` when a `fanOutPolicy: 'parallel'` join is SATISFIED (or fails). The envelope's `nodeId` carries the dispatching node id; `causationId` matches the paired `core.dispatch.fanOut`. `mergeOrder` is the canonical replay-deterministic record of the output-merge tiebreak: a replay MUST re-apply `outputMapping` in `mergeOrder` (the parent host's observed wall-clock terminal order), NOT in `nextWorkerIds` order.",
|
|
1503
|
+
"required": ["joinOutcome", "completedCount", "failedCount", "mergeOrder"],
|
|
1504
|
+
"properties": {
|
|
1505
|
+
"joinOutcome": { "type": "string", "enum": ["satisfied", "failed", "partial"], "description": "`'satisfied'` — `mode` met and `onChildFailure` did not fail the node. `'failed'` — `onChildFailure: 'fail-fast'` tripped, or `mode` could not be satisfied (e.g. quorum unreachable); the dispatch node fails. `'partial'` — `mode` satisfied but ≥1 child is non-`completed` under `collect`/`absorb`; the node SUCCEEDS." },
|
|
1506
|
+
"completedCount": { "type": "integer", "minimum": 0, "description": "Number of children that reached `completed`." },
|
|
1507
|
+
"failedCount": { "type": "integer", "minimum": 0, "description": "Number of children that reached `failed`." },
|
|
1508
|
+
"cancelledCount": { "type": "integer", "minimum": 0, "description": "Number of children cancelled (e.g. in-flight when `quorum`/`first`/`race`/`fail-fast` short-circuited the join)." },
|
|
1509
|
+
"mergeOrder": { "type": "array", "items": { "type": "string" }, "description": "`childRunId`s in the parent host's observed wall-clock terminal order — the replay-deterministic tiebreak for colliding `outputMapping` keys. Recorded at terminal-fold time; never recomputed from child timestamps." }
|
|
1510
|
+
},
|
|
1511
|
+
"additionalProperties": false
|
|
1512
|
+
},
|
|
1513
|
+
|
|
1485
1514
|
"conversationOpened": {
|
|
1486
1515
|
"type": "object",
|
|
1487
1516
|
"description": "Multi-Agent Shift Phase 4. Emitted when a `core.conversationGate` (or host-extension equivalent) opens a new multi-turn conversation context. Pairs with the closing `conversation.closed` via shared `conversationId`.",
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
3
|
"$id": "https://openwop.dev/spec/v1/ui-plugin-message.schema.json",
|
|
4
4
|
"title": "UiPluginMessage",
|
|
5
|
-
"description": "RFC 0117. The `ui-plugin/1`
|
|
5
|
+
"description": "RFC 0117 (amended by RFC 0119). The `ui-plugin/1` host-RPC envelope exchanged between a sandboxed front-end plugin and its host across the isolation boundary (transport per `capabilities.uiPlugins.isolation` — browser `postMessage` for `cross-origin-iframe`, host-call for `wasm`, IPC for `process`/`container`/`vm`; the envelope shape is identical across all). Every message carries `openwop: \"ui-plugin/1\"` (the protocol version tag — a host MUST ignore messages whose tag it does not recognize) and is one of: a plugin→host `request` (a `method` from the closed allowlist + a monotonic `id` for response correlation), a host→plugin `response` (`ok` + `result` on success, or `ok:false` + `error` on failure), or a host→plugin `event` (a one-way notification, e.g. `host.themeChanged`). The host exposes ONLY the methods the plugin declared in its manifest `hostApi` AND that the host recognizes; any other method MUST be rejected with an `error` response, never silently executed (`frontend-plugin-rpc-allowlist`). `artifact.write` carries the optimistic-concurrency `version` token (RFC 0117 §Concurrency, modeled on the RFC 0059 `host.workspace` `If-Match`/ETag pattern). See `spec/v1/frontend-plugin-packs.md`.",
|
|
6
6
|
"oneOf": [
|
|
7
7
|
{ "$ref": "#/$defs/request" },
|
|
8
8
|
{ "$ref": "#/$defs/response" },
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Connection-pack `provider.apiHosts` — credential-egress allow-list
|
|
3
|
+
* (RFC 0120; `connection-packs.md` §Manifest items 10–15 +
|
|
4
|
+
* `schemas/connection-pack-manifest.schema.json`).
|
|
5
|
+
*
|
|
6
|
+
* Public tests for two SECURITY invariants:
|
|
7
|
+
* - `connection-pack-api-host-shape` (always-on, server-free schema legs)
|
|
8
|
+
* - `connection-pack-egress-host-bound` (server-free matching-rule legs +
|
|
9
|
+
* capability-gated behavioral leg)
|
|
10
|
+
*
|
|
11
|
+
* Legs:
|
|
12
|
+
* A. Schema (always-on, server-free) — exercises the new `provider.apiHosts`
|
|
13
|
+
* property + the provider-level `allOf` conditional MUST:
|
|
14
|
+
* 1. Positive: the `connection-pack-apihosts-valid` fixture (an
|
|
15
|
+
* `openapi`-reach provider declaring `apiHosts:["facebook.com"]`)
|
|
16
|
+
* validates.
|
|
17
|
+
* 2. Conditional MUST (item 13 / §A allOf): an `openapi`-reach provider
|
|
18
|
+
* that OMITS `apiHosts` is rejected (`required`).
|
|
19
|
+
* 3. Omission OK off the conditional: an `mcp`-reach provider without
|
|
20
|
+
* `apiHosts` validates (the github fixture).
|
|
21
|
+
* 4. Entry shape (item 11): IPv4 literal, wildcard, port-bearing,
|
|
22
|
+
* single-label, and uppercase entries are each rejected (`pattern`).
|
|
23
|
+
* B. Matching rule (always-on, server-free) — asserts the item-10 dot-anchored
|
|
24
|
+
* eTLD+1 suffix-containment rule + the no-substring-escape property that the
|
|
25
|
+
* `connection-pack-egress-host-bound` invariant rests on.
|
|
26
|
+
* C. Behavioral egress allow-list (capability-gated) — drives a host
|
|
27
|
+
* advertising `connections.packsSupported` through the
|
|
28
|
+
* `POST /v1/host/sample/connection-packs/egress-check` seam
|
|
29
|
+
* (`host-sample-test-seams.md` §10): a credential-bearing egress to a host
|
|
30
|
+
* matching `apiHosts` is PERMITTED; one that does not is FAIL-CLOSED, with
|
|
31
|
+
* no substring/suffix escape. Soft-skips when unadvertised / seam unwired;
|
|
32
|
+
* hard-fails under `OPENWOP_REQUIRE_BEHAVIOR=true`.
|
|
33
|
+
*
|
|
34
|
+
* @see spec/v1/connection-packs.md
|
|
35
|
+
* @see schemas/connection-pack-manifest.schema.json
|
|
36
|
+
* @see RFCS/0120-connection-pack-api-hosts.md
|
|
37
|
+
* @see spec/v1/host-sample-test-seams.md
|
|
38
|
+
*/
|
|
39
|
+
|
|
40
|
+
import { describe, it, expect } from 'vitest';
|
|
41
|
+
import { readFileSync } from 'node:fs';
|
|
42
|
+
import { join } from 'node:path';
|
|
43
|
+
import Ajv2020 from 'ajv/dist/2020.js';
|
|
44
|
+
import addFormats from 'ajv-formats';
|
|
45
|
+
import type { ErrorObject } from 'ajv';
|
|
46
|
+
import { SCHEMAS_DIR, FIXTURES_DIR } from '../lib/paths.js';
|
|
47
|
+
import { driver } from '../lib/driver.js';
|
|
48
|
+
import { behaviorGate } from '../lib/behavior-gate.js';
|
|
49
|
+
import { readCapabilityFamily } from '../lib/discovery-capabilities.js';
|
|
50
|
+
|
|
51
|
+
const SCHEMA_PATH = join(SCHEMAS_DIR, 'connection-pack-manifest.schema.json');
|
|
52
|
+
const APIHOSTS_FIXTURE = join(FIXTURES_DIR, 'connection-packs', 'connection-pack-apihosts-valid.json');
|
|
53
|
+
const MCP_FIXTURE = join(FIXTURES_DIR, 'connection-packs', 'connection-pack-github.json');
|
|
54
|
+
|
|
55
|
+
type Manifest = Record<string, unknown> & {
|
|
56
|
+
provider: Record<string, unknown> & { id: string; apiHosts?: string[] };
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
function apiHostsFixture(): Manifest {
|
|
60
|
+
return JSON.parse(readFileSync(APIHOSTS_FIXTURE, 'utf8')) as Manifest;
|
|
61
|
+
}
|
|
62
|
+
function mcpFixture(): Manifest {
|
|
63
|
+
return JSON.parse(readFileSync(MCP_FIXTURE, 'utf8')) as Manifest;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* The item-10 matching rule, exactly as the spec defines it: a request host
|
|
68
|
+
* matches an `apiHosts` entry IFF it equals the entry OR ends with `"." + entry`
|
|
69
|
+
* (dot-anchored suffix containment — never a bare substring). This is the
|
|
70
|
+
* server-free, host-independent encoding of `connection-pack-egress-host-bound`.
|
|
71
|
+
*/
|
|
72
|
+
function matchesApiHost(requestHost: string, entry: string): boolean {
|
|
73
|
+
const h = requestHost.toLowerCase();
|
|
74
|
+
const e = entry.toLowerCase();
|
|
75
|
+
return h === e || h.endsWith('.' + e);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
describe('category: connection-pack apiHosts — schema shape (RFC 0120 §A; invariant connection-pack-api-host-shape)', () => {
|
|
79
|
+
const ajv = new Ajv2020({ allErrors: true, strict: false });
|
|
80
|
+
addFormats(ajv);
|
|
81
|
+
const schema = JSON.parse(readFileSync(SCHEMA_PATH, 'utf8'));
|
|
82
|
+
const validate = ajv.compile(schema);
|
|
83
|
+
|
|
84
|
+
const failsWith = (manifest: unknown, keyword: string): ErrorObject[] => {
|
|
85
|
+
const ok = validate(manifest);
|
|
86
|
+
expect(ok).toBe(false);
|
|
87
|
+
return (validate.errors ?? []).filter((e) => e.keyword === keyword);
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
it('positive: an openapi-reach provider declaring apiHosts validates', () => {
|
|
91
|
+
expect(
|
|
92
|
+
validate(apiHostsFixture()),
|
|
93
|
+
`connection-packs.md §Manifest item 10: a well-formed apiHosts allow-list MUST validate. Errors: ${JSON.stringify(validate.errors)}`,
|
|
94
|
+
).toBe(true);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('conditional MUST (item 13 / §A allOf): an openapi-reach provider that omits apiHosts is rejected', () => {
|
|
98
|
+
const m = apiHostsFixture();
|
|
99
|
+
delete m.provider.apiHosts;
|
|
100
|
+
const errs = failsWith(m, 'required');
|
|
101
|
+
expect(
|
|
102
|
+
errs.some((e) => (e.params as { missingProperty?: string }).missingProperty === 'apiHosts'),
|
|
103
|
+
'connection-packs.md §Manifest item 13: when reach is openapi, apiHosts is REQUIRED (the provider-level allOf)',
|
|
104
|
+
).toBe(true);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it('omission OK off the conditional: an mcp-reach provider without apiHosts validates', () => {
|
|
108
|
+
const m = mcpFixture();
|
|
109
|
+
expect(m.provider.apiHosts, 'precondition: the github fixture is mcp-reach and declares no apiHosts').toBeUndefined();
|
|
110
|
+
expect(
|
|
111
|
+
validate(m),
|
|
112
|
+
`connection-packs.md §Manifest item 13: a non-openapi provider MAY omit apiHosts. Errors: ${JSON.stringify(validate.errors)}`,
|
|
113
|
+
).toBe(true);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it('entry shape (item 11): IPv4 literal / wildcard / port / single-label / uppercase entries are each rejected', () => {
|
|
117
|
+
for (const bad of ['127.0.0.1', '10.0.0.1', '*.facebook.com', 'graph.facebook.com:8443', 'facebook', 'Facebook.com']) {
|
|
118
|
+
const m = apiHostsFixture();
|
|
119
|
+
m.provider.apiHosts = [bad];
|
|
120
|
+
const errs = failsWith(m, 'pattern');
|
|
121
|
+
expect(
|
|
122
|
+
errs.length,
|
|
123
|
+
`connection-packs.md §Manifest item 11: "${bad}" MUST be rejected — apiHosts entries are bare, lowercase, alphabetic-TLD registrable hostnames (connection_pack_invalid_api_host)`,
|
|
124
|
+
).toBeGreaterThan(0);
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
describe('category: connection-pack apiHosts — matching rule (RFC 0120 item 10; invariant connection-pack-egress-host-bound)', () => {
|
|
130
|
+
it('permits the entry host and its subdomains (eTLD+1 floor)', () => {
|
|
131
|
+
expect(matchesApiHost('facebook.com', 'facebook.com'), 'item 10: a request host equal to the entry matches').toBe(true);
|
|
132
|
+
expect(matchesApiHost('graph.facebook.com', 'facebook.com'), 'item 10: a subdomain of the entry matches (eTLD+1 floor)').toBe(true);
|
|
133
|
+
expect(matchesApiHost('api.example.com', 'example.com'), 'item 10: a subdomain matches').toBe(true);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it('a pack MAY tighten to an exact host without admitting siblings', () => {
|
|
137
|
+
expect(matchesApiHost('googleads.googleapis.com', 'googleads.googleapis.com'), 'item 10: the tighter exact host matches itself').toBe(true);
|
|
138
|
+
expect(
|
|
139
|
+
matchesApiHost('bigquery.googleapis.com', 'googleads.googleapis.com'),
|
|
140
|
+
'item 10: a sibling subdomain MUST NOT match a tighter exact-host entry',
|
|
141
|
+
).toBe(false);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it('no substring / suffix / prefix escape — non-matching hosts fail closed', () => {
|
|
145
|
+
for (const evil of ['notexample.com', 'evil.com', 'example.com.evil.com', 'myexample.com']) {
|
|
146
|
+
expect(
|
|
147
|
+
matchesApiHost(evil, 'example.com'),
|
|
148
|
+
`item 10: "${evil}" MUST NOT match "example.com" (dot-anchored containment, never a bare substring)`,
|
|
149
|
+
).toBe(false);
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
interface EgressResult {
|
|
155
|
+
allowed?: boolean;
|
|
156
|
+
code?: string;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
async function gate(): Promise<boolean> {
|
|
160
|
+
const connections = await readCapabilityFamily<{ packsSupported?: boolean }>('connections');
|
|
161
|
+
return behaviorGate('connections.packsSupported', connections?.packsSupported === true);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
describe('connection-pack apiHosts — behavioral egress allow-list (RFC 0120 item 10; capability-gated)', () => {
|
|
165
|
+
it('permits a credential-bearing egress to an apiHosts match; fails closed otherwise (no substring escape)', async () => {
|
|
166
|
+
if (!(await gate())) return;
|
|
167
|
+
|
|
168
|
+
const install = await driver.post('/v1/host/sample/connection-packs/install', { manifest: apiHostsFixture() });
|
|
169
|
+
if (install.status === 404 || install.status === 403) return; // seam unwired — soft-skip
|
|
170
|
+
|
|
171
|
+
const probe = async (requestHost: string): Promise<EgressResult | undefined> => {
|
|
172
|
+
const res = await driver.post('/v1/host/sample/connection-packs/egress-check', {
|
|
173
|
+
provider: 'meta-ads',
|
|
174
|
+
requestHost,
|
|
175
|
+
});
|
|
176
|
+
if (res.status === 404 || res.status === 403) return undefined; // egress-check seam unwired — soft-skip
|
|
177
|
+
return res.json as EgressResult | undefined;
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
const allowed = await probe('graph.facebook.com');
|
|
181
|
+
if (allowed === undefined) return; // soft-skip
|
|
182
|
+
expect(
|
|
183
|
+
allowed.allowed,
|
|
184
|
+
driver.describe('connection-packs.md §Manifest item 10', 'a credential-bearing egress to a host matching apiHosts MUST be permitted'),
|
|
185
|
+
).toBe(true);
|
|
186
|
+
|
|
187
|
+
for (const evil of ['evil.com', 'notfacebook.com', 'facebook.com.evil.com']) {
|
|
188
|
+
const denied = await probe(evil);
|
|
189
|
+
if (denied === undefined) return; // soft-skip
|
|
190
|
+
expect(
|
|
191
|
+
denied.allowed,
|
|
192
|
+
driver.describe('connection-packs.md §Manifest item 10', `egress to "${evil}" (no apiHosts match) MUST fail closed — no credential sent`),
|
|
193
|
+
).toBe(false);
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
});
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parallel sub-workflow fan-out and join — `node-packs.md` §"`core.dispatch` parallel
|
|
3
|
+
* fan-out and join" (RFC 0118). Closes RFC 0007 §K3. Validates the additive
|
|
4
|
+
* `fanOutPolicy: 'parallel'` + `joinPolicy` + `maxConcurrency` wire shape on
|
|
5
|
+
* `dispatch-config.schema.json` and the `core.dispatch.fanOut` / `core.dispatch.join`
|
|
6
|
+
* event `$defs` on `run-event-payloads.schema.json`.
|
|
7
|
+
*
|
|
8
|
+
* Two layers:
|
|
9
|
+
*
|
|
10
|
+
* A. Always-on, server-free schema probe — the `DispatchConfig` schema accepts a
|
|
11
|
+
* valid parallel config (each `joinPolicy.mode`), the new `maxConcurrency`, and the
|
|
12
|
+
* `'parallel'` enum value; the `dispatchFanOut` / `dispatchJoin` event `$defs`
|
|
13
|
+
* validate well-formed payloads and reject malformed ones. (Registration-time
|
|
14
|
+
* validation_errors — joinPolicy-without-parallel, quorum-without-quorum-field — are
|
|
15
|
+
* HOST behaviors driven in layer B, since the wire schema admits the field shape;
|
|
16
|
+
* the cross-field MUSTs are enforced by the host at `POST /v1/workflows`.)
|
|
17
|
+
*
|
|
18
|
+
* B. Capability-gated behavioral legs — on a host advertising
|
|
19
|
+
* `capabilities.dispatch.fanOutSupported: true` (+ `"parallel"` in `fanOutPolicies`)
|
|
20
|
+
* that exposes the `POST /v1/host/sample/dispatch/fanout` test seam, a wait-all
|
|
21
|
+
* collect join completes with `joinOutcome: 'satisfied'` and a `children[]` of the
|
|
22
|
+
* right length, and the replay re-applies `outputMapping` in the logged `mergeOrder`.
|
|
23
|
+
* No conformant host advertises parallel fan-out yet — these legs soft-skip until a
|
|
24
|
+
* reference host wires it (the first witness toward `Active → Accepted`).
|
|
25
|
+
*
|
|
26
|
+
* @see spec/v1/node-packs.md §"core.dispatch parallel fan-out and join (RFC 0118)"
|
|
27
|
+
* @see spec/v1/capabilities.md §dispatch
|
|
28
|
+
* @see RFCS/0118-parallel-subworkflow-fan-out-and-join.md
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
import { describe, it, expect } from 'vitest';
|
|
32
|
+
import { readFileSync } from 'node:fs';
|
|
33
|
+
import { join } from 'node:path';
|
|
34
|
+
import Ajv2020 from 'ajv/dist/2020.js';
|
|
35
|
+
import addFormats from 'ajv-formats';
|
|
36
|
+
import { SCHEMAS_DIR } from '../lib/paths.js';
|
|
37
|
+
import { driver } from '../lib/driver.js';
|
|
38
|
+
import { behaviorGate } from '../lib/behavior-gate.js';
|
|
39
|
+
import { readCapabilityFamily } from '../lib/discovery-capabilities.js';
|
|
40
|
+
|
|
41
|
+
const DISPATCH_CONFIG = join(SCHEMAS_DIR, 'dispatch-config.schema.json');
|
|
42
|
+
const EVENT_PAYLOADS = join(SCHEMAS_DIR, 'run-event-payloads.schema.json');
|
|
43
|
+
|
|
44
|
+
function def(schemaPath: string, name: string): Record<string, unknown> {
|
|
45
|
+
const doc = JSON.parse(readFileSync(schemaPath, 'utf8')) as { $defs: Record<string, Record<string, unknown>> };
|
|
46
|
+
return doc.$defs[name];
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
describe('dispatch-fanout: DispatchConfig schema (always-on, server-free)', () => {
|
|
50
|
+
const ajv = new Ajv2020({ allErrors: true, strict: false });
|
|
51
|
+
addFormats(ajv);
|
|
52
|
+
const validate = ajv.compile(JSON.parse(readFileSync(DISPATCH_CONFIG, 'utf8')));
|
|
53
|
+
|
|
54
|
+
it("accepts fanOutPolicy 'parallel' with a wait-all/collect joinPolicy + maxConcurrency", () => {
|
|
55
|
+
const cfg = {
|
|
56
|
+
workerDispatchModel: 'child-run',
|
|
57
|
+
fanOutPolicy: 'parallel',
|
|
58
|
+
maxConcurrency: 5,
|
|
59
|
+
joinPolicy: { mode: 'wait-all', onChildFailure: 'collect' },
|
|
60
|
+
};
|
|
61
|
+
expect(validate(cfg), `node-packs.md §parallel fan-out — a valid parallel config MUST validate. Errors: ${JSON.stringify(validate.errors)}`).toBe(true);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('accepts each joinPolicy.mode (wait-all/quorum/first/race)', () => {
|
|
65
|
+
for (const jp of [
|
|
66
|
+
{ mode: 'wait-all' },
|
|
67
|
+
{ mode: 'quorum', quorum: 3 },
|
|
68
|
+
{ mode: 'first' },
|
|
69
|
+
{ mode: 'race' },
|
|
70
|
+
]) {
|
|
71
|
+
expect(validate({ fanOutPolicy: 'parallel', joinPolicy: jp }), `joinPolicy ${JSON.stringify(jp)} MUST validate`).toBe(true);
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('rejects an unknown fanOutPolicy and an unknown joinPolicy.mode', () => {
|
|
76
|
+
expect(validate({ fanOutPolicy: 'broadcast' }), "fanOutPolicy MUST be one of sequential/reject/parallel").toBe(false);
|
|
77
|
+
expect(validate({ fanOutPolicy: 'parallel', joinPolicy: { mode: 'any' } }), 'joinPolicy.mode MUST be in the closed enum').toBe(false);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('rejects an unknown onChildFailure and additionalProperties on joinPolicy', () => {
|
|
81
|
+
expect(validate({ fanOutPolicy: 'parallel', joinPolicy: { onChildFailure: 'explode' } }), 'onChildFailure MUST be collect/fail-fast/absorb').toBe(false);
|
|
82
|
+
expect(validate({ fanOutPolicy: 'parallel', joinPolicy: { mode: 'wait-all', extra: 1 } }), 'joinPolicy is additionalProperties:false').toBe(false);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('rejects a non-positive maxConcurrency', () => {
|
|
86
|
+
expect(validate({ fanOutPolicy: 'parallel', maxConcurrency: 0 }), 'maxConcurrency minimum is 1').toBe(false);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('the default config (no fanOutPolicy) stays valid — additive, default sequential', () => {
|
|
90
|
+
expect(validate({ workerDispatchModel: 'child-run' }), 'a pre-RFC-0118 config MUST stay valid').toBe(true);
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
describe('dispatch-fanout: event $defs (always-on, server-free)', () => {
|
|
95
|
+
const ajv = new Ajv2020({ allErrors: true, strict: false });
|
|
96
|
+
addFormats(ajv);
|
|
97
|
+
const validateFanOut = ajv.compile(def(EVENT_PAYLOADS, 'dispatchFanOut'));
|
|
98
|
+
const validateJoin = ajv.compile(def(EVENT_PAYLOADS, 'dispatchJoin'));
|
|
99
|
+
|
|
100
|
+
it('a well-formed core.dispatch.fanOut payload validates; fanOutPolicy is const "parallel"', () => {
|
|
101
|
+
expect(validateFanOut({ fanOutPolicy: 'parallel', childCount: 5, maxConcurrency: 5, joinMode: 'wait-all' })).toBe(true);
|
|
102
|
+
expect(validateFanOut({ fanOutPolicy: 'sequential', childCount: 5 }), 'fanOut is emitted only on the parallel path (const)').toBe(false);
|
|
103
|
+
expect(validateFanOut({ fanOutPolicy: 'parallel', childCount: 1 }), 'childCount minimum is 2 (> 1 by construction)').toBe(false);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('a well-formed core.dispatch.join payload carries mergeOrder (replay tiebreak)', () => {
|
|
107
|
+
const ok = {
|
|
108
|
+
joinOutcome: 'satisfied',
|
|
109
|
+
completedCount: 3,
|
|
110
|
+
failedCount: 0,
|
|
111
|
+
cancelledCount: 0,
|
|
112
|
+
mergeOrder: ['run-a', 'run-b', 'run-c'],
|
|
113
|
+
};
|
|
114
|
+
expect(validateJoin(ok), `node-packs.md §parallel — join carries mergeOrder. Errors: ${JSON.stringify(validateJoin.errors)}`).toBe(true);
|
|
115
|
+
expect(validateJoin({ joinOutcome: 'satisfied', completedCount: 3, failedCount: 0 }), 'mergeOrder is required (replay determinism)').toBe(false);
|
|
116
|
+
expect(validateJoin({ ...ok, joinOutcome: 'aborted' }), 'joinOutcome MUST be satisfied/failed/partial').toBe(false);
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
describe('dispatch-fanout: parallel behavior (capability-gated, RFC 0118)', () => {
|
|
121
|
+
it('a wait-all/collect parallel fan-out joins on all children with joinOutcome satisfied', async () => {
|
|
122
|
+
const dispatch = await readCapabilityFamily<{ fanOutSupported?: boolean; fanOutPolicies?: string[] }>('dispatch');
|
|
123
|
+
if (!behaviorGate('dispatch.fanOutSupported', dispatch?.fanOutSupported === true)) return;
|
|
124
|
+
if (!(dispatch?.fanOutPolicies ?? []).includes('parallel')) return; // parallel unsupported → out of scope
|
|
125
|
+
|
|
126
|
+
const res = await driver.post('/v1/host/sample/dispatch/fanout', {
|
|
127
|
+
nextWorkerIds: ['conformance.child.a', 'conformance.child.b', 'conformance.child.c'],
|
|
128
|
+
config: { fanOutPolicy: 'parallel', joinPolicy: { mode: 'wait-all', onChildFailure: 'collect' } },
|
|
129
|
+
});
|
|
130
|
+
if (res.status === 404 || res.status === 403) return; // seam unwired — soft-skip
|
|
131
|
+
|
|
132
|
+
const body = res.json as { joinOutcome?: string; children?: unknown[]; mergeOrder?: string[] } | undefined;
|
|
133
|
+
expect(
|
|
134
|
+
body?.joinOutcome,
|
|
135
|
+
driver.describe('node-packs.md §parallel fan-out', 'wait-all + collect over all-completing children → joinOutcome satisfied'),
|
|
136
|
+
).toBe('satisfied');
|
|
137
|
+
expect(
|
|
138
|
+
body?.children?.length,
|
|
139
|
+
driver.describe('node-packs.md §parallel fan-out', 'the output children[] reports every dispatched child'),
|
|
140
|
+
).toBe(3);
|
|
141
|
+
expect(
|
|
142
|
+
Array.isArray(body?.mergeOrder),
|
|
143
|
+
driver.describe('node-packs.md §parallel fan-out', 'the join records mergeOrder for replay-deterministic output merge'),
|
|
144
|
+
).toBe(true);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it('parallel fan-out is rejected at registration on a host advertising fanOutSupported:false', async () => {
|
|
148
|
+
const dispatch = await readCapabilityFamily<{ supported?: boolean; fanOutSupported?: boolean }>('dispatch');
|
|
149
|
+
if (!dispatch?.supported) return; // no dispatch surface → out of scope
|
|
150
|
+
if (dispatch.fanOutSupported === true) return; // this leg targets non-supporting hosts
|
|
151
|
+
|
|
152
|
+
const res = await driver.post('/v1/workflows', {
|
|
153
|
+
id: 'conformance.dispatch.parallel-unsupported',
|
|
154
|
+
nodes: [{ nodeId: 'd', typeId: 'core.dispatch', config: { fanOutPolicy: 'parallel', joinPolicy: { mode: 'wait-all' } } }],
|
|
155
|
+
});
|
|
156
|
+
if (res.status === 404) return; // registration surface not exposed here — soft-skip
|
|
157
|
+
|
|
158
|
+
expect(
|
|
159
|
+
res.status >= 400 && res.status < 500,
|
|
160
|
+
driver.describe('node-packs.md §parallel fan-out', "a host advertising fanOutSupported:false MUST reject fanOutPolicy:'parallel' with a validation_error (4xx), never accept it"),
|
|
161
|
+
).toBe(true);
|
|
162
|
+
});
|
|
163
|
+
});
|
|
@@ -161,16 +161,24 @@ describe('ui-plugin/1 message: schema layer (always-on, server-free)', () => {
|
|
|
161
161
|
});
|
|
162
162
|
|
|
163
163
|
describe('frontend-plugin: isolation advertisement (always-on, capability shape)', () => {
|
|
164
|
-
|
|
164
|
+
// RFC 0119: `isolation` is a categorical model, not a single browser const. A conformant host
|
|
165
|
+
// advertises a member of the enum (cross-origin-iframe default) or an x-host-* vendor model —
|
|
166
|
+
// every value denotes the SAME mandatory property (in-process loading is a protocol-tier MUST NOT,
|
|
167
|
+
// regardless of mechanism). cross-origin-iframe stays valid + default, so existing browser hosts
|
|
168
|
+
// pass unchanged; a weaker/unknown non-x-host value is rejected.
|
|
169
|
+
const CONFORMANT_ISOLATION = ['cross-origin-iframe', 'wasm', 'process', 'container', 'vm'];
|
|
170
|
+
const X_HOST = /^x-host-[a-z0-9-]+-[a-z0-9-]+$/;
|
|
171
|
+
it('a host advertising uiPlugins MUST advertise a conformant isolation model', async () => {
|
|
165
172
|
const uiPlugins = await readCapabilityFamily<{ supported?: boolean; isolation?: string }>('uiPlugins');
|
|
166
173
|
if (!uiPlugins?.supported) return; // unadvertised → out of scope (graceful degradation)
|
|
174
|
+
const iso = uiPlugins.isolation;
|
|
167
175
|
expect(
|
|
168
|
-
|
|
176
|
+
iso !== undefined && (CONFORMANT_ISOLATION.includes(iso) || X_HOST.test(iso)),
|
|
169
177
|
driver.describe(
|
|
170
|
-
'frontend-plugin-packs.md §Isolation',
|
|
171
|
-
'frontend-plugin-isolation —
|
|
178
|
+
'frontend-plugin-packs.md §Isolation (RFC 0119)',
|
|
179
|
+
'frontend-plugin-isolation — isolation MUST be a conformant model (cross-origin-iframe default | wasm | process | container | vm | x-host-*); every value denotes the same property, in-process loading is a protocol-tier MUST NOT regardless of mechanism',
|
|
172
180
|
),
|
|
173
|
-
).toBe(
|
|
181
|
+
).toBe(true);
|
|
174
182
|
});
|
|
175
183
|
});
|
|
176
184
|
|