@openwop/openwop-conformance 1.10.0 → 1.12.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.
Files changed (60) hide show
  1. package/CHANGELOG.md +48 -0
  2. package/README.md +2 -2
  3. package/api/asyncapi.yaml +70 -0
  4. package/api/openapi.yaml +268 -1
  5. package/coverage.md +33 -2
  6. package/fixtures/oauth-providers/synthetic.json +38 -0
  7. package/fixtures.md +10 -0
  8. package/package.json +1 -1
  9. package/schemas/README.md +12 -0
  10. package/schemas/agent-deployment-transition.schema.json +49 -0
  11. package/schemas/agent-deployment.schema.json +54 -0
  12. package/schemas/agent-eval-suite.schema.json +140 -0
  13. package/schemas/agent-inventory-response.schema.json +25 -0
  14. package/schemas/agent-manifest.schema.json +5 -0
  15. package/schemas/agent-org-chart.schema.json +82 -0
  16. package/schemas/agent-ref.schema.json +12 -2
  17. package/schemas/agent-roster-entry.schema.json +81 -0
  18. package/schemas/agent-roster-response.schema.json +21 -0
  19. package/schemas/budget-policy.schema.json +18 -0
  20. package/schemas/capabilities.schema.json +277 -0
  21. package/schemas/credential-provenance.schema.json +18 -0
  22. package/schemas/eval-summary.schema.json +92 -0
  23. package/schemas/node-pack-manifest.schema.json +17 -0
  24. package/schemas/org-chart-responsibility-view.schema.json +26 -0
  25. package/schemas/run-event-payloads.schema.json +286 -3
  26. package/schemas/run-event.schema.json +19 -0
  27. package/schemas/tool-descriptor.schema.json +63 -0
  28. package/schemas/trigger-subscription.schema.json +26 -0
  29. package/src/lib/agentOrgChart.ts +82 -0
  30. package/src/lib/agentRoster.ts +76 -0
  31. package/src/lib/liveRuntime.ts +59 -0
  32. package/src/lib/profiles.ts +157 -0
  33. package/src/lib/runtimeRequires.ts +38 -0
  34. package/src/lib/safeFetch.ts +87 -0
  35. package/src/lib/triggerBridge.ts +74 -0
  36. package/src/scenarios/agent-deployment-shape.test.ts +139 -0
  37. package/src/scenarios/agent-eval-suite-shape.test.ts +167 -0
  38. package/src/scenarios/agent-live-allowlist-enforced.test.ts +53 -0
  39. package/src/scenarios/agent-live-invocation-bracket.test.ts +98 -0
  40. package/src/scenarios/agent-live-runtime-shape.test.ts +98 -0
  41. package/src/scenarios/agent-live-structured-output.test.ts +58 -0
  42. package/src/scenarios/agent-org-chart-scoping.test.ts +137 -0
  43. package/src/scenarios/agent-org-chart-shape.test.ts +127 -0
  44. package/src/scenarios/agent-platform-profile.test.ts +158 -0
  45. package/src/scenarios/agent-roster-attribution.test.ts +179 -0
  46. package/src/scenarios/agent-roster-shape.test.ts +146 -0
  47. package/src/scenarios/budget-policy-shape.test.ts +136 -0
  48. package/src/scenarios/egress-provenance-shape.test.ts +137 -0
  49. package/src/scenarios/memory-capability-model-shape.test.ts +186 -0
  50. package/src/scenarios/oauth-authorization-code-roundtrip.test.ts +145 -0
  51. package/src/scenarios/org-position-no-authority-escalation.test.ts +78 -0
  52. package/src/scenarios/runtime-requires-install-gate.test.ts +92 -0
  53. package/src/scenarios/runtime-requires-shape.test.ts +134 -0
  54. package/src/scenarios/safefetch-behavior.test.ts +99 -0
  55. package/src/scenarios/safefetch-live-audit.test.ts +175 -0
  56. package/src/scenarios/spec-corpus-validity.test.ts +19 -3
  57. package/src/scenarios/tool-descriptor-shape.test.ts +133 -0
  58. package/src/scenarios/trigger-bridge-delivery.test.ts +126 -0
  59. package/src/scenarios/trigger-bridge-shape.test.ts +135 -0
  60. package/src/scenarios/x-openwop-form-pack-manifest.test.ts +155 -0
