spec(0.9.0): run_outcome and reservoir_delta are described — adr/0003's adoption arm - #7
Merged
Conversation
The reference wire has carried a terminal run verdict at the MetaLog root since before v0.8.0, and no spec text described it. adr/0003 decision 1 disposed of it: ADOPT. It is standard-shaped — a closed, low-cardinality label the observed stream carries about itself, the same species as `level` and §3.8's `component` — so it stays exactly where it is and becomes described, which is the opposite move from the vendor relocation that granted the diff root its container. §2.5 is the new section: `success` / `failure` / `unstable` / `aborted`, closed. The normative half is where the value is: it MUST come from the OBSERVED events and never from producer state or an out-of-band control plane, because a field sourced from outside the window's bytes is not re-derivable from them and §15's replay guarantee is what this format sells. It MUST be omitted when no verdict was observed — there is no wire value for "unknown", and that absence of an absence token is what makes a missing member mean the same thing in every version of this spec, including every document written before it. Consumers MUST NOT read absence as success. Composition was adr/0003's open question and it is closed here in the direction that cannot assert something false: a composed document MUST NOT carry a verdict unless every input that carries one agrees. The naive form — omit on disagreement without saying what absence means — would have made a composed document silently claim "no verdict was observed" about a window in which two were. `aborted` is the value with a consequence: the stream is TRUNCATED, so every count in that document is a count over a stream that stopped early, and a template that looks vanished may simply never have been reached. Stated in §2.5 because a consumer that misses it reads a truncation as a behavioural change. THE VALUES ARE LOWER-CASE, like every other vocabulary this spec mints (`sketch_type`, `cube.axes[].kind`) and unlike `level`, whose tokens come from the observed stream rather than from here. The reference implementation currently spells them upper case; under GOVERNANCE §3 the spec wins and that producer is non-conformant on this member until it normalises. The cost is zero published bytes — the member has never reached a published surface — and the README's implementation row now says so. §2's top-level table gains the row, and `coordinate` — described in §15 and in the schema since v0.5.0, and absent from that table ever since — gains the row it never had. The shipped example carries `run_outcome` so CI exercises the enum. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
adr/0003 decision 2: ADOPT. It is not vendor data at all — it diffs the STANDARD
§3.7 reservoir, and §13 already carries a delta for every other stats block
(template_deltas for top_k, tail_delta for tail_summary, field_histogram_deltas
for the per-slot histograms). Without it a consumer cannot see change in the block
that exists precisely to keep the rare-but-important SINGLE: a lone fatal that
starts appearing, or stops, moves no count large enough to surface anywhere else
in a diff. Structurally the same gap field_histogram_deltas closed in 0.8.0.
§13.7 is the new section, and the rule it exists to state is the one an
implementer gets wrong: MEMBERSHIP IS DECIDED OVER top_k UNION reservoir, NEVER
OVER reservoir ALONE. A template frequent enough for top_k in one window and only
salient enough for the reservoir in the other has not appeared or disappeared — it
moved between two retention mechanisms. Differencing the reservoirs alone reports
that migration as a birth AND a death, and both are false. Verified against the
reference producer: it already decides membership over the union, and the spec
would have been wrong to say otherwise. The two blocks are disjoint by
construction (§3.7.1), so the union needs no precedence rule.
frontier_crossings reports a template present on BOTH sides whose level crossed
the {ERROR, FATAL} frontier — the same absolute frontier §16.10 forbids banding
across. Frontier membership MUST be a SET test, never an ordinal compare against
ERROR: a level ladder carrying any value above FATAL would otherwise classify it
as a failure by accident of ordering, which is a real ladder and a real accident.
`direction` is oriented previous->current and POLARITY-MUTE: a template leaving
ERROR because its code path stopped running is not a repair, and this spec cannot
tell the two apart, so the escalation/recovery reading stays the consumer's.
count and salience are COPIED from the document the entry comes from, never
re-derived at diff time — a diff is a statement about two documents, not a third
computation over them. salience is comparable across the pair only because §2.4's
gate already requires a matching retention_profile; across profiles the numbers
are not on one scale. And §3.7.2.1 applies in full: across a template-text change
the delta is RE-SELECTION, not signal.
§13.2's satisfying set gains the member, because a diff whose only finding is a
reservoir membership change is a correct document and the clause would otherwise
reject it. The enumeration is loosened, never tightened. It stays hand-kept and
still lags the schema by three members; that correction belongs with the clause's
rewrite, not with this adoption.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Describing a member at an OPEN root introduces a check nothing else in this set can distinguish, and that is the test for whether a fixture is owed. Before the adoption `run_outcome: "SUCCESS"` validated — the root is open — and after it the value is rejected by a closed, case-sensitive enum. Same shape one document type over: `reservoir_delta` was any object at all, and is now a closed block whose `direction` is `up` or `down`. `invalid/run_outcome_vocabulary.metalog.jsonl` carries TWO documents, because one document carries one verdict and both arms are needed: `failure` must validate and must not be reported undescribed, `SUCCESS` must be rejected. `SUCCESS` is not an invented mistake — it is exactly what the reference implementation emits today, so this fixture is the executable form of the divergence GOVERNANCE §3 resolves in the spec's favour, and it stops being red the day that producer normalises. It is also the only fixture in this set that exercises an `enum` violation at all; every other finding here is additionalProperties, required or pattern. `invalid/reservoir_delta_direction.diff.json` puts a fully conformant `new_salient` entry beside a `frontier_crossings` entry spelling `direction` as `increased`, so the report must name the offending member rather than reject the block. `direction` is the right place for teeth: it is the one member here where a plausible synonym changes meaning, and the enum is what stops a producer minting `escalated`/`recovered` and shipping the polarity reading §13.7.2 refuses to make. Mutation-measured on all four ways a wrong adoption could look right. Remove either property and the matching fixture reds three ways — the finding disappears, the exit drops to 0, and the member joins the undescribed list. Grant either as an unconstrained `string` / `object` — a schema that still "describes" the member to anyone skimming it — and the same fixture reds on the finding. 1 of 16 each time, nothing else moving. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
coderoast-dev
force-pushed
the
spec/adopt-run-outcome-and-reservoir-delta
branch
2 times, most recently
from
August 24, 2026 14:01
ca3b916 to
9bd21f9
Compare
coderoast-dev
changed the base branch from
spec/diff-root-extensions-grant
to
main
August 24, 2026 14:01
coderoast-dev
deleted the
spec/adopt-run-outcome-and-reservoir-delta
branch
September 19, 2026 08:03
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Additive under
GOVERNANCE.md§2 — two new optional fields. The CHANGELOG entries join the existing## [0.9.0] — unreleasedsection;v0.8.0is still the last tag.Executes the adoption arm of
adr/0003— decisions 1 (run_outcome) and 2 (reservoir_delta). This is the opposite move from the diff-root grant: those two members are standard-shaped, so they stay exactly where they are and become described. Nothing relocates.The sweep — every bare member at both roots, re-derived from the current wire
adr/0003enumerated four members in August. Rather than trust that count, both document roots were re-derived from the reference implementation's serializer and compared, mechanically, against both schemas' root properties.run_outcomereservoir_deltaMetaLogDiffrootrulesetextensions["fr.coderoast.ruleset"]service_edge_deltaMetaLogDiffrootextensions["fr.coderoast.service_edge_delta"]The list is exactly those four, and after this PR both roots are fully described. The same comparison was run one level down at every position the schema closes —
producer,window,source,stats,behavior,stability,cube,coordinate,provenance[], and the diff'scurrent/previous/cube_diff— and found no member the schema does not describe.One member outside the roots is worth naming, because no instrument here can see it. The reference producer writes a
latency_shiftkey intocube_diffborder-cell coordinates. §16.4 defines a coordinate's keys as axis names, andlatency_shiftis not among the declaredaxes— it cannot be, sincecube_diff.axesmust equal both inputs'cube.axesand this is a diff-time value. It is nonetheless schema-valid: a coordinate is a map, closed over its values and open over its key set, so no closed object and no root closure reaches it, and it is not reported as undescribed either. It is out of scope here and is left as-is; the disposition it wants is a reverse-DNS key, which a map admits without any spec change.run_outcome— §2.5A closed enum:
success/failure/unstable/aborted. Same species asleveland §3.8'scomponent— a low-cardinality label the observed stream carries about itself — which is what makes it freezable vendor-neutrally today.The normative half is where the value is:
success.abortedcarries a consequence: the observed stream is truncated, so every count in that document is a count over a stream that stopped early, and a template that looks vanished may simply never have been reached. A consumer that misses this reads a truncation as a behaviour change.compose()wasadr/0003's open question, and it is closed in the only direction that cannot assert something false. A composed document MUST NOT carry a verdict unless every input that carries one carries the same value. The ADR's conservative proposal — omit when inputs disagree — is right, but it needs absence to be defined as "this document asserts no verdict" rather than "no verdict was observed"; otherwise a composed document silently claims nothing was observed about a window in which two verdicts were. §2.5 states the wider meaning and lists all three causes.The one place the spec and the shipped producer disagree
The values are lower-case. Every vocabulary this spec mints is lower-case —
sketch_type(§6),cube.axes[].kind(§16.2) — andlevelis the deliberate contrast: its tokens come from the observed stream, not from here.The reference implementation currently emits
SUCCESS/FAILURE/UNSTABLE/ABORTED. UnderGOVERNANCE.md§3 the spec wins and that producer is non-conformant on this member until it normalises. Four facts made this the right way to resolve it rather than widening the enum to match:adr/0003decision 1 — accepted by the editor — writes the vocabulary in lower case.reservoir_delta'sdirection, in lower case. The upper case is an artefact of reusing an internal display spelling, not a wire vocabulary decision.README.md's implementation row now records the divergence rather than hiding it.reservoir_delta— §13.7§13 already carries a delta for every other
statsblock. Without this one a consumer cannot see change in the block that exists precisely to keep the rare-but-important single: a lone fatal that starts appearing, or stops, moves no count large enough to surface anywhere else in a diff. Structurally the same gapfield_histogram_deltasclosed in 0.8.0.The rule an implementer would otherwise get wrong: membership is decided over
top_k∪reservoir, never overreservoiralone. A template frequent enough fortop_kin one window and only salient enough for thereservoirin the other has not appeared or disappeared — it moved between two retention mechanisms, and differencing the reservoirs alone reports that migration as a birth and a death, both false. §3.7.1 already makes the two blocks disjoint, so the union needs no precedence rule.frontier_crossingsreports a template present on both sides whoselevelcrossed the{ERROR, FATAL}frontier — the same absolute frontier §16.10 forbids banding across.ERROR. Alevelladder carrying any value aboveFATALwould otherwise classify it as a failure by accident of ordering.levelvocabulary does not use those tokens MUST map onto the frontier or omit the list, and MUST NOT report crossings against a private frontier — the member's whole meaning is that two producers name the same boundary.directionis orientedprevious→currentand polarity-mute: a template leavingERRORbecause its code path stopped running is not a repair, and this spec cannot tell the two apart, so that reading stays the consumer's.countandsalienceare copied from the document the entry comes from, never re-derived at diff time — a diff is a statement about two documents, not a third computation over them.salienceis comparable across the pair only because §2.4's gate already requires a matchingretention_profile. §3.7.2.1 applies in full: across a template-text change the delta is re-selection, not signal.§13.2's satisfying set gains
reservoir_delta, because a diff whose only finding is a reservoir membership change is a correct document and the clause would otherwise reject it. The enumeration is loosened, never tightened. It stays hand-kept and still lags the schema by three members (stability_score,field_histogram_deltas,cube_diff); that correction belongs with the clause's rewrite, not with this adoption.Why nothing here invalidates a 0.8.0 document
The only class that could falsify 0.9.0's "no conformant 0.8.0 document becomes invalid" is a 0.8.0 document carrying a bare root member named
run_outcomeorreservoir_deltain a shape the new schema rejects — a wrong-case verdict, an unknown member inside the delta block. Such a document validated at 0.8.0, because both roots are open.It was never conformant. §7's placement rule (v0.8.0) is unambiguous:
extensionsis the only carrier of non-standard members, and a producer MUST NOT write vendor data as a bare member of a standard object at any depth, including objects the schema does not currently close. A member no spec text described is exactly that. So no §7-conformant 0.8.0 document carries a barerun_outcomeorreservoir_deltaat all, whatever its shape.That generalises, and it is worth stating once: §7's placement rule is what makes every future root adoption additive. Since v0.8.0 the only legal home for an undescribed member is
extensions, whose payloads are unconstrained — so describing a member at an open root can never invalidate a document that was conformant.The rest is inert: §13.2 is loosened only; §2's table is informative; the shipped example is not normative.
Is a fixture owed?
Yes — one per document type, on the same test #6 used: what check does this introduce that nothing else in the set can distinguish? Describing a member at an open root turns "anything validates" into a closed vocabulary, and no existing fixture reaches either position. Neither, in fact, exercises an
enumviolation anywhere — every finding in the set wasadditionalProperties,requiredorpattern.invalid/run_outcome_vocabulary.metalog.jsonl— two documents, because one document carries one verdict and both arms are needed:failuremust validate and must not be reported undescribed;SUCCESSmust be rejected.SUCCESSis not an invented mistake — it is what the reference implementation emits today, so this fixture is the executable form of the divergence above, and it stops being red the day that producer normalises.invalid/reservoir_delta_direction.diff.json— a fully conformantnew_saliententry beside afrontier_crossingsentry spellingdirectionasincreased, so the report must name the offending member rather than reject the block.directionis the right place for teeth: it is the one member here where a plausible synonym changes meaning.Mutation-measured on all four ways a wrong adoption could look right. Remove either property and its fixture reds three ways — the finding disappears, the exit drops to 0, the member joins the undescribed list. Grant either as an unconstrained
string/object— a schema that still "describes" the member to anyone skimming it — and the same fixture reds on the finding. 1 of 16 each time, nothing else moving.Gates
--selftest$defsagreeschema/metalog.v0.example.json(now carryingrun_outcome)19 published documents, verdict unchanged.
For the editor
0.9.0heading's— unreleaseddate is still yours. With this PR and spec(0.9.0): the extension container is granted at the MetaLogDiff root #6,adr/0003's Consequences are complete: both adoptions described, the diff-root placement granted, both relocations already executed by the producer.run_outcome, andcoordinate— described in §15 and in the schema since v0.5.0, and missing from that table ever since. The second is beyondadr/0003's scope and rides in therun_outcomecommit rather than its own; say the word and it comes back out.structural_role's values are spelledterminatorin §3.7.1 andTerminator/GroupBeginin §16.2. It is an open string and this PR mints nothing for it, so nothing here depends on the answer — but the two spellings should agree before anything does.