planr 1.6.0 → 1.7.1

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 (63) hide show
  1. package/README.md +46 -22
  2. package/docs/ARCHITECTURE.md +12 -1
  3. package/docs/RELEASE.md +37 -7
  4. package/docs/SWITCHLOOM_COMPATIBILITY.md +46 -0
  5. package/docs/contracts/EVAL_CONTRACT_V1.md +2072 -0
  6. package/docs/documentation/CONTRACT.md +7 -7
  7. package/docs/documentation/COVERAGE.md +5 -3
  8. package/docs/documentation/INFORMATION_ARCHITECTURE.md +5 -2
  9. package/docs/fixtures/mcp-contract.json +12 -0
  10. package/npm/native/darwin-arm64/planr +0 -0
  11. package/npm/native/darwin-x86_64/planr +0 -0
  12. package/npm/native/linux-arm64/planr +0 -0
  13. package/npm/native/linux-x86_64/planr +0 -0
  14. package/package.json +5 -1
  15. package/plugins/planr/.claude-plugin/plugin.json +1 -1
  16. package/plugins/planr/.codex-plugin/plugin.json +1 -1
  17. package/plugins/planr/agents/planr-worker.md +1 -1
  18. package/plugins/planr/skills/planr-goal/SKILL.md +21 -49
  19. package/plugins/planr/skills/planr-loop/SKILL.md +28 -94
  20. package/plugins/planr/skills/planr-loop/agents/planr-worker.md +1 -1
  21. package/plugins/planr/skills/planr-loop/references/host-dispatch.md +10 -0
  22. package/plugins/planr/skills/planr-loop/references/recovery-and-verification.md +24 -0
  23. package/plugins/planr/skills/planr-task-graph/SKILL.md +21 -190
  24. package/docs/CI.md +0 -54
  25. package/docs/CLAUDE_CODE.md +0 -52
  26. package/docs/CLI_REFERENCE.md +0 -160
  27. package/docs/CODEX.md +0 -56
  28. package/docs/CURSOR.md +0 -114
  29. package/docs/EXAMPLE_WEBAPP.md +0 -103
  30. package/docs/GOALS.md +0 -175
  31. package/docs/HANDOFFS_AND_STORIES.md +0 -121
  32. package/docs/HOOKS.md +0 -34
  33. package/docs/IMPORT.md +0 -21
  34. package/docs/INSTALL.md +0 -115
  35. package/docs/MCP_CONTRACT.md +0 -73
  36. package/docs/MCP_GUIDE.md +0 -40
  37. package/docs/MODEL_ROUTING.md +0 -33
  38. package/docs/NPM.md +0 -40
  39. package/docs/OPERATING_MODEL.md +0 -250
  40. package/docs/ROUTING_BUNDLES.md +0 -15
  41. package/docs/SECURITY.md +0 -8
  42. package/docs/SKILLS.md +0 -261
  43. package/docs/TASK_GRAPH_MODEL.md +0 -272
  44. package/docs/TESTING.md +0 -87
  45. package/docs/TROUBLESHOOTING.md +0 -30
  46. package/docs/planr-spec/ADRS.md +0 -160
  47. package/docs/planr-spec/AI_SPEC.md +0 -138
  48. package/docs/planr-spec/ANALYTICS_OBSERVABILITY_SPEC.md +0 -124
  49. package/docs/planr-spec/API_AND_DATA_MODEL.md +0 -517
  50. package/docs/planr-spec/BACKEND_IMPLEMENTATION_SPEC.md +0 -178
  51. package/docs/planr-spec/CLIENT_IMPLEMENTATION_SPEC.md +0 -119
  52. package/docs/planr-spec/DESIGN_SYSTEM_SPEC.md +0 -102
  53. package/docs/planr-spec/PRODUCT_SPEC.md +0 -193
  54. package/docs/planr-spec/QA_ACCEPTANCE_TESTS.md +0 -146
  55. package/docs/planr-spec/README.md +0 -67
  56. package/docs/planr-spec/REFERENCES.md +0 -29
  57. package/docs/planr-spec/RELEASE_READINESS.md +0 -95
  58. package/docs/planr-spec/SAFETY_PRIVACY_SECURITY.md +0 -169
  59. package/docs/planr-spec/TASKS.md +0 -932
  60. package/docs/planr-spec/TECH_ARCHITECTURE.md +0 -143
  61. package/docs/planr-spec/UX_FLOWS.md +0 -235
  62. package/docs/release-candidates/planr-v1.5.2.md +0 -164
  63. /package/docs/{planr-spec → contracts}/V1_1_DIFFERENTIATION_CONTRACT.md +0 -0