@@ -0,0 +1,82 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://openwop.dev/spec/v1/agent-org-chart.schema.json",
4
+ "title": "AgentOrgChart",
5
+ "description": "RFC 0087 §A. A tenant-scoped, DESCRIPTIVE grouping of RFC 0086 standing roster members (agent-roster-entry.schema.json) into departments + roles with acyclic `reportsTo` edges. The load-bearing constraint (§B `org-position-no-authority-escalation`): an org edge confers NO authority — there is NO `permissions`/`scopes`/`canDispatch`/`authority` field anywhere in this schema BY DESIGN, and every object is `additionalProperties:false` so a host cannot smuggle one in. Authority stays in `toolAllowlist` (RFC 0002 §A14), RBAC (RFC 0049, the sole authority source, fail-closed), and approval gates (RFC 0051); org position MUST NOT be consulted as an authorization input. Position describes; it never authorizes. Tenant-scoped (RFC 0074): a chart is served only to its `owner` triple. The record is host-internal; this is the canonical wire shape behind GET /v1/agents/org-chart (the endpoint lands at Active → Accepted per RFC 0087 §Conformance). The `Department`/`Role`/`Member` subschemas are published as named `$defs` so dependent schemas (e.g. org-chart-responsibility-view.schema.json) reference a stable anchor rather than a positional `properties/.../items` pointer.",
6
+ "type": "object",
7
+ "additionalProperties": false,
8
+ "required": ["owner", "departments", "members"],
9
+ "properties": {
10
+ "owner": {
11
+ "type": "object",
12
+ "additionalProperties": false,
13
+ "required": ["tenantId"],
14
+ "description": "RFC 0048 owner triple the chart is scoped by (RFC 0074). A chart is served only to a principal within this triple; a member/department/edge outside it is never disclosed (CTI-1 carry-forward).",
15
+ "properties": {
16
+ "tenantId": { "type": "string", "minLength": 1, "maxLength": 256, "description": "Owning tenant." },
17
+ "workspaceId": { "type": "string", "minLength": 1, "maxLength": 256, "description": "MAY. Owning workspace within the tenant." }
18
+ }
19
+ },
20
+ "departments": {
21
+ "type": "array",
22
+ "description": "The departments, forming a tree via `parentDepartmentId`.",
23
+ "items": { "$ref": "#/$defs/Department" }
24
+ },
25
+ "members": {
26
+ "type": "array",
27
+ "description": "The members — each is an RFC 0086 roster instance placed in a department + role, with an optional reporting edge. Every field is descriptive; there is NO authority-bearing field (§B).",
28
+ "items": { "$ref": "#/$defs/Member" }
29
+ }
30
+ },
31
+ "$defs": {
32
+ "Department": {
33
+ "type": "object",
34
+ "additionalProperties": false,
35
+ "required": ["departmentId", "name", "roles"],
36
+ "properties": {
37
+ "departmentId": { "type": "string", "minLength": 1, "maxLength": 128, "description": "Stable department id, unique within the chart." },
38
+ "name": { "type": "string", "minLength": 1, "maxLength": 200, "description": "Human department name (e.g. \"Marketing\")." },
39
+ "parentDepartmentId": {
40
+ "type": ["string", "null"],
41
+ "maxLength": 128,
42
+ "description": "MAY. The parent department id (department nesting); `null` for a top-level department. A host advertising `agents.orgChart.departmentNesting: false` MUST reject a non-null value."
43
+ },
44
+ "roles": {
45
+ "type": "array",
46
+ "description": "The roles defined in this department.",
47
+ "items": { "$ref": "#/$defs/Role" }
48
+ }
49
+ }
50
+ },
51
+ "Role": {
52
+ "type": "object",
53
+ "additionalProperties": false,
54
+ "required": ["roleId", "name"],
55
+ "properties": {
56
+ "roleId": { "type": "string", "minLength": 1, "maxLength": 128, "description": "Stable role id, unique within the chart." },
57
+ "name": { "type": "string", "minLength": 1, "maxLength": 200, "description": "Human role name (e.g. \"Campaign Manager\"). DESCRIPTIVE — a role grants no authority (§B)." }
58
+ }
59
+ },
60
+ "Member": {
61
+ "type": "object",
62
+ "additionalProperties": false,
63
+ "required": ["rosterId", "departmentId", "roleId", "reportsTo"],
64
+ "properties": {
65
+ "rosterId": {
66
+ "type": "string",
67
+ "pattern": "^host:[a-z0-9][a-z0-9._-]*$",
68
+ "minLength": 6,
69
+ "maxLength": 128,
70
+ "description": "An RFC 0086 roster entry id (`agent-roster-entry.schema.json` `rosterId`). MUST reference a roster entry in the same `owner` tenant (no cross-tenant membership — §C)."
71
+ },
72
+ "departmentId": { "type": "string", "minLength": 1, "maxLength": 128, "description": "The department this member belongs to (MUST exist in `departments[]`)." },
73
+ "roleId": { "type": "string", "minLength": 1, "maxLength": 128, "description": "The member's role (MUST exist in the chart's roles)." },
74
+ "reportsTo": {
75
+ "type": ["string", "null"],
76
+ "maxLength": 128,
77
+ "description": "Another member's `rosterId` (the manager), or `null` for the root. The edge set MUST be acyclic (a cycle is a `validation_error`, §A). A `reportsTo` edge is METADATA ONLY — it confers no authority over the report (§B `org-position-no-authority-escalation`)."
78
+ }
79
+ }
80
+ }
81
+ }
82
+ }
@@ -41,7 +41,13 @@
41
41
  "version": {
42
42
  "type": "string",
43
43
  "maxLength": 64,
44
- "description": "Optional version pin for the agent definition (matches `AgentManifest.version`). Lets audit consumers trace which version of an agent definition was active for a given run. Pinning is encouraged for replay determinism; absent values mean 'host's current resolution of the agent'."
44
+ "description": "Optional EXACT version pin for the agent definition (matches `AgentManifest.version`). Lets audit consumers trace which version of an agent definition was active for a given run. Pinning is encouraged for replay determinism; absent values mean 'host's current resolution of the agent'. Mutually exclusive with `channel` (RFC 0082 §A) — set at most one."
45
+ },
46
+ "channel": {
47
+ "type": "string",
48
+ "minLength": 1,
49
+ "maxLength": 64,
50
+ "description": "RFC 0082 §A. Optional NAMED deployment-channel binding (e.g. `stable`, `canary`, or the reserved `latest` = highest active semver), as an alternative to the exact `version` pin. A host advertising `capabilities.agents.deployment.supported: true` resolves the channel to a concrete version and pins it per-(run, agentId, channel) at first resolution (RFC 0082 §B) — the resolved version is a recorded fact carried on `agent.invocation.started.resolvedAgentVersion`, re-read on replay and NEVER re-resolved against a moved channel. Mutually exclusive with `version` (the `not` clause below). A channel that resolves to no `active` version fails the run with `no_active_deployment`. Hosts that omit `agents.deployment` MUST reject a `channel`-bearing ref with `validation_error` (the channel has nowhere to resolve)."
45
51
  },
46
52
  "sourceManifestId": {
47
53
  "type": "string",
@@ -49,5 +55,9 @@
49
55
  "description": "Optional provenance pointer back to the `AgentManifest.agentId` this AgentRef was projected from. Lets pack-aware hosts trace runtime AgentRefs back to their distribution origin. Absent for host-internal `host:<id>` agents (which have no manifest)."
50
56
  }
51
57
  },
52
- "additionalProperties": false
58
+ "additionalProperties": false,
59
+ "not": {
60
+ "required": ["version", "channel"],
61
+ "$comment": "RFC 0082 §A. `version` (exact pin) and `channel` (named deployment channel) are mutually exclusive — a ref MAY set at most one. Setting both is a validation_error. Setting neither is valid (host-default resolution). Additive-safe: no pre-RFC-0082 ref can carry `channel`, so this clause never invalidates an existing AgentRef."
62
+ }
53
63
  }
