mirror of
https://github.com/boshu2/agentops.git
synced 2026-09-14 15:08:13 +08:00
0f16a574bc
## What
Defines the machine-checkable contract for projecting the **locked eval
substrate** into an **Outcomes-style grading payload** (Anthropic
Managed Agents Outcomes analog) — holdout-safe by construction *and* by
schema.
The schema validates the **actual** output of `ao eval outcomes compile`
(`evalsubstrate.Rubric`), not the bead's pre-implementation prose sketch
(which imagined `suite_id`/`metric`/`decision_rule`/`split`). Per
source-of-truth precedence (executable > narrative), the shipped
projection deliberately emits *fewer* fields — a smaller cloud boundary
is a smaller leak surface. The "deliberately NOT projected" rationale is
documented in the contract.
## The load-bearing invariant
**Managed Agents are not ZDR** — a rubric crossing the cloud boundary
must never carry a ground-truth answer / holdout value / `target` /
`expected_output`. Enforced three ways:
1. **By construction** — `ProjectRubric` can't copy ground truth
(existing).
2. **By re-scan** — `Rubric.ContainsAny` guard (existing).
3. **By schema (this PR)** — `additionalProperties:false` at **every
nesting level** → a malformed compiler emission that smuggles a leak
field **fails schema validation**, not merely a code check.
## Files
- `schemas/outcomes-rubric.v1.schema.json` — the contract schema
-
`tests/fixtures/outcomes-rubric/{valid-dev,valid-holdout-criteria-only,invalid-contains-target}.json`
- `scripts/validate-outcomes-rubric.sh` — standalone validator (python
`jsonschema`, mirrors `validate-next-work.sh`); `--selftest` asserts
pass/pass/fail
- `tests/scripts/validate-outcomes-rubric.bats` — 9 acceptance tests
- `cli/internal/evalsubstrate/rubric_schema_test.go` — Go schema↔struct
drift guard (reflection)
- `docs/contracts/outcomes-rubric-projection.md` —
SCHEMA-field→Outcomes-field mapping; catalogued in doc index;
cross-linked from `eval-verdict-pipeline.md`
The contract **references** the LOCKED `~/.agents/evals/SCHEMA.md`
§rc2/§4 — it never edits it. Substrate EXTENDED, never relitigated.
## Evidence
- `scripts/validate-outcomes-rubric.sh --selftest` → PASS/PASS/FAIL
(exit 0)
- `bats tests/scripts/validate-outcomes-rubric.bats` → 9/9 ok
- `go test ./internal/evalsubstrate/` → 82 passed; `go vet` clean; `go
build ./...` success
- `check-contract-compatibility.sh` → contract catalogued + schema
reference OK
- `shellcheck scripts/validate-outcomes-rubric.sh` → clean
Closes-scenario: ag-hguuf#outcomes-rubric-projection-contract
Bounded-context: BC4-Evaluation
Evidence: schemas/outcomes-rubric.v1.schema.json
101 lines
3.5 KiB
Bash
Executable File
101 lines
3.5 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# validate-outcomes-rubric.sh — Validate Outcomes-rubric projection payloads
|
|
# against schemas/outcomes-rubric.v1.schema.json (ag-hguuf).
|
|
#
|
|
# The Outcomes rubric is the holdout-safe projection of a locked eval Task's
|
|
# grading criteria (cli/internal/evalsubstrate/rubric.go). This validator is the
|
|
# bead's "standalone validator" acceptance surface: it enforces the committed
|
|
# JSON Schema structurally, so a malformed compiler emission that smuggles a
|
|
# leak field (target / ground_truth / expected_output) FAILS validation, not
|
|
# merely a code check. Managed Agents are not ZDR — a leak is permanent.
|
|
#
|
|
# Usage:
|
|
# scripts/validate-outcomes-rubric.sh <payload.json> [<payload.json>...]
|
|
# Validate each file. Exit 0 only if EVERY file is schema-valid.
|
|
# scripts/validate-outcomes-rubric.sh --selftest
|
|
# Validate the three tracked fixtures with expected pass/pass/fail and
|
|
# exit 0 only if all three match expectations.
|
|
#
|
|
# Gating validator: python `jsonschema` (Draft7), mirroring the posture in
|
|
# scripts/validate-next-work.sh. If python3/jsonschema is unavailable the script
|
|
# SKIPs (exit 0 with a notice) so it never blocks an environment without the
|
|
# tool — the bats suite and the Go schema↔struct drift test are the always-on
|
|
# guards; CI provides python3+jsonschema for the gating structural pass.
|
|
set -euo pipefail
|
|
|
|
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
SCHEMA="$REPO_ROOT/schemas/outcomes-rubric.v1.schema.json"
|
|
FIXTURES="$REPO_ROOT/tests/fixtures/outcomes-rubric"
|
|
|
|
if [[ ! -f "$SCHEMA" ]]; then
|
|
echo "FAIL: schema not found: $SCHEMA" >&2
|
|
exit 2
|
|
fi
|
|
|
|
if ! command -v python3 >/dev/null 2>&1 || ! python3 -c 'import jsonschema' >/dev/null 2>&1; then
|
|
echo "SKIP: python3 + jsonschema not available; structural validation skipped" >&2
|
|
exit 0
|
|
fi
|
|
|
|
# validate_one <file> -> exit 0 if schema-valid, 1 if invalid, 2 on read error.
|
|
validate_one() {
|
|
python3 - "$SCHEMA" "$1" <<'PY'
|
|
import json, sys
|
|
from jsonschema import Draft7Validator
|
|
|
|
schema_path, data_path = sys.argv[1], sys.argv[2]
|
|
try:
|
|
with open(schema_path) as f:
|
|
schema = json.load(f)
|
|
with open(data_path) as f:
|
|
doc = json.load(f)
|
|
except Exception as e:
|
|
print(f"read error: {e}", file=sys.stderr)
|
|
sys.exit(2)
|
|
|
|
errors = sorted(Draft7Validator(schema).iter_errors(doc), key=lambda e: e.path)
|
|
if errors:
|
|
for e in errors:
|
|
loc = "/".join(str(p) for p in e.path) or "(root)"
|
|
print(f" invalid at {loc}: {e.message}", file=sys.stderr)
|
|
sys.exit(1)
|
|
sys.exit(0)
|
|
PY
|
|
}
|
|
|
|
if [[ "${1:-}" == "--selftest" ]]; then
|
|
rc=0
|
|
declare -A EXPECT=(
|
|
[valid-dev.json]=0
|
|
[valid-holdout-criteria-only.json]=0
|
|
[invalid-contains-target.json]=1
|
|
)
|
|
for name in valid-dev.json valid-holdout-criteria-only.json invalid-contains-target.json; do
|
|
want="${EXPECT[$name]}"
|
|
if validate_one "$FIXTURES/$name" >/dev/null 2>&1; then got=0; else got=1; fi
|
|
if [[ "$got" == "$want" ]]; then
|
|
echo "PASS: $name (expected $([[ $want == 0 ]] && echo valid || echo invalid))"
|
|
else
|
|
echo "FAIL: $name (expected $([[ $want == 0 ]] && echo valid || echo invalid), got $([[ $got == 0 ]] && echo valid || echo invalid))" >&2
|
|
rc=1
|
|
fi
|
|
done
|
|
exit "$rc"
|
|
fi
|
|
|
|
if [[ $# -lt 1 ]]; then
|
|
echo "usage: $(basename "$0") <payload.json> [<payload.json>...] | --selftest" >&2
|
|
exit 2
|
|
fi
|
|
|
|
overall=0
|
|
for f in "$@"; do
|
|
if validate_one "$f"; then
|
|
echo "PASS: $f"
|
|
else
|
|
echo "FAIL: $f (schema-invalid)" >&2
|
|
overall=1
|
|
fi
|
|
done
|
|
exit "$overall"
|