Protected evaluation for CI decisions¶
Existing COLEMAN analysis reports summarize native TCPCI output. A ranking or an average over repeated seeds is descriptive; it does not establish independent scientific evidence. This additive library API separates chronological eligibility, frozen candidate/protocol declarations, recorded holdout access and paired inference. It uses the versioned result contract, existing native runner extensions, sinks, artifacts and manifests. No scheduling algorithm or legacy result meaning is replaced.
Temporal eligibility¶
from coleman.evaluation.protected import TemporalObservation, TemporalPlan
plan = TemporalPlan.model_validate({
"train_end": "2026-01-02T23:59:59Z",
"validation_end": "2026-01-04T23:59:59Z",
"test_end": "2026-01-06T23:59:59Z",
})
late = TemporalObservation.model_validate({
"observation_id": "repository-a/build-1",
"group_id": "repository-a",
"event_at": "2026-01-02T12:00:00Z",
"available_at": "2026-01-03T12:00:00Z",
})
assert not plan.training_eligible(late)
assert plan.decide(late).reason == "outcome_not_available"
Train is (-infinity, train_end], validation (train_end, validation_end], and test
(validation_end, test_end]. Cutoffs must increase strictly. Zoned ISO timestamps
are normalized to UTC; naive/date-only times and numeric epochs/strings fail.
Event time fixes the nominal partition: late labels never move training events
into validation/test. Both label availability and the entire closed target interval
must be within that partition's cutoff. This conservative purge excludes a training
target extending into validation or test, even if an early outcome is already known.
TemporalPlan.split(tuple_of_metadata) returns sorted typed decisions with an
eligibility flag and reason (eligible, outside_window, outcome_not_available,
overlapping_target). It rejects groups spanning nominal partitions, including late
rows, duplicate revision identities, and revisions changing event/group identity.
Post-cutoff revisions are retained as excluded decisions rather than overwriting
legitimate historical observations. It does not choose or aggregate competing
revisions; a producer must predeclare its point-in-time revision selection.
plan.observation(mapping) is a dataset-boundary adapter. Configure event_field,
availability_field, group_field, observation_field, revision_field and
target_end_field for an existing dataset. Required metadata missing from a mapping
raises KeyError; invalid values raise Pydantic ValidationError. Optional
target_end defaults to a point interval at the event, and revision_id defaults
to original. Other dataset columns stay outside the metadata contract.
Freeze before protected outcomes¶
FrozenCandidate.from_configuration accepts a complete JSON configuration at the
boundary and stores canonical JSON bytes in an immutable typed object. Include all
hyperparameters, preprocessing/feature definitions, learned-state fingerprints and
external dependency versions used by that candidate. A hash cannot freeze an
undeclared dependency or prove that an external fit did not inspect future labels.
freeze takes typed candidates, the canonical dataset fingerprint, the temporal
plan, an EvaluationProtocol and a tuple of ComparisonHypothesis declarations.
The result is a FrozenEvaluation; persist it with EvaluationAudit.save_freeze
before running or evaluating the candidates. Changing data, candidates, thresholds,
units, assumptions, plan or family changes its SHA-256 fingerprint. Candidate and
hypothesis declaration order is canonicalized; duplicate/self/reversed/unfrozen
comparisons are rejected. Never add a favorable hypothesis after reading outcomes.
The protocol declares:
metric_name,unit,meaning, and objectivedirection;independent_unitanddependence(independent,serial,clustered);- explicit
normal_differences_assumed, required by the v1 inference method; - nonnegative
practical_effectin the metric's own unit, family-wisealpha, andminimum_units(default 20, always at least 2); - a stable
holdout_id, retained across candidates, protocol revisions and dataset revisions exposing the same protected observations.
For NAPFD/APFDc, larger is better and the unit is dimensionless 1. Native
cost is historically named but contains APFDc, not monetary expense. Actual
execution cost needs its own measured unit/meaning and minimization objective.
named_seed(root_seed, candidate_id, purpose) hashes canonical named input into
64-bit entropy compatible with NumPy, Polars and Python RNGs. Candidate/worker
ordering does not affect the stream. Use distinct purposes for training, selection,
evaluation or dataset-specific execution. It does not modify existing run IDs,
sweep semantics or legacy native RNG derivation, and does not make repeated seeds
independent evidence. Truncated seeds are reproducibility tools, not unique IDs;
content identities continue to use full SHA-256 fingerprints.
Access history and interrupted work¶
EvaluationAudit(artifact_root) stores immutable frozen contracts, access receipts
and completed reports through the existing ArtifactWriter. evaluate reserves
a test/confirmatory access before invoking the outcome loader. Every invocation
counts, including attempts whose loader/validation/inference later fails. A retry
after publication cannot claim a previously untouched holdout. Calling
audit.access also records train/development, validation/tuning and test/verification
accesses. Any test exposure, including tuning or verification, counts toward reuse.
Keep a shared durable audit root and stable holdout ID for every workflow using the same protected observations. Changing candidates, protocol or dataset fingerprint does not reset a holdout ID's exposure count. Creating a different root or disguising the holdout with another ID cannot establish fresh evidence. This is an audit of calls through these APIs; it is not access control over files, proof of undisclosed external access, or a cryptographically authenticated tamper seal.
Reservations form a validated ordinal/hash chain. Concurrent processes compete on atomic create-if-absent publication and retry with a fresh chain snapshot; no lock file survives interruption. One contender records the first test access. Temporary files are ignored. Missing internal entries, changed predecessors, corrupt content and unknown versions fail closed. External removal of an entire history cannot be detected by that history alone; protect/retain the audit root as part of the research record. Local hard-link/directory-fsync requirements and power-loss limitations are the same as domain artifacts.
A reservation is an attempted access, not a completed evaluation. save_report
requires a matching persisted receipt and frozen contract, then atomically publishes
validated report content. Point estimates must be present for uncertainty or
confirmatory claims, follow the protocol-defined sign (relative tolerance 1e-12,
zero absolute tolerance) and lie within their reported interval. Report validation
recomputes Holm corrections from the complete frozen raw p-value family (unavailable
tests count as p=1); stored adjusted values must match that deterministic calculation
exactly. Every report embeds its versioned raw records and verifies their logical
keys and content digests. Reading or persisting a report recomputes the complete
frozen method from those records. Scalar agreement permits relative 1e-12 and
absolute 1e-15 numerical variation across compatible libraries; counts, reasons
and conclusions must agree exactly, so a threshold crossing fails closed.
Failed evaluation leaves the attempt and creates no completed report. Replaying
the same report/sink publication is idempotent.
flowchart TD
Metadata[Point-in-time metadata] --> Split[Typed split and exclusion decisions]
Candidates[Complete candidate configurations] --> Freeze[Immutable frozen protocol and family]
Split --> Freeze
Freeze --> Runner[Existing native runner extension]
Runner --> Records[Versioned raw CI measurements]
Freeze --> Reserve[Durable access receipt before outcome loading]
Reserve --> Load[Typed protected outcome loader]
Records --> Load
Load --> Check[Unit, provenance, pairing and temporal validation]
Check --> Compare[Paired effects, Holm p-values, simultaneous intervals]
Compare --> Report[Versioned report with embedded raw records, keys and digests]
Report --> Artifacts[Existing ArtifactWriter, local sinks and manifest]
Paired method and conservative conclusions¶
PairedObservation links a candidate ID, TemporalObservation and DomainRecord.
The metadata's group is the independent unit. Exactly one result per candidate/unit
is accepted, with the same metadata across the pair. Repeated seeds/revisions on
one unit raise ValueError. Raw record logical keys must also be globally unique
across every candidate and unit in the report, even if candidate names, metadata
labels, provenance, values or content digests differ; use a predeclared
upstream aggregation if replicates
are required. The evaluator verifies frozen dataset/configuration/context
fingerprints, observed availability and metric units/meaning/objective before
calculating a comparison. Unpaired units and incompatible measurements fail.
Version 1 provides paired Student-t inference for a declared normal population of
independent pair differences. Serial/clustered units or an undeclared normality
assumption raise NotImplementedError before loading outcomes. Adjacent builds from
one real repository generally need a justified dependence model; labeling them with
different seeds does not supply one. No automatic normality or independence detector
is provided. Normality/independence declarations are assumptions to justify in a
study, not certifications inferred by the library.
The implementation applies a one-sample t procedure to paired differences,
equivalent to SciPy's paired t procedure.
Holm's step-down Bonferroni adjustment controls family-wise p-value error across the
entire frozen family; unavailable comparisons remain in that family as p=1.
See the reference Holm implementation.
Intervals use the conservative simultaneous Bonferroni level 1 - alpha/m, where
m is the full family size. They are not unadjusted individual intervals or Holm
intervals. Cross-hypothesis independence is not required by these corrections.
raw_mean_difference is candidate minus baseline, in the original metric unit.
improvement reverses its sign for minimization. A confirmatory benefit/harm needs
both a Holm-adjusted p-value within alpha and a simultaneous interval entirely
beyond the signed practical-effect threshold. Otherwise the result is inconclusive.
There is no automatic policy promotion. Confidence intervals are conditional on
the declared sampling assumptions; p-values are not calibrated failure probabilities
or learning rewards.
Too few units, incomplete outcomes, identical pair differences or nonfinite
uncertainty yield insufficient_evidence, with raw signed means retained where
well-defined and unavailable uncertainty represented as null. Unknown outcomes are
not silently removed for a complete-case test. A negative result remains visible.
On a reused holdout, numerical estimates remain available, but a confirmatory
benefit/harm conclusion is suppressed; inspect confirmatory_eligible and the
receipt's prior_test_accesses. First recorded access means first within the
complete declared audit history, not proof that nobody ever saw those data.
Reports contain the complete freeze, access receipt, independent unit IDs, estimates,
conclusions and each embedded raw record's logical key/content digest plus temporal
metadata. The required UTC completed_at records when calculation and source
reference assembly finish, after the protected loader and inference. It cannot
predate the access receipt or consumed outcomes. Exported comparison records use
this completion time as available_at, never the earlier access request time.
Use the embedded records to verify results against immutable sink records; reserve a
test/verification access before inspecting protected raw outcomes again.
FamilyReport.domain_records(run_id) exports typed #251 measurements with explicit
units/signs/unavailable states and no synthetic learning reward. The full report
retains statistical conclusions and access context that a scalar alone cannot convey.
Runnable installed-package workflow¶
Install the reviewed wheel, then copy and run ci.py from a directory outside the repository:
The standalone script freezes Greedy/Random RunSpecs before invoking
run_with_extension. Four synthetic repository units exercise the real native
runner, RNFail reward, NAPFD metric and Parquet sink. It traces candidate fingerprints
to saved specs, publishes a queryable report and #251 records, and generates a
manifest. It intentionally returns insufficient_evidence for four units under a
20-unit minimum. A repeated access is recorded as reuse before the final manifest
is generated, so both receipts appear in its inventory. These synthetic repositories
validate plumbing, not production independence, normality, effectiveness or ROI.
Compatibility and limits¶
Frozen/access/report artifacts require integer schema_version=1. Duplicate JSON
keys, unknown fields and missing/older/future versions are rejected. No automatic
migration invents missing provenance, units or access history. Legacy native results,
RunSpec IDs, sweep APIs and descriptive reports retain their existing readers and
behavior. Existing domain records can be evaluated only if they already carry the
matching frozen context and candidate configuration; never relabel old artifacts
silently to claim protected evidence.
Version 1 intentionally excludes serial/block/cluster inference, robust/nonparametric intervals, sequential testing/alpha spending, unknown external state verification, remote artifact transactions and policy activation. Access-history scanning is linear in scope history, and contention retries are bounded at 1000 publications. Reports retain the complete consumed record payload and recompute estimates on validation; their size and verification cost grow with the paired input. Record hashes and recomputation establish internal consistency, not authenticity of an externally supplied dataset or proof of independent sampling. Keep confirmatory access scarce and predeclared. Larger benchmark/corpus methodology and mathematical/performance validation remain separate roadmap work.
coleman.evaluation.protected ¶
Library-first protected temporal evaluation and versioned paired reports.
AccessReceipt ¶
Bases: EvaluationContract
One durable attempted access, including failures after reservation.
Sequence counts all train/validation/test accesses within the same explicit
holdout scope. prior_test_accesses counts every previous test access,
including tuning or failed evaluations, so reuse cannot masquerade as virgin.
Changing candidates/protocols/data does not reset a stable holdout_id.
Timestamp is audit request time (not outcome availability); access_id names
a unique API invocation, not independent scientific evidence or an RNG seed.
ComparisonHypothesis ¶
Bases: BaseModel
Predeclared candidate/baseline pair with a unique auditable hypothesis ID.
EvaluationAudit ¶
Persist frozen contracts and serialized access reservations without a new runner.
Immutable ordinal files use atomic create-if-absent publication. Concurrent contenders reread the chain before retrying; no stale lock survives a crash. A reservation precedes outcome loading, and is counted even if loading or inference later fails. This is an audit of this API, not access control over externally readable files or proof that a caller did not inspect data elsewhere. Holdout IDs must remain stable across experiments on the same observations. Chain validation detects gaps/corruption; it is not an authenticated tamper seal.
__init__ ¶
Configure paths only; filesystem side effects begin with save/access.
access ¶
Reserve an access before any outcome loader runs, returning a durable receipt.
Every invocation is a new access, including retries after interruption;
no caller-supplied idempotency key can hide repeated confirmatory reads.
Test tuning counts as test exposure. Native training/validation history
is recorded without incrementing protected exposure. At most 1000
publication conflicts are retried, then RuntimeError reports contention.
Other validation/filesystem errors propagate immediately.
history ¶
Read and validate the complete immutable scope chain, ignoring .tmp files.
Snapshot iteration is not a global transaction. Missing/corrupt/unknown entries fail closed; no repair invents past access events. External deletion is unsupported and detected by subsequent chain mismatches.
load_freeze ¶
Load a v1 contract by identified fingerprint, failing on mismatched content.
save_freeze ¶
Publish a revalidated frozen contract before protected access.
Identical publication is idempotent. Conflict, unknown versions or I/O failures propagate; the writer's local-filesystem durability limits apply.
save_report ¶
Publish a versioned report only when its access receipt is in this audit.
Identical report publication is idempotent. Unreserved/mismatched receipts fail before file writes; interrupted reservations remain attempts, not completed reports. Local writer errors propagate to the caller.
EvaluationProtocol ¶
Bases: BaseModel
Frozen metric, estimand, independence, family error rate and practical threshold.
practical_effect is a nonnegative improvement in the metric's own unit.
Positive improvement favors the candidate (sign reverses for minimization).
Alpha controls family-wise error; paired Student-t tests assume independent
units and normal differences. These assumptions must be explicitly declared,
not inferred from seeds or successful normality tests. Serial/clustered units
are representable but explicitly unsupported by this v1 inference method.
holdout_id must persist across candidates/protocol revisions using the same
observations; changing it to disguise reuse violates the contract.
FamilyReport ¶
Bases: EvaluationContract
Versioned report tied to an immutable freeze and pre-load access receipt.
Receipt history determines confirmatory eligibility, independently of point
estimates. This report never activates a policy. The frozen protocol declares
the metric units, practical threshold, independent-unit meaning and assumptions.
Digest-checked raw records are embedded and statistics recomputed on validation.
completed_at is UTC calculation/source-assembly completion, after access
and input availability; derived records retain this availability timestamp.
confirmatory_eligible
property
¶
Return whether this API recorded the first protected confirmatory access.
domain_records ¶
Convert estimates to #251 records, retaining nulls, signs, units and provenance.
Each access/hypothesis has a distinct logical decision identity; repeating serialization or sink publication is idempotent. Unknown uncertainty is explicitly unavailable. Access IDs/sequences never count as independent evidence. No transformed learning reward is created from inference.
FrozenCandidate ¶
Bases: BaseModel
Named candidate with its entire resolved configuration frozen as canonical JSON.
Include hyperparameters, learned-state fingerprints, preprocessing and feature
definitions in this object before protected evaluation. Only JSON objects are
accepted; use from_configuration at the input boundary. Opaque config bytes
cannot be mutated through nested dictionaries. A fingerprint is not evidence
that an undeclared external dependency was frozen.
FrozenEvaluation ¶
Bases: EvaluationContract
Pre-outcome snapshot including full candidates, chronology, metrics and family.
Candidate and hypothesis ordering is canonicalized, never used as RNG entropy. This contract does not execute/tune candidates or replace the native runner. All preprocessing/hyperparameters belong in frozen candidate configurations; exclusion intervals and grouped point-in-time semantics belong in the plan.
ObservationReference ¶
Bases: BaseModel
Embedded immutable raw result with verified join/digest and temporal metadata.
PairedObservation ¶
Bases: BaseModel
One candidate's raw result for a declared independent group/unit.
metadata.group_id is the independent unit, not a seed/worker identity.
Exactly one observation per candidate/unit is accepted; repeated seed rows
require a predeclared upstream aggregation and never count as new units.
Outcome availability must agree with the versioned result record.
SplitDecision ¶
Bases: BaseModel
Auditable inclusion/exclusion; excluded rows retain their nominal partition.
TemporalObservation ¶
Bases: BaseModel
Metadata for one revision of an observation; no outcomes are inferred.
All times are explicitly zoned and normalized to UTC. Outcome availability
cannot precede the event. Target intervals are closed, start at event_at
and optionally end at target_end (otherwise the event itself). A revision
retains its event/group identity but has its own availability and revision ID.
TemporalPlan ¶
Bases: BaseModel
Chronological closed-right splits with conservative point-in-time purging.
Train is (-infinity, train_end], validation is (train_end, validation_end], test is (validation_end, test_end]. Event time fixes the nominal partition; late availability never moves a training row into validation/test. Availability and the entire target interval must be within that partition's cutoff. Exact endpoints are inclusive. Missing metadata fails; ineligible rows are excluded with reasons. Groups spanning any two nominal partitions fail even when one of their observations would otherwise be excluded for late availability.
decide ¶
Validate one metadata object and record eligibility without reading values.
nominal_partition ¶
Assign event time only, using closed-right cutoff inclusivity.
observation ¶
Convert a dataset mapping once at the boundary; missing required keys fail.
Unused dataset columns remain outside the typed metadata contract. Optional revision/interval columns may be omitted; explicit null revision is invalid. No fallback timestamp, group or observation identity is invented.
split ¶
Validate revision identity/group separation, returning deterministic decisions.
Duplicate (observation_id, revision_id) or an observation ID changing its event/group identity fails. This excludes post-cutoff revisions without overwriting legitimate historical observations. No source data is mutated.
training_eligible ¶
Return train eligibility for typed metadata; no automatic fold reassignment.
evaluate ¶
Reserve protected access, load typed records and evaluate the full frozen family.
Paired t inference requires declared independent units and normal differences;
serial/clustered observations and undeclared normality assumptions raise
NotImplementedError before outcome loading. Complete pairing is required;
repeated seed rows are rejected. Missing outcomes produce insufficient evidence
rather than selective complete-case tests. Holm p correction and Bonferroni
simultaneous intervals use the entire predeclared family. Reuse is durably
recorded and prevents a confirmatory conclusion, even after candidate changes.
Loader/validation failures leave access reservations visible for honest retries.
freeze ¶
Validate and freeze the complete protocol before reading protected outcomes.
Pure operation; changes to candidate, data, splits, assumptions, practical threshold, metric or comparison family change the canonical fingerprint. The caller owns pre-outcome timing and must persist the contract before access.
holm_adjust ¶
Return Holm-Bonferroni adjusted p-values controlling family-wise error.
Uses all predeclared hypotheses, with unavailable tests supplied as p=1 by the evaluator. Finite values in [0,1] are required. Sorting is internal and original ordering is preserved; empty input returns an empty tuple. No RNG, independence-between-hypotheses or additional optional dependency is needed.
named_seed ¶
Derive 64-bit NumPy/Polars/Python seed entropy from stable, purpose-separated names.
Reordering candidates/workers has no effect. Nonnegative integer roots and nonempty names are required; no global RNG state is modified. Repeated seeds on the same observations never increase the independent-unit count.