@@ -0,0 +1,81 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://openwop.dev/spec/v1/agent-roster-entry.schema.json",
4
+ "title": "AgentRosterEntry",
5
+ "description": "RFC 0086 §A. A standing agent INSTANCE — a named, tenant-scoped, mutable agent (the 'digital-twin employee', e.g. \"Sally\") that REFERENCES a manifest/deployment (RFC 0070/0082) and OWNS a workflow portfolio (the workflows it is responsible for by role). Distinct from the immutable AgentManifest (the pack-distribution class) and from the RFC 0082 deployment record (the per-version channel). `rosterId` IS a dispatchable `host:<id>` AgentRef agentId — the runtime-synthesis namespace RFC 0002 reserves for host-internal agents that don't ship as packs (NOT a parallel id space): dispatching it resolves the bound `agentRef` and projects `persona`. Content-free of any system-prompt body or credential material (SR-1). The record is host-internal + mutable; this schema is the canonical wire shape a host exposes via GET /v1/agents/roster (the endpoint lands at Active → Accepted per RFC 0086 §Conformance).",
6
+ "type": "object",
7
+ "additionalProperties": false,
8
+ "required": ["rosterId", "persona", "agentRef", "owner"],
9
+ "properties": {
10
+ "rosterId": {
11
+ "type": "string",
12
+ "pattern": "^host:[a-z0-9][a-z0-9._-]*$",
13
+ "minLength": 6,
14
+ "maxLength": 128,
15
+ "description": "Host-issued stable instance id in the reserved `host:<id>` AgentRef form (RFC 0002 / RFC 0086 §A). The dispatch handle for 'run as this agent': a `WorkflowNode.agent: { agentId: rosterId }` resolves to this entry's `agentRef` + stamps `persona`."
16
+ },
17
+ "persona": {
18
+ "type": "string",
19
+ "minLength": 1,
20
+ "maxLength": 200,
21
+ "description": "Human display name (e.g. \"Sally\"). Reuses `AgentRef.persona` semantics (RFC 0002) — projected onto the dispatch AgentRef. Free-form; MAY collide within a tenant (`rosterId` is the uniqueness key)."
22
+ },
23
+ "agentRef": {
24
+ "type": "object",
25
+ "additionalProperties": false,
26
+ "required": ["agentId"],
27
+ "description": "The manifest/deployment this instance instantiates (a trimmed AgentRef — RFC 0002). `version` (exact) XOR `channel` (RFC 0082) — never both; absent ⇒ host-default resolution (RFC 0070).",
28
+ "properties": {
29
+ "agentId": {
30
+ "type": "string",
31
+ "minLength": 3,
32
+ "maxLength": 256,
33
+ "description": "The manifest agentId this instance runs (matches `AgentManifest.agentId`). MUST be resolvable by the host (RFC 0070)."
34
+ },
35
+ "version": {
36
+ "type": "string",
37
+ "maxLength": 64,
38
+ "description": "Exact agent-definition version pin (RFC 0002). Mutually exclusive with `channel`."
39
+ },
40
+ "channel": {
41
+ "type": "string",
42
+ "minLength": 1,
43
+ "maxLength": 64,
44
+ "description": "Named deployment channel (RFC 0082 §A), e.g. `stable`. Resolved + pinned per run at first resolution. Mutually exclusive with `version`."
45
+ }
46
+ },
47
+ "not": { "required": ["version", "channel"] }
48
+ },
49
+ "workflows": {
50
+ "type": "array",
51
+ "uniqueItems": true,
52
+ "items": { "type": "string", "minLength": 1, "maxLength": 128 },
53
+ "description": "The standing portfolio: workflow ids this agent owns by role (RFC 0086 §A/§B). Each MUST be resolvable by the host and within the entry's `owner` tenant scope (the WCT/CTI carry-forward). Absent ⇒ empty portfolio."
54
+ },
55
+ "owner": {
56
+ "type": "object",
57
+ "additionalProperties": false,
58
+ "required": ["tenantId"],
59
+ "description": "RFC 0048 owner triple the entry is scoped by. On a `'tenant'`-install host (RFC 0074), GET /v1/agents/roster returns only entries within the caller's owner triple; a cross-tenant entry 404s.",
60
+ "properties": {
61
+ "tenantId": { "type": "string", "minLength": 1, "maxLength": 256, "description": "Owning tenant." },
62
+ "workspaceId": { "type": "string", "minLength": 1, "maxLength": 256, "description": "MAY. Owning workspace within the tenant." }
63
+ }
64
+ },
65
+ "enabled": {
66
+ "type": "boolean",
67
+ "description": "When `false`, the entry's portfolio triggers are inert (no run fires) — the member is paused but still discoverable (RFC 0086 §A). Absent ⇒ `true`."
68
+ },
69
+ "label": {
70
+ "type": "string",
71
+ "minLength": 1,
72
+ "maxLength": 100,
73
+ "description": "MAY. Short UI label; falls back to `persona`."
74
+ },
75
+ "description": {
76
+ "type": "string",
77
+ "maxLength": 500,
78
+ "description": "MAY. One-line summary for catalog / console surfaces."
79
+ }
80
+ }
81
+ }
@@ -0,0 +1,21 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://openwop.dev/spec/v1/agent-roster-response.schema.json",
4
+ "title": "AgentRosterResponse",
5
+ "description": "RFC 0086 §B. Response body for `GET /v1/agents/roster` — the standing agent roster (named instances + their workflow portfolios) visible to the authenticated principal. Tenant-scoped per RFC 0074 on an `installScope: 'tenant'` host: only the caller's owner-triple entries are returned. Each entry is the canonical `agent-roster-entry.schema.json` shape; content-free of any system-prompt body or credential material (SR-1).",
6
+ "type": "object",
7
+ "additionalProperties": false,
8
+ "required": ["roster", "total"],
9
+ "properties": {
10
+ "roster": {
11
+ "type": "array",
12
+ "items": { "$ref": "./agent-roster-entry.schema.json" },
13
+ "description": "The standing roster entries, in a stable (rosterId-sorted) order."
14
+ },
15
+ "total": {
16
+ "type": "integer",
17
+ "minimum": 0,
18
+ "description": "Count of entries returned (== roster.length)."
19
+ }
20
+ }
21
+ }
@@ -0,0 +1,18 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://openwop.dev/spec/v1/budget-policy.schema.json",
4
+ "title": "BudgetPolicy",
5
+ "description": "RFC 0084 §A. The reserved `budget` run-options key (on `RunOptions.configurable`) — an enforceable per-run SPEND policy. Wall-time + loop-iterations are deliberately NOT here: those are RFC 0058's `runTimeoutMs` / `maxLoopIterations` (the §E orthogonality seam). 0084 governs spend; 0058 governs execution bounds; they share only the `cap.breached` overflow event. Every dimension is optional; an absent dimension is unbounded (host default).",
6
+ "type": "object",
7
+ "additionalProperties": false,
8
+ "properties": {
9
+ "maxTokens": { "type": "integer", "minimum": 1, "description": "Total input+output tokens across all `provider.usage` (RFC 0026) in the run." },
10
+ "maxCostUsd": { "type": "number", "minimum": 0, "description": "Total cost summed from `provider.usage` `costEstimateUsd` (host-defined pricing per RFC 0026)." },
11
+ "maxToolCalls": { "type": "integer", "minimum": 1, "description": "Total `agent.toolCalled` events in the run." },
12
+ "maxRetries": { "type": "integer", "minimum": 0, "description": "Ceiling over the cumulative RFC 0009 retry count (node + envelope retries); the run fails when cumulative retries hit it. NOT a separate retry mechanism (RFC 0084 §UQ4)." },
13
+ "modelAllow": { "type": "array", "items": { "type": "string" }, "uniqueItems": true, "description": "Optional allowlist (glob over provider model ids, e.g. `claude-*`). A run resolving to a model outside the allowlist is refused with `budget_model_denied` before the call." },
14
+ "modelDeny": { "type": "array", "items": { "type": "string" }, "uniqueItems": true, "description": "Optional denylist. `modelDeny` wins on conflict with `modelAllow` (fail-closed)." },
15
+ "thresholdPercent": { "type": "number", "minimum": 0, "maximum": 100, "description": "Emit `budget.threshold.crossed` once per dimension at this percent of the dimension's limit." },
16
+ "onExhaustion": { "type": "string", "enum": ["fail", "interrupt"], "description": "`fail` (default): `cap.breached{kind:\"budget-*\"}` → `run.failed{budget_exhausted}`. `interrupt`: raise an RFC 0051 approval interrupt so a human MAY extend the budget." }
17
+ }
18
+ }
@@ -54,6 +54,16 @@
54
54
  "type": "integer",
