Interface Guardrail
docs/process/scorer-interface.md §3).
The clause this whole class exists for: a guardrail that cannot check itself says UNKNOWN
A guardrail evaluates a finished run and returns aGuardrail.Verdict. There are three of them, not two,
and the third is the point:
Guardrail.Verdict.holds()— checked, and fine.Guardrail.Verdict.violated(String)— checked, and broken. Effectively infinite penalty, so the run is ineligible regardless of its objective.Guardrail.Verdict.unknown(String)— could not be checked from this run at all.
Several of §3's envelope terms are not log facts. "All invariant-tier tests green" is a Gradle exit
code. "R8: this code passed in sim first" is a fact about history. "Logging overhead stays
under its loop-time budget" needs a budget nobody has measured yet. A guardrail for one of those,
handed only an RLOG, cannot answer — and if it quietly returns zero penalty, the envelope silently
degrades into "whatever happened to be loggable", which is exactly the reward hack the envelope exists
to stop. So it says UNKNOWN, loudly, and E2's acceptance gate decides what to do about it.
UNKNOWN is not a pass and not a failure. It carries no penalty — inventing one would
be a made-up number — and it must never be read as "holds". Scorer keeps unknowns in its
breakdown and counts them separately for precisely this reason.
Invariants only, never implementation facts
kP > 0 is not an envelope term (§3, §4): it belongs to the implementation tier, and putting it
here would block a legitimate controller swap. An envelope term survives a redesign.-
Nested Class Summary
Nested ClassesModifier and TypeInterfaceDescriptionstatic enumWhat a guardrail concluded about one run.static final classA guardrail's conclusion about one run: a status, a penalty, and why. -
Field Summary
FieldsModifier and TypeFieldDescriptionstatic final doubleThe penalty applied to a violated envelope term. -
Method Summary
Modifier and TypeMethodDescriptionChecks one finished run.name()What this guardrail is called, for the score breakdown a human reads.
-
Field Details
-
ENVELOPE_VIOLATION_PENALTY
static final double ENVELOPE_VIOLATION_PENALTYThe penalty applied to a violated envelope term.Double.POSITIVE_INFINITYrather than a large finite number, so no objective — however good — can ever buy its way past a violation. A finite penalty is a price; this is a wall.- See Also:
-
-
Method Details
-
name
String name()What this guardrail is called, for the score breakdown a human reads.Abstract on purpose, which is also why this interface is not a
@FunctionalInterface. It was briefly adefaultreturninggetClass().getSimpleName(), and that is a trap: a lambda cannot override a default method, so every lambda-built guardrail would have taken the JVM's synthesized lambda class name instead. Those names vary by JVM and by run, which would make the breakdown's keys unstable across a replay (R5) and would let two unrelated custom guardrails collide on one name. Two abstract methods means the compiler rejects the bare lambda instead, and the author has to say what the term is called.- Returns:
- a stable name for this term, used as its key in the score breakdown
-
evaluate
Checks one finished run.Return a verdict; do not throw. If this term needs a key the run does not have, return
Guardrail.Verdict.unknown(String)— that is whatUNKNOWNis for. Reaching straight forRunResult.metric(String)without checkingRunResult.hasKey(String)first throwsRunResult.MetricNotFoundExceptionout throughScorer.score(RunResult), which stops the loop rather than scoring the run ineligible. That is loud and safe — nothing gets promoted on a crash — but it is not the behaviour anybody wants, and the fix is onehasKeycheck.- Parameters:
run- the finished run to check- Returns:
- this term's conclusion about
run; never null
-