|
| 1 | +{ |
| 2 | + "description": "Context.create unified-signature contract across all SDKs. Validates the v0.22.0 normative input list (identity, trace_parent, cancel_token, data, services, global_deadline), the removal of executor and caller_id as inputs, the Executor binding rules (local create, cross-process deserialize, hot-reload restore), and child() propagation. See docs/features/core-executor.md §Contract: Context.create + §Contract: Executor binding to Context, and Issue #66.", |
| 3 | + "test_cases": [ |
| 4 | + { |
| 5 | + "id": "create_minimal_all_defaults", |
| 6 | + "description": "Context.create() with no arguments yields a fresh top-level Context — null executor, null identity, null cancel_token, empty data, empty call_chain, null caller_id, 32-char hex trace_id.", |
| 7 | + "input": {}, |
| 8 | + "expected": { |
| 9 | + "trace_id_pattern": "^[0-9a-f]{32}$", |
| 10 | + "identity": null, |
| 11 | + "executor": null, |
| 12 | + "cancel_token": null, |
| 13 | + "services": null, |
| 14 | + "global_deadline": null, |
| 15 | + "caller_id": null, |
| 16 | + "call_chain": [], |
| 17 | + "data_empty": true |
| 18 | + } |
| 19 | + }, |
| 20 | + { |
| 21 | + "id": "create_with_identity_only", |
| 22 | + "description": "Most common adapter pattern: only identity supplied. trace_id auto-generated, all other caller-inputs null.", |
| 23 | + "input": { |
| 24 | + "identity": { |
| 25 | + "id": "user-42", |
| 26 | + "type": "user", |
| 27 | + "roles": ["analyst"] |
| 28 | + } |
| 29 | + }, |
| 30 | + "expected": { |
| 31 | + "trace_id_pattern": "^[0-9a-f]{32}$", |
| 32 | + "identity_id": "user-42", |
| 33 | + "executor": null, |
| 34 | + "cancel_token": null |
| 35 | + } |
| 36 | + }, |
| 37 | + { |
| 38 | + "id": "create_with_cancel_token", |
| 39 | + "description": "cancel_token is a first-class parameter (v0.22.0+). Verifies the token is carried on the returned Context without post-hoc assignment.", |
| 40 | + "input": { |
| 41 | + "cancel_token_handle": "TOKEN_FIXTURE_A" |
| 42 | + }, |
| 43 | + "expected": { |
| 44 | + "cancel_token_bound": true, |
| 45 | + "cancel_token_matches_input": true, |
| 46 | + "executor_at_create_time": null |
| 47 | + } |
| 48 | + }, |
| 49 | + { |
| 50 | + "id": "create_with_global_deadline", |
| 51 | + "description": "global_deadline is an accepted caller input. Local-only — does not affect serialize round-trip (see context_create_distributed).", |
| 52 | + "input": { |
| 53 | + "global_deadline": 1234567890.5 |
| 54 | + }, |
| 55 | + "expected": { |
| 56 | + "global_deadline": 1234567890.5, |
| 57 | + "executor": null |
| 58 | + } |
| 59 | + }, |
| 60 | + { |
| 61 | + "id": "create_rejects_executor_input", |
| 62 | + "description": "executor MUST NOT be a public Context.create() parameter. SDKs MAY enforce by signature (no such parameter exists) or by runtime check. Either is conforming as long as 'pass executor at construction' is not possible through the public API.", |
| 63 | + "input": { |
| 64 | + "attempt_pass_executor": true |
| 65 | + }, |
| 66 | + "expected": { |
| 67 | + "executor_is_not_a_parameter": true |
| 68 | + } |
| 69 | + }, |
| 70 | + { |
| 71 | + "id": "create_rejects_caller_id_input", |
| 72 | + "description": "caller_id MUST NOT be a public Context.create() parameter. Top-level Contexts always have caller_id = null; the field is managed exclusively by Context.child().", |
| 73 | + "input": { |
| 74 | + "attempt_pass_caller_id": "fake.caller" |
| 75 | + }, |
| 76 | + "expected": { |
| 77 | + "caller_id_is_not_a_parameter": true, |
| 78 | + "caller_id_after_create": null |
| 79 | + } |
| 80 | + }, |
| 81 | + { |
| 82 | + "id": "executor_binds_on_first_call_local", |
| 83 | + "description": "Local construction → first Executor.call() binds the Executor to ctx.executor. After the call returns, ctx.executor MUST equal the Executor instance.", |
| 84 | + "input": { |
| 85 | + "construction": "Context.create()", |
| 86 | + "call_module": "test.echo" |
| 87 | + }, |
| 88 | + "expected": { |
| 89 | + "executor_at_create_time": null, |
| 90 | + "executor_after_first_call_bound": true, |
| 91 | + "binding_pre_step_1": true |
| 92 | + } |
| 93 | + }, |
| 94 | + { |
| 95 | + "id": "executor_binds_idempotent_same_instance", |
| 96 | + "description": "Reusing the same Context across multiple top-level Executor.call() invocations on the same Executor instance is a noop on subsequent calls — MUST NOT raise.", |
| 97 | + "input": { |
| 98 | + "construction": "Context.create()", |
| 99 | + "calls": ["test.echo", "test.echo", "test.echo"], |
| 100 | + "same_executor": true |
| 101 | + }, |
| 102 | + "expected": { |
| 103 | + "rebind_noop": true, |
| 104 | + "raised_error": false, |
| 105 | + "executor_identity_stable": true |
| 106 | + } |
| 107 | + }, |
| 108 | + { |
| 109 | + "id": "executor_rejects_cross_executor_rebind", |
| 110 | + "description": "If ctx.executor is already bound to Executor A and Executor B receives the same Context, B SHOULD raise ContextBindingError. SDKs that accept silently MUST document this deviation prominently.", |
| 111 | + "input": { |
| 112 | + "executor_a_first": true, |
| 113 | + "executor_b_second": true, |
| 114 | + "same_executor": false |
| 115 | + }, |
| 116 | + "expected_one_of": [ |
| 117 | + { |
| 118 | + "behavior": "raise", |
| 119 | + "error_type": "ContextBindingError" |
| 120 | + }, |
| 121 | + { |
| 122 | + "behavior": "silent_accept", |
| 123 | + "documented_deviation_required": true |
| 124 | + } |
| 125 | + ] |
| 126 | + }, |
| 127 | + { |
| 128 | + "id": "child_propagates_executor", |
| 129 | + "description": "Context.child() MUST propagate the bound executor to the child Context.", |
| 130 | + "input": { |
| 131 | + "bind_executor": true, |
| 132 | + "create_child_module_id": "test.target" |
| 133 | + }, |
| 134 | + "expected": { |
| 135 | + "child_executor_matches_parent": true, |
| 136 | + "child_caller_id_from_parent_chain_tip": true, |
| 137 | + "child_call_chain_appends_target": true |
| 138 | + } |
| 139 | + }, |
| 140 | + { |
| 141 | + "id": "child_propagates_cancel_token", |
| 142 | + "description": "Context.child() MUST propagate the cancel_token to the child Context. Same reference (or equivalent linked instance) — modules deep in the call chain MUST observe cancellation.", |
| 143 | + "input": { |
| 144 | + "create_with_cancel_token": "TOKEN_FIXTURE_B", |
| 145 | + "create_child_module_id": "test.target" |
| 146 | + }, |
| 147 | + "expected": { |
| 148 | + "child_cancel_token_bound": true, |
| 149 | + "child_cancel_token_matches_parent": true |
| 150 | + } |
| 151 | + }, |
| 152 | + { |
| 153 | + "id": "deserialize_then_call_binds_local_executor", |
| 154 | + "description": "Cross-process scenario: deserialize a serialized Context (executor / cancel_token / services / global_deadline all stripped per §5.7), then call() on the receiving node. The local Executor MUST bind itself per the same rule as local Context.create().", |
| 155 | + "input": { |
| 156 | + "serialized_context": { |
| 157 | + "_context_version": 1, |
| 158 | + "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736", |
| 159 | + "caller_id": "remote.caller", |
| 160 | + "call_chain": ["remote.caller", "remote.target"], |
| 161 | + "identity": {"id": "user-remote", "type": "user", "roles": [], "attrs": {}}, |
| 162 | + "data": {} |
| 163 | + }, |
| 164 | + "call_module": "local.target" |
| 165 | + }, |
| 166 | + "expected": { |
| 167 | + "executor_after_deserialize": null, |
| 168 | + "cancel_token_after_deserialize": null, |
| 169 | + "services_after_deserialize": null, |
| 170 | + "global_deadline_after_deserialize": null, |
| 171 | + "caller_id_preserved": "remote.caller", |
| 172 | + "executor_bound_on_first_call": true |
| 173 | + } |
| 174 | + }, |
| 175 | + { |
| 176 | + "id": "distributed_cancel_token_synthesized_locally", |
| 177 | + "description": "When a deserialized Context arrives on a remote node, the receiving Executor MUST synthesize a fresh local CancelToken at pipeline entry. Distributed cancellation does NOT ride the in-context cancel_token field.", |
| 178 | + "input": { |
| 179 | + "deserialized_context_has_no_cancel_token": true, |
| 180 | + "execute_module": "local.slow" |
| 181 | + }, |
| 182 | + "expected": { |
| 183 | + "fresh_cancel_token_synthesized": true, |
| 184 | + "cancel_token_is_local_instance": true |
| 185 | + } |
| 186 | + }, |
| 187 | + { |
| 188 | + "id": "distributed_global_deadline_recomputed_locally", |
| 189 | + "description": "When a deserialized Context arrives on a remote node, the receiving Executor MUST recompute global_deadline from local executor.global_timeout config. The originating node's deadline intent does not propagate via global_deadline.", |
| 190 | + "input": { |
| 191 | + "deserialized_context_has_no_global_deadline": true, |
| 192 | + "local_executor_global_timeout_ms": 60000 |
| 193 | + }, |
| 194 | + "expected": { |
| 195 | + "global_deadline_recomputed_locally": true, |
| 196 | + "global_deadline_derived_from_config": true |
| 197 | + } |
| 198 | + }, |
| 199 | + { |
| 200 | + "id": "tracestate_carried_inside_traceparent", |
| 201 | + "description": "TraceParent type MUST carry tracestate as a field (no separate Context.create() parameter). Verifies cross-SDK shape: Python/TS/Rust all embed tracestate in TraceParent.", |
| 202 | + "input": { |
| 203 | + "trace_parent": { |
| 204 | + "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736", |
| 205 | + "parent_id": "00f067aa0ba902b7", |
| 206 | + "trace_flags": "01", |
| 207 | + "tracestate": [["vendor1", "value1"], ["vendor2", "value2"]] |
| 208 | + } |
| 209 | + }, |
| 210 | + "expected": { |
| 211 | + "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736", |
| 212 | + "tracestate_preserved": true, |
| 213 | + "no_separate_tracestate_parameter": true |
| 214 | + } |
| 215 | + } |
| 216 | + ] |
| 217 | +} |
0 commit comments