55
55
  "minimum": 1,
56
56
  "description": "RFC 0058. Authoritative engine-side ceiling on agent-loop iterations. Upper bound for `RunOptions.configurable.maxLoopIterations`. Breach emits `cap.breached { kind: 'loop-iterations' }` + error `loop_limit_exceeded`. Optional; hosts that advertise it MUST enforce it."
57
+ },
58
+ "maxBudgetTokens": {
59
+ "type": "integer",
60
+ "minimum": 1,
61
+ "description": "RFC 0084. Engine-side ceiling clamping `RunOptions.configurable.budget.maxTokens` (`min(requested, ceiling)`). Optional; only meaningful with `capabilities.budget`."
62
+ },
63
+ "maxBudgetCostUsd": {
64
+ "type": "number",
65
+ "minimum": 0,
66
+ "description": "RFC 0084. Engine-side cost ceiling clamping `RunOptions.configurable.budget.maxCostUsd`. Optional; only meaningful with `capabilities.budget`."
57
67
  }
58
68
  },
59
69
  "additionalProperties": false,
@@ -866,6 +876,140 @@
866
876
  }
867
877
  }
868
878
  },
879
+ "liveRuntime": {
880
+ "type": "object",
881
+ "description": "RFC 0077. The host executes manifest agents against LIVE models and tools (not the deterministic RFC 0070 sample floor) per the normative AgentManifest→live-run mapping (`multi-agent-execution.md` §\"Live manifest dispatch\"), and emits the `agent.invocation.started`/`agent.invocation.completed` content-free bracket around the existing `agent.*` family. REQUIRES `agents.manifestRuntime.supported: true` — `liveRuntime` is a strict superset of the floor. The floor's mandatory safety guarantees (toolAllowlist enforcement, handoff inbound validation, tenant scoping, untrusted-model-output handling, fail-closed per-tool authorization) stay unconditional under `liveRuntime`. Hosts that omit this block run the floor only; the behavioral conformance scenarios skip cleanly.",
882
+ "additionalProperties": false,
883
+ "required": ["supported"],
884
+ "properties": {
885
+ "supported": {
886
+ "type": "boolean",
887
+ "description": "REQUIRED when present. When `true`, the host performs live manifest dispatch per the §B mapping and emits the `agent.invocation.*` bracket. Gated on `agents.manifestRuntime.supported: true`."
888
+ },
889
+ "structuredOutput": {
890
+ "type": "boolean",
891
+ "description": "When `true`, the host validates the terminal result against the agent's `handoff.returnSchemaRef` and fails the run with a structured-output error on a non-conforming result rather than shipping it. Absent ⇒ `false` (runs live but does not enforce `returnSchemaRef`)."
892
+ },
893
+ "confidenceEscalation": {
894
+ "type": "boolean",
895
+ "description": "When `true`, the host honors `AgentManifest.confidence.defaultThreshold` and triggers the RFC 0002 §F escalation contract when an `agent.decided` confidence falls below the effective threshold, rather than silently accepting the decision. Absent ⇒ `false`."
896
+ },
897
+ "sources": {
898
+ "type": "array",
899
+ "uniqueItems": true,
900
+ "items": { "type": "string", "enum": ["workflow-node", "run-api", "chat-mention"] },
901
+ "description": "Which invocation entry points the host exposes for live manifest dispatch. `workflow-node`: an agent step inside a workflow run (RFC 0072 §B); `run-api`: an agent as the root of POST /v1/runs; `chat-mention`: a chat @agent invocation mapped onto the run surface. Enum membership is NOT mandatory — a host with no chat surface simply omits `chat-mention`. Absent ⇒ `['workflow-node']` (the RFC 0072 §B normative path). All advertised sources MUST emit the identical `agent.invocation.*` + `agent.*` event family."
902
+ }
903
+ }
904
+ },
905
+ "evalSuite": {
906
+ "type": "object",
907
+ "description": "RFC 0081. The host runs portable `agent-eval-suite.schema.json` suites as eval runs (a `mode: \"eval\"` projection over POST /v1/runs, RFC 0081 §B), emits the `eval.started`/`eval.scored`/`eval.completed` content-free family, and terminates with an `eval-summary.schema.json` scorecard. Composes RFC 0026 (per-task cost), RFC 0054 (regression baseline diff), RFC 0056 (human override of an auto-score). Hosts that omit this block reject `mode: \"eval\"` with 501; the behavioral conformance scenario soft-skips. The summary + events are content-free (SECURITY invariant `eval-summary-no-content-leak`).",
908
+ "additionalProperties": false,
909
+ "required": ["supported"],
910
+ "properties": {
911
+ "supported": {
912
+ "type": "boolean",
913
+ "description": "REQUIRED when present. When `true`, the host accepts `mode: \"eval\"` runs against an `evalSuiteRef`, scores each task, and serves `GET /v1/runs/{runId}/eval-summary`."
914
+ },
915
+ "modes": {
916
+ "type": "array",
917
+ "uniqueItems": true,
918
+ "items": { "type": "string", "enum": ["golden", "rubric", "adversarial", "regression", "live-shadow"] },
919
+ "description": "Which eval modes the host actually implements (RFC 0081 §D closed vocabulary). Truthful advertisement (RFC 0031): a host advertises ONLY the modes it gates on; a suite requesting an unadvertised mode is rejected at run-create with `400 validation_error`. Absent ⇒ no modes (the host advertises `supported` but gates nothing — effectively shape-only)."
920
+ },
921
+ "maxTasksPerSuite": {
922
+ "type": "integer",
923
+ "minimum": 1,
924
+ "description": "MAY. Host ceiling on tasks per suite; a suite exceeding it is rejected at run-create (the RFC 0058 §A clamp pattern)."
925
+ },
926
+ "maxCostUsdPerSuite": {
927
+ "type": "number",
928
+ "minimum": 0,
929
+ "description": "MAY. Host ceiling on total eval-run cost; composes RFC 0084 budget enforcement when advertised."
930
+ }
931
+ }
932
+ },
933
+ "deployment": {
934
+ "type": "object",
935
+ "description": "RFC 0082. The host implements an agent deployment lifecycle: per-(agentId, version) deployment records with the seven-state machine (draft/test/staged/active/paused/deprecated/rolled-back), named-channel binding (`agentId@channel` / `@latest` resolved + pinned per-(run, agentId, channel) at first resolution per §B), optional canary traffic-split, a rollback pointer, the content-free `deployment.*` audit events, and the `POST /v1/agents/{agentId}/deployments` promotion contract composing RFC 0049 (`deploy:*` fail-closed scopes) + RFC 0051 (approvalGate) + RFC 0081 (`requiredEval`). Hosts that omit this block reject a `channel`-bearing `AgentRef` with `validation_error` and 501 the deployment endpoint. The promotion endpoint + behavioral lifecycle scenario + reference-host store land at Active → Accepted.",
936
+ "additionalProperties": false,
937
+ "required": ["supported"],
938
+ "properties": {
939
+ "supported": {
940
+ "type": "boolean",
941
+ "description": "REQUIRED when present. When `true`, the host resolves `AgentRef.channel` bindings, serves deployment records, and accepts promotion transitions."
942
+ },
943
+ "channels": {
944
+ "type": "array",
945
+ "uniqueItems": true,
946
+ "items": { "type": "string", "minLength": 1 },
947
+ "description": "The named channels the host resolves (e.g. `[\"stable\", \"canary\", \"latest\"]`). Truthful advertisement (RFC 0031): a `channel` not in this list resolves to no version and fails the run with `no_active_deployment`."
948
+ },
949
+ "canary": {
950
+ "type": "boolean",
951
+ "description": "When `true`, the host implements canary traffic-split (a per-run §B draw assigns the run to one of the channel's active versions by `canaryPercent`). When `false`/absent, the host MUST reject any `canaryPercent < 100`."
952
+ },
953
+ "rollback": {
954
+ "type": "boolean",
955
+ "description": "When `true`, the host implements the `rollbackPointer` recovery path (active→rolled-back restoring a prior version to active)."
956
+ },
957
+ "states": {
958
+ "type": "array",
959
+ "uniqueItems": true,
960
+ "items": { "type": "string", "enum": ["draft", "test", "staged", "active", "paused", "deprecated", "rolled-back"] },
961
+ "description": "The subset of the seven lifecycle states the host implements. Truthful advertisement (RFC 0031): the host MUST reject a transition into a state not advertised here."
962
+ }
963
+ }
964
+ },
965
+ "roster": {
966
+ "type": "object",
967
+ "description": "RFC 0086. The host maintains a standing agent roster: named, tenant-scoped agent INSTANCES (the 'digital-twin employee') that reference a manifest/deployment and own a workflow portfolio, discoverable via GET /v1/agents/roster, with trigger-fired portfolio runs attributed to the member via the content-free `roster.run.initiated` event. REQUIRES `agents.manifestRuntime.supported: true` (a roster entry instantiates a manifest agent). Triggers compose RFC 0052 (schedule) + RFC 0083 (durable work-item bridge) — no new WorkflowTrigger.type; the concrete work surface (a Kanban board) stays a host/vendor extension (§E). Hosts that omit this block do not maintain a roster (the roster reads 501). The roster-management endpoints + behavioral attribution scenario + reference-host store land at Active → Accepted.",
968
+ "additionalProperties": false,
969
+ "required": ["supported"],
970
+ "properties": {
971
+ "supported": {
972
+ "type": "boolean",
973
+ "description": "REQUIRED when present. When `true`, the host serves the standing roster, projects it on the inventory, and emits `roster.run.initiated` on trigger-fired portfolio runs."
974
+ },
975
+ "installScope": {
976
+ "type": "string",
977
+ "enum": ["host", "tenant"],
978
+ "description": "RFC 0074 carry-forward. `host`: a single global roster. `tenant`: roster entries are scoped per owner triple; GET /v1/agents/roster returns only the caller's entries and a cross-tenant entry 404s. MUST equal `agents.manifestRuntime.installScope` (a roster cannot be host-global while its manifests are tenant-scoped, or vice-versa)."
979
+ },
980
+ "portfolioTriggerSources": {
981
+ "type": "array",
982
+ "uniqueItems": true,
983
+ "items": { "type": "string", "minLength": 1 },
984
+ "description": "Which RFC 0052/0083 trigger sources fire portfolio runs on this host (e.g. `[\"schedule\", \"queue\", \"webhook\"]`). Truthful advertisement (RFC 0031): a source not listed does not fire portfolios here."
985
+ }
986
+ }
987
+ },
988
+ "orgChart": {
989
+ "type": "object",
990
+ "description": "RFC 0087. The host maintains a tenant-scoped, DESCRIPTIVE org-chart over RFC 0086 roster members: departments + roles with acyclic `reportsTo` edges + a derived responsibility roll-up, discoverable via GET /v1/agents/org-chart. The load-bearing guarantee (§B `org-position-no-authority-escalation`): an org edge confers NO authority — it MUST NOT widen `toolAllowlist` (RFC 0002 §A14), grant an RBAC scope (RFC 0049), or bypass an approval gate (RFC 0051); org position MUST NOT be an authorization input. The schema carries no authority-bearing field, and a conformant host MUST NOT derive authority from position out-of-band. REQUIRES `agents.roster.supported: true` (the chart's members are roster entries). Hosts that omit this block have no org-chart surface (the read 501s). The org-chart-management endpoints + behavioral non-authority scenario + reference-host store land at Active → Accepted.",
991
+ "additionalProperties": false,
992
+ "required": ["supported"],
993
+ "properties": {
994
+ "supported": {
995
+ "type": "boolean",
996
+ "description": "REQUIRED when present. When `true`, the host serves the tenant-scoped org-chart + the responsibility roll-up. The §B non-authority guarantee holds at every `installScope` — it is never gated, weakened, or opt-out."
997
+ },
998
+ "installScope": {
999
+ "type": "string",
1000
+ "enum": ["host", "tenant"],
1001
+ "description": "RFC 0074 carry-forward. `host`: a single global chart. `tenant`: charts are scoped per owner triple; GET /v1/agents/org-chart returns only the caller's chart. SHOULD equal `agents.roster.installScope` (the members are roster entries)."
1002
+ },
1003
+ "departmentNesting": {
1004
+ "type": "boolean",
1005
+ "description": "When `true`, the host supports `parentDepartmentId` trees. When `false`/absent, the host MUST reject a non-null `parentDepartmentId` (truthful advertisement, RFC 0031)."
1006
+ },
1007
+ "responsibilityView": {
1008
+ "type": "boolean",
1009
+ "description": "When `true`, the host computes the §D responsibility roll-up (the union of a department's members' RFC 0086 portfolios) on GET /v1/agents/org-chart/{departmentId}."
1010
+ }
1011
+ }
1012
+ },
869
1013
  "dispatchMapping": {
870
1014
  "type": "boolean",
871
1015
  "default": false,
@@ -976,6 +1120,34 @@
976
1120
  }
