spec: 0108 annotator stages (boss-signed) + tier-boundary characterization pin

Cycle 0108, anchored on #200. Encodes the five swarm-triaged decisions
(#200 comment 2026-07-03, all convergent on cited ground, no bounce):
std::monte_carlo bootstraps the stage's incoming R-evidence (per-survivor
after sweep/gates, pooled per-window OOS series after walk_forward) via
the shipped r_bootstrap; std::generalize runs at campaign scope over
per-cell nominees per (strategy, window); both stay terminal annotators
(unanimous); realization widens existing records (StageRealization.bootstrap,
CampaignRunRecord.generalizations, serde-default); v2 executable shape
sweep [gate]* [wf]? [mc]? [generalize]? with static preflight guards.
No document-schema bytes move — every existing content id survives.

Boss-signed via grounding-check PASS (11/11). The first dispatch
BLOCKed on one unpinned current-behaviour assumption ([sweep, mc,
walk_forward] passes the intrinsic tier); the gap was closed by the
green characterization test
validate_process_permits_walk_forward_after_an_annotator (pins the
intrinsic/executor tier boundary), and the fresh re-dispatch ratified
all eleven assumptions.

refs #200
This commit is contained in:
2026-07-03 23:44:20 +02:00
parent 4da47cb919
commit 38f7098bdb
2 changed files with 300 additions and 0 deletions
+20
View File
@@ -1168,6 +1168,26 @@ mod tests {
assert!(ca.contains(r#""values":[8.0,12.0,16.0]"#));
}
/// The intrinsic tier does NOT constrain where a population-transforming
/// stage sits relative to the annotators (terminal_seen gates only Gate
/// stages): `[sweep, monte_carlo, walk_forward]` is intrinsically valid.
/// Execution-shape refusals are the executor preflight's job, one tier up
/// — this pin is the boundary between the two tiers.
#[test]
fn validate_process_permits_walk_forward_after_an_annotator() {
let doc = parse_process(
r#"{ "format_version": 1, "kind": "process", "name": "wf-after-mc",
"pipeline": [
{ "block": "std::sweep", "metric": "sqn", "select": "argmax" },
{ "block": "std::monte_carlo", "resamples": 100, "block_len": 5 },
{ "block": "std::walk_forward", "in_sample_ms": 4000, "out_of_sample_ms": 1000,
"step_ms": 1000, "mode": "rolling", "metric": "sqn", "select": "argmax" }
] }"#,
)
.unwrap();
assert_eq!(validate_process(&doc), Vec::new());
}
#[test]
fn validate_process_accepts_the_fixture_and_reports_each_fault() {
let ok = parse_process(PROCESS_FIXTURE).unwrap();