@@ -0,0 +1,2072 @@
1
+ # Eval Contract v1
2
+
3
+ Status: frozen for implementation; v1.1 additive efficiency-evidence amendment frozen for implementation
4
+
5
+ Eval Contract v1 is a Planr-owned, local-first contract for deciding whether a candidate revision is better than a frozen baseline. It defines authored suite inputs, immutable run evidence, recomputable comparisons, CLI/MCP response shapes, and the ownership boundaries that prevent eval verdicts from becoming a second source of truth for map work closure. The v1.1 amendment is additive: it freezes attempt lineage, metering basis, effective treatment identity, versioned estimate provenance, and derived failure-inclusive efficiency metrics without changing the four comparison verdicts.
6
+
7
+ ## Ownership
8
+
9
+ - Repository files own authored suite manifests, fixtures, expected lifecycle assertions, and checked-in examples.
10
+ - The suite loader owns parsing, repository containment, normalization, canonical ordering, fixture hashing, and suite digests.
11
+ - SQLite owns immutable suite snapshots, runs, case results, samples, comparisons, invalidations, rescoring provenance, and evidence references.
12
+ - The comparison engine owns compatibility, freshness, coverage, correctness, quality, protected-dimension, performance, and cost gates.
13
+ - Existing Planr map state owns item status, dependencies, picks, logs, reviews, approvals, and closure. An eval verdict can be attached as evidence, but it cannot close or reopen work by itself.
14
+ - CLI, MCP, and HTTP adapters may render the contract; they must call shared application services and must not implement independent scoring or status transitions.
15
+ - Eval Contract v1.1 owns derived efficiency semantics over EvalSample evidence. It does not own a second evidence store, provider catalog, pricing service, router, model leaderboard, or closure authority.
16
+
17
+ ## V1.1 Additive Efficiency Evidence
18
+
19
+ V1.1 extends the V1 objects with the smallest shape needed to compare cost and wall time per independently verified success while preserving backward compatibility. V1 readers may ignore the additive fields and still interpret V1 snapshots, runs, samples, comparisons, invalidations, evidence refs, exit codes, and the four existing comparison verdicts. V1.1 readers must retain the V1 verdict vocabulary exactly: `improved`, `regressed`, `no_material_difference`, and `insufficient_evidence`.
20
+
21
+ Attempt lineage is countable evidence, not a separate run model:
22
+
23
+ - One countable attempt is one case, repetition, and seed execution under one effective treatment, including failures, retries, availability fallbacks, quality escalations, quota downgrades, safety stops, and resumed attempts that re-execute the subject.
24
+ - Every countable attempt records `task_id`, `case_id`, `repetition_index`, `seed`, `attempt_index`, `retry_of`, `fallback_of`, `escalation_of`, `resume_of`, terminal status, reason codes, runner/harness version, and the effective treatment identity described below.
25
+ - Failed and retried attempts remain durable and participate in aggregate cost and wall-time totals. They cannot be silently dropped, overwritten by a later success, or double-counted as both their original attempt and their retry.
26
+ - Warmups may be retained for audit but are excluded from efficiency denominators unless the suite explicitly marks them countable.
27
+
28
+ Metering basis is per dimension:
29
+
30
+ - `actual_trusted`: value came from an allowlisted process timer, Planr route observation, host usage receipt, or scorer/event meter whose source and confidence are recorded and accepted by policy.
31
+ - `estimated`: value was computed from durable measured inputs and a versioned estimate reference, such as `pricing_reference_id`, `pricing_reference_version`, `currency`, and `effective_at`. Estimated values are visible but cannot masquerade as trusted actuals.
32
+ - `mixed`: an aggregate combines multiple accepted bases, such as trusted tool calls with estimated credits, or baseline and candidate runs with different bases. Mixed evidence is non-promotable unless the comparison policy explicitly permits the named mixed basis.
33
+ - `unavailable`: the value is unknown. Unavailable never coerces to `0`, never satisfies budget or material-improvement evidence, and must remain `null` or omitted with an `unavailable` basis in JSON projections.
34
+
35
+ Allowlisted dimensions are `wall_time_ms`, `tool_calls`, `input_tokens`, `output_tokens`, `total_tokens`, `credits_micros`, and `cost_micros`. Each EvalSample for these dimensions carries `metering_basis`, `basis_source`, `basis_confidence`, and optional versioned estimate provenance. Unknown or untrusted values stay unavailable rather than falling back to authored defaults, requested route declarations, package metadata, or provider catalog guesses.
36
+
37
+ Effective treatment identity is the comparison key:
38
+
39
+ - Each attempt records `client`, `provider`, `runtime`, `model`, `effort`, `profile_id`, `profile_digest`, `route_policy_digest`, `runner_version`, `harness_version`, task, repetition, and seed.
40
+ - Requested route values are not effective treatment evidence. A requested model, effort, provider, runtime, or profile may be stored as intent, but comparison identity uses only resolved/effective values with confidence provenance.
41
+ - Silent treatment fallback invalidates matched comparison for the affected cell. If the candidate requested one treatment but effectively ran another and every changed treatment field is not accounted for by a field-specific fallback, escalation, effort downgrade, or quota downgrade reason, the comparison records `treatment_fallback_unattributed` and returns `insufficient_evidence`. Retry and resume lineage alone do not attribute treatment changes, and fallback or escalation lineage pointers are audit context rather than blanket attribution for unrelated fields.
42
+
43
+ Derived efficiency metrics are recomputable from EvalSample and case/attempt status rows:
44
+
45
+ - `cost_per_attempt` and `wall_time_per_attempt` divide all countable attempt totals by countable attempts.
46
+ - `cost_per_verified_success` and `wall_time_per_verified_success` divide all countable attempt totals, including failed attempts, retries, fallbacks, escalations, and downgrades, by independently verified successful attempts.
47
+ - Zero verified successes produce `unavailable` or `unbounded`, never `0`, for per-success efficiency. The comparison must use `insufficient_evidence` or `regressed` according to policy, but it must not report a free success.
48
+ - Solve rate, verified-success count, sample count, confidence intervals, variance, and metering-basis mix stay alongside efficiency metrics. They are not collapsed into a single opaque score.
49
+
50
+ Schema and surface impact is additive:
51
+
52
+ - SQLite extends run evidence with attempt lineage and additional EvalSample metadata. It must reuse immutable EvalSample rows and route-observation metering rather than adding a second JSONL evidence store, usage ledger, or provider/pricing catalog.
53
+ - CLI, MCP, and HTTP eval responses expose the same efficiency, metering-basis, estimate-provenance, effective-treatment, and reason-code fields inside the existing eval envelope.
54
+ - Package export/import includes the attempt lineage, sample metadata, estimate references, and derived comparison outputs needed to recompute the same result locally. Import preflights reject unknown-to-zero coercion and missing effective treatment identity.
55
+ - Migrations are additive and preserve existing V1 rows. Missing V1.1 fields on older evidence read as unavailable or non-comparable, not as zero cost, zero time, or a successful matched treatment.
56
+
57
+ V1.1 executable contract vectors:
58
+
59
+ ```json
60
+ {
61
+ "object": {
62
+ "v1_1_contract_vectors": {
63
+ "sample_identity_key": [
64
+ "run_id",
65
+ "case_id",
66
+ "scorer_id",
67
+ "scorer_version",
68
+ "attempt_id",
69
+ "measure"
70
+ ],
71
+ "attempts": [
72
+ {
73
+ "attempt_id": "evatt-baseline-000001",
74
+ "task_id": "task-create-plan-map-close",
75
+ "run_id": "evrun-20260721-baseline",
76
+ "case_id": "create-plan-map-close",
77
+ "repetition_index": 0,
78
+ "seed": 12345,
79
+ "attempt_index": 0,
80
+ "countable": true,
81
+ "retry_of": null,
82
+ "fallback_of": null,
83
+ "escalation_of": null,
84
+ "resume_of": null,
85
+ "terminal_status": "verified_success",
86
+ "reasons": [],
87
+ "requested_treatment": {
88
+ "client": "codex",
89
+ "provider": "openai",
90
+ "runtime": "codex-cli",
91
+ "model": "gpt-5.6-terra",
92
+ "effort": "medium",
93
+ "profile_id": "terra-medium",
94
+ "profile_digest": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
95
+ "route_policy_digest": "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
96
+ },
97
+ "effective_treatment": {
98
+ "client": "codex",
99
+ "provider": "openai",
100
+ "runtime": "codex-cli",
101
+ "model": "gpt-5.6-terra",
102
+ "effort": "medium",
103
+ "profile_id": "terra-medium",
104
+ "profile_digest": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
105
+ "route_policy_digest": "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
106
+ "runner_version": "eval-runner-v1",
107
+ "harness_version": "planr-eval-harness-v1",
108
+ "confidence": "verified"
109
+ }
110
+ },
111
+ {
112
+ "attempt_id": "evatt-candidate-000001",
113
+ "task_id": "task-create-plan-map-close",
114
+ "run_id": "evrun-20260721-candidate",
115
+ "case_id": "create-plan-map-close",
116
+ "repetition_index": 0,
117
+ "seed": 12345,
118
+ "attempt_index": 0,
119
+ "countable": true,
120
+ "retry_of": null,
121
+ "fallback_of": null,
122
+ "escalation_of": null,
123
+ "resume_of": null,
124
+ "terminal_status": "failed",
125
+ "reasons": ["process_failed"],
126
+ "requested_treatment": {
127
+ "client": "codex",
128
+ "provider": "openai",
129
+ "runtime": "codex-cli",
130
+ "model": "gpt-5.6-terra",
131
+ "effort": "medium",
132
+ "profile_id": "terra-medium",
133
+ "profile_digest": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
134
+ "route_policy_digest": "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
135
+ },
136
+ "effective_treatment": {
137
+ "client": "codex",
138
+ "provider": "openai",
139
+ "runtime": "codex-cli",
140
+ "model": "gpt-5.6-terra",
141
+ "effort": "medium",
142
+ "profile_id": "terra-medium",
143
+ "profile_digest": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
144
+ "route_policy_digest": "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
145
+ "runner_version": "eval-runner-v1",
146
+ "harness_version": "planr-eval-harness-v1",
147
+ "confidence": "verified"
148
+ }
149
+ },
150
+ {
151
+ "attempt_id": "evatt-candidate-000002",
152
+ "task_id": "task-create-plan-map-close",
153
+ "run_id": "evrun-20260721-candidate",
154
+ "case_id": "create-plan-map-close",
155
+ "repetition_index": 0,
156
+ "seed": 12345,
157
+ "attempt_index": 1,
158
+ "countable": true,
159
+ "retry_of": "evatt-candidate-000001",
160
+ "fallback_of": null,
161
+ "escalation_of": null,
162
+ "resume_of": null,
163
+ "terminal_status": "verified_success",
164
+ "reasons": [],
165
+ "requested_treatment": {
166
+ "client": "codex",
167
+ "provider": "openai",
168
+ "runtime": "codex-cli",
169
+ "model": "gpt-5.6-terra",
170
+ "effort": "medium",
171
+ "profile_id": "terra-medium",
172
+ "profile_digest": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
173
+ "route_policy_digest": "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
174
+ },
175
+ "effective_treatment": {
176
+ "client": "codex",
177
+ "provider": "openai",
178
+ "runtime": "codex-cli",
179
+ "model": "gpt-5.6-terra",
180
+ "effort": "medium",
181
+ "profile_id": "terra-medium",
182
+ "profile_digest": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
183
+ "route_policy_digest": "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
184
+ "runner_version": "eval-runner-v1",
185
+ "harness_version": "planr-eval-harness-v1",
186
+ "confidence": "verified"
187
+ }
188
+ },
189
+ {
190
+ "attempt_id": "evatt-candidate-000003",
191
+ "task_id": "task-create-plan-map-close",
192
+ "run_id": "evrun-20260721-candidate",
193
+ "case_id": "create-plan-map-close",
194
+ "repetition_index": 0,
195
+ "seed": 12345,
196
+ "attempt_index": 2,
197
+ "countable": true,
198
+ "retry_of": "evatt-candidate-000002",
199
+ "fallback_of": null,
200
+ "escalation_of": null,
201
+ "resume_of": null,
202
+ "terminal_status": "failed",
203
+ "reasons": ["metering_unavailable"],
204
+ "requested_treatment": {
205
+ "client": "codex",
206
+ "provider": "openai",
207
+ "runtime": "codex-cli",
208
+ "model": "gpt-5.6-terra",
209
+ "effort": "medium",
210
+ "profile_id": "terra-medium",
211
+ "profile_digest": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
212
+ "route_policy_digest": "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
213
+ },
214
+ "effective_treatment": {
215
+ "client": "codex",
216
+ "provider": "openai",
217
+ "runtime": "codex-cli",
218
+ "model": "gpt-5.6-terra",
219
+ "effort": "medium",
220
+ "profile_id": "terra-medium",
221
+ "profile_digest": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
222
+ "route_policy_digest": "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
223
+ "runner_version": "eval-runner-v1",
224
+ "harness_version": "planr-eval-harness-v1",
225
+ "confidence": "verified"
226
+ }
227
+ }
228
+ ],
229
+ "samples": [
230
+ {
231
+ "sample_id": "evsample-candidate-cost",
232
+ "run_id": "evrun-20260721-candidate",
233
+ "case_id": "create-plan-map-close",
234
+ "scorer_id": "map-lifecycle",
235
+ "scorer_version": "1.0.0",
236
+ "attempt_id": "evatt-candidate-000001",
237
+ "repetition_index": 0,
238
+ "warmup": false,
239
+ "seed": 12345,
240
+ "measure": "cost_micros",
241
+ "value": 300,
242
+ "unit": "micros",
243
+ "source": "metering",
244
+ "metering_basis": "actual_trusted",
245
+ "basis_source": "host_usage_receipt",
246
+ "basis_confidence": "verified",
247
+ "estimate_provenance": null,
248
+ "captured_at": "2026-07-21T14:21:10Z"
249
+ },
250
+ {
251
+ "sample_id": "evsample-candidate-wall-time",
252
+ "run_id": "evrun-20260721-candidate",
253
+ "case_id": "create-plan-map-close",
254
+ "scorer_id": "map-lifecycle",
255
+ "scorer_version": "1.0.0",
256
+ "attempt_id": "evatt-candidate-000001",
257
+ "repetition_index": 0,
258
+ "warmup": false,
259
+ "seed": 12345,
260
+ "measure": "wall_time_ms",
261
+ "value": 1200,
262
+ "unit": "ms",
263
+ "source": "process",
264
+ "metering_basis": "actual_trusted",
265
+ "basis_source": "allowlisted_process_timer",
266
+ "basis_confidence": "verified",
267
+ "estimate_provenance": null,
268
+ "captured_at": "2026-07-21T14:21:11Z"
269
+ },
270
+ {
271
+ "sample_id": "evsample-candidate-cost-estimated",
272
+ "run_id": "evrun-20260721-candidate",
273
+ "case_id": "create-plan-map-close",
274
+ "scorer_id": "map-lifecycle",
275
+ "scorer_version": "1.0.0",
276
+ "attempt_id": "evatt-candidate-000002",
277
+ "repetition_index": 0,
278
+ "warmup": false,
279
+ "seed": 12345,
280
+ "measure": "cost_micros",
281
+ "value": 700,
282
+ "unit": "micros",
283
+ "source": "metering",
284
+ "metering_basis": "estimated",
285
+ "basis_source": "pricing_reference",
286
+ "basis_confidence": "estimated",
287
+ "estimate_provenance": {
288
+ "pricing_reference_id": "openai-prices",
289
+ "pricing_reference_version": "2026-07-01",
290
+ "currency": "USD",
291
+ "effective_at": "2026-07-01T00:00:00Z"
292
+ },
293
+ "captured_at": "2026-07-21T14:22:11Z"
294
+ },
295
+ {
296
+ "sample_id": "evsample-candidate-cost-unavailable",
297
+ "run_id": "evrun-20260721-candidate",
298
+ "case_id": "create-plan-map-close",
299
+ "scorer_id": "map-lifecycle",
300
+ "scorer_version": "1.0.0",
301
+ "attempt_id": "evatt-candidate-000003",
302
+ "repetition_index": 0,
303
+ "warmup": false,
304
+ "seed": 12345,
305
+ "measure": "cost_micros",
306
+ "value": null,
307
+ "unit": "micros",
308
+ "source": "metering",
309
+ "metering_basis": "unavailable",
310
+ "basis_source": "host_usage_receipt_absent",
311
+ "basis_confidence": "unavailable",
312
+ "estimate_provenance": null,
313
+ "captured_at": "2026-07-21T14:23:11Z"
314
+ }
315
+ ],
316
+ "efficiency_cases": [
317
+ {
318
+ "name": "zero-success-is-unavailable-not-zero",
319
+ "attempt_ids": ["evatt-candidate-000001"],
320
+ "measure": "cost_micros",
321
+ "expected_countable_attempts": 1,
322
+ "expected_verified_successes": 0,
323
+ "expected_total": 300,
324
+ "expected_per_verified_success": null,
325
+ "expected_per_verified_success_state": "unavailable",
326
+ "expected_basis": "unavailable",
327
+ "expected_reasons": ["zero_verified_successes", "samples_below_minimum"]
328
+ },
329
+ {
330
+ "name": "retry-accounting-includes-failed-attempt",
331
+ "attempt_ids": ["evatt-candidate-000001", "evatt-candidate-000002"],
332
+ "measure": "cost_micros",
333
+ "expected_countable_attempts": 2,
334
+ "expected_verified_successes": 1,
335
+ "expected_total": 1000,
336
+ "expected_per_verified_success": 1000,
337
+ "expected_per_verified_success_state": "available",
338
+ "expected_basis": "mixed",
339
+ "expected_reasons": []
340
+ },
341
+ {
342
+ "name": "unavailable-failed-attempt-cost-blocks-promotable-per-success",
343
+ "attempt_ids": ["evatt-candidate-000002", "evatt-candidate-000003"],
344
+ "measure": "cost_micros",
345
+ "expected_countable_attempts": 2,
346
+ "expected_verified_successes": 1,
347
+ "expected_total": null,
348
+ "expected_per_verified_success": null,
349
+ "expected_per_verified_success_state": "unavailable",
350
+ "expected_basis": "unavailable",
351
+ "expected_reasons": ["samples_unavailable"]
352
+ }
353
+ ],
354
+ "invalid_efficiency_cases": [
355
+ {
356
+ "name": "successful-only-cost-accounting",
357
+ "attempt_ids": ["evatt-candidate-000001", "evatt-candidate-000002"],
358
+ "measure": "cost_micros",
359
+ "expected_countable_attempts": 2,
360
+ "expected_verified_successes": 1,
361
+ "expected_total": 700,
362
+ "expected_per_verified_success": 700,
363
+ "expected_per_verified_success_state": "available",
364
+ "expected_basis": "estimated",
365
+ "expected_error": "failed_attempt_cost_excluded"
366
+ }
367
+ ],
368
+ "invalid_samples": [
369
+ {
370
+ "name": "unknown-to-zero-coercion",
371
+ "sample": {
372
+ "sample_id": "evsample-invalid-zero",
373
+ "run_id": "evrun-20260721-candidate",
374
+ "case_id": "create-plan-map-close",
375
+ "scorer_id": "map-lifecycle",
376
+ "scorer_version": "1.0.0",
377
+ "attempt_id": "evatt-candidate-000001",
378
+ "repetition_index": 0,
379
+ "warmup": false,
380
+ "seed": 12345,
381
+ "measure": "cost_micros",
382
+ "value": 0,
383
+ "unit": "micros",
384
+ "source": "metering",
385
+ "metering_basis": "unavailable",
386
+ "basis_source": "host_usage_receipt_absent",
387
+ "basis_confidence": "unavailable",
388
+ "estimate_provenance": null,
389
+ "captured_at": "2026-07-21T14:21:10Z"
390
+ },
391
+ "expected_error": "unavailable_value_must_be_null"
392
+ }
393
+ ],
394
+ "valid_treatment_transitions": [
395
+ {
396
+ "name": "field-specific-model-fallback-attributed",
397
+ "attempt": {
398
+ "attempt_id": "evatt-candidate-model-fallback-attributed",
399
+ "task_id": "task-create-plan-map-close",
400
+ "run_id": "evrun-20260721-candidate",
401
+ "case_id": "create-plan-map-close",
402
+ "repetition_index": 0,
403
+ "seed": 12345,
404
+ "attempt_index": 3,
405
+ "countable": true,
406
+ "retry_of": null,
407
+ "fallback_of": "evatt-candidate-primary-unavailable",
408
+ "escalation_of": null,
409
+ "resume_of": null,
410
+ "terminal_status": "verified_success",
411
+ "reasons": ["model_fallback"],
412
+ "requested_treatment": {
413
+ "client": "codex",
414
+ "provider": "openai",
415
+ "runtime": "codex-cli",
416
+ "model": "gpt-5.6-terra",
417
+ "effort": "medium",
418
+ "profile_id": "terra-medium",
419
+ "profile_digest": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
420
+ "route_policy_digest": "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
421
+ },
422
+ "effective_treatment": {
423
+ "client": "codex",
424
+ "provider": "openai",
425
+ "runtime": "codex-cli",
426
+ "model": "gpt-5.6-sol",
427
+ "effort": "medium",
428
+ "profile_id": "terra-medium",
429
+ "profile_digest": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
430
+ "route_policy_digest": "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
431
+ "runner_version": "eval-runner-v1",
432
+ "harness_version": "planr-eval-harness-v1",
433
+ "confidence": "verified"
434
+ }
435
+ }
436
+ },
437
+ {
438
+ "name": "field-specific-escalation-and-downgrade-attributed",
439
+ "attempt": {
440
+ "attempt_id": "evatt-candidate-escalation-attributed",
441
+ "task_id": "task-create-plan-map-close",
442
+ "run_id": "evrun-20260721-candidate",
443
+ "case_id": "create-plan-map-close",
444
+ "repetition_index": 0,
445
+ "seed": 12345,
446
+ "attempt_index": 4,
447
+ "countable": true,
448
+ "retry_of": null,
449
+ "fallback_of": null,
450
+ "escalation_of": "evatt-candidate-quality-inconclusive",
451
+ "resume_of": null,
452
+ "terminal_status": "verified_success",
453
+ "reasons": ["provider_escalation", "model_escalation", "effort_downgrade"],
454
+ "requested_treatment": {
455
+ "client": "codex",
456
+ "provider": "openai",
457
+ "runtime": "codex-cli",
458
+ "model": "gpt-5.6-terra",
459
+ "effort": "high",
460
+ "profile_id": "terra-high",
461
+ "profile_digest": "sha256:dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd",
462
+ "route_policy_digest": "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
463
+ },
464
+ "effective_treatment": {
465
+ "client": "codex",
466
+ "provider": "azure-openai",
467
+ "runtime": "codex-cli",
468
+ "model": "gpt-5.6-sol",
469
+ "effort": "medium",
470
+ "profile_id": "terra-high",
471
+ "profile_digest": "sha256:dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd",
472
+ "route_policy_digest": "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
473
+ "runner_version": "eval-runner-v1",
474
+ "harness_version": "planr-eval-harness-v1",
475
+ "confidence": "verified"
476
+ }
477
+ }
478
+ }
479
+ ],
480
+ "invalid_attempts": [
481
+ {
482
+ "name": "silent-treatment-fallback",
483
+ "attempt": {
484
+ "attempt_id": "evatt-candidate-fallback-unattributed",
485
+ "task_id": "task-create-plan-map-close",
486
+ "run_id": "evrun-20260721-candidate",
487
+ "case_id": "create-plan-map-close",
488
+ "repetition_index": 0,
489
+ "seed": 12345,
490
+ "attempt_index": 0,
491
+ "countable": true,
492
+ "retry_of": null,
493
+ "fallback_of": null,
494
+ "escalation_of": null,
495
+ "resume_of": null,
496
+ "terminal_status": "verified_success",
497
+ "reasons": [],
498
+ "requested_treatment": {
499
+ "client": "codex",
500
+ "provider": "openai",
501
+ "runtime": "codex-cli",
502
+ "model": "gpt-5.6-terra",
503
+ "effort": "medium",
504
+ "profile_id": "terra-medium",
505
+ "profile_digest": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
506
+ "route_policy_digest": "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
507
+ },
508
+ "effective_treatment": {
509
+ "client": "codex",
510
+ "provider": "openai",
511
+ "runtime": "codex-cli",
512
+ "model": "gpt-5.6-sol",
513
+ "effort": "high",
514
+ "profile_id": "sol-high",
515
+ "profile_digest": "sha256:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc",
516
+ "route_policy_digest": "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
517
+ "runner_version": "eval-runner-v1",
518
+ "harness_version": "planr-eval-harness-v1",
519
+ "confidence": "verified"
520
+ }
521
+ },
522
+ "expected_reason": "treatment_fallback_unattributed"
523
+ },
524
+ {
525
+ "name": "silent-route-policy-digest-change",
526
+ "attempt": {
527
+ "attempt_id": "evatt-candidate-route-policy-unattributed",
528
+ "task_id": "task-create-plan-map-close",
529
+ "run_id": "evrun-20260721-candidate",
530
+ "case_id": "create-plan-map-close",
531
+ "repetition_index": 0,
532
+ "seed": 12345,
533
+ "attempt_index": 8,
534
+ "countable": true,
535
+ "retry_of": null,
536
+ "fallback_of": null,
537
+ "escalation_of": null,
538
+ "resume_of": null,
539
+ "terminal_status": "verified_success",
540
+ "reasons": [],
541
+ "requested_treatment": {
542
+ "client": "codex",
543
+ "provider": "openai",
544
+ "runtime": "codex-cli",
545
+ "model": "gpt-5.6-terra",
546
+ "effort": "medium",
547
+ "profile_id": "terra-medium",
548
+ "profile_digest": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
549
+ "route_policy_digest": "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
550
+ },
551
+ "effective_treatment": {
552
+ "client": "codex",
553
+ "provider": "openai",
554
+ "runtime": "codex-cli",
555
+ "model": "gpt-5.6-terra",
556
+ "effort": "medium",
557
+ "profile_id": "terra-medium",
558
+ "profile_digest": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
559
+ "route_policy_digest": "sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
560
+ "runner_version": "eval-runner-v1",
561
+ "harness_version": "planr-eval-harness-v1",
562
+ "confidence": "verified"
563
+ }
564
+ },
565
+ "expected_reason": "treatment_fallback_unattributed"
566
+ },
567
+ {
568
+ "name": "retry-lineage-does-not-attribute-model-fallback",
569
+ "attempt": {
570
+ "attempt_id": "evatt-candidate-fallback-retry-only",
571
+ "task_id": "task-create-plan-map-close",
572
+ "run_id": "evrun-20260721-candidate",
573
+ "case_id": "create-plan-map-close",
574
+ "repetition_index": 0,
575
+ "seed": 12345,
576
+ "attempt_index": 1,
577
+ "countable": true,
578
+ "retry_of": "evatt-candidate-fallback-unattributed",
579
+ "fallback_of": null,
580
+ "escalation_of": null,
581
+ "resume_of": null,
582
+ "terminal_status": "verified_success",
583
+ "reasons": ["process_failed"],
584
+ "requested_treatment": {
585
+ "client": "codex",
586
+ "provider": "openai",
587
+ "runtime": "codex-cli",
588
+ "model": "gpt-5.6-terra",
589
+ "effort": "medium",
590
+ "profile_id": "terra-medium",
591
+ "profile_digest": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
592
+ "route_policy_digest": "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
593
+ },
594
+ "effective_treatment": {
595
+ "client": "codex",
596
+ "provider": "openai",
597
+ "runtime": "codex-cli",
598
+ "model": "gpt-5.6-sol",
599
+ "effort": "medium",
600
+ "profile_id": "terra-medium",
601
+ "profile_digest": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
602
+ "route_policy_digest": "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
603
+ "runner_version": "eval-runner-v1",
604
+ "harness_version": "planr-eval-harness-v1",
605
+ "confidence": "verified"
606
+ }
607
+ },
608
+ "expected_reason": "treatment_fallback_unattributed"
609
+ },
610
+ {
611
+ "name": "resume-lineage-does-not-attribute-effort-downgrade",
612
+ "attempt": {
613
+ "attempt_id": "evatt-candidate-downgrade-resume-only",
614
+ "task_id": "task-create-plan-map-close",
615
+ "run_id": "evrun-20260721-candidate",
616
+ "case_id": "create-plan-map-close",
617
+ "repetition_index": 0,
618
+ "seed": 12345,
619
+ "attempt_index": 2,
620
+ "countable": true,
621
+ "retry_of": null,
622
+ "fallback_of": null,
623
+ "escalation_of": null,
624
+ "resume_of": "evatt-candidate-fallback-unattributed",
625
+ "terminal_status": "verified_success",
626
+ "reasons": ["host_resumed"],
627
+ "requested_treatment": {
628
+ "client": "codex",
629
+ "provider": "openai",
630
+ "runtime": "codex-cli",
631
+ "model": "gpt-5.6-sol",
632
+ "effort": "high",
633
+ "profile_id": "sol-high",
634
+ "profile_digest": "sha256:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc",
635
+ "route_policy_digest": "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
636
+ },
637
+ "effective_treatment": {
638
+ "client": "codex",
639
+ "provider": "openai",
640
+ "runtime": "codex-cli",
641
+ "model": "gpt-5.6-sol",
642
+ "effort": "medium",
643
+ "profile_id": "sol-high",
644
+ "profile_digest": "sha256:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc",
645
+ "route_policy_digest": "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
646
+ "runner_version": "eval-runner-v1",
647
+ "harness_version": "planr-eval-harness-v1",
648
+ "confidence": "verified"
649
+ }
650
+ },
651
+ "expected_reason": "treatment_fallback_unattributed"
652
+ },
653
+ {
654
+ "name": "generic-fallback-lineage-does-not-attribute-model-change",
655
+ "attempt": {
656
+ "attempt_id": "evatt-candidate-generic-fallback-lineage",
657
+ "task_id": "task-create-plan-map-close",
658
+ "run_id": "evrun-20260721-candidate",
659
+ "case_id": "create-plan-map-close",
660
+ "repetition_index": 0,
661
+ "seed": 12345,
662
+ "attempt_index": 5,
663
+ "countable": true,
664
+ "retry_of": null,
665
+ "fallback_of": "evatt-candidate-primary-unavailable",
666
+ "escalation_of": null,
667
+ "resume_of": null,
668
+ "terminal_status": "verified_success",
669
+ "reasons": ["treatment_fallback_attributed"],
670
+ "requested_treatment": {
671
+ "client": "codex",
672
+ "provider": "openai",
673
+ "runtime": "codex-cli",
674
+ "model": "gpt-5.6-terra",
675
+ "effort": "medium",
676
+ "profile_id": "terra-medium",
677
+ "profile_digest": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
678
+ "route_policy_digest": "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
679
+ },
680
+ "effective_treatment": {
681
+ "client": "codex",
682
+ "provider": "openai",
683
+ "runtime": "codex-cli",
684
+ "model": "gpt-5.6-sol",
685
+ "effort": "medium",
686
+ "profile_id": "terra-medium",
687
+ "profile_digest": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
688
+ "route_policy_digest": "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
689
+ "runner_version": "eval-runner-v1",
690
+ "harness_version": "planr-eval-harness-v1",
691
+ "confidence": "verified"
692
+ }
693
+ },
694
+ "expected_reason": "treatment_fallback_unattributed"
695
+ },
696
+ {
697
+ "name": "generic-escalation-lineage-does-not-attribute-provider-change",
698
+ "attempt": {
699
+ "attempt_id": "evatt-candidate-generic-escalation-lineage",
700
+ "task_id": "task-create-plan-map-close",
701
+ "run_id": "evrun-20260721-candidate",
702
+ "case_id": "create-plan-map-close",
703
+ "repetition_index": 0,
704
+ "seed": 12345,
705
+ "attempt_index": 6,
706
+ "countable": true,
707
+ "retry_of": null,
708
+ "fallback_of": null,
709
+ "escalation_of": "evatt-candidate-quality-inconclusive",
710
+ "resume_of": null,
711
+ "terminal_status": "verified_success",
712
+ "reasons": ["treatment_transition_attributed"],
713
+ "requested_treatment": {
714
+ "client": "codex",
715
+ "provider": "openai",
716
+ "runtime": "codex-cli",
717
+ "model": "gpt-5.6-terra",
718
+ "effort": "medium",
719
+ "profile_id": "terra-medium",
720
+ "profile_digest": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
721
+ "route_policy_digest": "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
722
+ },
723
+ "effective_treatment": {
724
+ "client": "codex",
725
+ "provider": "azure-openai",
726
+ "runtime": "codex-cli",
727
+ "model": "gpt-5.6-terra",
728
+ "effort": "medium",
729
+ "profile_id": "terra-medium",
730
+ "profile_digest": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
731
+ "route_policy_digest": "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
732
+ "runner_version": "eval-runner-v1",
733
+ "harness_version": "planr-eval-harness-v1",
734
+ "confidence": "verified"
735
+ }
736
+ },
737
+ "expected_reason": "treatment_fallback_unattributed"
738
+ },
739
+ {
740
+ "name": "partial-field-attribution-does-not-cover-profile-change",
741
+ "attempt": {
742
+ "attempt_id": "evatt-candidate-partial-attribution",
743
+ "task_id": "task-create-plan-map-close",
744
+ "run_id": "evrun-20260721-candidate",
745
+ "case_id": "create-plan-map-close",
746
+ "repetition_index": 0,
747
+ "seed": 12345,
748
+ "attempt_index": 7,
749
+ "countable": true,
750
+ "retry_of": null,
751
+ "fallback_of": "evatt-candidate-primary-unavailable",
752
+ "escalation_of": null,
753
+ "resume_of": null,
754
+ "terminal_status": "verified_success",
755
+ "reasons": ["model_fallback"],
756
+ "requested_treatment": {
757
+ "client": "codex",
758
+ "provider": "openai",
759
+ "runtime": "codex-cli",
760
+ "model": "gpt-5.6-terra",
761
+ "effort": "medium",
762
+ "profile_id": "terra-medium",
763
+ "profile_digest": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
764
+ "route_policy_digest": "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
765
+ },
766
+ "effective_treatment": {
767
+ "client": "codex",
768
+ "provider": "openai",
769
+ "runtime": "codex-cli",
770
+ "model": "gpt-5.6-sol",
771
+ "effort": "medium",
772
+ "profile_id": "sol-medium",
773
+ "profile_digest": "sha256:eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee",
774
+ "route_policy_digest": "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
775
+ "runner_version": "eval-runner-v1",
776
+ "harness_version": "planr-eval-harness-v1",
777
+ "confidence": "verified"
778
+ }
779
+ },
780
+ "expected_reason": "treatment_fallback_unattributed"
781
+ }
782
+ ]
783
+ }
784
+ }
785
+ }
786
+ ```
787
+
788
+ ## Repository Manifest
789
+
790
+ An authored manifest is a repository-relative JSON or TOML file. V1 accepts one normalized semantic shape regardless of source syntax.
791
+
792
+ Required fields:
793
+
794
+ - `schema_version`: exactly `eval.suite.v1`.
795
+ - `suite_id`: stable kebab-case id scoped to the repository.
796
+ - `suite_version`: SemVer string.
797
+ - `description`: short human description.
798
+ - `fixtures`: repository-relative fixture list with `id`, `path`, `digest_algorithm`, and optional `role`.
799
+ - `scorers`: deterministic scorer list with `id`, `version`, `kind`, and scorer-specific `config`; scorer controls are declared on bounded cases that prove polarity.
800
+ - `cases`: non-empty ordered input before normalization; each case has identity, fixture/scorer references, command contract, assertions, measures, budgets, and sampling policy.
801
+ - `comparison_policy`: coverage, freshness, compatibility, quality, performance, cost, and verdict thresholds.
802
+ - `safety`: execution and retention limits.
803
+ - `metadata`: optional owner/team labels that are excluded from verdict math unless explicitly named in `comparison_policy`.
804
+
805
+ Example:
806
+
807
+ ```json
808
+ {
809
+ "schema_version": "eval.suite.v1",
810
+ "suite_id": "planr-lifecycle-smoke",
811
+ "suite_version": "1.0.0",
812
+ "description": "Planr lifecycle cases for feature-promotion evidence.",
813
+ "fixtures": [
814
+ {
815
+ "id": "basic-project",
816
+ "path": "eval/fixtures/basic-project",
817
+ "digest_algorithm": "sha256",
818
+ "role": "repository"
819
+ }
820
+ ],
821
+ "scorers": [
822
+ {
823
+ "id": "map-lifecycle",
824
+ "version": "1.0.0",
825
+ "kind": "planr_lifecycle",
826
+ "config": {
827
+ "required_events": ["project.created", "map.built", "item.closed"]
828
+ }
829
+ }
830
+ ],
831
+ "cases": [
832
+ {
833
+ "case_id": "create-plan-map-close",
834
+ "title": "Create a project, build a map, close one item",
835
+ "tags": ["lifecycle", "correctness"],
836
+ "fixture_ids": ["basic-project"],
837
+ "scorer_ids": ["map-lifecycle@1.0.0"],
838
+ "subject": {
839
+ "kind": "planr_cli",
840
+ "argv": ["planr", "project", "init", "EvalDemo"]
841
+ },
842
+ "assertions": [
843
+ {
844
+ "kind": "exit_code",
845
+ "expected": 0
846
+ },
847
+ {
848
+ "kind": "event_sequence",
849
+ "expected": ["project.created", "map.built", "item.closed"]
850
+ }
851
+ ],
852
+ "measures": ["duration_ms", "tool_calls"],
853
+ "budgets": {
854
+ "duration_ms_p95_max": 5000,
855
+ "tool_calls_max": 30
856
+ },
857
+ "sampling": {
858
+ "repetitions": 5,
859
+ "warmups": 1,
860
+ "seed": 12345,
861
+ "min_successful_samples": 4
862
+ },
863
+ "timeout_ms": 15000,
864
+ "output_limit_bytes": 65536
865
+ }
866
+ ],
867
+ "comparison_policy": {
868
+ "freshness_max_age_hours": 168,
869
+ "required_case_coverage": 1.0,
870
+ "compatible_testbed_fields": ["os", "arch", "planr_version", "cpu_class"],
871
+ "quality_non_inferiority_margin": 0.0,
872
+ "material_improvement": {
873
+ "duration_ms_p95_relative": -0.1
874
+ },
875
+ "material_regression": {
876
+ "duration_ms_p95_relative": 0.1
877
+ },
878
+ "protected_dimensions": {
879
+ "correctness_pass_rate_min": 1.0,
880
+ "tool_calls_relative_max": 0.05
881
+ }
882
+ },
883
+ "safety": {
884
+ "allow_network": false,
885
+ "allow_shell": false,
886
+ "max_concurrency": 2,
887
+ "capture_stdout": "digest_and_bounded_excerpt",
888
+ "capture_stderr": "digest_and_bounded_excerpt",
889
+ "forbid_prompt_capture": true,
890
+ "forbid_source_capture": true,
891
+ "forbid_secret_capture": true
892
+ },
893
+ "metadata": {
894
+ "owner": "planr-core"
895
+ }
896
+ }
897
+ ```
898
+
899
+ ## Normalized Suite Snapshot
900
+
901
+ The normalized snapshot is the immutable object embedded in each run. It is derived by validation followed by canonicalization:
902
+
903
+ - Reject unknown `schema_version`, duplicate ids, dangling fixture/scorer references, empty case lists, invalid SemVer, ambiguous scorer ids, unsupported status/reason values, and mixed source syntaxes that normalize to different values.
904
+ - Reject scorer controls with incorrect polarity, duplicate controls, missing oracle or negative coverage for an opted-in scorer, dangling scorer identities, or nondeterministic bounds.
905
+ - Resolve every path against the canonical repository root. Reject absolute paths, `..`, symlink escapes, home paths, user/global config targets, device paths, and non-file fixture targets.
906
+ - Normalize path separators to `/`, ids to exact case-sensitive strings, durations to integer milliseconds, booleans to booleans, and absent optional fields to explicit documented defaults.
907
+ - Sort maps by key, fixtures by `id`, scorers by `id@version`, and cases by `case_id`; preserve explicit per-case assertion order.
908
+ - Hash fixture bytes with `sha256`, then hash the canonical UTF-8 JSON snapshot with `sha256`.
909
+ - Exclude comments, file formatting, and `metadata` keys not named by policy from the digest.
910
+
911
+ Documented defaults applied before digesting:
912
+
913
+ - `fixtures[].role`: `"repository"`.
914
+ - `cases[].tags`: `[]`.
915
+ - `cases[].measures`: `[]`.
916
+ - `cases[].budgets`: `{}`.
917
+ - `cases[].scorer_control`: omitted for ordinary cases. When present, `kind` is `oracle`, `known_bad`, or `no_op` (`null` normalizes to `no_op`), and `expect` is `pass` for oracle controls and `fail` for negative controls.
918
+ - `cases[].sampling.repetitions`: `1`.
919
+ - `cases[].sampling.warmups`: `0`.
920
+ - `cases[].sampling.seed`: `0`.
921
+ - `cases[].sampling.min_successful_samples`: equal to `repetitions`.
922
+ - `cases[].timeout_ms`: `30000`.
923
+ - `cases[].output_limit_bytes`: `65536`.
924
+ - `comparison_policy.freshness_max_age_hours`: `168`.
925
+ - `comparison_policy.required_case_coverage`: `1.0`.
926
+ - `comparison_policy.compatible_testbed_fields`: `["os", "arch", "planr_version"]`.
927
+ - `comparison_policy.quality_non_inferiority_margin`: `0.0`.
928
+ - `comparison_policy.material_improvement`: `{}`.
929
+ - `comparison_policy.material_regression`: `{}`.
930
+ - `comparison_policy.protected_dimensions`: `{}`.
931
+ - `safety.allow_network`: `false`.
932
+ - `safety.allow_shell`: `false`.
933
+ - `safety.max_concurrency`: `1`.
934
+ - `safety.capture_stdout`: `"digest_and_bounded_excerpt"`.
935
+ - `safety.capture_stderr`: `"digest_and_bounded_excerpt"`.
936
+ - `safety.forbid_prompt_capture`: `true`.
937
+ - `safety.forbid_source_capture`: `true`.
938
+ - `safety.forbid_secret_capture`: `true`.
939
+ - `metadata`: `{}`.
940
+
941
+ ### Scorer Controls
942
+
943
+ Scorer controls are additive case metadata used to keep weak or inverted scorers from producing promotion evidence:
944
+
945
+ - `oracle` controls must pass under the scorer and prove known-good evidence is accepted.
946
+ - `known_bad` controls must fail under the scorer and prove bad evidence is rejected.
947
+ - `no_op` controls must fail and prove null/no-output subject behavior cannot pass accidentally.
948
+ - Any scorer that opts into controls must include at least one oracle control and at least one negative control (`known_bad` or `no_op`).
949
+ - Controls must be bounded and repeatable: at least two repetitions and no retry/fallback attempts (`max_attempts = 1`).
950
+ - Stored comparisons project controls from the frozen suite snapshot. Failed oracle controls, negative controls that pass, no-op controls that pass, or changed control metadata return explicit non-promotable reasons before ordinary quality and performance gates.
951
+
952
+ Canonical serialization is RFC 8785 JSON Canonicalization Scheme (JCS): UTF-8 JSON, no insignificant whitespace, object properties sorted lexicographically by unsigned UTF-16 code units, ECMAScript-compatible string escaping, ECMAScript `JSON.stringify` number serialization, and lowercase literals. Inputs must satisfy I-JSON constraints before digesting: no duplicate object names after parsing, strings must be valid Unicode scalar values, numbers must be finite and integer values must remain within the interoperable `[-9007199254740991, 9007199254740991]` range unless represented as strings. TOML manifests normalize to this JSON value before serialization, so semantically identical JSON and TOML inputs produce the same canonical bytes and digest.
953
+
954
+ Digest vectors:
955
+
956
+ ```json
957
+ {
958
+ "object": {
959
+ "canonicalization_vectors": [
960
+ {
961
+ "name": "utf16-key-order-with-supplementary-plane-key",
962
+ "input_json": "{\"\":1,\"𐀀\":2,\"a\":3}",
963
+ "semantic_json": {
964
+ "": 1,
965
+ "𐀀": 2,
966
+ "a": 3
967
+ },
968
+ "canonical_json": "{\"a\":3,\"𐀀\":2,\"\":1}",
969
+ "sha256": "e7286ce065b2cc74ec502be56a09b4210f1533604cb4929d1ca01559ef1b8b76"
970
+ },
971
+ {
972
+ "name": "number-edge-cases",
973
+ "input_json": "{\"minusZero\":-0,\"small\":0.000001,\"int\":9007199254740991}",
974
+ "semantic_json": {
975
+ "minusZero": -0.0,
976
+ "small": 0.000001,
977
+ "int": 9007199254740991
978
+ },
979
+ "canonical_json": "{\"int\":9007199254740991,\"minusZero\":0,\"small\":0.000001}",
980
+ "sha256": "7b423780480affcc244cb986f75813eca804812768b2b8f85b6a94b90144f24a"
981
+ },
982
+ {
983
+ "name": "rfc8785-appendix-b-rounded-ieee754-values",
984
+ "input_json": "{\"rfc333\":333333333.33333329,\"rfcfrac\":1.2345678901234567}",
985
+ "semantic_json": {
986
+ "rfc333": 333333333.33333329,
987
+ "rfcfrac": 1.2345678901234567
988
+ },
989
+ "canonical_json": "{\"rfc333\":333333333.3333333,\"rfcfrac\":1.2345678901234567}",
990
+ "sha256": "2da2a5a90d21b3687fa8882b65959bd4e24ce0a72997255b464f3529a33493cf"
991
+ }
992
+ ],
993
+ "toml_equivalence": {
994
+ "fixture_bytes_utf8": "fixture:basic-project:v1\n",
995
+ "json_manifest": {
996
+ "schema_version": "eval.suite.v1",
997
+ "suite_id": "planr-lifecycle-smoke",
998
+ "suite_version": "1.0.0",
999
+ "fixtures": [
1000
+ {
1001
+ "id": "basic-project",
1002
+ "path": "eval/fixtures/basic-project",
1003
+ "digest_algorithm": "sha256"
1004
+ }
1005
+ ],
1006
+ "scorers": [
1007
+ {
1008
+ "id": "map-lifecycle",
1009
+ "version": "1.0.0",
1010
+ "kind": "planr_lifecycle",
1011
+ "config": {}
1012
+ }
1013
+ ],
1014
+ "cases": [
1015
+ {
1016
+ "case_id": "create-plan-map-close",
1017
+ "fixture_ids": ["basic-project"],
1018
+ "scorer_ids": ["map-lifecycle@1.0.0"],
1019
+ "subject": {
1020
+ "kind": "planr_cli",
1021
+ "argv": ["planr"]
1022
+ },
1023
+ "assertions": []
1024
+ }
1025
+ ],
1026
+ "comparison_policy": {},
1027
+ "safety": {}
1028
+ },
1029
+ "toml_manifest": "schema_version = \"eval.suite.v1\"\nsuite_id = \"planr-lifecycle-smoke\"\nsuite_version = \"1.0.0\"\n\n[[fixtures]]\nid = \"basic-project\"\npath = \"eval/fixtures/basic-project\"\ndigest_algorithm = \"sha256\"\n\n[[scorers]]\nid = \"map-lifecycle\"\nversion = \"1.0.0\"\nkind = \"planr_lifecycle\"\nconfig = {}\n\n[[cases]]\ncase_id = \"create-plan-map-close\"\nfixture_ids = [\"basic-project\"]\nscorer_ids = [\"map-lifecycle@1.0.0\"]\nassertions = []\n[cases.subject]\nkind = \"planr_cli\"\nargv = [\"planr\"]\n\n[comparison_policy]\n\n[safety]\n",
1030
+ "json_manifest_digest": "sha256:e2f7f013c72b6519499dfb45df0628d9bb823544a88695b0543464bd1745d79a",
1031
+ "toml_manifest_digest": "sha256:e2f7f013c72b6519499dfb45df0628d9bb823544a88695b0543464bd1745d79a"
1032
+ }
1033
+ }
1034
+ }
1035
+ ```
1036
+
1037
+ Example response:
1038
+
1039
+ ```json
1040
+ {
1041
+ "schema_version": "eval.suite.snapshot.v1",
1042
+ "suite_id": "planr-lifecycle-smoke",
1043
+ "suite_version": "1.0.0",
1044
+ "digest": "sha256:7c4b44e9f6a6113fdf1344f3e14c65b07e32e63e8b22c34784d51a6b9a4ec001",
1045
+ "case_order": ["create-plan-map-close"],
1046
+ "fixture_digests": {
1047
+ "basic-project": "sha256:2bb80d537b1da3e38bd30361aa855686bde0ba89dd8c7d463f537c41b30b25a9"
1048
+ },
1049
+ "scorer_identities": ["map-lifecycle@1.0.0"],
1050
+ "defaults": {
1051
+ "allow_network": false,
1052
+ "allow_shell": false,
1053
+ "capture_stdout": "digest_and_bounded_excerpt",
1054
+ "capture_stderr": "digest_and_bounded_excerpt"
1055
+ }
1056
+ }
1057
+ ```
1058
+
1059
+ ## Status Vocabulary
1060
+
1061
+ Run status:
1062
+
1063
+ - `started`: run record exists and at least one case may still execute.
1064
+ - `success`: every required case reached a terminal usable status and run-level persistence completed.
1065
+ - `error`: infrastructure or runner failure prevented a usable run.
1066
+ - `inconclusive`: run completed but has no comparison-eligible evidence.
1067
+ - `invalidated`: operator or service provenance marks the run unusable for future comparisons.
1068
+
1069
+ Case status:
1070
+
1071
+ - `pass`: deterministic assertions passed and samples are comparison-eligible.
1072
+ - `fail`: deterministic assertions prove candidate behavior wrong for the case.
1073
+ - `error`: command, fixture, parser, timeout, or scorer infrastructure failed.
1074
+ - `skipped`: policy excluded the case before execution.
1075
+ - `inconclusive`: execution completed but evidence cannot support pass or fail.
1076
+
1077
+ Comparison verdict:
1078
+
1079
+ - `improved`: correctness/safety/coverage/freshness/compatibility pass, protected dimensions stay inside budget, and at least one target dimension improves materially.
1080
+ - `regressed`: correctness, safety, quality, protected dimension, performance, or cost is worse beyond the declared threshold.
1081
+ - `no_material_difference`: evidence is compatible and adequate but does not prove material improvement or regression.
1082
+ - `insufficient_evidence`: missing, stale, incompatible, under-covered, under-sampled, invalidated, or excessively noisy evidence prevents a stronger verdict.
1083
+
1084
+ ## Reason Codes
1085
+
1086
+ Reason codes are stable snake-case strings. V1 reason arrays are additive and ordered by gate.
1087
+
1088
+ - Input and safety: `manifest_invalid`, `unsupported_schema_version`, `duplicate_identity`, `dangling_reference`, `unsafe_path`, `symlink_escape`, `absolute_path`, `shell_not_allowed`, `network_not_allowed`, `output_limit_exceeded`, `secret_like_output`.
1089
+ - Run and case: `process_timeout`, `process_failed`, `missing_output`, `invalid_output`, `scorer_error`, `assertion_failed`, `case_skipped`, `case_inconclusive`, `run_interrupted`.
1090
+ - Compatibility and evidence: `suite_digest_mismatch`, `fixture_digest_mismatch`, `scorer_version_mismatch`, `subject_identity_mismatch`, `testbed_incompatible`, `evidence_stale`, `run_invalidated`, `coverage_below_minimum`, `samples_below_minimum`, `samples_unavailable`, `variance_too_high`, `missing_baseline`, `missing_candidate`.
1091
+ - Scorer controls: `scorer_control_incorrect_polarity`, `scorer_control_missing_oracle`, `scorer_control_missing_negative`, `scorer_control_nondeterministic`, `scorer_control_oracle_failed`, `scorer_control_negative_passed`, `scorer_control_unexpected_no_op_success`.
1092
+ - V1.1 treatment and metering: `effective_treatment_missing`, `treatment_fallback_unattributed`, field-specific fallback reasons such as `client_fallback`, `provider_fallback`, `runtime_fallback`, `model_fallback`, `effort_fallback`, `profile_id_fallback`, `profile_digest_fallback`, and `route_policy_digest_fallback`, field-specific escalation reasons such as `client_escalation`, `provider_escalation`, `runtime_escalation`, `model_escalation`, `effort_escalation`, `profile_id_escalation`, `profile_digest_escalation`, and `route_policy_digest_escalation`, effort transition reasons `effort_downgrade` and `quota_downgrade`, `unavailable_value_must_be_null`, `mixed_basis_not_allowed`, `estimate_provenance_missing`, `attempt_identity_conflict`.
1093
+ - Verdict gates: `correctness_regressed`, `safety_regressed`, `quality_regressed`, `protected_dimension_regressed`, `performance_regressed`, `cost_regressed`, `no_material_effect`, `candidate_improved`.
1094
+
1095
+ ## Evidence Identity
1096
+
1097
+ Every stored object carries enough identity to prove where it came from without retaining secrets or full transcripts.
1098
+
1099
+ Run identity fields:
1100
+
1101
+ - `run_id`, `suite_digest`, `subject_kind`, `subject_revision`, `runner_version`, `planr_version`, `created_at`, `completed_at`.
1102
+ - `testbed_fingerprint`: OS, arch, Planr version, CPU class, memory class, and allowlisted environment names without values unless the value is explicitly non-secret and hashed.
1103
+ - `source_state`: repository root digest inputs, commit when available, dirty-state summary, and fixture digests.
1104
+ - `lineage`: optional parent run id, resume reason, invalidation id, rescore id, or recompute id.
1105
+
1106
+ Case/sample identity fields:
1107
+
1108
+ - `run_id`, `case_id`, `scorer_id`, `scorer_version`, `fixture_digest`, `repetition_index`, `warmup`, `seed`.
1109
+ - `command`: argv array, working directory relative to repository root, timeout, output limit, exit status, stdout digest, stderr digest, and optional bounded artifact ids.
1110
+ - `attempts`: attempt id, task/case/repetition/seed identity, attempt index, countable flag, retry/fallback/escalation/resume lineage, terminal status, reason codes, runner/harness versions, requested treatment intent, and effective treatment evidence.
1111
+ - `samples`: measure name, nullable value, unit, source (`process`, `planr_event`, `scorer`, `metering`), attempt id, capture timestamp, metering basis, basis source/confidence, and optional estimate provenance.
1112
+
1113
+ Comparison identity fields:
1114
+
1115
+ - `comparison_id`, baseline run id, candidate run id, comparison policy digest, runner version, created timestamp, gate results, verdict, reason codes, matched effective-treatment identity, efficiency summary, and recompute/rescore lineage.
1116
+
1117
+ ## Stored Objects
1118
+
1119
+ The fields below are normative for V1 storage and wire projections. Required fields must be present in JSON responses. Optional fields may be `null` only when the value is unknowable; otherwise defaults are applied before persistence.
1120
+
1121
+ ### EvalSuiteSnapshot
1122
+
1123
+ Required fields:
1124
+
1125
+ - `schema_version` string: exactly `eval.suite.snapshot.v1`.
1126
+ - `suite_id` string.
1127
+ - `suite_version` string.
1128
+ - `digest` string: `sha256:<64 lowercase hex>`.
1129
+ - `normalized_manifest` object: canonical semantic manifest after defaults.
1130
+ - `fixture_digests` object: fixture id to `sha256:<hex>`.
1131
+ - `scorer_identities` array of `id@version` strings.
1132
+ - `case_order` array of case ids.
1133
+ - `baseline_selector` object: `kind` (`explicit_run_id | latest_successful | git_ref`) plus selector-specific fields.
1134
+ - `comparison_policy_digest` string.
1135
+ - `created_at` RFC 3339 timestamp.
1136
+
1137
+ Optional fields:
1138
+
1139
+ - `metadata` object, default `{}`.
1140
+ - `source_path` repository-relative string, default `null`.
1141
+
1142
+ ```json
1143
+ {
1144
+ "object": {
1145
+ "schema_version": "eval.suite.snapshot.v1",
1146
+ "suite_id": "planr-lifecycle-smoke",
1147
+ "suite_version": "1.0.0",
1148
+ "digest": "sha256:7c4b44e9f6a6113fdf1344f3e14c65b07e32e63e8b22c34784d51a6b9a4ec001",
1149
+ "normalized_manifest": {
1150
+ "schema_version": "eval.suite.v1",
1151
+ "suite_id": "planr-lifecycle-smoke",
1152
+ "suite_version": "1.0.0"
1153
+ },
1154
+ "fixture_digests": {
1155
+ "basic-project": "sha256:2bb80d537b1da3e38bd30361aa855686bde0ba89dd8c7d463f537c41b30b25a9"
1156
+ },
1157
+ "scorer_identities": ["map-lifecycle@1.0.0"],
1158
+ "case_order": ["create-plan-map-close"],
1159
+ "baseline_selector": {
1160
+ "kind": "explicit_run_id",
1161
+ "run_id": "evrun-20260721-000001"
1162
+ },
1163
+ "comparison_policy_digest": "sha256:5a5d4f6ad2c4e98d4f16d857f0f6e2c3f4e5d6c7b8a90123456789abcdef0001",
1164
+ "created_at": "2026-07-21T14:20:00Z",
1165
+ "metadata": {},
1166
+ "source_path": "eval/suites/planr-lifecycle-smoke.json"
1167
+ }
1168
+ }
1169
+ ```
1170
+
1171
+ ### EvalRun
1172
+
1173
+ Required fields:
1174
+
1175
+ - `run_id` string.
1176
+ - `suite_digest` string.
1177
+ - `suite_snapshot` EvalSuiteSnapshot object or immutable snapshot id.
1178
+ - `subject` object: `kind`, `revision`, `path`, `argv`, and optional `label`.
1179
+ - `runner_version` string.
1180
+ - `planr_version` string.
1181
+ - `status` run status.
1182
+ - `created_at`, `started_at`, `completed_at` RFC 3339 timestamps; `completed_at` is `null` until terminal.
1183
+ - `testbed_fingerprint` object.
1184
+ - `source_state` object: commit, dirty summary, root path digest, and fixture digests.
1185
+ - `case_counts` object with explicit zeros for every case status.
1186
+ - `lineage` object with `parent_run_id`, `resume_of`, `rescore_of`, `recompute_of`, all nullable.
1187
+
1188
+ Optional fields:
1189
+
1190
+ - `invalidated_by` invalidation id, default `null`.
1191
+ - `aggregate_summary` object, default `{}`.
1192
+
1193
+ ```json
1194
+ {
1195
+ "object": {
1196
+ "run_id": "evrun-20260721-000002",
1197
+ "suite_digest": "sha256:7c4b44e9f6a6113fdf1344f3e14c65b07e32e63e8b22c34784d51a6b9a4ec001",
1198
+ "suite_snapshot": "evsuite-7c4b44e9",
1199
+ "subject": {
1200
+ "kind": "planr_cli",
1201
+ "revision": "candidate-abc123",
1202
+ "path": ".",
1203
+ "argv": ["planr"],
1204
+ "label": "candidate"
1205
+ },
1206
+ "runner_version": "eval-runner-v1",
1207
+ "planr_version": "1.5.2",
1208
+ "status": "success",
1209
+ "created_at": "2026-07-21T14:21:00Z",
1210
+ "started_at": "2026-07-21T14:21:01Z",
1211
+ "completed_at": "2026-07-21T14:22:00Z",
1212
+ "testbed_fingerprint": {
1213
+ "os": "macos",
1214
+ "arch": "aarch64",
1215
+ "planr_version": "1.5.2",
1216
+ "cpu_class": "apple-silicon",
1217
+ "memory_class": "16gb",
1218
+ "env": {
1219
+ "PLANR_PROFILE": "sha256:7d9c"
1220
+ }
1221
+ },
1222
+ "source_state": {
1223
+ "commit": "abc123",
1224
+ "dirty": false,
1225
+ "root_digest": "sha256:1111111111111111111111111111111111111111111111111111111111111111",
1226
+ "fixture_digests": {
1227
+ "basic-project": "sha256:2bb80d537b1da3e38bd30361aa855686bde0ba89dd8c7d463f537c41b30b25a9"
1228
+ }
1229
+ },
1230
+ "case_counts": {
1231
+ "pass": 1,
1232
+ "fail": 0,
1233
+ "error": 0,
1234
+ "skipped": 0,
1235
+ "inconclusive": 0
1236
+ },
1237
+ "lineage": {
1238
+ "parent_run_id": null,
1239
+ "resume_of": null,
1240
+ "rescore_of": null,
1241
+ "recompute_of": null
1242
+ },
1243
+ "invalidated_by": null,
1244
+ "aggregate_summary": {}
1245
+ }
1246
+ }
1247
+ ```
1248
+
1249
+ ### EvalCaseResult
1250
+
1251
+ Required fields:
1252
+
1253
+ - `object_kind` string: exactly `eval_case_result`.
1254
+ - `run_id` string.
1255
+ - `case_id` string.
1256
+ - `scorer_id` string.
1257
+ - `scorer_version` string.
1258
+ - `fixture_digest` string.
1259
+ - `status` case status.
1260
+ - `repetition_count` integer, required and non-negative.
1261
+ - `warmup_count` integer, required and non-negative.
1262
+ - `assertions` array of assertion result objects, default `[]`.
1263
+ Safety verdict replay reads a durable scorer observation from either `{"safety_pass": true|false}` or a `{"kind": "safety_pass", "status": "pass"|"fail"}` assertion.
1264
+ Quality verdict replay reads a durable scorer observation from either `{"quality_pass": true|false}` or a `{"kind": "quality_pass", "status": "pass"|"fail"}` assertion.
1265
+ - `command` object with `argv`, `cwd`, `timeout_ms`, `output_limit_bytes`, `exit_code`, `stdout_digest`, `stderr_digest`, and `artifact_ids`.
1266
+ - `sample_ids` array of strings, default `[]`.
1267
+ - `reasons` array of reason codes, default `[]`.
1268
+
1269
+ ```json
1270
+ {
1271
+ "object": {
1272
+ "object_kind": "eval_case_result",
1273
+ "run_id": "evrun-20260721-000002",
1274
+ "case_id": "create-plan-map-close",
1275
+ "scorer_id": "map-lifecycle",
1276
+ "scorer_version": "1.0.0",
1277
+ "fixture_digest": "sha256:2bb80d537b1da3e38bd30361aa855686bde0ba89dd8c7d463f537c41b30b25a9",
1278
+ "status": "pass",
1279
+ "repetition_count": 5,
1280
+ "warmup_count": 1,
1281
+ "assertions": [
1282
+ {
1283
+ "kind": "exit_code",
1284
+ "status": "pass",
1285
+ "expected": 0,
1286
+ "actual": 0
1287
+ }
1288
+ ],
1289
+ "command": {
1290
+ "argv": ["planr", "project", "init", "EvalDemo"],
1291
+ "cwd": ".",
1292
+ "timeout_ms": 15000,
1293
+ "output_limit_bytes": 65536,
1294
+ "exit_code": 0,
1295
+ "stdout_digest": "sha256:3333333333333333333333333333333333333333333333333333333333333333",
1296
+ "stderr_digest": "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
1297
+ "artifact_ids": []
1298
+ },
1299
+ "sample_ids": ["evsample-000001"],
1300
+ "reasons": []
1301
+ }
1302
+ }
1303
+ ```
1304
+
1305
+ ### EvalAttempt
1306
+
1307
+ Required fields:
1308
+
1309
+ - `object_kind` string: exactly `eval_attempt`.
1310
+ - `attempt_id` string.
1311
+ - `task_id` string.
1312
+ - `run_id` string.
1313
+ - `case_id` string.
1314
+ - `repetition_index` integer, required and non-negative.
1315
+ - `seed` integer.
1316
+ - `attempt_index` integer, required and non-negative within the same case/repetition/seed.
1317
+ - `countable` boolean: false only for explicit warmups or retained non-denominator audit attempts.
1318
+ - `retry_of`, `fallback_of`, `escalation_of`, `resume_of` nullable attempt ids. At most one lineage pointer should be non-null for a single transition unless the runner records a combined transition reason.
1319
+ - `terminal_status` string enum: `verified_success | failed | error | skipped | inconclusive | stopped`.
1320
+ - `reasons` array of reason codes, default `[]`.
1321
+ - `requested_treatment` object: client/provider/runtime/model/effort/profile intent as requested before execution. This is audit context only.
1322
+ - `effective_treatment` object: client, provider, runtime, model, effort, profile_id, profile_digest, route_policy_digest, runner_version, harness_version, and confidence provenance actually observed for comparison identity.
1323
+
1324
+ Optional fields:
1325
+
1326
+ - `started_at`, `completed_at` RFC 3339 timestamps, default `null`.
1327
+ - `lineage_note` string, default `null`.
1328
+
1329
+ ```json
1330
+ {
1331
+ "object": {
1332
+ "object_kind": "eval_attempt",
1333
+ "attempt_id": "evatt-candidate-000002",
1334
+ "task_id": "task-create-plan-map-close",
1335
+ "run_id": "evrun-20260721-000002",
1336
+ "case_id": "create-plan-map-close",
1337
+ "repetition_index": 0,
1338
+ "seed": 12345,
1339
+ "attempt_index": 1,
1340
+ "countable": true,
1341
+ "retry_of": "evatt-candidate-000001",
1342
+ "fallback_of": null,
1343
+ "escalation_of": null,
1344
+ "resume_of": null,
1345
+ "terminal_status": "verified_success",
1346
+ "reasons": [],
1347
+ "requested_treatment": {
1348
+ "client": "codex",
1349
+ "provider": "openai",
1350
+ "runtime": "codex-cli",
1351
+ "model": "gpt-5.6-terra",
1352
+ "effort": "medium",
1353
+ "profile_id": "terra-medium",
1354
+ "profile_digest": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
1355
+ "route_policy_digest": "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
1356
+ },
1357
+ "effective_treatment": {
1358
+ "client": "codex",
1359
+ "provider": "openai",
1360
+ "runtime": "codex-cli",
1361
+ "model": "gpt-5.6-terra",
1362
+ "effort": "medium",
1363
+ "profile_id": "terra-medium",
1364
+ "profile_digest": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
1365
+ "route_policy_digest": "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
1366
+ "runner_version": "eval-runner-v1",
1367
+ "harness_version": "planr-eval-harness-v1",
1368
+ "confidence": "verified"
1369
+ },
1370
+ "started_at": "2026-07-21T14:21:30Z",
1371
+ "completed_at": "2026-07-21T14:22:00Z",
1372
+ "lineage_note": null
1373
+ }
1374
+ }
1375
+ ```
1376
+
1377
+ ### EvalSample
1378
+
1379
+ Required fields:
1380
+
1381
+ - `sample_id` string.
1382
+ - `run_id` string.
1383
+ - `case_id` string.
1384
+ - `scorer_id` string.
1385
+ - `scorer_version` string.
1386
+ - `attempt_id` string: references EvalAttempt and is part of sample identity.
1387
+ - `repetition_index` integer, required and non-negative.
1388
+ - `warmup` boolean.
1389
+ - `seed` integer.
1390
+ - `measure` string.
1391
+ - `value` number, boolean, or `null`; finite when numeric. `null` is required when `metering_basis` is `unavailable`.
1392
+ - `unit` string.
1393
+ - `source` string enum: `process | planr_event | scorer | metering`.
1394
+ - `metering_basis` string enum: `actual_trusted | estimated | mixed | unavailable`.
1395
+ - `basis_source` string.
1396
+ - `basis_confidence` string enum: `verified | estimated | mixed | unavailable`.
1397
+ - `estimate_provenance` object or `null`: required for estimated values and carries pricing/reference id, version, currency, and effective timestamp when cost or credit estimates are used.
1398
+ - `captured_at` RFC 3339 timestamp.
1399
+
1400
+ Identity invariant:
1401
+
1402
+ - The database uniqueness key is `(run_id, case_id, scorer_id, scorer_version, attempt_id, measure)`. `attempt_id` includes task, repetition, seed, and attempt_index identity, so retries and multiple measures within one repetition can coexist without overwriting each other.
1403
+ - `metering_basis = unavailable` requires `value = null`, `basis_confidence = unavailable`, and no estimate provenance. Missing or unavailable values must not be imported, exported, aggregated, or compared as `0`.
1404
+
1405
+ ```json
1406
+ {
1407
+ "object": {
1408
+ "sample_id": "evsample-000001",
1409
+ "run_id": "evrun-20260721-000002",
1410
+ "case_id": "create-plan-map-close",
1411
+ "scorer_id": "map-lifecycle",
1412
+ "scorer_version": "1.0.0",
1413
+ "attempt_id": "evatt-candidate-000002",
1414
+ "repetition_index": 0,
1415
+ "warmup": false,
1416
+ "seed": 12345,
1417
+ "measure": "wall_time_ms",
1418
+ "value": 912,
1419
+ "unit": "ms",
1420
+ "source": "process",
1421
+ "metering_basis": "actual_trusted",
1422
+ "basis_source": "allowlisted_process_timer",
1423
+ "basis_confidence": "verified",
1424
+ "estimate_provenance": null,
1425
+ "captured_at": "2026-07-21T14:21:10Z"
1426
+ }
1427
+ }
1428
+ ```
1429
+
1430
+ ### EvalComparison
1431
+
1432
+ Required fields:
1433
+
1434
+ - `comparison_id` string.
1435
+ - `baseline_run_id` string.
1436
+ - `candidate_run_id` string.
1437
+ - `policy_digest` string.
1438
+ - `runner_version` string.
1439
+ - `created_at` RFC 3339 timestamp.
1440
+ - `verdict` comparison verdict.
1441
+ - `reasons` array of reason codes.
1442
+ - `gates` array of gate result objects.
1443
+ - `effect_estimates` object of measure name to numeric effect.
1444
+ - `uncertainty` object of measure name to interval or variance metadata.
1445
+ - `protected_dimensions` object of measure name to numeric result.
1446
+ - `matched_effective_treatment` object with baseline and candidate effective-treatment identities used for compatible matching.
1447
+ - `efficiency_summary` object with countable attempts, verified successes, solve rate, total cost/wall-time, per-attempt and per-verified-success metrics, metering-basis mix, sample count, confidence intervals, and reason codes. Per-success values are `null` with `basis = unavailable` or `state = unbounded` when verified successes are zero.
1448
+ - `lineage` object with nullable `recompute_of` and `rescore_of`.
1449
+
1450
+ ```json
1451
+ {
1452
+ "object": {
1453
+ "comparison_id": "evcmp-20260721-000001",
1454
+ "baseline_run_id": "evrun-20260721-000001",
1455
+ "candidate_run_id": "evrun-20260721-000002",
1456
+ "policy_digest": "sha256:5a5d4f6ad2c4e98d4f16d857f0f6e2c3f4e5d6c7b8a90123456789abcdef0001",
1457
+ "runner_version": "eval-runner-v1",
1458
+ "created_at": "2026-07-21T14:23:00Z",
1459
+ "verdict": "insufficient_evidence",
1460
+ "reasons": ["samples_unavailable"],
1461
+ "gates": [
1462
+ {
1463
+ "gate": "compatibility",
1464
+ "status": "pass",
1465
+ "reasons": []
1466
+ },
1467
+ {
1468
+ "gate": "correctness",
1469
+ "status": "pass",
1470
+ "reasons": []
1471
+ },
1472
+ {
1473
+ "gate": "performance",
1474
+ "status": "blocked",
1475
+ "reasons": ["samples_unavailable"]
1476
+ }
1477
+ ],
1478
+ "effect_estimates": {
1479
+ "duration_ms_p95_relative": -0.12
1480
+ },
1481
+ "uncertainty": {
1482
+ "duration_ms_p95_ci95": [-0.18, -0.07],
1483
+ "variance": "acceptable"
1484
+ },
1485
+ "protected_dimensions": {
1486
+ "correctness_pass_rate": 1.0,
1487
+ "tool_calls_relative": 0.0
1488
+ },
1489
+ "matched_effective_treatment": {
1490
+ "baseline": {
1491
+ "client": "codex",
1492
+ "provider": "openai",
1493
+ "runtime": "codex-cli",
1494
+ "model": "gpt-5.6-terra",
1495
+ "effort": "medium",
1496
+ "profile_id": "terra-medium",
1497
+ "profile_digest": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
1498
+ "route_policy_digest": "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
1499
+ "runner_version": "eval-runner-v1",
1500
+ "harness_version": "planr-eval-harness-v1",
1501
+ "confidence": "verified"
1502
+ },
1503
+ "candidate": {
1504
+ "client": "codex",
1505
+ "provider": "openai",
1506
+ "runtime": "codex-cli",
1507
+ "model": "gpt-5.6-terra",
1508
+ "effort": "medium",
1509
+ "profile_id": "terra-medium",
1510
+ "profile_digest": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
1511
+ "route_policy_digest": "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
1512
+ "runner_version": "eval-runner-v1",
1513
+ "harness_version": "planr-eval-harness-v1",
1514
+ "confidence": "verified"
1515
+ }
1516
+ },
1517
+ "efficiency_summary": {
1518
+ "countable_attempts": 2,
1519
+ "verified_successes": 1,
1520
+ "solve_rate": 0.5,
1521
+ "total_cost_micros": {
1522
+ "value": null,
1523
+ "basis": "unavailable",
1524
+ "reasons": ["samples_unavailable"]
1525
+ },
1526
+ "cost_per_attempt_micros": {
1527
+ "value": null,
1528
+ "basis": "unavailable",
1529
+ "state": "unavailable"
1530
+ },
1531
+ "cost_per_verified_success_micros": {
1532
+ "value": null,
1533
+ "basis": "unavailable",
1534
+ "state": "unavailable",
1535
+ "reasons": ["samples_unavailable"]
1536
+ },
1537
+ "wall_time_per_verified_success_ms": {
1538
+ "value": 2112,
1539
+ "basis": "actual_trusted"
1540
+ },
1541
+ "sample_count": 4,
1542
+ "basis_mix": ["actual_trusted", "estimated", "unavailable"],
1543
+ "confidence_intervals": {},
1544
+ "reasons": ["samples_unavailable"]
1545
+ },
1546
+ "lineage": {
1547
+ "recompute_of": null,
1548
+ "rescore_of": null
1549
+ }
1550
+ }
1551
+ }
1552
+ ```
1553
+
1554
+ ### EvalInvalidation
1555
+
1556
+ Required fields:
1557
+
1558
+ - `invalidation_id` string.
1559
+ - `target_kind` string enum: `run | comparison`.
1560
+ - `target_id` string.
1561
+ - `reason` string.
1562
+ - `reason_codes` array of reason codes.
1563
+ - `created_at` RFC 3339 timestamp.
1564
+ - `created_by` string.
1565
+ - `replacement_hint` string or `null`.
1566
+
1567
+ ```json
1568
+ {
1569
+ "object": {
1570
+ "invalidation_id": "evinv-20260721-000001",
1571
+ "target_kind": "run",
1572
+ "target_id": "evrun-20260721-000001",
1573
+ "reason": "baseline captured on incompatible testbed",
1574
+ "reason_codes": ["testbed_incompatible"],
1575
+ "created_at": "2026-07-21T14:24:00Z",
1576
+ "created_by": "maker-1",
1577
+ "replacement_hint": "recapture baseline on the current testbed"
1578
+ }
1579
+ }
1580
+ ```
1581
+
1582
+ ### EvalEvidenceRef
1583
+
1584
+ Required fields:
1585
+
1586
+ - `evidence_ref` string.
1587
+ - `target_kind` string enum: `run | comparison`.
1588
+ - `target_id` string.
1589
+ - `planr_attachment_kind` string enum: `log | review | artifact`.
1590
+ - `planr_attachment_id` string.
1591
+ - `item_id` string.
1592
+ - `created_at` RFC 3339 timestamp.
1593
+ - `closure_authority` boolean, always `false` in V1.
1594
+
1595
+ ```json
1596
+ {
1597
+ "object": {
1598
+ "evidence_ref": "eval-comparison:evcmp-20260721-000001",
1599
+ "target_kind": "comparison",
1600
+ "target_id": "evcmp-20260721-000001",
1601
+ "planr_attachment_kind": "log",
1602
+ "planr_attachment_id": "log-2ae87225",
1603
+ "item_id": "i-freeze-eval-contract-v1-and-owne-e51f",
1604
+ "created_at": "2026-07-21T14:25:00Z",
1605
+ "closure_authority": false
1606
+ }
1607
+ }
1608
+ ```
1609
+
1610
+ ## CLI and MCP Responses
1611
+
1612
+ All eval CLI JSON and MCP tool responses use one envelope:
1613
+
1614
+ - `ok` boolean.
1615
+ - `command` string: canonical command name.
1616
+ - `object` object or `null`: primary result.
1617
+ - `warnings` array, default `[]`.
1618
+ - `reasons` array, default `[]`.
1619
+ - `error` object or `null`: `{ code, message, reasons, field }` on failure.
1620
+
1621
+ MCP tools mirror the CLI object exactly inside the normal MCP text content wrapper. HTTP, if added, returns the same envelope as the response body.
1622
+
1623
+ Suite check:
1624
+
1625
+ ```json
1626
+ {
1627
+ "ok": true,
1628
+ "command": "eval.suite.check",
1629
+ "object": {
1630
+ "suite_id": "planr-lifecycle-smoke",
1631
+ "suite_version": "1.0.0",
1632
+ "digest": "sha256:7c4b44e9f6a6113fdf1344f3e14c65b07e32e63e8b22c34784d51a6b9a4ec001",
1633
+ "case_count": 1,
1634
+ "scorer_count": 1,
1635
+ "fixture_count": 1
1636
+ },
1637
+ "warnings": [],
1638
+ "reasons": [],
1639
+ "error": null
1640
+ }
1641
+ ```
1642
+
1643
+ Run:
1644
+
1645
+ ```json
1646
+ {
1647
+ "ok": true,
1648
+ "command": "eval.run",
1649
+ "object": {
1650
+ "run_id": "evrun-20260721-000001",
1651
+ "suite_digest": "sha256:7c4b44e9f6a6113fdf1344f3e14c65b07e32e63e8b22c34784d51a6b9a4ec001",
1652
+ "status": "success",
1653
+ "attempt_summary": {
1654
+ "countable_attempts": 2,
1655
+ "verified_successes": 1,
1656
+ "failed_attempts": 1,
1657
+ "retry_attempts": 1,
1658
+ "basis_mix": ["actual_trusted", "estimated"]
1659
+ },
1660
+ "attempt_lineage": [
1661
+ {
1662
+ "attempt_id": "evatt-candidate-000001",
1663
+ "attempt_index": 0,
1664
+ "terminal_status": "failed",
1665
+ "retry_of": null,
1666
+ "fallback_of": null,
1667
+ "escalation_of": null,
1668
+ "resume_of": null,
1669
+ "requested_treatment": {
1670
+ "client": "codex",
1671
+ "provider": "openai",
1672
+ "runtime": "codex-cli",
1673
+ "model": "gpt-5.6-terra",
1674
+ "effort": "medium",
1675
+ "profile_id": "terra-medium",
1676
+ "profile_digest": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
1677
+ "route_policy_digest": "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
1678
+ },
1679
+ "effective_treatment": {
1680
+ "client": "codex",
1681
+ "provider": "openai",
1682
+ "runtime": "codex-cli",
1683
+ "model": "gpt-5.6-terra",
1684
+ "effort": "medium",
1685
+ "profile_id": "terra-medium",
1686
+ "profile_digest": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
1687
+ "route_policy_digest": "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
1688
+ "runner_version": "eval-runner-v1",
1689
+ "harness_version": "planr-eval-harness-v1",
1690
+ "confidence": "verified"
1691
+ },
1692
+ "reasons": ["process_failed"]
1693
+ },
1694
+ {
1695
+ "attempt_id": "evatt-candidate-000002",
1696
+ "attempt_index": 1,
1697
+ "terminal_status": "verified_success",
1698
+ "retry_of": "evatt-candidate-000001",
1699
+ "fallback_of": null,
1700
+ "escalation_of": null,
1701
+ "resume_of": null,
1702
+ "requested_treatment": {
1703
+ "client": "codex",
1704
+ "provider": "openai",
1705
+ "runtime": "codex-cli",
1706
+ "model": "gpt-5.6-terra",
1707
+ "effort": "medium",
1708
+ "profile_id": "terra-medium",
1709
+ "profile_digest": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
1710
+ "route_policy_digest": "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
1711
+ },
1712
+ "effective_treatment": {
1713
+ "client": "codex",
1714
+ "provider": "openai",
1715
+ "runtime": "codex-cli",
1716
+ "model": "gpt-5.6-terra",
1717
+ "effort": "medium",
1718
+ "profile_id": "terra-medium",
1719
+ "profile_digest": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
1720
+ "route_policy_digest": "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
1721
+ "runner_version": "eval-runner-v1",
1722
+ "harness_version": "planr-eval-harness-v1",
1723
+ "confidence": "verified"
1724
+ },
1725
+ "reasons": []
1726
+ }
1727
+ ],
1728
+ "sample_metering": [
1729
+ {
1730
+ "sample_id": "evsample-candidate-cost",
1731
+ "attempt_id": "evatt-candidate-000001",
1732
+ "measure": "cost_micros",
1733
+ "metering_basis": "actual_trusted",
1734
+ "basis_source": "host_usage_receipt",
1735
+ "basis_confidence": "verified",
1736
+ "estimate_provenance": null
1737
+ },
1738
+ {
1739
+ "sample_id": "evsample-candidate-cost-estimated",
1740
+ "attempt_id": "evatt-candidate-000002",
1741
+ "measure": "cost_micros",
1742
+ "metering_basis": "estimated",
1743
+ "basis_source": "pricing_reference",
1744
+ "basis_confidence": "estimated",
1745
+ "estimate_provenance": {
1746
+ "pricing_reference_id": "openai-prices",
1747
+ "pricing_reference_version": "2026-07-01",
1748
+ "currency": "USD",
1749
+ "effective_at": "2026-07-01T00:00:00Z"
1750
+ }
1751
+ }
1752
+ ],
1753
+ "efficiency_summary": {
1754
+ "countable_attempts": 2,
1755
+ "verified_successes": 1,
1756
+ "total_cost_micros": {
1757
+ "value": 1000,
1758
+ "basis": "mixed",
1759
+ "estimate_provenance": ["openai-prices@2026-07-01"]
1760
+ },
1761
+ "cost_per_verified_success_micros": {
1762
+ "value": 1000,
1763
+ "basis": "mixed",
1764
+ "state": "available",
1765
+ "estimate_provenance": ["openai-prices@2026-07-01"]
1766
+ },
1767
+ "reasons": []
1768
+ },
1769
+ "case_counts": {
1770
+ "pass": 1,
1771
+ "fail": 0,
1772
+ "error": 0,
1773
+ "skipped": 0,
1774
+ "inconclusive": 0
1775
+ },
1776
+ "evidence_ref": "eval-run:evrun-20260721-000001"
1777
+ },
1778
+ "warnings": [],
1779
+ "reasons": [],
1780
+ "error": null
1781
+ }
1782
+ ```
1783
+
1784
+ Show run or comparison:
1785
+
1786
+ ```json
1787
+ {
1788
+ "ok": true,
1789
+ "command": "eval.show",
1790
+ "object": {
1791
+ "target_kind": "comparison",
1792
+ "target_id": "evcmp-20260721-000001",
1793
+ "summary": {
1794
+ "verdict": "insufficient_evidence",
1795
+ "first_blocking_gate": "performance"
1796
+ },
1797
+ "matched_effective_treatment": {
1798
+ "baseline": {
1799
+ "client": "codex",
1800
+ "provider": "openai",
1801
+ "runtime": "codex-cli",
1802
+ "model": "gpt-5.6-terra",
1803
+ "effort": "medium",
1804
+ "profile_id": "terra-medium",
1805
+ "profile_digest": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
1806
+ "route_policy_digest": "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
1807
+ "runner_version": "eval-runner-v1",
1808
+ "harness_version": "planr-eval-harness-v1",
1809
+ "confidence": "verified"
1810
+ },
1811
+ "candidate": {
1812
+ "client": "codex",
1813
+ "provider": "openai",
1814
+ "runtime": "codex-cli",
1815
+ "model": "gpt-5.6-terra",
1816
+ "effort": "medium",
1817
+ "profile_id": "terra-medium",
1818
+ "profile_digest": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
1819
+ "route_policy_digest": "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
1820
+ "runner_version": "eval-runner-v1",
1821
+ "harness_version": "planr-eval-harness-v1",
1822
+ "confidence": "verified"
1823
+ }
1824
+ },
1825
+ "attempt_lineage": [
1826
+ {
1827
+ "attempt_id": "evatt-candidate-000002",
1828
+ "retry_of": "evatt-candidate-000001",
1829
+ "fallback_of": null,
1830
+ "escalation_of": null,
1831
+ "resume_of": null,
1832
+ "reasons": ["process_failed"],
1833
+ "effective_treatment": {
1834
+ "client": "codex",
1835
+ "provider": "openai",
1836
+ "runtime": "codex-cli",
1837
+ "model": "gpt-5.6-terra",
1838
+ "effort": "medium",
1839
+ "profile_id": "terra-medium",
1840
+ "profile_digest": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
1841
+ "route_policy_digest": "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
1842
+ "runner_version": "eval-runner-v1",
1843
+ "harness_version": "planr-eval-harness-v1",
1844
+ "confidence": "verified"
1845
+ }
1846
+ }
1847
+ ],
1848
+ "sample_metering": [
1849
+ {
1850
+ "attempt_id": "evatt-candidate-000002",
1851
+ "measure": "cost_micros",
1852
+ "metering_basis": "unavailable",
1853
+ "basis_source": "provider_unavailable",
1854
+ "basis_confidence": "unavailable",
1855
+ "estimate_provenance": null
1856
+ }
1857
+ ],
1858
+ "efficiency_summary": {
1859
+ "countable_attempts": 2,
1860
+ "verified_successes": 1,
1861
+ "cost_per_verified_success_micros": {
1862
+ "value": null,
1863
+ "basis": "unavailable",
1864
+ "state": "unavailable",
1865
+ "reasons": ["samples_unavailable"]
1866
+ },
1867
+ "reasons": ["samples_unavailable"]
1868
+ },
1869
+ "evidence_ref": "eval-comparison:evcmp-20260721-000001"
1870
+ },
1871
+ "warnings": [],
1872
+ "reasons": [],
1873
+ "error": null
1874
+ }
1875
+ ```
1876
+
1877
+ Compare:
1878
+
1879
+ ```json
1880
+ {
1881
+ "ok": true,
1882
+ "command": "eval.compare",
1883
+ "object": {
1884
+ "comparison_id": "evcmp-20260721-000001",
1885
+ "baseline_run_id": "evrun-20260721-000001",
1886
+ "candidate_run_id": "evrun-20260721-000002",
1887
+ "verdict": "insufficient_evidence",
1888
+ "reasons": ["samples_unavailable"],
1889
+ "matched_effective_treatment": {
1890
+ "baseline": {
1891
+ "client": "codex",
1892
+ "provider": "openai",
1893
+ "runtime": "codex-cli",
1894
+ "model": "gpt-5.6-terra",
1895
+ "effort": "medium",
1896
+ "profile_id": "terra-medium",
1897
+ "profile_digest": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
1898
+ "route_policy_digest": "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
1899
+ "runner_version": "eval-runner-v1",
1900
+ "harness_version": "planr-eval-harness-v1",
1901
+ "confidence": "verified"
1902
+ },
1903
+ "candidate": {
1904
+ "client": "codex",
1905
+ "provider": "openai",
1906
+ "runtime": "codex-cli",
1907
+ "model": "gpt-5.6-terra",
1908
+ "effort": "medium",
1909
+ "profile_id": "terra-medium",
1910
+ "profile_digest": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
1911
+ "route_policy_digest": "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
1912
+ "runner_version": "eval-runner-v1",
1913
+ "harness_version": "planr-eval-harness-v1",
1914
+ "confidence": "verified"
1915
+ }
1916
+ },
1917
+ "efficiency_summary": {
1918
+ "countable_attempts": 2,
1919
+ "verified_successes": 1,
1920
+ "solve_rate": 0.5,
1921
+ "cost_per_verified_success_micros": {
1922
+ "value": null,
1923
+ "basis": "unavailable",
1924
+ "state": "unavailable",
1925
+ "reasons": ["samples_unavailable"]
1926
+ },
1927
+ "wall_time_per_verified_success_ms": {
1928
+ "value": 2112,
1929
+ "basis": "actual_trusted"
1930
+ },
1931
+ "basis_mix": ["actual_trusted", "estimated", "unavailable"],
1932
+ "reasons": ["samples_unavailable"]
1933
+ },
1934
+ "gates": [
1935
+ {
1936
+ "gate": "compatibility",
1937
+ "status": "pass",
1938
+ "reasons": []
1939
+ },
1940
+ {
1941
+ "gate": "correctness",
1942
+ "status": "pass",
1943
+ "reasons": []
1944
+ },
1945
+ {
1946
+ "gate": "performance",
1947
+ "status": "blocked",
1948
+ "reasons": ["samples_unavailable"]
1949
+ }
1950
+ ],
1951
+ "evidence_ref": "eval-comparison:evcmp-20260721-000001"
1952
+ },
1953
+ "warnings": [],
1954
+ "reasons": [],
1955
+ "error": null
1956
+ }
1957
+ ```
1958
+
1959
+ Gate:
1960
+
1961
+ ```json
1962
+ {
1963
+ "ok": true,
1964
+ "command": "eval.gate",
1965
+ "object": {
1966
+ "comparison_id": "evcmp-20260721-000001",
1967
+ "verdict": "insufficient_evidence",
1968
+ "first_blocking_gate": "performance",
1969
+ "promotion_allowed": false,
1970
+ "reasons": ["samples_unavailable"]
1971
+ },
1972
+ "warnings": [],
1973
+ "reasons": ["samples_unavailable"],
1974
+ "error": null
1975
+ }
1976
+ ```
1977
+
1978
+ Invalidate:
1979
+
1980
+ ```json
1981
+ {
1982
+ "ok": true,
1983
+ "command": "eval.invalidate",
1984
+ "object": {
1985
+ "invalidation_id": "evinv-20260721-000001",
1986
+ "target_kind": "run",
1987
+ "target_id": "evrun-20260721-000001",
1988
+ "status": "invalidated",
1989
+ "reason_codes": ["testbed_incompatible"]
1990
+ },
1991
+ "warnings": [],
1992
+ "reasons": ["testbed_incompatible"],
1993
+ "error": null
1994
+ }
1995
+ ```
1996
+
1997
+ Rescore:
1998
+
1999
+ ```json
2000
+ {
2001
+ "ok": true,
2002
+ "command": "eval.rescore",
2003
+ "object": {
2004
+ "source_run_id": "evrun-20260721-000002",
2005
+ "rescored_run_id": "evrun-20260721-000003",
2006
+ "scorer_identity": "map-lifecycle@1.1.0",
2007
+ "lineage": {
2008
+ "rescore_of": "evrun-20260721-000002"
2009
+ }
2010
+ },
2011
+ "warnings": [],
2012
+ "reasons": [],
2013
+ "error": null
2014
+ }
2015
+ ```
2016
+
2017
+ Invalid input:
2018
+
2019
+ ```json
2020
+ {
2021
+ "ok": false,
2022
+ "command": "eval.suite.check",
2023
+ "object": null,
2024
+ "warnings": [],
2025
+ "reasons": ["unsafe_path", "symlink_escape"],
2026
+ "error": {
2027
+ "code": "invalid_input",
2028
+ "message": "manifest contains an unsafe fixture path",
2029
+ "reasons": ["unsafe_path", "symlink_escape"],
2030
+ "field": "fixtures[0].path"
2031
+ }
2032
+ }
2033
+ ```
2034
+
2035
+ ## Exit Codes
2036
+
2037
+ - `0`: successful command; gate may be `improved` or another non-error verdict depending on subcommand.
2038
+ - `1`: regression or gate failure for commands intended to enforce promotion.
2039
+ - `2`: insufficient evidence.
2040
+ - `3`: invalid input or unsafe manifest.
2041
+ - `4`: infrastructure error.
2042
+
2043
+ ## Database Ownership
2044
+
2045
+ The first implementation slice must add SQLite tables or typed repository methods for run evidence only. It must not add item status columns, alternate closure state, or eval-owned review gates.
2046
+
2047
+ Required persistence rules:
2048
+
2049
+ - Insert one immutable suite snapshot per digest.
2050
+ - Insert one run transition or one completed case plus its samples in a transaction.
2051
+ - Insert one immutable attempt row per `(run_id, case_id, repetition_index, seed, attempt_index)` and preserve `retry_of`, `fallback_of`, `escalation_of`, and `resume_of` references as nullable foreign-key-style ids. Attempt rows own terminal status, reason codes, requested treatment intent, and effective treatment evidence.
2052
+ - Use uniqueness constraints for `(run_id, case_id, scorer_id, scorer_version, attempt_id, measure)` samples. Multiple measures in one attempt and multiple retry attempts in one repetition must coexist; importing another row with the same tuple is an identity conflict unless every field is byte-identical.
2053
+ - Preserve nullable sample values. `metering_basis = unavailable` requires `value IS NULL`; `actual_trusted`, `estimated`, and `mixed` require a finite number or boolean according to the measure. Do not materialize unavailable cost, token, time, or tool-call values as zero in migration defaults, package imports, CLI output, MCP output, HTTP output, aggregate caches, or comparison math.
2054
+ - Append invalidation, rescore, and recompute provenance; never update historical verdicts in place.
2055
+ - Store aggregate summaries as derived cache fields only when raw samples remain available.
2056
+ - Store Planr log/review attachments as `EvalEvidenceRef` rows or existing artifact/log references that point to eval ids.
2057
+
2058
+ Package and migration projection rules:
2059
+
2060
+ - Eval packages include `suite_snapshots`, `runs`, `case_results`, `attempts`, `samples`, `comparisons`, `invalidations`, and `evidence_refs` arrays. `attempts` and `samples` carry the same field names and nullable invariants as the stored objects above.
2061
+ - Package import preflight validates every sample against its attempt, rejects `unavailable` samples with numeric or boolean values, rejects estimated samples without estimate provenance, and rejects silent treatment fallback unless field-specific transition reasons account for every requested/effective treatment field mismatch. Generic fallback/escalation pointers, retry lineage, resume lineage, `treatment_fallback_attributed`, and `treatment_transition_attributed` do not satisfy this preflight.
2062
+ - Additive migrations create attempt and sample metadata columns with nullable defaults. Backfilled V1 rows are marked non-comparable for V1.1 efficiency until effective treatment identity and metering basis are supplied by durable evidence.
2063
+ - CLI, MCP, HTTP, package export, package import preview, and migration diagnostics must all use the stable reason-code vocabulary above; adapters may not invent surface-local strings for v1.1 metering or treatment failures.
2064
+
2065
+ ## Security Constraints
2066
+
2067
+ - Execute only explicit argv arrays; do not pass authored commands through a shell.
2068
+ - Bound time, output bytes, concurrency, repetitions, and retained artifact size before execution.
2069
+ - Reject path traversal, absolute paths, symlink escapes, and user/global configuration targets before any execution or database mutation.
2070
+ - Do not capture prompts, responses, source files, secrets, full transcripts, or unrestricted stdout/stderr by default.
2071
+ - Scrub bounded excerpts with the existing secret detection policy before persistence.
2072
+ - Treat fabricated result files, stale baselines, and incompatible testbeds as `insufficient_evidence`, not as passes.