977
1121
  },
978
1122
  "additionalProperties": false
1123
+ },
1124
+ "writable": {
1125
+ "type": "boolean",
1126
+ "description": "RFC 0080 §A (`write` dimension). Absent ⇒ the host implements the full RFC 0004 four-operation MemoryAdapter (`put`/`delete` available — i.e. writable), the back-compatible default. A read-only host (`get`/`list` only) MUST set `writable: false` so a consumer can distinguish a read-only store from a read/write one. Only meaningful when `supported: true`."
1127
+ },
1128
+ "search": {
1129
+ "type": "object",
1130
+ "description": "RFC 0080 §A (`search` dimension) — NEW optional. Semantic or filtered query beyond the RFC 0004 `list` enumeration. Absent ⇒ only `list`/`get` retrieval is advertised. The query path itself stays the host-internal MemoryAdapter (RFC 0080 §B — no portable `GET /v1/memory` at v1.x).",
1131
+ "required": ["supported"],
1132
+ "additionalProperties": false,
1133
+ "properties": {
1134
+ "supported": { "type": "boolean", "description": "REQUIRED when the sub-block is present. When `true`, the host supports memory query beyond `list` (semantic and/or filter modes per `modes`)." },
1135
+ "modes": {
1136
+ "type": "array",
1137
+ "items": { "type": "string", "enum": ["semantic", "filter"] },
1138
+ "uniqueItems": true,
1139
+ "description": "The query modes the host supports. `semantic` = embedding/similarity retrieval; `filter` = structured predicate query over entry metadata. Omitted ⇒ unspecified mode (the host supports some query beyond `list`)."
1140
+ }
1141
+ }
1142
+ },
1143
+ "retention": {
1144
+ "type": "object",
1145
+ "description": "RFC 0080 §A (`retention/forget` dimension) — NEW optional. TTL expiry (`agent-memory.md §TTL`) and/or an explicit forget operation. Absent ⇒ no TTL beyond `ttlSupported` and no forget operation advertised. `forget` is a host-managed mutation OUTSIDE the replay envelope (RFC 0080 §UQ3 / `replay.md` §Recorded-fact events — a replay re-reads the log-recorded snapshot, not live memory).",
1146
+ "additionalProperties": false,
1147
+ "properties": {
1148
+ "ttl": { "type": "boolean", "description": "When `true`, memory entries expire per `expiresAt` (the `ttlSupported` semantics surfaced as a named retention dimension)." },
1149
+ "forget": { "type": "boolean", "description": "When `true`, the host supports a tenant-scoped delete-by-subject forget operation (composes the CTI-1 cross-tenant invariant — a forget MUST NOT cross tenant boundaries)." }
1150
+ }
979
1151
  }
