@openwop/openwop-conformance 1.3.0 → 1.5.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 +132 -1
- package/README.md +3 -2
- package/api/asyncapi.yaml +8 -0
- package/api/openapi.yaml +371 -1
- package/coverage.md +26 -6
- package/fixtures/conformance-envelope-nl-to-format-engaged.json +41 -0
- package/fixtures/conformance-envelope-recovery-applied.json +39 -0
- package/fixtures/conformance-envelope-refusal.json +38 -0
- package/fixtures/conformance-envelope-retry-attempted.json +39 -0
- package/fixtures/conformance-envelope-retry-exhausted.json +38 -0
- package/fixtures/conformance-envelope-truncated.json +39 -0
- package/fixtures/conformance-envelope-truncation-cap-exhaustion.json +39 -0
- package/fixtures/conformance-model-capability-insufficient.json +25 -0
- package/fixtures/conformance-multi-agent-confidence-escalation.json +49 -0
- package/fixtures/conformance-multi-agent-handoff-child.json +27 -0
- package/fixtures/conformance-multi-agent-handoff.json +49 -0
- package/fixtures/conformance-prompt-all-four-kinds.json +39 -0
- package/fixtures/conformance-prompt-end-to-end.json +33 -0
- package/fixtures/conformance-subworkflow-mid-run-mutation-child.json +31 -0
- package/fixtures/conformance-subworkflow-mid-run-mutation.json +33 -0
- package/fixtures/openwop-smoke-cost-emit.json +37 -0
- package/fixtures/prompt-templates/conformance-prompt-few-shot-2.json +14 -0
- package/fixtures/prompt-templates/conformance-prompt-few-shot.json +14 -0
- package/fixtures/prompt-templates/conformance-prompt-schema-hint.json +14 -0
- package/fixtures/prompt-templates/conformance-prompt-secret-redaction.json +23 -0
- package/fixtures/prompt-templates/conformance-prompt-trust-marker.json +23 -0
- package/fixtures/prompt-templates/conformance-prompt-writer-system.json +15 -0
- package/fixtures/prompt-templates/conformance-prompt-writer-user.json +15 -0
- package/fixtures.md +39 -0
- package/package.json +1 -1
- package/schemas/README.md +5 -0
- package/schemas/agent-manifest.schema.json +16 -0
- package/schemas/capabilities.schema.json +384 -1
- package/schemas/envelopes/clarification.request.schema.json +9 -0
- package/schemas/envelopes/error.schema.json +4 -0
- package/schemas/envelopes/schema.request.schema.json +4 -0
- package/schemas/envelopes/schema.response.schema.json +1 -1
- package/schemas/node-pack-manifest.schema.json +28 -0
- package/schemas/orchestrator-decision.schema.json +12 -0
- package/schemas/prompt-kind.schema.json +8 -0
- package/schemas/prompt-pack-manifest.schema.json +80 -0
- package/schemas/prompt-ref.schema.json +40 -0
- package/schemas/prompt-template.schema.json +149 -0
- package/schemas/registry-version-manifest.schema.json +5 -0
- package/schemas/run-ancestry-response.schema.json +54 -0
- package/schemas/run-event-payloads.schema.json +479 -11
- package/schemas/run-event.schema.json +15 -1
- package/schemas/run-snapshot.schema.json +3 -2
- package/schemas/workflow-definition.schema.json +19 -1
- package/src/lib/llm-cache-key-recipe.ts +68 -0
- package/src/scenarios/aiEnvelope.contractRefusal.test.ts +104 -13
- package/src/scenarios/aiEnvelope.correlationReplay.test.ts +32 -15
- package/src/scenarios/aiEnvelope.redaction.test.ts +6 -5
- package/src/scenarios/aiEnvelope.schemaDrift.test.ts +5 -5
- package/src/scenarios/aiEnvelope.trustBoundaryPropagation.test.ts +211 -12
- package/src/scenarios/aiEnvelope.universalKinds.test.ts +7 -7
- package/src/scenarios/blob-presign-expiry.test.ts +7 -7
- package/src/scenarios/cache-ttl-expiry.test.ts +6 -6
- package/src/scenarios/cost-attribution.test.ts +124 -11
- package/src/scenarios/cross-engine-append-ordering.test.ts +99 -0
- package/src/scenarios/cross-host-ancestry-endpoint.test.ts +136 -0
- package/src/scenarios/cross-host-causation-shape.test.ts +117 -0
- package/src/scenarios/cross-host-traceparent-propagation.test.ts +60 -0
- package/src/scenarios/envelope-completion-distinguishes-truncation.test.ts +223 -0
- package/src/scenarios/envelope-nl-to-format-engaged.test.ts +152 -0
- package/src/scenarios/envelope-reasoning-secret-redaction.test.ts +343 -0
- package/src/scenarios/envelope-reasoning-shape.test.ts +190 -0
- package/src/scenarios/envelope-recovery-applied.test.ts +229 -0
- package/src/scenarios/envelope-refusal-shape.test.ts +289 -0
- package/src/scenarios/envelope-retry-attempted.test.ts +258 -0
- package/src/scenarios/envelope-retry-exhausted.test.ts +168 -0
- package/src/scenarios/envelope-tier-one-subset-static.test.ts +229 -0
- package/src/scenarios/envelope-truncated.test.ts +136 -0
- package/src/scenarios/envelope-truncation-cap-exhaustion.test.ts +144 -0
- package/src/scenarios/envelope-variant-discriminator-static.test.ts +152 -0
- package/src/scenarios/fixtures-valid.test.ts +123 -15
- package/src/scenarios/kv-ttl-expiry.test.ts +7 -7
- package/src/scenarios/model-capability-insufficient.test.ts +221 -0
- package/src/scenarios/model-capability-substituted.test.ts +203 -0
- package/src/scenarios/multi-agent-confidence-escalation.test.ts +201 -0
- package/src/scenarios/multi-agent-handoff-state-machine.test.ts +167 -0
- package/src/scenarios/multi-agent-memory-lifecycle.test.ts +124 -0
- package/src/scenarios/multi-region-idempotency.test.ts +58 -0
- package/src/scenarios/node-module-required-capabilities-shape.test.ts +185 -0
- package/src/scenarios/prompt-all-four-kinds-events.test.ts +198 -0
- package/src/scenarios/prompt-composed-secret-redaction.test.ts +178 -0
- package/src/scenarios/prompt-composed-trust-marker.test.ts +165 -0
- package/src/scenarios/prompt-end-to-end-events.test.ts +202 -0
- package/src/scenarios/prompt-list-and-fetch.test.ts +207 -0
- package/src/scenarios/prompt-mutable-lifecycle.test.ts +216 -0
- package/src/scenarios/prompt-pack-install.test.ts +187 -0
- package/src/scenarios/prompt-render-deterministic.test.ts +240 -0
- package/src/scenarios/prompt-resolution-chain-agent-intrinsic.test.ts +140 -0
- package/src/scenarios/prompt-resolution-chain-fallback-cascade.test.ts +172 -0
- package/src/scenarios/prompt-resolution-chain-node-wins.test.ts +144 -0
- package/src/scenarios/prompt-template-shape.test.ts +359 -0
- package/src/scenarios/queue-ack-nack-dlq.test.ts +7 -7
- package/src/scenarios/queue-publish-consume-roundtrip.test.ts +7 -7
- package/src/scenarios/replay-divergence-at-refusal.test.ts +134 -0
- package/src/scenarios/replay-llm-cache-key-portable.test.ts +197 -0
- package/src/scenarios/replay-llm-cache-key.test.ts +1 -40
- package/src/scenarios/replay-observable-sequence-determinism.test.ts +80 -0
- package/src/scenarios/sandbox-capability-gate-respected.test.ts +27 -0
- package/src/scenarios/sandbox-memory-cap.test.ts +58 -0
- package/src/scenarios/sandbox-no-cross-pack-mutation.test.ts +30 -0
- package/src/scenarios/sandbox-no-host-env-leak.test.ts +27 -0
- package/src/scenarios/sandbox-no-host-fs-escape.test.ts +88 -0
- package/src/scenarios/sandbox-no-host-process-escape.test.ts +31 -0
- package/src/scenarios/sandbox-no-network-escape.test.ts +28 -0
- package/src/scenarios/sandbox-timeout-cap.test.ts +58 -0
- package/src/scenarios/search-bm25-roundtrip.test.ts +7 -7
- package/src/scenarios/spec-corpus-validity.test.ts +34 -6
- package/src/scenarios/sql-transaction-atomicity.test.ts +6 -6
- package/src/scenarios/stream-subscribe-from-beginning.test.ts +7 -7
- package/src/scenarios/subworkflow-input-mapping.test.ts +70 -4
- package/src/scenarios/table-cursor-pagination.test.ts +7 -7
- package/src/scenarios/table-schema-enforcement.test.ts +7 -7
- package/src/scenarios/vector-knn-roundtrip.test.ts +7 -7
|
@@ -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\n64 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": {
|
|
@@ -48,18 +48,128 @@
|
|
|
48
48
|
"lease.lost": { "$ref": "#/$defs/leaseLifecycle" },
|
|
49
49
|
"lease.handed-off": { "$ref": "#/$defs/leaseHandedOff" },
|
|
50
50
|
"replay.diverged": { "$ref": "#/$defs/replayDiverged" },
|
|
51
|
+
"replay.divergedAtRefusal": { "$ref": "#/$defs/replayDivergedAtRefusal" },
|
|
51
52
|
"agent.reasoned": { "$ref": "#/$defs/agentReasoned" },
|
|
52
53
|
"agent.reasoning.delta": { "$ref": "#/$defs/agentReasoningDelta" },
|
|
53
54
|
"provider.usage": { "$ref": "#/$defs/providerUsage" },
|
|
55
|
+
"prompt.composed": { "$ref": "#/$defs/promptComposed" },
|
|
56
|
+
"agent.promptResolved": { "$ref": "#/$defs/agentPromptResolved" },
|
|
57
|
+
"model.capability.substituted": { "$ref": "#/$defs/modelCapabilitySubstituted" },
|
|
58
|
+
"model.capability.insufficient": { "$ref": "#/$defs/modelCapabilityInsufficient" },
|
|
59
|
+
"envelope.retry.attempted": { "$ref": "#/$defs/envelopeRetryAttempted" },
|
|
60
|
+
"envelope.retry.exhausted": { "$ref": "#/$defs/envelopeRetryExhausted" },
|
|
61
|
+
"envelope.refusal": { "$ref": "#/$defs/envelopeRefusal" },
|
|
62
|
+
"envelope.truncated": { "$ref": "#/$defs/envelopeTruncated" },
|
|
63
|
+
"envelope.nlToFormat.engaged": { "$ref": "#/$defs/envelopeNlToFormatEngaged" },
|
|
64
|
+
"envelope.recovery.applied": { "$ref": "#/$defs/envelopeRecoveryApplied" },
|
|
54
65
|
"agent.toolCalled": { "$ref": "#/$defs/agentToolCalled" },
|
|
55
66
|
"agent.toolReturned": { "$ref": "#/$defs/agentToolReturned" },
|
|
56
67
|
"agent.handoff": { "$ref": "#/$defs/agentHandoff" },
|
|
57
68
|
"agent.decided": { "$ref": "#/$defs/agentDecided" },
|
|
58
69
|
"runOrchestrator.decided": { "$ref": "#/$defs/runOrchestratorDecided" },
|
|
70
|
+
"node.dispatched": { "$ref": "#/$defs/nodeDispatched" },
|
|
59
71
|
"conversation.opened": { "$ref": "#/$defs/conversationOpened" },
|
|
60
72
|
"conversation.exchanged": { "$ref": "#/$defs/conversationExchanged" },
|
|
61
73
|
"conversation.closed": { "$ref": "#/$defs/conversationClosed" },
|
|
62
|
-
"memory.compacted": { "$ref": "#/$defs/memoryCompacted" }
|
|
74
|
+
"memory.compacted": { "$ref": "#/$defs/memoryCompacted" },
|
|
75
|
+
"core.workflowChain.event": { "$ref": "#/$defs/coreWorkflowChainEvent" },
|
|
76
|
+
"core.workflowChain.confidence-escalated": { "$ref": "#/$defs/coreWorkflowChainConfidenceEscalated" }
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
|
|
80
|
+
"coreWorkflowChainEvent": {
|
|
81
|
+
"description": "RFC 0037 — emitted on every planner→worker handoff state-machine transition. Required when `capabilities.multiAgent.executionModel.supported: true`; MUST NOT be emitted otherwise. RFC 0040 §A extends with optional `causationHostId` (when `crossHostCausation.supported: true`): present + non-empty when the `causationId` points at an event on a different host; absent when the chained-event is on the same host (existing semantics).",
|
|
82
|
+
"type": "object",
|
|
83
|
+
"additionalProperties": false,
|
|
84
|
+
"required": ["phase", "workerId", "parentRunId"],
|
|
85
|
+
"properties": {
|
|
86
|
+
"phase": {
|
|
87
|
+
"type": "string",
|
|
88
|
+
"enum": [
|
|
89
|
+
"dispatch.began",
|
|
90
|
+
"dispatch.succeeded",
|
|
91
|
+
"dispatch.failed",
|
|
92
|
+
"child.completed",
|
|
93
|
+
"child.failed",
|
|
94
|
+
"child.cancelled",
|
|
95
|
+
"output.harvested"
|
|
96
|
+
],
|
|
97
|
+
"description": "Which handoff-state-machine transition this event records. See spec/v1/multi-agent-execution.md §'Handoff state machine'."
|
|
98
|
+
},
|
|
99
|
+
"workerId": {
|
|
100
|
+
"type": "string",
|
|
101
|
+
"minLength": 1,
|
|
102
|
+
"description": "The dispatched worker's workflowId — matches the entry in the supervisor's OrchestratorDecision.nextWorkerIds[]."
|
|
103
|
+
},
|
|
104
|
+
"parentRunId": {
|
|
105
|
+
"type": "string",
|
|
106
|
+
"minLength": 1,
|
|
107
|
+
"description": "The orchestrator-driven parent run's runId."
|
|
108
|
+
},
|
|
109
|
+
"childRunId": {
|
|
110
|
+
"type": "string",
|
|
111
|
+
"minLength": 1,
|
|
112
|
+
"description": "The dispatched child run's runId. REQUIRED on phases `dispatch.succeeded` and beyond; absent on `dispatch.began` and `dispatch.failed`."
|
|
113
|
+
},
|
|
114
|
+
"harvestedKeys": {
|
|
115
|
+
"type": "array",
|
|
116
|
+
"items": { "type": "string" },
|
|
117
|
+
"description": "On phase `output.harvested`: which parent-variable keys were populated by the dispatch config's outputMapping per RFC 0022 §A. SHOULD be present; conformance asserts presence when outputMapping is non-empty."
|
|
118
|
+
},
|
|
119
|
+
"error": {
|
|
120
|
+
"$ref": "#/$defs/_errorObject",
|
|
121
|
+
"description": "On phases `dispatch.failed` / `child.failed` / `child.cancelled`: the canonical error envelope."
|
|
122
|
+
},
|
|
123
|
+
"causationHostId": {
|
|
124
|
+
"type": "string",
|
|
125
|
+
"minLength": 1,
|
|
126
|
+
"description": "RFC 0040 §A — optional cross-host causation pointer. Present + non-empty when this event's `causationId` (top-level on RunEventDoc) points at an event on a DIFFERENT host; absent when the chained-event is on the SAME host (existing semantics). Value MUST equal the originating host's `capabilities.multiAgent.executionModel.crossHostCausation.hostId` advertisement. Hosts that don't advertise crossHostCausation MUST NOT emit this field."
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
},
|
|
130
|
+
|
|
131
|
+
"coreWorkflowChainConfidenceEscalated": {
|
|
132
|
+
"description": "RFC 0039 §A — emitted when a supervisor's OrchestratorDecision carries `confidence` below the active confidence floor (spec floor 0.5 OR operator-stricter `capabilities.multiAgent.executionModel.confidenceEscalationFloor`). Recorded BEFORE the host fires the matching clarify-or-escalate interrupt so the run event log carries the decision point even if the user later confirms the original decision. MUST NOT be emitted unless `capabilities.multiAgent.executionModel.version >= 2`.",
|
|
133
|
+
"type": "object",
|
|
134
|
+
"additionalProperties": false,
|
|
135
|
+
"required": ["confidence", "floor", "escalationKind", "parentRunId"],
|
|
136
|
+
"properties": {
|
|
137
|
+
"confidence": {
|
|
138
|
+
"type": "number",
|
|
139
|
+
"minimum": 0,
|
|
140
|
+
"maximum": 1,
|
|
141
|
+
"description": "The supervisor's stated confidence on the escalated decision, copied verbatim from `OrchestratorDecision.confidence`."
|
|
142
|
+
},
|
|
143
|
+
"floor": {
|
|
144
|
+
"type": "number",
|
|
145
|
+
"minimum": 0.5,
|
|
146
|
+
"maximum": 1,
|
|
147
|
+
"description": "The floor that triggered escalation — either the spec floor (0.5) when the host doesn't advertise a stricter `confidenceEscalationFloor`, or the host-advertised stricter value."
|
|
148
|
+
},
|
|
149
|
+
"escalationKind": {
|
|
150
|
+
"type": "string",
|
|
151
|
+
"enum": ["clarify", "escalate"],
|
|
152
|
+
"description": "Which interrupt-kind the host fired in response. `clarify` is preferred per RFC 0039 §A; `escalate` is permitted when the host doesn't expose a clarification UI."
|
|
153
|
+
},
|
|
154
|
+
"workerId": {
|
|
155
|
+
"type": "string",
|
|
156
|
+
"minLength": 1,
|
|
157
|
+
"description": "The decision's intended next-worker workflowId (when the escalated decision's kind is `next-worker`). OMITTED for `kind: 'terminate'` escalations — terminate decisions have no worker to name, so absent-field is the correct signal rather than empty-string."
|
|
158
|
+
},
|
|
159
|
+
"parentRunId": {
|
|
160
|
+
"type": "string",
|
|
161
|
+
"minLength": 1,
|
|
162
|
+
"description": "The orchestrator-driven parent run's runId."
|
|
163
|
+
},
|
|
164
|
+
"originalDecision": {
|
|
165
|
+
"$ref": "orchestrator-decision.schema.json",
|
|
166
|
+
"description": "The OrchestratorDecision that triggered escalation, captured verbatim. The host's existing event-payload redaction harness (the same one enforcing SECURITY invariant `secret-leakage-eventlog-payload` per `conformance/src/scenarios/redaction.test.ts`) applies uniformly across all RunEventType payloads including this one — no per-event-type opt-in is required. The protocol-tier MUST-NOT for raw key material in persisted payloads is the canonical contract; this field inherits that protection."
|
|
167
|
+
},
|
|
168
|
+
"causationHostId": {
|
|
169
|
+
"type": "string",
|
|
170
|
+
"minLength": 1,
|
|
171
|
+
"description": "RFC 0040 §A — optional cross-host causation pointer. Present + non-empty when this event's `causationId` points at an event on a DIFFERENT host; absent when same-host (existing semantics). Value MUST equal the originating host's `crossHostCausation.hostId` advertisement."
|
|
172
|
+
}
|
|
63
173
|
}
|
|
64
174
|
},
|
|
65
175
|
|
|
@@ -126,7 +236,8 @@
|
|
|
126
236
|
"properties": {
|
|
127
237
|
"reason": { "type": "string" },
|
|
128
238
|
"cancelledBy": { "type": "string", "description": "Caller identity (uid / API key fingerprint / `system`)." },
|
|
129
|
-
"durationMs": { "type": "integer", "minimum": 0 }
|
|
239
|
+
"durationMs": { "type": "integer", "minimum": 0 },
|
|
240
|
+
"parentRunId": { "type": "string", "minLength": 1, "description": "When this cancellation was triggered by a parent-cancel cascade (`interrupt-profiles.md §openwop-interrupt-cascade-cancel`), the parent runId that initiated it. Pairs with `reason: 'parent-cancelled'`. Absent for direct cancellations." }
|
|
130
241
|
},
|
|
131
242
|
"additionalProperties": true
|
|
132
243
|
},
|
|
@@ -541,15 +652,61 @@
|
|
|
541
652
|
"additionalProperties": true
|
|
542
653
|
},
|
|
543
654
|
|
|
655
|
+
"replayDivergedAtRefusal": {
|
|
656
|
+
"type": "object",
|
|
657
|
+
"description": "RFC 0041 §B — emitted by `:fork` mode `replay` when the replay's LLM call returns a refusal but the original run got a valid envelope (or vice-versa: the original refused, the replay succeeded). Distinct from the generic `replay.diverged` event (which covers structural output/missing/extra/type-mismatch divergence) to let operators audit safety-policy shifts without filtering through the generic divergence stream. When this event fires, the host MUST also fail the replay with `error.code: \"replay_diverged_at_refusal\"` per `spec/v1/rest-endpoints.md` §\"Common error codes\". Capability-gated on `capabilities.multiAgent.executionModel.replayDeterminism.refusalDivergenceEmission: true`; non-Phase-4 hosts MUST NOT emit this event.",
|
|
658
|
+
"required": ["sourceRunId", "atSequence", "originalEnvelopeKind", "replayEnvelopeKind"],
|
|
659
|
+
"properties": {
|
|
660
|
+
"sourceRunId": {
|
|
661
|
+
"type": "string",
|
|
662
|
+
"minLength": 1,
|
|
663
|
+
"description": "The original run's runId (the source of the replay)."
|
|
664
|
+
},
|
|
665
|
+
"atSequence": {
|
|
666
|
+
"type": "integer",
|
|
667
|
+
"minimum": 0,
|
|
668
|
+
"description": "Event-log index at which the refusal-divergence was detected (the index of the LLM call whose envelope shifted)."
|
|
669
|
+
},
|
|
670
|
+
"originalEventId": {
|
|
671
|
+
"type": "string",
|
|
672
|
+
"description": "Optional eventId of the original event whose envelope is being compared. SHOULD be set when the host has a stable identifier for the event; MAY be omitted when only the sequence index is known."
|
|
673
|
+
},
|
|
674
|
+
"nodeId": {
|
|
675
|
+
"type": "string",
|
|
676
|
+
"minLength": 1,
|
|
677
|
+
"description": "ID of the node whose LLM call diverged. Operators use this to localize the safety-policy shift in the workflow definition."
|
|
678
|
+
},
|
|
679
|
+
"originalEnvelopeKind": {
|
|
680
|
+
"type": "string",
|
|
681
|
+
"enum": ["valid", "refusal"],
|
|
682
|
+
"description": "Whether the original run's LLM envelope was a valid response or a refusal."
|
|
683
|
+
},
|
|
684
|
+
"replayEnvelopeKind": {
|
|
685
|
+
"type": "string",
|
|
686
|
+
"enum": ["valid", "refusal"],
|
|
687
|
+
"description": "Whether the replay's LLM envelope was a valid response or a refusal. MUST differ from `originalEnvelopeKind` — otherwise there is no divergence to report."
|
|
688
|
+
},
|
|
689
|
+
"refusalReason": {
|
|
690
|
+
"type": "string",
|
|
691
|
+
"description": "Provider-supplied refusal reason when available (e.g., the model's `refusal` field on a chat-completion response). OPTIONAL; hosts MAY include for operator triage but MUST redact provider-specific identifiers per `SECURITY/threat-model-secret-leakage.md`."
|
|
692
|
+
}
|
|
693
|
+
},
|
|
694
|
+
"additionalProperties": false
|
|
695
|
+
},
|
|
696
|
+
|
|
544
697
|
"replayDiverged": {
|
|
545
698
|
"type": "object",
|
|
546
|
-
"description": "Emitted by `:fork` when a replay re-execution produces a different output than the original at a given sequence. See replay.md §divergence detection.",
|
|
699
|
+
"description": "Emitted by `:fork` when a replay re-execution produces a different output than the original at a given sequence. See replay.md §divergence detection. `divergencePoint` (RFC 0027 §F) is the canonical optional field for naming which event-emission diverged; the two fields are complementary, not mutually exclusive.",
|
|
547
700
|
"required": ["sourceRunId", "atSequence"],
|
|
548
701
|
"properties": {
|
|
549
702
|
"sourceRunId": { "type": "string", "minLength": 1 },
|
|
550
703
|
"atSequence": { "type": "integer", "minimum": 0 },
|
|
551
704
|
"originalEventId": { "type": "string" },
|
|
552
|
-
"divergenceKind": { "type": "string", "enum": ["output", "missing", "extra", "type-mismatch"] }
|
|
705
|
+
"divergenceKind": { "type": "string", "enum": ["output", "missing", "extra", "type-mismatch"] },
|
|
706
|
+
"divergencePoint": {
|
|
707
|
+
"type": "string",
|
|
708
|
+
"description": "RFC 0027 §F. Verbatim `RunEventType` enum string identifying which event-emission the replay diverged at (e.g., `\"prompt.composed\"` per RFC 0027, `\"agent.promptResolved\"` per RFC 0029, `\"envelope.retry.exhausted\"` / `\"envelope.recovery.applied\"` per RFC 0032). Set when divergence is detected on a specific cross-kind operational event's payload. When divergence is purely structural (output/missing/extra at the event-array level rather than within a typed payload), `divergenceKind` carries the shape and `divergencePoint` MAY be omitted. The two fields are complementary: a single `replay.diverged` event MAY carry both (e.g., `{ divergenceKind: \"output\", divergencePoint: \"prompt.composed\" }` reads as \"the `prompt.composed` event at sequence N had a different output on replay than the original\"). The field is defined by RFC 0027; values are contributed by consuming RFCs (0029 adds `agent.promptResolved`, 0032 adds the envelope-reliability event names)."
|
|
709
|
+
}
|
|
553
710
|
},
|
|
554
711
|
"additionalProperties": true
|
|
555
712
|
},
|
|
@@ -561,7 +718,8 @@
|
|
|
561
718
|
"properties": {
|
|
562
719
|
"agentId": { "type": "string", "minLength": 3, "maxLength": 256, "description": "AgentRef.agentId of the reasoning agent." },
|
|
563
720
|
"reasoning": { "type": "string", "description": "Reasoning text. Bounded by `capabilities.agents.reasoning.tokenLimit` when verbosity is `summary` (default 512 tokens)." },
|
|
564
|
-
"verbosity": { "type": "string", "enum": ["summary", "full", "off"], "description": "Verbosity mode this trace was produced under. Hosts MAY emit `'off'` to record the suppression decision without payload content." }
|
|
721
|
+
"verbosity": { "type": "string", "enum": ["summary", "full", "off"], "description": "Verbosity mode this trace was produced under. Hosts MAY emit `'off'` to record the suppression decision without payload content." },
|
|
722
|
+
"causationHostId": { "type": "string", "minLength": 1, "description": "RFC 0040 §A — optional cross-host causation pointer. Present + non-empty when this event's `causationId` points at an event on a DIFFERENT host; absent when same-host. MUST equal the originating host's `crossHostCausation.hostId`." }
|
|
565
723
|
},
|
|
566
724
|
"additionalProperties": true
|
|
567
725
|
},
|
|
@@ -598,6 +756,299 @@
|
|
|
598
756
|
"additionalProperties": false
|
|
599
757
|
},
|
|
600
758
|
|
|
759
|
+
"promptComposed": {
|
|
760
|
+
"type": "object",
|
|
761
|
+
"description": "RFC 0027. Emitted once per (nodeId, kind) composition when a host resolves a PromptRef on a node and assembles its body for an LLM call. Gated on `capabilities.prompts.supported: true` AND `capabilities.prompts.observability !== 'off'`. Body fields (`systemPrompt`, `userPrompt`, `variableBindings`) are populated only under `observability: 'full'`; under `hashed` the event carries only `hash` + `variableHashes`. Replay-deterministic over `(refs, variableHashes, contentTrust)` per `spec/v1/prompts.md §\"Replay determinism\"`. Secret-source variable values MUST be replaced with `[REDACTED:<secretId>]` markers per SECURITY invariant `prompt-composed-secret-redaction`; untrusted-input segments MUST be wrapped with `<UNTRUSTED>...</UNTRUSTED>` markers per SECURITY invariant `prompt-composed-trust-marker`.",
|
|
762
|
+
"required": ["nodeId", "refs", "kind", "hash"],
|
|
763
|
+
"properties": {
|
|
764
|
+
"nodeId": {
|
|
765
|
+
"type": "string",
|
|
766
|
+
"description": "Lifted to top-level RunEventDoc.nodeId; mirrored here for self-contained payload validation."
|
|
767
|
+
},
|
|
768
|
+
"refs": {
|
|
769
|
+
"type": "array",
|
|
770
|
+
"items": { "type": "string", "pattern": "^prompt:[a-z0-9][a-z0-9._-]{0,127}(@\\d+\\.\\d+\\.\\d+(?:-[0-9A-Za-z.-]+)?(?:\\+[0-9A-Za-z.-]+)?)?$" },
|
|
771
|
+
"description": "Ordered list of PromptRef values (stringy form) that contributed to this composition: typically system, then user, then any few-shot or schema-hint additions. Object-form refs are projected to their stringy equivalent for stability."
|
|
772
|
+
},
|
|
773
|
+
"kind": {
|
|
774
|
+
"type": "string",
|
|
775
|
+
"enum": ["system+user", "system-only", "user-only", "agent-reasoning"],
|
|
776
|
+
"description": "Composition shape — what the host actually assembled. `system+user`: both system and user templates resolved. `system-only`: only a system template applied (e.g., agent intrinsic). `user-only`: only a user template applied. `agent-reasoning`: composition was emitted ahead of an agent reasoning trace per RFC 0024."
|
|
777
|
+
},
|
|
778
|
+
"hash": {
|
|
779
|
+
"type": "string",
|
|
780
|
+
"pattern": "^sha256:[0-9a-f]{64}$",
|
|
781
|
+
"description": "SHA-256 of the composed body (post-substitution, post-redaction). Stable across replay. Always present, including under observability=hashed."
|
|
782
|
+
},
|
|
783
|
+
"composed": {
|
|
784
|
+
"type": "string",
|
|
785
|
+
"description": "Generic composed-body field. Present only when `capabilities.prompts.observability` is `full`. Populated for ALL four PromptKind values (system / user / few-shot / schema-hint) so consumers have a single body field to read — necessary for few-shot + schema-hint templates that don't fit the system/user split below. Same secret-redaction + trust-marker invariants apply as for the kind-specific fields. The kind-specific `systemPrompt` + `userPrompt` fields below remain populated for system/user kinds so existing consumers that key off them continue to work; both fields carry identical content for those kinds."
|
|
786
|
+
},
|
|
787
|
+
"systemPrompt": {
|
|
788
|
+
"type": "string",
|
|
789
|
+
"description": "Composed system prompt body. Present only when `capabilities.prompts.observability` is `full` AND template `kind: \"system\"`. Secret-sourced variable values MUST be replaced with `[REDACTED:<secretId>]` markers. Untrusted-content `<UNTRUSTED>...</UNTRUSTED>` markers MUST be preserved verbatim. Identical to `composed` when present."
|
|
790
|
+
},
|
|
791
|
+
"userPrompt": {
|
|
792
|
+
"type": "string",
|
|
793
|
+
"description": "Composed user prompt body. Same presence + redaction rules as `systemPrompt`."
|
|
794
|
+
},
|
|
795
|
+
"variableBindings": {
|
|
796
|
+
"type": "object",
|
|
797
|
+
"additionalProperties": true,
|
|
798
|
+
"description": "Variable-name → bound-value map. Secret-source bindings MUST appear as `[REDACTED:<secretId>]`. Only present under `observability: 'full'`."
|
|
799
|
+
},
|
|
800
|
+
"variableHashes": {
|
|
801
|
+
"type": "object",
|
|
802
|
+
"additionalProperties": { "type": "string", "pattern": "^sha256:[0-9a-f]{64}$" },
|
|
803
|
+
"description": "Variable-name → sha256(value) map. Always present (under both `hashed` and `full`). Enables replay-determinism checks without exposing values."
|
|
804
|
+
},
|
|
805
|
+
"contentTrust": {
|
|
806
|
+
"type": "string",
|
|
807
|
+
"enum": ["trusted", "untrusted"],
|
|
808
|
+
"description": "Aggregate trust marker. `untrusted` iff ANY contributing input was tagged untrusted per RFC 0020 §D / AIEnvelope.meta.contentTrust propagation. When `untrusted`, the composed bodies MUST contain `<UNTRUSTED>...</UNTRUSTED>` markers around the untrusted segments."
|
|
809
|
+
},
|
|
810
|
+
"causationHostId": {
|
|
811
|
+
"type": "string",
|
|
812
|
+
"minLength": 1,
|
|
813
|
+
"description": "RFC 0040 §A — optional cross-host causation pointer. Present + non-empty when this event's `causationId` points at an event on a DIFFERENT host; absent when same-host. MUST equal the originating host's `crossHostCausation.hostId`."
|
|
814
|
+
}
|
|
815
|
+
},
|
|
816
|
+
"additionalProperties": false
|
|
817
|
+
},
|
|
818
|
+
|
|
819
|
+
"agentPromptResolved": {
|
|
820
|
+
"type": "object",
|
|
821
|
+
"description": "RFC 0029 §B. Emitted once per (nodeId, kind) pair BEFORE the corresponding `prompt.composed` event (when emitted). Surfaces the four-layer resolution chain per `spec/v1/prompts.md` §\"Resolution chain (normative)\" so cross-host debuggers and multi-agent visualizers can render which PromptRef applied at this node and why. Gated on `capabilities.prompts.supported: true`; payload is durable in the event log and participates in replay. Carries refs (not bodies), so emission is safe regardless of `capabilities.prompts.observability` — the secret-redaction + trust-marker invariants on `prompt.composed` (RFC 0027 §G) don't apply here.",
|
|
822
|
+
"required": ["nodeId", "kind", "chain", "resolved"],
|
|
823
|
+
"properties": {
|
|
824
|
+
"nodeId": {
|
|
825
|
+
"type": "string",
|
|
826
|
+
"description": "Lifted to top-level RunEventDoc.nodeId; mirrored here for self-contained payload validation."
|
|
827
|
+
},
|
|
828
|
+
"kind": { "$ref": "./prompt-kind.schema.json" },
|
|
829
|
+
"agentId": {
|
|
830
|
+
"type": "string",
|
|
831
|
+
"description": "AgentManifest.agentId when the node has `config.agentId` set; omitted otherwise."
|
|
832
|
+
},
|
|
833
|
+
"chain": {
|
|
834
|
+
"type": "array",
|
|
835
|
+
"description": "Ordered traversal of the four normative resolution layers. Hosts MUST emit one entry per layer attempted; skipped layers produce an entry with `applied: false` and `reason: \"...\"` describing why (e.g., `\"agentId unresolved\"`, `\"no candidate at this layer\"`, `\"superseded by node-config layer\"`).",
|
|
836
|
+
"items": {
|
|
837
|
+
"type": "object",
|
|
838
|
+
"additionalProperties": false,
|
|
839
|
+
"required": ["layer", "applied"],
|
|
840
|
+
"properties": {
|
|
841
|
+
"layer": {
|
|
842
|
+
"type": "string",
|
|
843
|
+
"enum": ["run-configurable", "node", "agent-intrinsic", "agent-overrides", "agent-library-default", "workflow-defaults", "host-defaults"],
|
|
844
|
+
"description": "Identifies which precedence layer this chain entry represents. The four normative layers per RFC 0029 §A are `node`, `agent-*`, `workflow-defaults`, `host-defaults`. `run-configurable` is reserved for the optional `RunOptions.configurable.promptOverrides` extension described in RFC 0029 §\"Alternatives considered\" #5; hosts that honor that extension MUST emit a chain entry with this layer value above the `node` entry."
|
|
845
|
+
},
|
|
846
|
+
"source": {
|
|
847
|
+
"type": "string",
|
|
848
|
+
"description": "The PromptRef stringy form this layer would have applied (e.g., `prompt:writer@1.0.0`). Object-form refs are projected to their stringy equivalent for stability. Present only when this layer had a candidate."
|
|
849
|
+
},
|
|
850
|
+
"applied": {
|
|
851
|
+
"type": "boolean",
|
|
852
|
+
"description": "True for exactly one entry in the chain — the layer whose ref was selected. False for layers that yielded null OR were skipped after a higher-precedence layer already applied."
|
|
853
|
+
},
|
|
854
|
+
"reason": {
|
|
855
|
+
"type": "string",
|
|
856
|
+
"description": "Non-normative human-readable explanation. Hosts MAY omit; clients MUST tolerate absence."
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
},
|
|
861
|
+
"resolved": {
|
|
862
|
+
"type": ["string", "null"],
|
|
863
|
+
"description": "The winning PromptRef in stringy form (`prompt:templateId@version`), or null if no layer yielded a candidate. Mirrors the `chain[].source` whose `applied: true` when present."
|
|
864
|
+
},
|
|
865
|
+
"causationHostId": {
|
|
866
|
+
"type": "string",
|
|
867
|
+
"minLength": 1,
|
|
868
|
+
"description": "RFC 0040 §A — optional cross-host causation pointer. Present + non-empty when this event's `causationId` points at an event on a DIFFERENT host; absent when same-host. MUST equal the originating host's `crossHostCausation.hostId`."
|
|
869
|
+
}
|
|
870
|
+
},
|
|
871
|
+
"additionalProperties": false
|
|
872
|
+
},
|
|
873
|
+
|
|
874
|
+
"modelCapabilitySubstituted": {
|
|
875
|
+
"type": "object",
|
|
876
|
+
"additionalProperties": false,
|
|
877
|
+
"description": "RFC 0031 §D. Emitted when a host substitutes the active model with a NodeModule's declared `fallbackModel` because the active model lacks one or more of the `requiredModelCapabilities`. MUST event per RFC 0031 §B step 3. The `fallbackProvider` + `fallbackModel` pair MAY be redacted as all-or-nothing `\"[REDACTED]\"` when workspace policy treats multi-vendor posture as confidential per SECURITY invariant `model-capability-substituted-no-credential-disclosure`. The other fields are not redactable — `originalProvider` is already public via `RunOptions.configurable.ai.provider`, and `nodeId` / `missingCapabilities` carry no provider-possession information.",
|
|
878
|
+
"required": ["nodeId", "originalProvider", "originalModel", "fallbackProvider", "fallbackModel", "missingCapabilities"],
|
|
879
|
+
"properties": {
|
|
880
|
+
"nodeId": { "type": "string", "description": "The node whose dispatch triggered the substitution." },
|
|
881
|
+
"originalProvider": { "type": "string", "description": "Provider id of the active model the host was about to use." },
|
|
882
|
+
"originalModel": { "type": "string", "description": "Model id of the active model." },
|
|
883
|
+
"fallbackProvider": { "type": "string", "description": "Provider id of the substitute model from `NodeModule.fallbackModel.provider`, or `\"[REDACTED]\"` when the host's workspace policy redacts multi-vendor posture." },
|
|
884
|
+
"fallbackModel": { "type": "string", "description": "Model id of the substitute model from `NodeModule.fallbackModel.model`, or `\"[REDACTED]\"` when redacted (always paired all-or-nothing with `fallbackProvider`)." },
|
|
885
|
+
"missingCapabilities": {
|
|
886
|
+
"type": "array",
|
|
887
|
+
"items": { "type": "string" },
|
|
888
|
+
"uniqueItems": true,
|
|
889
|
+
"description": "Subset of `NodeModule.requiredModelCapabilities` that the active model did not satisfy."
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
},
|
|
893
|
+
|
|
894
|
+
"modelCapabilityInsufficient": {
|
|
895
|
+
"type": "object",
|
|
896
|
+
"additionalProperties": false,
|
|
897
|
+
"description": "RFC 0031 §D. Emitted when a host refuses to dispatch a NodeModule because the active model lacks declared `requiredModelCapabilities` AND no viable fallback is available (no `fallbackModel` declared, OR the host cannot authenticate to the fallback provider). MUST event per RFC 0031 §B step 4. Pairs with `RunSnapshot.error.code = \"capability_not_provided\"` per `capabilities.md` §\"Unsupported capability — refusal contract\". Recursive fallback is NOT permitted (RFC 0031 §\"Unresolved questions\" #3): if a host attempts substitution and the fallback itself fails, the host MUST emit this event with `fallbackAttempted: true` and refuse, not chain to another fallback.",
|
|
898
|
+
"required": ["nodeId", "provider", "model", "missingCapabilities"],
|
|
899
|
+
"properties": {
|
|
900
|
+
"nodeId": { "type": "string", "description": "The node whose dispatch was refused." },
|
|
901
|
+
"provider": { "type": "string", "description": "Provider id of the active model the host attempted to use." },
|
|
902
|
+
"model": { "type": "string", "description": "Model id of the active model." },
|
|
903
|
+
"missingCapabilities": {
|
|
904
|
+
"type": "array",
|
|
905
|
+
"items": { "type": "string" },
|
|
906
|
+
"uniqueItems": true,
|
|
907
|
+
"description": "Subset of `NodeModule.requiredModelCapabilities` that the active model did not satisfy."
|
|
908
|
+
},
|
|
909
|
+
"fallbackAttempted": {
|
|
910
|
+
"type": "boolean",
|
|
911
|
+
"default": false,
|
|
912
|
+
"description": "True if the host attempted to authenticate to a declared `fallbackModel` and that attempt failed (e.g., no credential resolvable, or the fallback provider was outside `capabilities.aiProviders.supported`). False if no `fallbackModel` was declared on the NodeModule."
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
},
|
|
916
|
+
|
|
917
|
+
"envelopeRetryAttempted": {
|
|
918
|
+
"type": "object",
|
|
919
|
+
"additionalProperties": false,
|
|
920
|
+
"description": "RFC 0032 §B.1. Emitted when a host retries an envelope emission after a parse or validation failure on a prior attempt. The first attempt does NOT emit this event; the second attempt emits with `attempt: 2`, etc. SHOULD-tier — hosts that don't implement retry don't emit; hosts that DO retry on validation failure SHOULD emit per attempt past the first.",
|
|
921
|
+
"required": ["nodeId", "attempt", "reason"],
|
|
922
|
+
"properties": {
|
|
923
|
+
"nodeId": { "type": "string", "description": "The node whose envelope emission is being retried." },
|
|
924
|
+
"attempt": {
|
|
925
|
+
"type": "integer",
|
|
926
|
+
"minimum": 1,
|
|
927
|
+
"maximum": 16,
|
|
928
|
+
"description": "1-indexed attempt counter. The first attempt does NOT emit this event; the second attempt emits with `attempt: 2`, etc."
|
|
929
|
+
},
|
|
930
|
+
"reason": {
|
|
931
|
+
"type": "string",
|
|
932
|
+
"anyOf": [
|
|
933
|
+
{ "enum": ["schema-violation", "truncation", "type-drift", "type-mismatch", "refusal", "parse-error", "unknown"] },
|
|
934
|
+
{ "pattern": "^x-host-[a-z][a-z0-9-]*-[a-z][a-z0-9-]*$" }
|
|
935
|
+
],
|
|
936
|
+
"description": "Why the prior attempt failed. Spec-reserved values: `schema-violation` (model emitted wrong-shape JSON; corrective fragment + retry per RFC 0033 §C), `truncation` (stop_reason: max_tokens; budget-double + retry per RFC 0033 §B), `type-drift` (envelope `type` discriminator drifted from what was advertised mid-run), `type-mismatch` (a typed payload field was emitted with the wrong runtime type — e.g., string where number was declared), `refusal` (provider safety-stop; NO retry per RFC 0032 §B.3), `parse-error` (response was not extractable as JSON even after lenient parsing), `unknown` (host could not classify). Host-private extensions MUST prefix with `x-host-<host>-` per `host-extensions.md` §\"Canonical-prefix table\"; matches the RFC 0031 §B `requiredModelCapabilities` precedent."
|
|
937
|
+
},
|
|
938
|
+
"previousError": {
|
|
939
|
+
"type": ["string", "null"],
|
|
940
|
+
"description": "Diagnostic text from the failing attempt. MUST NOT contain prompt or response substring excerpts; hosts SHOULD limit to validator output (e.g., \"required field 'steps' missing\"). Subject to SR-1 redaction per `ai-envelope.md` §\"Redaction (SR-1 carry-forward)\"."
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
},
|
|
944
|
+
|
|
945
|
+
"envelopeRetryExhausted": {
|
|
946
|
+
"type": "object",
|
|
947
|
+
"additionalProperties": false,
|
|
948
|
+
"description": "RFC 0032 §B.2. Emitted when a host has exhausted its retry budget and is about to surface a terminal envelope failure to the node. MUST-tier — alternative is silent terminal failure with no event surface, leaving conformance suites unable to assert correct give-up behavior. Hosts that don't retry MUST still emit this event when an envelope attempt terminally fails (with `totalAttempts: 1`). Pairs with `cap.breached` when the failure mode is schema-violation or truncation per RFC 0033 §B + §C.",
|
|
949
|
+
"required": ["nodeId", "totalAttempts", "finalReason"],
|
|
950
|
+
"properties": {
|
|
951
|
+
"nodeId": { "type": "string" },
|
|
952
|
+
"totalAttempts": { "type": "integer", "minimum": 1 },
|
|
953
|
+
"finalReason": {
|
|
954
|
+
"type": "string",
|
|
955
|
+
"anyOf": [
|
|
956
|
+
{ "enum": ["schema-violation", "truncation", "type-drift", "type-mismatch", "refusal", "parse-error", "unknown"] },
|
|
957
|
+
{ "pattern": "^x-host-[a-z][a-z0-9-]*-[a-z][a-z0-9-]*$" }
|
|
958
|
+
],
|
|
959
|
+
"description": "Same value set as `envelope.retry.attempted.reason` (§B.1). Spec-reserved closed enum plus `x-host-<host>-` extensions."
|
|
960
|
+
},
|
|
961
|
+
"finalError": {
|
|
962
|
+
"type": ["string", "null"],
|
|
963
|
+
"description": "Diagnostic text from the final attempt. Same redaction discipline as `envelope.retry.attempted.previousError`."
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
},
|
|
967
|
+
|
|
968
|
+
"envelopeRefusal": {
|
|
969
|
+
"type": "object",
|
|
970
|
+
"additionalProperties": false,
|
|
971
|
+
"description": "RFC 0032 §B.3. Emitted when the underlying LLM provider returns an explicit refusal (e.g., OpenAI `message.refusal`, Anthropic safety-stop, Gemini safety-block). MUST-tier. Hosts MUST NOT retry on refusal — retrying refusal with prompt mutation creates a circumvention concern (the host automatically searches for a prompt the model will accept, evading the safety filter's intent) per RFC 0032 §B.3 + RFC 0033 §D. The node MUST fail with `error.code = \"envelope_refusal\"` per RFC 0033 §F (renamed from `envelope_refused_by_provider` per the 2026-05-21 RFC adoption-feedback amendment).",
|
|
972
|
+
"required": ["nodeId", "provider", "model"],
|
|
973
|
+
"properties": {
|
|
974
|
+
"nodeId": { "type": "string" },
|
|
975
|
+
"provider": { "type": "string" },
|
|
976
|
+
"model": { "type": "string" },
|
|
977
|
+
"refusalText": {
|
|
978
|
+
"type": ["string", "null"],
|
|
979
|
+
"description": "Provider-returned refusal message, if any. MUST be passed through the host's BYOK redaction harness AND prompt-content redaction pipeline before emission (per SECURITY invariant `envelope-refusal-no-prompt-leak`). Provider safety-refusal messages can echo offending prompt substrings; emitting them verbatim would create a side channel for prompt-injection-attack telemetry exfiltration AND for SR-1 secret-leak. Replay consumers MUST tolerate `null` even when the original was non-null (host redaction policies legitimately tighten over time)."
|
|
980
|
+
},
|
|
981
|
+
"safetyCategory": {
|
|
982
|
+
"type": ["string", "null"],
|
|
983
|
+
"description": "Provider-specific safety category if available (e.g., Anthropic `harmful-content`, Gemini `SAFETY_BLOCK_HARASSMENT`, OpenAI `policy_violation`). Verbatim from provider; no normalization."
|
|
984
|
+
}
|
|
985
|
+
}
|
|
986
|
+
},
|
|
987
|
+
|
|
988
|
+
"envelopeTruncated": {
|
|
989
|
+
"type": "object",
|
|
990
|
+
"additionalProperties": false,
|
|
991
|
+
"description": "RFC 0032 §B.4. Emitted when the LLM emission was cut off before the envelope was complete (typically `stop_reason: \"max_tokens\"`). SHOULD-tier — hosts that distinguish truncation from schema-violation per RFC 0033 §A MUST emit this event when truncation occurs. Hosts that conflate truncation with schema-violation (legacy behavior) MAY omit; they then fail RFC 0033 conformance.",
|
|
992
|
+
"required": ["nodeId", "provider", "model", "stopReason"],
|
|
993
|
+
"properties": {
|
|
994
|
+
"nodeId": { "type": "string" },
|
|
995
|
+
"provider": { "type": "string" },
|
|
996
|
+
"model": { "type": "string" },
|
|
997
|
+
"stopReason": {
|
|
998
|
+
"type": "string",
|
|
999
|
+
"enum": ["max_tokens", "length", "stop_sequence", "unknown"],
|
|
1000
|
+
"description": "Provider-normalized stop reason. `max_tokens` covers OpenAI `length` + Anthropic `max_tokens`; `length` preserved as a separate value for hosts that distinguish provider-side `length` (model self-determined length cap) from `max_tokens` (host-side budget cap)."
|
|
1001
|
+
},
|
|
1002
|
+
"partialPayloadAvailable": {
|
|
1003
|
+
"type": "boolean",
|
|
1004
|
+
"default": false,
|
|
1005
|
+
"description": "True if the host recovered a partial envelope before truncation. Hosts MAY use this signal to route to a recovery path (e.g., budget-doubled retry per RFC 0033 §B)."
|
|
1006
|
+
},
|
|
1007
|
+
"outputTokenCount": {
|
|
1008
|
+
"type": ["integer", "null"],
|
|
1009
|
+
"minimum": 0,
|
|
1010
|
+
"description": "Tokens emitted before truncation. Sourced from the provider response's usage block. MUST replay identically."
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
},
|
|
1014
|
+
|
|
1015
|
+
"envelopeNlToFormatEngaged": {
|
|
1016
|
+
"type": "object",
|
|
1017
|
+
"additionalProperties": false,
|
|
1018
|
+
"description": "RFC 0032 §B.5. Emitted when the host has escalated to a two-call NL-to-Format fallback after retry exhaustion (per Tam et al., arXiv 2408.02442 mitigation strategy: free-form reasoning in the first call → schema coercion in the second call). MAY-tier — NL-to-Format is one of many possible recovery strategies; hosts that don't implement it don't advertise it.",
|
|
1019
|
+
"required": ["nodeId", "originalEnvelopeType"],
|
|
1020
|
+
"properties": {
|
|
1021
|
+
"nodeId": { "type": "string" },
|
|
1022
|
+
"originalEnvelopeType": { "type": "string", "description": "The envelope kind the original attempt was trying to emit." },
|
|
1023
|
+
"fallbackCalls": {
|
|
1024
|
+
"type": "integer",
|
|
1025
|
+
"minimum": 1,
|
|
1026
|
+
"default": 1,
|
|
1027
|
+
"description": "Number of secondary LLM calls used to reformat free-form output into the envelope's schema."
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
},
|
|
1031
|
+
|
|
1032
|
+
"envelopeRecoveryApplied": {
|
|
1033
|
+
"type": "object",
|
|
1034
|
+
"additionalProperties": false,
|
|
1035
|
+
"description": "RFC 0032 §B.6. Emitted when lenient parsing recovered a malformed envelope (e.g., JSON repair via `jsonrepair`, markdown fence stripping, last-balanced-object extraction). Recovery is internal to the parsing step, BEFORE validation; recovery does NOT consume a retry attempt per RFC 0033 §D. MAY-tier — lenient parsing is a host-discretion recovery path. The event payload MUST NOT carry the recovered envelope content or any substring from the model's pre-recovery output per SECURITY invariant `envelope-recovery-no-content-leak`; only the recovery path identifier and the optional `byteOffset` are emitted. The recovered content rides on the subsequent envelope acceptance + downstream `RunEventDoc`, NOT on this event.",
|
|
1036
|
+
"required": ["nodeId", "path"],
|
|
1037
|
+
"properties": {
|
|
1038
|
+
"nodeId": { "type": "string" },
|
|
1039
|
+
"path": {
|
|
1040
|
+
"type": "string",
|
|
1041
|
+
"enum": ["direct", "jsonrepair", "markdown-fence", "brace-walker", "custom"],
|
|
1042
|
+
"description": "Which recovery path succeeded. `direct` is reserved for the no-recovery-needed path and is informational; hosts MAY omit emission when `path: \"direct\"`. MUST replay identically (deterministic parsing → deterministic recovery outcome)."
|
|
1043
|
+
},
|
|
1044
|
+
"byteOffset": {
|
|
1045
|
+
"type": ["integer", "null"],
|
|
1046
|
+
"minimum": 0,
|
|
1047
|
+
"description": "Byte position where recovery succeeded. Useful for debugging which fraction of the model's output was salvageable."
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
1050
|
+
},
|
|
1051
|
+
|
|
601
1052
|
"agentToolCalled": {
|
|
602
1053
|
"type": "object",
|
|
603
1054
|
"description": "Multi-Agent Shift Phase 1. Emitted when an agent invokes a tool. Pairs with `agent.toolReturned` via shared `callId`.",
|
|
@@ -606,7 +1057,8 @@
|
|
|
606
1057
|
"agentId": { "type": "string", "minLength": 3, "maxLength": 256 },
|
|
607
1058
|
"toolName": { "type": "string", "minLength": 1, "description": "Tool identifier (typically a node-pack typeId or function name)." },
|
|
608
1059
|
"callId": { "type": "string", "minLength": 1, "description": "Unique correlation id linking this call to the subsequent `agent.toolReturned`. Hosts SHOULD use UUIDs or content-addressable hashes." },
|
|
609
|
-
"inputs": { "description": "Tool inputs. Shape is tool-specific; consumers MUST NOT assume an object." }
|
|
1060
|
+
"inputs": { "description": "Tool inputs. Shape is tool-specific; consumers MUST NOT assume an object." },
|
|
1061
|
+
"causationHostId": { "type": "string", "minLength": 1, "description": "RFC 0040 §A — optional cross-host causation pointer. Present + non-empty when this event's `causationId` points at an event on a DIFFERENT host; absent when same-host. MUST equal the originating host's `crossHostCausation.hostId`." }
|
|
610
1062
|
},
|
|
611
1063
|
"additionalProperties": true
|
|
612
1064
|
},
|
|
@@ -620,7 +1072,8 @@
|
|
|
620
1072
|
"toolName": { "type": "string", "minLength": 1 },
|
|
621
1073
|
"callId": { "type": "string", "minLength": 1 },
|
|
622
1074
|
"outcome": { "description": "Tool result. Discriminated by host on success vs error; payload shape is tool-specific." },
|
|
623
|
-
"error": { "$ref": "#/$defs/_errorObject", "description": "Set on tool-execution failure. Mutually exclusive with `outcome`." }
|
|
1075
|
+
"error": { "$ref": "#/$defs/_errorObject", "description": "Set on tool-execution failure. Mutually exclusive with `outcome`." },
|
|
1076
|
+
"causationHostId": { "type": "string", "minLength": 1, "description": "RFC 0040 §A — optional cross-host causation pointer. Present + non-empty when this event's `causationId` points at an event on a DIFFERENT host; absent when same-host. MUST equal the originating host's `crossHostCausation.hostId`." }
|
|
624
1077
|
},
|
|
625
1078
|
"additionalProperties": true
|
|
626
1079
|
},
|
|
@@ -632,7 +1085,8 @@
|
|
|
632
1085
|
"properties": {
|
|
633
1086
|
"fromAgentId": { "type": "string", "minLength": 3, "maxLength": 256, "description": "Agent surrendering control." },
|
|
634
1087
|
"toAgentId": { "type": "string", "minLength": 3, "maxLength": 256, "description": "Agent receiving control." },
|
|
635
|
-
"reason": { "type": "string", "description": "Optional handoff rationale (e.g., 'specialist routing', 'escalation', 'dispatch-loop-iteration')." }
|
|
1088
|
+
"reason": { "type": "string", "description": "Optional handoff rationale (e.g., 'specialist routing', 'escalation', 'dispatch-loop-iteration')." },
|
|
1089
|
+
"causationHostId": { "type": "string", "minLength": 1, "description": "RFC 0040 §A — optional cross-host causation pointer. Present + non-empty when this event's `causationId` points at an event on a DIFFERENT host; absent when same-host. MUST equal the originating host's `crossHostCausation.hostId`." }
|
|
636
1090
|
},
|
|
637
1091
|
"additionalProperties": true
|
|
638
1092
|
},
|
|
@@ -644,7 +1098,8 @@
|
|
|
644
1098
|
"properties": {
|
|
645
1099
|
"agentId": { "type": "string", "minLength": 3, "maxLength": 256 },
|
|
646
1100
|
"decision": { "description": "Typed decision payload. Shape is decision-specific; consumers MUST NOT assume an object." },
|
|
647
|
-
"confidence": { "type": "number", "minimum": 0, "maximum": 1, "description": "Optional confidence in `[0, 1]`. Below the threshold → escalate via `node.suspended { reason: 'low-confidence' }`. Absent values are treated as 'no signal' — no escalation." }
|
|
1101
|
+
"confidence": { "type": "number", "minimum": 0, "maximum": 1, "description": "Optional confidence in `[0, 1]`. Below the threshold → escalate via `node.suspended { reason: 'low-confidence' }`. Absent values are treated as 'no signal' — no escalation." },
|
|
1102
|
+
"causationHostId": { "type": "string", "minLength": 1, "description": "RFC 0040 §A — optional cross-host causation pointer. Present + non-empty when this event's `causationId` points at an event on a DIFFERENT host; absent when same-host. MUST equal the originating host's `crossHostCausation.hostId`." }
|
|
648
1103
|
},
|
|
649
1104
|
"additionalProperties": true
|
|
650
1105
|
},
|
|
@@ -655,7 +1110,20 @@
|
|
|
655
1110
|
"required": ["agentId", "decision"],
|
|
656
1111
|
"properties": {
|
|
657
1112
|
"agentId": { "type": "string", "minLength": 3, "maxLength": 256, "description": "AgentRef.agentId of the orchestrator. MUST equal `RunSnapshot.runOrchestrator.agentId` for the run's lifetime — orchestrator identity is set at first decision and does not change." },
|
|
658
|
-
"decision": { "$ref": "orchestrator-decision.schema.json" }
|
|
1113
|
+
"decision": { "$ref": "orchestrator-decision.schema.json" },
|
|
1114
|
+
"causationHostId": { "type": "string", "minLength": 1, "description": "RFC 0040 §A — optional cross-host causation pointer. Present + non-empty when this event's `causationId` points at an event on a DIFFERENT host; absent when same-host. MUST equal the originating host's `crossHostCausation.hostId`." }
|
|
1115
|
+
},
|
|
1116
|
+
"additionalProperties": false
|
|
1117
|
+
},
|
|
1118
|
+
|
|
1119
|
+
"nodeDispatched": {
|
|
1120
|
+
"type": "object",
|
|
1121
|
+
"description": "Emitted by `core.dispatch` (RFC 0007 §D + RFC 0022 §A) for each child workflow spawned by a `next-worker` OrchestratorDecision. The envelope's top-level `nodeId` carries the dispatching `core.dispatch` node's id; the payload names the spawned child run + its terminal status at the moment of emission. Lets observers reconstruct the parent → child linkage without scanning the runs table.",
|
|
1122
|
+
"required": ["childRunId", "childWorkflowId"],
|
|
1123
|
+
"properties": {
|
|
1124
|
+
"childRunId": { "type": "string", "minLength": 1, "description": "RunId of the spawned child run." },
|
|
1125
|
+
"childWorkflowId": { "type": "string", "minLength": 1, "description": "WorkflowId of the spawned child." },
|
|
1126
|
+
"childStatus": { "type": "string", "description": "Status of the child run at the moment the dispatch step returned (typically `completed`, `failed`, `cancelled`, or one of the `waiting-*` states when the child suspended)." }
|
|
659
1127
|
},
|
|
660
1128
|
"additionalProperties": false
|
|
661
1129
|
},
|