980
1152
  },
981
1153
  "additionalProperties": true
@@ -1130,6 +1302,54 @@
1130
1302
  "perToolRateLimit": { "type": "boolean", "description": "Host applies a per-`(principal, tool)` token-bucket rate limit; exhaustion yields `agent.toolReturned { status: 'rate_limited' }` + a `rate_limited` (429) error." }
1131
1303
  }
1132
1304
  },
1305
+ "toolCatalog": {
1306
+ "type": "object",
1307
+ "description": "RFC 0078 (`Active`). The host exposes a read-only projection of its tool surfaces (node-pack / workflow / MCP / connector / host-extension) at `GET /v1/tools` + `GET /v1/tools/{toolId}`, returning `ToolDescriptor` records (`tool-descriptor.schema.json`). Optional; hosts that omit it expose no catalog (today's behavior) and the conformance scenarios skip cleanly. Read-only — the catalog never mutates tools; tool invocation stays on the existing surfaces (agent dispatch, `core.dispatch`, MCP). The listing is authorization-scoped + non-disclosing (RFC 0074 pattern).",
1308
+ "required": ["supported"],
1309
+ "additionalProperties": false,
1310
+ "properties": {
1311
+ "supported": { "type": "boolean", "description": "REQUIRED when present. `true` ⇒ `GET /v1/tools` + `GET /v1/tools/{toolId}` are served per RFC 0078 §B." },
1312
+ "sources": {
1313
+ "type": "array",
1314
+ "uniqueItems": true,
1315
+ "items": { "type": "string", "enum": ["node-pack", "workflow", "mcp", "connector", "host-extension"] },
1316
+ "description": "Which tool sources the catalog projects. A host advertises only the sources it actually surfaces; a consumer MUST tolerate any subset. Absent ⇒ all sources the host implements."
1317
+ },
1318
+ "sessionLifecycle": { "type": "boolean", "description": "`true` ⇒ the host emits the RFC 0078 §D tool-session lifecycle events (`tool.session.opened`/`tool.session.closed`, content-free) bracketing the existing RFC 0064 `agent.toolCalled`/`agent.toolReturned` call events for multi-step interactions. Absent ⇒ `false` (single-shot tool calls only)." }
1319
+ }
1320
+ },
1321
+ "httpClient": {
1322
+ "type": "object",
1323
+ "description": "Host outbound-HTTP surface. The host's HTTP-client node egress (e.g. `core.http.request`) MUST be SSRF-guarded (`ssrfGuard: true`) with a positive `maxResponseBodyBytes` cap — the `http-client-ssrf-guard` protocol invariant. RFC 0076 §B adds the OPTIONAL `safeFetch` sub-capability: a host-mediated `ctx.http.safeFetch(url, init?)` exposed to pack runtime code, backed by the SAME SSRF guard (resolve→pin→connect, metadata-endpoint blocklist, DNS-rebinding defeat) so packs need not reach for raw DNS/sockets. See `host-capabilities.md` §host.http.",
1324
+ "required": ["supported"],
1325
+ "additionalProperties": false,
1326
+ "properties": {
1327
+ "supported": { "type": "boolean" },
1328
+ "ssrfGuard": { "type": "boolean", "description": "Host rejects egress to loopback / RFC 1918 / link-local / cloud-metadata addresses (resolve→pin→connect). MUST be `true` when `supported` (the `http-client-ssrf-guard` invariant)." },
1329
+ "maxResponseBodyBytes": { "type": "integer", "minimum": 1, "description": "Positive ceiling on a response body the host will buffer. Reused by `safeFetch`." },
1330
+ "requestTimeoutMs": { "type": "integer", "minimum": 1, "description": "Host-enforced per-request wall-clock timeout (also applies to `safeFetch`)." },
1331
+ "methods": { "type": "array", "items": { "type": "string" }, "description": "HTTP methods the client surface accepts (e.g. `GET`, `POST`)." },
1332
+ "safeFetch": {
1333
+ "type": "object",
1334
+ "description": "RFC 0076 §B. Host-provided `ctx.http.safeFetch(url, init?)` for pack runtime code — the pack-facing exposure of the SSRF-guarded client. When advertised, the host MUST apply the §host.http SSRF defense + clamps (incl. refusing `Connection: upgrade`), and — when `toolHooks.prePostEvents` is also advertised — MUST emit the `agent.toolCalled`/`agent.toolReturned` pair (`transport: 'http'`) for each call. A pack that uses `safeFetch` need not declare `net.dns` in `runtime.requires` (the host owns resolution; RFC 0076 §A).",
1335
+ "required": ["supported"],
1336
+ "additionalProperties": false,
1337
+ "properties": {
1338
+ "supported": { "type": "boolean" }
1339
+ }
1340
+ },
1341
+ "egressPolicy": {
1342
+ "type": "object",
1343
+ "additionalProperties": false,
1344
+ "description": "RFC 0079. The host evaluates credential provenance (`credential-provenance.schema.json`) + the audience-binding MUST (§C) on credentialed egress and emits `egress.decided` (§B). Requires `httpClient.safeFetch` (the egress mechanism). Absent ⇒ the host does not perform provenance binding (the RFC 0076 §B SSRF guard still applies); the conformance behavioral scenarios skip cleanly. Closes the credential↔destination-binding question RFC 0076 §B parked.",
1345
+ "required": ["supported"],
1346
+ "properties": {
1347
+ "supported": { "type": "boolean", "description": "REQUIRED when present. `true` ⇒ the §C audience-binding MUST is enforced + `egress.decided` is emitted." },
1348
+ "decisions": { "type": "array", "uniqueItems": true, "items": { "type": "string", "enum": ["allowed", "denied", "downgraded", "approval-required"] }, "description": "MAY — which decision outcomes the host implements. Absent ⇒ at least `allowed` + `denied`." }
1349
+ }
1350
+ }
1351
+ }
1352
+ },
1133
1353
  "deadLetter": {
1134
1354
  "type": "object",
1135
1355
  "description": "RFC 0053 (`Draft`). Run-level dead-letter sink for terminally-failed runs/nodes. On retry exhaustion (RFC 0009), the run is routed to a durable, inspectable sink and a `run.dead_lettered` event is emitted; dead-lettered runs remain fork-eligible (RFC 0011) for the retention window. Distinct from `queueBus.deadLetterSupported`, which dead-letters transport *messages*, not *runs*.",
@@ -1140,6 +1360,58 @@
1140
1360
  },
1141
1361
  "additionalProperties": false
1142
1362
  },
1363
+ "webhooks": {
1364
+ "type": "object",
1365
+ "description": "Webhook delivery surface (`webhooks.md`). The base contract is best-effort (5s per-attempt timeout, a circuit breaker, no durable retry). RFC 0083 adds the OPTIONAL `durable` mode: when `true`, webhook delivery participates in the trigger-bridge durable model (subscription states + retry policy + dead-letter on exhaustion) instead of the best-effort circuit-breaker-then-drop. Absent `durable` ⇒ the best-effort default, explicitly unchanged.",
1366
+ "additionalProperties": true,
1367
+ "properties": {
1368
+ "supported": { "type": "boolean", "description": "Host serves the `webhooks.md` signed-delivery surface." },
1369
+ "signatureAlgorithms": { "type": "array", "items": { "type": "string" }, "uniqueItems": true, "description": "HMAC signature algorithm ids the host supports (e.g. `v1`)." },
1370
+ "durable": { "type": "boolean", "description": "RFC 0083 §A — OPTIONAL opt-in. `true` ⇒ webhook delivery is durable (a trigger-bridge source); absent/`false` ⇒ the best-effort `webhooks.md` contract, unchanged. The best-effort default is NOT relaxed." }
1371
+ }
1372
+ },
1373
+ "triggerBridge": {
1374
+ "type": "object",
1375
+ "description": "RFC 0083 (`Active`). Composes the existing scheduling (RFC 0052), dead-letter (RFC 0053), queue-bus (RFC 0017), webhook, and cross-host-causation (RFC 0040) primitives into one uniform durable inbound-work contract: standardized subscription states + a delivery-attempt/dedup/retry model + trigger→run causation. Backs the derived `openwop-trigger-bridge` profile. Channels (Slack/email/SMS) stay vendor extensions (§E) — only their bridge into a run is uniform. Hosts that omit it have no uniform trigger contract (today's behavior); the conformance behavioral scenarios skip cleanly.",
1376
+ "required": ["supported"],
1377
+ "additionalProperties": false,
1378
+ "properties": {
1379
+ "supported": { "type": "boolean", "description": "REQUIRED when present. `true` ⇒ the host implements the §B state machine + §C delivery model + emits the two `trigger.*` events." },
1380
+ "subscriptionStates": { "type": "array", "uniqueItems": true, "items": { "type": "string", "enum": ["active", "paused", "failed", "dead-lettered"] }, "description": "The subscription states the host implements (the §B four-state vocabulary). Absent ⇒ at least `active` + `dead-lettered`." },
1381
+ "dedup": { "type": "boolean", "description": "`true` ⇒ the host de-duplicates inbound events by `dedupKey` within the retention window (§C-1; at-least-once becomes effectively-once)." },
1382
+ "retryPolicy": {
1383
+ "type": "object",
1384
+ "additionalProperties": false,
1385
+ "description": "The host's default delivery retry policy (§C-2).",
1386
+ "properties": {
1387
+ "maxAttempts": { "type": "integer", "minimum": 1, "description": "Max delivery attempts before dead-lettering." },
1388
+ "backoff": { "type": "string", "enum": ["none", "fixed", "exponential"], "description": "Backoff strategy between attempts." }
1389
+ }
1390
+ },
1391
+ "sources": { "type": "array", "uniqueItems": true, "items": { "type": "string", "enum": ["webhook", "schedule", "queue", "email", "form"] }, "description": "Which trigger sources bridge uniformly. A source listed here MUST have a registerable `TriggerSubscription` driven through the four-state machine AND emit the two `trigger.*` events for that source — the list MUST NOT over-claim a source the host has as a feature but does not wire as a durable trigger subscription. A consumer MUST tolerate any subset." }
1392
+ }
1393
+ },
1394
+ "budget": {
1395
+ "type": "object",
1396
+ "description": "RFC 0084 (`Active`). Enforceable per-run SPEND governance — the reserved `budget` run-options key (`budget-policy.schema.json`), the content-free `budget.{reserved,consumed,threshold.crossed,exhausted}` events, and hard-stop enforcement via `cap.breached{kind:\"budget-*\"}`. Orthogonal to RFC 0058 (which owns wall-time + loop-iterations via `limits.maxRunDurationMs`/`maxLoopIterations`); they share only the `cap.breached` overflow event. Hosts that omit it perform no spend enforcement (today's behavior); the conformance behavioral scenarios skip cleanly.",
1397
+ "required": ["supported"],
1398
+ "additionalProperties": false,
1399
+ "properties": {
1400
+ "supported": { "type": "boolean", "description": "REQUIRED when present. `true` ⇒ the host resolves the `budget` policy, emits the `budget.*` events, and (when `enforce: \"hard\"`) stops the run on exhaustion." },
1401
+ "dimensions": { "type": "array", "uniqueItems": true, "items": { "type": "string", "enum": ["tokens", "cost", "toolCalls", "retries", "model"] }, "description": "Which budget dimensions the host actually enforces (truthful — advertise only what it honors). A consumer MUST tolerate any subset." },
1402
+ "enforce": { "type": "string", "enum": ["hard", "advisory"], "description": "`hard`: exhaustion emits `cap.breached` + stops the run. `advisory`: emits the events but MUST NOT stop the run (honest advertisement of observe-only)." },
1403
+ "scopes": { "type": "array", "uniqueItems": true, "items": { "type": "string", "enum": ["run", "workflow", "agent", "project"] }, "description": "Which budget scopes the host honors (§B). Absent ⇒ at least `run`." }
1404
+ }
1405
+ },
1406
+ "nondeterminismPolicy": {
1407
+ "type": "object",
1408
+ "description": "RFC 0085. A host that does NOT support replay/fork (`replay.supported`) MAY instead DECLARE that it is honestly nondeterministic — satisfying the `openwop-agent-platform` floor's replay-OR-policy term (`agent-platform-profile.md` §B) without claiming a replay capability it lacks. Absent ⇒ the floor's replay term must be met by `replay.supported`.",
1409
+ "required": ["declared"],
1410
+ "additionalProperties": false,
1411
+ "properties": {
1412
+ "declared": { "type": "boolean", "description": "When `true`, the host documents its nondeterminism (it does not guarantee deterministic replay). A bare flag for v1.x (RFC 0085 §UQ2); a structured per-source policy is a future refinement." }
1413
+ }
1414
+ },
1143
1415
  "workspace": {
1144
1416
  "type": "object",
1145
1417
  "description": "RFC 0059 (`Active`). Versioned, tenant·workspace-scoped ground-truth file store (the `host.workspace` capability). Scopes to the RFC 0048 owner triple. Atomic, optimistically-concurrent writes (`If-Match` ETag); a read snapshot is exposed to every run at `run.started` (deterministic for replay). Complements the transactional `MemoryAdapter` (RFC 0004) with a durable, path-addressable file layer. Endpoints (`/v1/host/workspace/files[/{path}]`) are gated on `supported: true`; unsupported hosts return `501 capability_not_provided`. SECURITY invariants `workspace-cross-tenant-isolation` (WCT-1) + the WSR-1 secret-redaction MUST land with their conformance tests at implementation (RFC 0059 §E).",
@@ -1360,6 +1632,11 @@
1360
1632
  "description": "Deterministic resolution rule for conflicting idempotency-key records when a partition healed. `last-writer-wins` / `first-writer-wins` are spec-reserved categorical rules; vendor strategies use `^x-host-<host>-<key>$`. Conformance asserts deterministic resolution actually applies; it does NOT prescribe which strategy a host picks."
1361
1633
  }
1362
1634
  }
1635
+ },
1636
+ "crossRegion": {
1637
+ "type": "string",
1638
+ "enum": ["single-region", "best-effort", "strict"],
1639
+ "description": "RFC 0036 — categorical multi-region idempotency posture (the canonical conformance-checked surface per spec/v1/idempotency.md §'Multi-region idempotency (annex)'). `single-region`: the host runs in one region and makes no cross-region claim (it MAY still implement the convergence resolver, demonstrable via the multi-region simulator seam). `best-effort`: cross-region reconciliation converges eventually under the annex's lex-min(runId) rule. `strict`: cross-region read-visibility is bounded by `multiRegion.replicationLagBoundMs`. When `best-effort` or `strict`, the host MUST emit the `openwop.idempotency.cross_region_conflicts_total` operator metric. The `multiRegion` sub-block above is the optional granular companion."
1363
1640
  }
1364
1641
  }
1365
1642
  },
@@ -0,0 +1,18 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://openwop.dev/spec/v1/credential-provenance.schema.json",
4
+ "title": "CredentialProvenance",
5
+ "description": "RFC 0079 §A. Metadata ABOUT a host-issued credential (an RFC 0046 stored reference or an RFC 0047 OAuth token) attached at the tool/egress boundary — never the secret value (SR-1; reuses the RFC 0046 `credential-payload-redaction` posture). Travels with the egress decision, not the credential material. The §C audience-binding MUST is evaluated against `audiences`.",
6
+ "type": "object",
7
+ "additionalProperties": false,
8
+ "required": ["credentialId", "issuer", "audiences"],
9
+ "properties": {
10
+ "credentialId": { "type": "string", "minLength": 1, "description": "Stable host-issued id for the credential (the RFC 0046 `ref`, or an OAuth-grant id). Correlates audit + the Keys-page view. NOT the secret value." },
11
+ "issuer": { "type": "string", "minLength": 1, "description": "Who minted/owns the credential — the host itself (`host`), an RFC 0047 OAuth provider id, or a BYOK principal (RFC 0046). Lets a consumer distinguish host-managed from caller-supplied credentials." },
12
+ "audiences": { "type": "array", "minItems": 1, "items": { "type": "string", "minLength": 1 }, "description": "REQUIRED. The destination hosts/domains (or destination-class ids) the credential is valid for. The §C binding MUST is evaluated against this list — a credential MUST NOT be attached to an egress outside its audiences. Matching is exact host or an explicit `*.domain` suffix form (no arbitrary regex — injection risk; RFC 0079 §UQ1)." },
13
+ "scopes": { "type": "array", "items": { "type": "string" }, "uniqueItems": true, "description": "MAY — RFC 0049 scopes the credential grants (e.g. `egress:stripe:charge`)." },
14
+ "expiresAt": { "type": "string", "format": "date-time", "description": "MAY — credential expiry; an expired credential MUST NOT be attached (deny, `reason: \"expired\"`)." },
15
+ "redactionPolicy": { "type": "string", "enum": ["always", "hash", "host-policy"], "description": "MAY — how the credential surfaces in logs/events. `always`: never appears (default posture, SR-1); `hash`: an SR-1-redacted digest MAY appear; `host-policy`: host-defined. The secret value itself is NEVER on the wire regardless." },
16
+ "auditCorrelationId": { "type": "string", "minLength": 1, "description": "MAY — id correlating this credential's egress decisions across the run's `egress.decided` events + the host audit log." }
17
+ }
18
+ }