feat(research, registry, cli, runner): C29 register seam -- description gate, store-boundary doc gate, authored root docs
Iteration 3 of the self-description plan, tasks 1-3 (checkpoint commit
mid-iteration; the register e2e, the op-script doc slot, and the full
gates follow):
- aura-research: DocFault::BadDescription { subject, fault }; a present
document description passes doc_gate in validate_process /
validate_campaign (absent is never a fault and stays byte-identical);
prose arm in doc_fault_prose; tests incl. content-id participation.
- aura-registry: the store boundary gates admission -- the public
put_blueprint parses the canonical bytes and walks root + named
nested composite docs (None refuses like ""), refusing via
UndescribedComposite { name, fault } / MalformedBlueprint; the raw
write survives as private put_blueprint_unchecked (store-mechanics
tests + the never-retroactive witness: doc-less pre-C29 entries stay
readable). Registry-internal seeders triaged junk-vs-real.
- authored root docs: the 4 example blueprints + their 4 open-fixture
twins, the scaffold starter template, the scaled_signal and hl_signal
inline fixtures, and the three programmatic member.rs builders
(breakout, meanrev, hl_channel) via GraphBuilder::doc -- mechanical
propagation of the authored lines.
Recovery note: the implement run's task 3 discarded tasks 1+2's edits
in three files via a file-level checkout (the issue-#23 guard shape);
the loop's boundary snapshot caught it and the content was restored
from recovery snapshot 11a6ae2, then re-verified (research 81 green,
registry 51 green, cli 58/59).
Known-open at this checkpoint: the op-script register test is red --
op-built composites have no doc surface (#125 scope cut); the op
vocabulary grows the anticipated doc slot in the next task of this
iteration (decision logged on the issue).
refs #316
This commit is contained in:
@@ -1 +1 @@
|
||||
{"format_version":1,"blueprint":{"name":"r_breakout_signal","nodes":[{"primitive":{"type":"Delay","bound":[{"pos":0,"name":"lag","kind":"I64","value":{"I64":1}}]}},{"primitive":{"type":"RollingMax","name":"channel_hi","bound":[{"pos":0,"name":"length","kind":"I64","value":{"I64":3}}]}},{"primitive":{"type":"RollingMin","name":"channel_lo","bound":[{"pos":0,"name":"length","kind":"I64","value":{"I64":3}}]}},{"primitive":{"type":"Gt"}},{"primitive":{"type":"Gt"}},{"primitive":{"type":"Latch"}},{"primitive":{"type":"Latch"}},{"primitive":{"type":"Sub"}}],"edges":[{"from":0,"to":1,"slot":0,"from_field":0},{"from":0,"to":2,"slot":0,"from_field":0},{"from":1,"to":3,"slot":1,"from_field":0},{"from":2,"to":4,"slot":0,"from_field":0},{"from":3,"to":5,"slot":0,"from_field":0},{"from":4,"to":5,"slot":1,"from_field":0},{"from":4,"to":6,"slot":0,"from_field":0},{"from":3,"to":6,"slot":1,"from_field":0},{"from":5,"to":7,"slot":0,"from_field":0},{"from":6,"to":7,"slot":1,"from_field":0}],"input_roles":[{"name":"price","targets":[{"node":0,"slot":0},{"node":3,"slot":0},{"node":4,"slot":1}],"source":"F64"}],"output":[{"node":7,"field":0,"name":"bias"}]}}
|
||||
{"format_version":1,"blueprint":{"name":"r_breakout_signal","doc":"rolling-extreme breakout latched into a long/short signal","nodes":[{"primitive":{"type":"Delay","bound":[{"pos":0,"name":"lag","kind":"I64","value":{"I64":1}}]}},{"primitive":{"type":"RollingMax","name":"channel_hi","bound":[{"pos":0,"name":"length","kind":"I64","value":{"I64":3}}]}},{"primitive":{"type":"RollingMin","name":"channel_lo","bound":[{"pos":0,"name":"length","kind":"I64","value":{"I64":3}}]}},{"primitive":{"type":"Gt"}},{"primitive":{"type":"Gt"}},{"primitive":{"type":"Latch"}},{"primitive":{"type":"Latch"}},{"primitive":{"type":"Sub"}}],"edges":[{"from":0,"to":1,"slot":0,"from_field":0},{"from":0,"to":2,"slot":0,"from_field":0},{"from":1,"to":3,"slot":1,"from_field":0},{"from":2,"to":4,"slot":0,"from_field":0},{"from":3,"to":5,"slot":0,"from_field":0},{"from":4,"to":5,"slot":1,"from_field":0},{"from":4,"to":6,"slot":0,"from_field":0},{"from":3,"to":6,"slot":1,"from_field":0},{"from":5,"to":7,"slot":0,"from_field":0},{"from":6,"to":7,"slot":1,"from_field":0}],"input_roles":[{"name":"price","targets":[{"node":0,"slot":0},{"node":3,"slot":0},{"node":4,"slot":1}],"source":"F64"}],"output":[{"node":7,"field":0,"name":"bias"}]}}
|
||||
@@ -1 +1 @@
|
||||
{"format_version":1,"blueprint":{"name":"hl_channel","nodes":[{"primitive":{"type":"Delay","name":"prev_high","bound":[{"pos":0,"name":"lag","kind":"I64","value":{"I64":1}}]}},{"primitive":{"type":"Delay","name":"prev_low","bound":[{"pos":0,"name":"lag","kind":"I64","value":{"I64":1}}]}},{"primitive":{"type":"RollingMax","name":"channel_hi","bound":[{"pos":0,"name":"length","kind":"I64","value":{"I64":3}}]}},{"primitive":{"type":"RollingMin","name":"channel_lo","bound":[{"pos":0,"name":"length","kind":"I64","value":{"I64":3}}]}},{"primitive":{"type":"Gt"}},{"primitive":{"type":"Gt"}},{"primitive":{"type":"Latch"}},{"primitive":{"type":"Latch"}},{"primitive":{"type":"Sub"}}],"edges":[{"from":0,"to":2,"slot":0,"from_field":0},{"from":1,"to":3,"slot":0,"from_field":0},{"from":2,"to":4,"slot":1,"from_field":0},{"from":3,"to":5,"slot":0,"from_field":0},{"from":4,"to":6,"slot":0,"from_field":0},{"from":5,"to":6,"slot":1,"from_field":0},{"from":5,"to":7,"slot":0,"from_field":0},{"from":4,"to":7,"slot":1,"from_field":0},{"from":6,"to":8,"slot":0,"from_field":0},{"from":7,"to":8,"slot":1,"from_field":0}],"input_roles":[{"name":"high","targets":[{"node":0,"slot":0}],"source":"F64"},{"name":"low","targets":[{"node":1,"slot":0}],"source":"F64"},{"name":"close","targets":[{"node":4,"slot":0},{"node":5,"slot":1}],"source":"F64"}],"output":[{"node":8,"field":0,"name":"bias"}]}}
|
||||
{"format_version":1,"blueprint":{"name":"hl_channel","doc":"prior high/low channel breaks latched into a directional signal","nodes":[{"primitive":{"type":"Delay","name":"prev_high","bound":[{"pos":0,"name":"lag","kind":"I64","value":{"I64":1}}]}},{"primitive":{"type":"Delay","name":"prev_low","bound":[{"pos":0,"name":"lag","kind":"I64","value":{"I64":1}}]}},{"primitive":{"type":"RollingMax","name":"channel_hi","bound":[{"pos":0,"name":"length","kind":"I64","value":{"I64":3}}]}},{"primitive":{"type":"RollingMin","name":"channel_lo","bound":[{"pos":0,"name":"length","kind":"I64","value":{"I64":3}}]}},{"primitive":{"type":"Gt"}},{"primitive":{"type":"Gt"}},{"primitive":{"type":"Latch"}},{"primitive":{"type":"Latch"}},{"primitive":{"type":"Sub"}}],"edges":[{"from":0,"to":2,"slot":0,"from_field":0},{"from":1,"to":3,"slot":0,"from_field":0},{"from":2,"to":4,"slot":1,"from_field":0},{"from":3,"to":5,"slot":0,"from_field":0},{"from":4,"to":6,"slot":0,"from_field":0},{"from":5,"to":6,"slot":1,"from_field":0},{"from":5,"to":7,"slot":0,"from_field":0},{"from":4,"to":7,"slot":1,"from_field":0},{"from":6,"to":8,"slot":0,"from_field":0},{"from":7,"to":8,"slot":1,"from_field":0}],"input_roles":[{"name":"high","targets":[{"node":0,"slot":0}],"source":"F64"},{"name":"low","targets":[{"node":1,"slot":0}],"source":"F64"},{"name":"close","targets":[{"node":4,"slot":0},{"node":5,"slot":1}],"source":"F64"}],"output":[{"node":8,"field":0,"name":"bias"}]}}
|
||||
@@ -1 +1 @@
|
||||
{"format_version":1,"blueprint":{"name":"r_meanrev_signal","nodes":[{"primitive":{"type":"EMA","name":"mean_window","bound":[{"pos":0,"name":"length","kind":"I64","value":{"I64":3}}]}},{"primitive":{"type":"Sub"}},{"primitive":{"type":"Mul"}},{"primitive":{"type":"EMA","name":"var_window","bound":[{"pos":0,"name":"length","kind":"I64","value":{"I64":3}}]}},{"primitive":{"type":"Sqrt"}},{"primitive":{"type":"Scale","name":"band","bound":[{"pos":0,"name":"factor","kind":"F64","value":{"F64":2.0}}]}},{"primitive":{"type":"Add"}},{"primitive":{"type":"Sub"}},{"primitive":{"type":"Gt"}},{"primitive":{"type":"Gt"}},{"primitive":{"type":"Latch"}},{"primitive":{"type":"Latch"}},{"primitive":{"type":"Sub"}}],"edges":[{"from":0,"to":1,"slot":1,"from_field":0},{"from":1,"to":2,"slot":0,"from_field":0},{"from":1,"to":2,"slot":1,"from_field":0},{"from":2,"to":3,"slot":0,"from_field":0},{"from":3,"to":4,"slot":0,"from_field":0},{"from":4,"to":5,"slot":0,"from_field":0},{"from":0,"to":6,"slot":0,"from_field":0},{"from":5,"to":6,"slot":1,"from_field":0},{"from":0,"to":7,"slot":0,"from_field":0},{"from":5,"to":7,"slot":1,"from_field":0},{"from":6,"to":8,"slot":1,"from_field":0},{"from":7,"to":9,"slot":0,"from_field":0},{"from":8,"to":10,"slot":0,"from_field":0},{"from":9,"to":10,"slot":1,"from_field":0},{"from":9,"to":11,"slot":0,"from_field":0},{"from":8,"to":11,"slot":1,"from_field":0},{"from":11,"to":12,"slot":0,"from_field":0},{"from":10,"to":12,"slot":1,"from_field":0}],"input_roles":[{"name":"price","targets":[{"node":0,"slot":0},{"node":1,"slot":0},{"node":8,"slot":0},{"node":9,"slot":1}],"source":"F64"}],"output":[{"node":12,"field":0,"name":"bias"}]}}
|
||||
{"format_version":1,"blueprint":{"name":"r_meanrev_signal","doc":"EMA deviation against a volatility band, latched into a mean-reversion signal","nodes":[{"primitive":{"type":"EMA","name":"mean_window","bound":[{"pos":0,"name":"length","kind":"I64","value":{"I64":3}}]}},{"primitive":{"type":"Sub"}},{"primitive":{"type":"Mul"}},{"primitive":{"type":"EMA","name":"var_window","bound":[{"pos":0,"name":"length","kind":"I64","value":{"I64":3}}]}},{"primitive":{"type":"Sqrt"}},{"primitive":{"type":"Scale","name":"band","bound":[{"pos":0,"name":"factor","kind":"F64","value":{"F64":2.0}}]}},{"primitive":{"type":"Add"}},{"primitive":{"type":"Sub"}},{"primitive":{"type":"Gt"}},{"primitive":{"type":"Gt"}},{"primitive":{"type":"Latch"}},{"primitive":{"type":"Latch"}},{"primitive":{"type":"Sub"}}],"edges":[{"from":0,"to":1,"slot":1,"from_field":0},{"from":1,"to":2,"slot":0,"from_field":0},{"from":1,"to":2,"slot":1,"from_field":0},{"from":2,"to":3,"slot":0,"from_field":0},{"from":3,"to":4,"slot":0,"from_field":0},{"from":4,"to":5,"slot":0,"from_field":0},{"from":0,"to":6,"slot":0,"from_field":0},{"from":5,"to":6,"slot":1,"from_field":0},{"from":0,"to":7,"slot":0,"from_field":0},{"from":5,"to":7,"slot":1,"from_field":0},{"from":6,"to":8,"slot":1,"from_field":0},{"from":7,"to":9,"slot":0,"from_field":0},{"from":8,"to":10,"slot":0,"from_field":0},{"from":9,"to":10,"slot":1,"from_field":0},{"from":9,"to":11,"slot":0,"from_field":0},{"from":8,"to":11,"slot":1,"from_field":0},{"from":11,"to":12,"slot":0,"from_field":0},{"from":10,"to":12,"slot":1,"from_field":0}],"input_roles":[{"name":"price","targets":[{"node":0,"slot":0},{"node":1,"slot":0},{"node":8,"slot":0},{"node":9,"slot":1}],"source":"F64"}],"output":[{"node":12,"field":0,"name":"bias"}]}}
|
||||
@@ -1 +1 @@
|
||||
{"format_version":1,"blueprint":{"name":"sma_signal","nodes":[{"primitive":{"type":"SMA","name":"fast","bound":[{"pos":0,"name":"length","kind":"I64","value":{"I64":2}}]}},{"primitive":{"type":"SMA","name":"slow","bound":[{"pos":0,"name":"length","kind":"I64","value":{"I64":4}}]}},{"primitive":{"type":"Sub"}},{"primitive":{"type":"Bias","name":"bias","bound":[{"pos":0,"name":"scale","kind":"F64","value":{"F64":0.5}}]}}],"edges":[{"from":0,"to":2,"slot":0,"from_field":0},{"from":1,"to":2,"slot":1,"from_field":0},{"from":2,"to":3,"slot":0,"from_field":0}],"input_roles":[{"name":"price","targets":[{"node":0,"slot":0},{"node":1,"slot":0}],"source":"F64"}],"output":[{"node":3,"field":0,"name":"bias"}]}}
|
||||
{"format_version":1,"blueprint":{"name":"sma_signal","doc":"fast/slow SMA difference clamped into a directional bias","nodes":[{"primitive":{"type":"SMA","name":"fast","bound":[{"pos":0,"name":"length","kind":"I64","value":{"I64":2}}]}},{"primitive":{"type":"SMA","name":"slow","bound":[{"pos":0,"name":"length","kind":"I64","value":{"I64":4}}]}},{"primitive":{"type":"Sub"}},{"primitive":{"type":"Bias","name":"bias","bound":[{"pos":0,"name":"scale","kind":"F64","value":{"F64":0.5}}]}}],"edges":[{"from":0,"to":2,"slot":0,"from_field":0},{"from":1,"to":2,"slot":1,"from_field":0},{"from":2,"to":3,"slot":0,"from_field":0}],"input_roles":[{"name":"price","targets":[{"node":0,"slot":0},{"node":1,"slot":0}],"source":"F64"}],"output":[{"node":3,"field":0,"name":"bias"}]}}
|
||||
@@ -191,6 +191,16 @@ pub(crate) fn doc_fault_prose(f: &DocFault) -> String {
|
||||
DocFault::ProcessRefMustBeContentId => {
|
||||
"process.ref: a process is referenced by content id in this version".into()
|
||||
}
|
||||
DocFault::BadDescription { subject, fault } => match fault {
|
||||
aura_core::DocGateFault::Empty => format!(
|
||||
"description: `{subject}`'s description is empty — omit the \
|
||||
field or write a one-line meaning (C29)"
|
||||
),
|
||||
aura_core::DocGateFault::RestatesName => format!(
|
||||
"description: `{subject}`'s description merely restates the \
|
||||
document name (C29)"
|
||||
),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -817,6 +827,35 @@ mod tests {
|
||||
assert!(!prose.contains("UnknownTap"), "Debug leak: {prose}");
|
||||
}
|
||||
|
||||
/// C29 (#316): the description-quality fault names the subject and the
|
||||
/// rule (C29) for both sub-faults — an empty description and one that
|
||||
/// merely restates the document name — never leaking the Debug variant
|
||||
/// names of either `DocFault` or the nested `DocGateFault`.
|
||||
#[test]
|
||||
fn bad_description_prose_names_subject_and_rule_for_both_faults() {
|
||||
let empty = doc_fault_prose(&DocFault::BadDescription {
|
||||
subject: "ger40-momentum".into(),
|
||||
fault: aura_core::DocGateFault::Empty,
|
||||
});
|
||||
assert!(
|
||||
empty.contains("`ger40-momentum`'s description is empty"),
|
||||
"subject and emptiness named: {empty}"
|
||||
);
|
||||
assert!(empty.contains("C29"), "rule cited: {empty}");
|
||||
assert!(!empty.contains("BadDescription") && !empty.contains("DocGateFault"), "Debug leak: {empty}");
|
||||
|
||||
let restated = doc_fault_prose(&DocFault::BadDescription {
|
||||
subject: "ger40-momentum".into(),
|
||||
fault: aura_core::DocGateFault::RestatesName,
|
||||
});
|
||||
assert!(
|
||||
restated.contains("`ger40-momentum`'s description merely restates the document name"),
|
||||
"subject and restatement named: {restated}"
|
||||
);
|
||||
assert!(restated.contains("C29"), "rule cited: {restated}");
|
||||
assert!(!restated.contains("BadDescription") && !restated.contains("DocGateFault"), "Debug leak: {restated}");
|
||||
}
|
||||
|
||||
/// #260: the instrument-map key-mismatch fault is path-addressed and names
|
||||
/// both directions — the campaign instruments the map misses AND the map
|
||||
/// keys naming no campaign instrument — in one line, no Debug leak. The
|
||||
|
||||
@@ -222,7 +222,7 @@ crate is attached to its project via `[nodes]` in the project's `Aura.toml`.
|
||||
|
||||
const GITIGNORE_PROJECT: &str = "/runs\n";
|
||||
|
||||
const SIGNAL_JSON_STD: &str = r#"{"format_version":1,"blueprint":{"name":"__NAME_SNAKE___signal","nodes":[{"primitive":{"type":"SMA","name":"fast","bound":[{"pos":0,"name":"length","kind":"I64","value":{"I64":2}}]}},{"primitive":{"type":"SMA","name":"slow","bound":[{"pos":0,"name":"length","kind":"I64","value":{"I64":4}}]}},{"primitive":{"type":"Sub"}},{"primitive":{"type":"Bias","name":"bias","bound":[{"pos":0,"name":"scale","kind":"F64","value":{"F64":0.5}}]}}],"edges":[{"from":0,"to":2,"slot":0,"from_field":0},{"from":1,"to":2,"slot":1,"from_field":0},{"from":2,"to":3,"slot":0,"from_field":0}],"input_roles":[{"name":"price","targets":[{"node":0,"slot":0},{"node":1,"slot":0}],"source":"F64"}],"output":[{"node":3,"field":0,"name":"bias"}]}}"#;
|
||||
const SIGNAL_JSON_STD: &str = r#"{"format_version":1,"blueprint":{"name":"__NAME_SNAKE___signal","doc":"fast/slow SMA difference clamped into a directional bias","nodes":[{"primitive":{"type":"SMA","name":"fast","bound":[{"pos":0,"name":"length","kind":"I64","value":{"I64":2}}]}},{"primitive":{"type":"SMA","name":"slow","bound":[{"pos":0,"name":"length","kind":"I64","value":{"I64":4}}]}},{"primitive":{"type":"Sub"}},{"primitive":{"type":"Bias","name":"bias","bound":[{"pos":0,"name":"scale","kind":"F64","value":{"F64":0.5}}]}}],"edges":[{"from":0,"to":2,"slot":0,"from_field":0},{"from":1,"to":2,"slot":1,"from_field":0},{"from":2,"to":3,"slot":0,"from_field":0}],"input_roles":[{"name":"price","targets":[{"node":0,"slot":0},{"node":1,"slot":0}],"source":"F64"}],"output":[{"node":3,"field":0,"name":"bias"}]}}"#;
|
||||
|
||||
const CLAUDE_MD_PROJECT: &str = r#"# __NAME__ — an aura research project (data-only)
|
||||
|
||||
|
||||
@@ -2182,7 +2182,7 @@ const HL_RANGE_CLOSED_BLUEPRINT: &str = r#"{
|
||||
"output": [{"node":0,"field":0,"name":"bias"}]
|
||||
}
|
||||
}"#;
|
||||
const HL_SIGNAL_OPEN_BLUEPRINT: &str = r#"{"format_version":1,"blueprint":{"name":"hl_signal","nodes":[{"primitive":{"type":"SMA","name":"fast"}},{"primitive":{"type":"SMA","name":"slow"}},{"primitive":{"type":"Sub"}},{"primitive":{"type":"Bias","name":"bias","bound":[{"pos":0,"name":"scale","kind":"F64","value":{"F64":0.5}}]}}],"edges":[{"from":0,"to":2,"slot":0,"from_field":0},{"from":1,"to":2,"slot":1,"from_field":0},{"from":2,"to":3,"slot":0,"from_field":0}],"input_roles":[{"name":"high","targets":[{"node":0,"slot":0}],"source":"F64"},{"name":"low","targets":[{"node":1,"slot":0}],"source":"F64"}],"output":[{"node":3,"field":0,"name":"bias"}]}}"#;
|
||||
const HL_SIGNAL_OPEN_BLUEPRINT: &str = r#"{"format_version":1,"blueprint":{"name":"hl_signal","doc":"high/low SMA difference clamped into a directional bias","nodes":[{"primitive":{"type":"SMA","name":"fast"}},{"primitive":{"type":"SMA","name":"slow"}},{"primitive":{"type":"Sub"}},{"primitive":{"type":"Bias","name":"bias","bound":[{"pos":0,"name":"scale","kind":"F64","value":{"F64":0.5}}]}}],"edges":[{"from":0,"to":2,"slot":0,"from_field":0},{"from":1,"to":2,"slot":1,"from_field":0},{"from":2,"to":3,"slot":0,"from_field":0}],"input_roles":[{"name":"high","targets":[{"node":0,"slot":0}],"source":"F64"},{"name":"low","targets":[{"node":1,"slot":0}],"source":"F64"}],"output":[{"node":3,"field":0,"name":"bias"}]}}"#;
|
||||
|
||||
/// Property (#231 task 4 quality follow-up): `aura run` over a multi-column
|
||||
/// blueprint with NO `--real` (the synthetic default) refuses through the
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
{"format_version":1,"blueprint":{"name":"r_breakout_signal","nodes":[{"primitive":{"type":"Delay","bound":[{"pos":0,"name":"lag","kind":"I64","value":{"I64":1}}]}},{"primitive":{"type":"RollingMax","name":"channel_hi"}},{"primitive":{"type":"RollingMin","name":"channel_lo"}},{"primitive":{"type":"Gt"}},{"primitive":{"type":"Gt"}},{"primitive":{"type":"Latch"}},{"primitive":{"type":"Latch"}},{"primitive":{"type":"Sub"}}],"edges":[{"from":0,"to":1,"slot":0,"from_field":0},{"from":0,"to":2,"slot":0,"from_field":0},{"from":1,"to":3,"slot":1,"from_field":0},{"from":2,"to":4,"slot":0,"from_field":0},{"from":3,"to":5,"slot":0,"from_field":0},{"from":4,"to":5,"slot":1,"from_field":0},{"from":4,"to":6,"slot":0,"from_field":0},{"from":3,"to":6,"slot":1,"from_field":0},{"from":5,"to":7,"slot":0,"from_field":0},{"from":6,"to":7,"slot":1,"from_field":0}],"input_roles":[{"name":"price","targets":[{"node":0,"slot":0},{"node":3,"slot":0},{"node":4,"slot":1}],"source":"F64"}],"output":[{"node":7,"field":0,"name":"bias"}],"gangs":[{"name":"channel_length","kind":"I64","members":[{"node":1,"pos":0,"name":"length"},{"node":2,"pos":0,"name":"length"}]}]}}
|
||||
{"format_version":1,"blueprint":{"name":"r_breakout_signal","doc":"rolling-extreme breakout latched into a long/short signal","nodes":[{"primitive":{"type":"Delay","bound":[{"pos":0,"name":"lag","kind":"I64","value":{"I64":1}}]}},{"primitive":{"type":"RollingMax","name":"channel_hi"}},{"primitive":{"type":"RollingMin","name":"channel_lo"}},{"primitive":{"type":"Gt"}},{"primitive":{"type":"Gt"}},{"primitive":{"type":"Latch"}},{"primitive":{"type":"Latch"}},{"primitive":{"type":"Sub"}}],"edges":[{"from":0,"to":1,"slot":0,"from_field":0},{"from":0,"to":2,"slot":0,"from_field":0},{"from":1,"to":3,"slot":1,"from_field":0},{"from":2,"to":4,"slot":0,"from_field":0},{"from":3,"to":5,"slot":0,"from_field":0},{"from":4,"to":5,"slot":1,"from_field":0},{"from":4,"to":6,"slot":0,"from_field":0},{"from":3,"to":6,"slot":1,"from_field":0},{"from":5,"to":7,"slot":0,"from_field":0},{"from":6,"to":7,"slot":1,"from_field":0}],"input_roles":[{"name":"price","targets":[{"node":0,"slot":0},{"node":3,"slot":0},{"node":4,"slot":1}],"source":"F64"}],"output":[{"node":7,"field":0,"name":"bias"}],"gangs":[{"name":"channel_length","kind":"I64","members":[{"node":1,"pos":0,"name":"length"},{"node":2,"pos":0,"name":"length"}]}]}}
|
||||
+1
-1
@@ -1 +1 @@
|
||||
{"format_version":1,"blueprint":{"name":"hl_channel","nodes":[{"primitive":{"type":"Delay","name":"prev_high","bound":[{"pos":0,"name":"lag","kind":"I64","value":{"I64":1}}]}},{"primitive":{"type":"Delay","name":"prev_low","bound":[{"pos":0,"name":"lag","kind":"I64","value":{"I64":1}}]}},{"primitive":{"type":"RollingMax","name":"channel_hi"}},{"primitive":{"type":"RollingMin","name":"channel_lo"}},{"primitive":{"type":"Gt"}},{"primitive":{"type":"Gt"}},{"primitive":{"type":"Latch"}},{"primitive":{"type":"Latch"}},{"primitive":{"type":"Sub"}}],"edges":[{"from":0,"to":2,"slot":0,"from_field":0},{"from":1,"to":3,"slot":0,"from_field":0},{"from":2,"to":4,"slot":1,"from_field":0},{"from":3,"to":5,"slot":0,"from_field":0},{"from":4,"to":6,"slot":0,"from_field":0},{"from":5,"to":6,"slot":1,"from_field":0},{"from":5,"to":7,"slot":0,"from_field":0},{"from":4,"to":7,"slot":1,"from_field":0},{"from":6,"to":8,"slot":0,"from_field":0},{"from":7,"to":8,"slot":1,"from_field":0}],"input_roles":[{"name":"high","targets":[{"node":0,"slot":0}],"source":"F64"},{"name":"low","targets":[{"node":1,"slot":0}],"source":"F64"},{"name":"close","targets":[{"node":4,"slot":0},{"node":5,"slot":1}],"source":"F64"}],"output":[{"node":8,"field":0,"name":"bias"}],"gangs":[{"name":"channel_length","kind":"I64","members":[{"node":2,"pos":0,"name":"length"},{"node":3,"pos":0,"name":"length"}]}]}}
|
||||
{"format_version":1,"blueprint":{"name":"hl_channel","doc":"prior high/low channel breaks latched into a directional signal","nodes":[{"primitive":{"type":"Delay","name":"prev_high","bound":[{"pos":0,"name":"lag","kind":"I64","value":{"I64":1}}]}},{"primitive":{"type":"Delay","name":"prev_low","bound":[{"pos":0,"name":"lag","kind":"I64","value":{"I64":1}}]}},{"primitive":{"type":"RollingMax","name":"channel_hi"}},{"primitive":{"type":"RollingMin","name":"channel_lo"}},{"primitive":{"type":"Gt"}},{"primitive":{"type":"Gt"}},{"primitive":{"type":"Latch"}},{"primitive":{"type":"Latch"}},{"primitive":{"type":"Sub"}}],"edges":[{"from":0,"to":2,"slot":0,"from_field":0},{"from":1,"to":3,"slot":0,"from_field":0},{"from":2,"to":4,"slot":1,"from_field":0},{"from":3,"to":5,"slot":0,"from_field":0},{"from":4,"to":6,"slot":0,"from_field":0},{"from":5,"to":6,"slot":1,"from_field":0},{"from":5,"to":7,"slot":0,"from_field":0},{"from":4,"to":7,"slot":1,"from_field":0},{"from":6,"to":8,"slot":0,"from_field":0},{"from":7,"to":8,"slot":1,"from_field":0}],"input_roles":[{"name":"high","targets":[{"node":0,"slot":0}],"source":"F64"},{"name":"low","targets":[{"node":1,"slot":0}],"source":"F64"},{"name":"close","targets":[{"node":4,"slot":0},{"node":5,"slot":1}],"source":"F64"}],"output":[{"node":8,"field":0,"name":"bias"}],"gangs":[{"name":"channel_length","kind":"I64","members":[{"node":2,"pos":0,"name":"length"},{"node":3,"pos":0,"name":"length"}]}]}}
|
||||
+1
-1
@@ -1 +1 @@
|
||||
{"format_version":1,"blueprint":{"name":"r_meanrev_signal","nodes":[{"primitive":{"type":"EMA","name":"mean_window"}},{"primitive":{"type":"Sub"}},{"primitive":{"type":"Mul"}},{"primitive":{"type":"EMA","name":"var_window"}},{"primitive":{"type":"Sqrt"}},{"primitive":{"type":"Scale","name":"band"}},{"primitive":{"type":"Add"}},{"primitive":{"type":"Sub"}},{"primitive":{"type":"Gt"}},{"primitive":{"type":"Gt"}},{"primitive":{"type":"Latch"}},{"primitive":{"type":"Latch"}},{"primitive":{"type":"Sub"}}],"edges":[{"from":0,"to":1,"slot":1,"from_field":0},{"from":1,"to":2,"slot":0,"from_field":0},{"from":1,"to":2,"slot":1,"from_field":0},{"from":2,"to":3,"slot":0,"from_field":0},{"from":3,"to":4,"slot":0,"from_field":0},{"from":4,"to":5,"slot":0,"from_field":0},{"from":0,"to":6,"slot":0,"from_field":0},{"from":5,"to":6,"slot":1,"from_field":0},{"from":0,"to":7,"slot":0,"from_field":0},{"from":5,"to":7,"slot":1,"from_field":0},{"from":6,"to":8,"slot":1,"from_field":0},{"from":7,"to":9,"slot":0,"from_field":0},{"from":8,"to":10,"slot":0,"from_field":0},{"from":9,"to":10,"slot":1,"from_field":0},{"from":9,"to":11,"slot":0,"from_field":0},{"from":8,"to":11,"slot":1,"from_field":0},{"from":11,"to":12,"slot":0,"from_field":0},{"from":10,"to":12,"slot":1,"from_field":0}],"input_roles":[{"name":"price","targets":[{"node":0,"slot":0},{"node":1,"slot":0},{"node":8,"slot":0},{"node":9,"slot":1}],"source":"F64"}],"output":[{"node":12,"field":0,"name":"bias"}],"gangs":[{"name":"window","kind":"I64","members":[{"node":0,"pos":0,"name":"length"},{"node":3,"pos":0,"name":"length"}]}]}}
|
||||
{"format_version":1,"blueprint":{"name":"r_meanrev_signal","doc":"EMA deviation against a volatility band, latched into a mean-reversion signal","nodes":[{"primitive":{"type":"EMA","name":"mean_window"}},{"primitive":{"type":"Sub"}},{"primitive":{"type":"Mul"}},{"primitive":{"type":"EMA","name":"var_window"}},{"primitive":{"type":"Sqrt"}},{"primitive":{"type":"Scale","name":"band"}},{"primitive":{"type":"Add"}},{"primitive":{"type":"Sub"}},{"primitive":{"type":"Gt"}},{"primitive":{"type":"Gt"}},{"primitive":{"type":"Latch"}},{"primitive":{"type":"Latch"}},{"primitive":{"type":"Sub"}}],"edges":[{"from":0,"to":1,"slot":1,"from_field":0},{"from":1,"to":2,"slot":0,"from_field":0},{"from":1,"to":2,"slot":1,"from_field":0},{"from":2,"to":3,"slot":0,"from_field":0},{"from":3,"to":4,"slot":0,"from_field":0},{"from":4,"to":5,"slot":0,"from_field":0},{"from":0,"to":6,"slot":0,"from_field":0},{"from":5,"to":6,"slot":1,"from_field":0},{"from":0,"to":7,"slot":0,"from_field":0},{"from":5,"to":7,"slot":1,"from_field":0},{"from":6,"to":8,"slot":1,"from_field":0},{"from":7,"to":9,"slot":0,"from_field":0},{"from":8,"to":10,"slot":0,"from_field":0},{"from":9,"to":10,"slot":1,"from_field":0},{"from":9,"to":11,"slot":0,"from_field":0},{"from":8,"to":11,"slot":1,"from_field":0},{"from":11,"to":12,"slot":0,"from_field":0},{"from":10,"to":12,"slot":1,"from_field":0}],"input_roles":[{"name":"price","targets":[{"node":0,"slot":0},{"node":1,"slot":0},{"node":8,"slot":0},{"node":9,"slot":1}],"source":"F64"}],"output":[{"node":12,"field":0,"name":"bias"}],"gangs":[{"name":"window","kind":"I64","members":[{"node":0,"pos":0,"name":"length"},{"node":3,"pos":0,"name":"length"}]}]}}
|
||||
+1
-1
@@ -1 +1 @@
|
||||
{"format_version":1,"blueprint":{"name":"sma_signal","nodes":[{"primitive":{"type":"SMA","name":"fast"}},{"primitive":{"type":"SMA","name":"slow"}},{"primitive":{"type":"Sub"}},{"primitive":{"type":"Bias","name":"bias","bound":[{"pos":0,"name":"scale","kind":"F64","value":{"F64":0.5}}]}}],"edges":[{"from":0,"to":2,"slot":0,"from_field":0},{"from":1,"to":2,"slot":1,"from_field":0},{"from":2,"to":3,"slot":0,"from_field":0}],"input_roles":[{"name":"price","targets":[{"node":0,"slot":0},{"node":1,"slot":0}],"source":"F64"}],"output":[{"node":3,"field":0,"name":"bias"}]}}
|
||||
{"format_version":1,"blueprint":{"name":"sma_signal","doc":"fast/slow SMA difference clamped into a directional bias","nodes":[{"primitive":{"type":"SMA","name":"fast"}},{"primitive":{"type":"SMA","name":"slow"}},{"primitive":{"type":"Sub"}},{"primitive":{"type":"Bias","name":"bias","bound":[{"pos":0,"name":"scale","kind":"F64","value":{"F64":0.5}}]}}],"edges":[{"from":0,"to":2,"slot":0,"from_field":0},{"from":1,"to":2,"slot":1,"from_field":0},{"from":2,"to":3,"slot":0,"from_field":0}],"input_roles":[{"name":"price","targets":[{"node":0,"slot":0},{"node":1,"slot":0}],"source":"F64"}],"output":[{"node":3,"field":0,"name":"bias"}]}}
|
||||
@@ -37,7 +37,7 @@ const GER40_TO_MS: &str = "1727740799999";
|
||||
/// via `aura nodes new ... --namespace knob_lab`) supplies the `knob_lab`
|
||||
/// vocabulary namespace — `Scale` is the starter node every node-crate
|
||||
/// scaffold emits (#183).
|
||||
const OPEN_BLUEPRINT: &str = r#"{"format_version":1,"blueprint":{"name":"scaled_signal","nodes":[{"primitive":{"type":"SMA","name":"fast","bound":[{"pos":0,"name":"length","kind":"I64","value":{"I64":2}}]}},{"primitive":{"type":"SMA","name":"slow","bound":[{"pos":0,"name":"length","kind":"I64","value":{"I64":4}}]}},{"primitive":{"type":"Sub"}},{"primitive":{"type":"Bias","name":"bias","bound":[{"pos":0,"name":"scale","kind":"F64","value":{"F64":0.5}}]}},{"primitive":{"type":"knob_lab::Scale","name":"gain"}}],"edges":[{"from":0,"to":2,"slot":0,"from_field":0},{"from":1,"to":2,"slot":1,"from_field":0},{"from":2,"to":3,"slot":0,"from_field":0},{"from":3,"to":4,"slot":0,"from_field":0}],"input_roles":[{"name":"price","targets":[{"node":0,"slot":0},{"node":1,"slot":0}],"source":"F64"}],"output":[{"node":4,"field":0,"name":"bias"}]}}"#;
|
||||
const OPEN_BLUEPRINT: &str = r#"{"format_version":1,"blueprint":{"name":"scaled_signal","doc":"fast/slow SMA bias scaled by an extension gain node","nodes":[{"primitive":{"type":"SMA","name":"fast","bound":[{"pos":0,"name":"length","kind":"I64","value":{"I64":2}}]}},{"primitive":{"type":"SMA","name":"slow","bound":[{"pos":0,"name":"length","kind":"I64","value":{"I64":4}}]}},{"primitive":{"type":"Sub"}},{"primitive":{"type":"Bias","name":"bias","bound":[{"pos":0,"name":"scale","kind":"F64","value":{"F64":0.5}}]}},{"primitive":{"type":"knob_lab::Scale","name":"gain"}}],"edges":[{"from":0,"to":2,"slot":0,"from_field":0},{"from":1,"to":2,"slot":1,"from_field":0},{"from":2,"to":3,"slot":0,"from_field":0},{"from":3,"to":4,"slot":0,"from_field":0}],"input_roles":[{"name":"price","targets":[{"node":0,"slot":0},{"node":1,"slot":0}],"source":"F64"}],"output":[{"node":4,"field":0,"name":"bias"}]}}"#;
|
||||
|
||||
/// The minimal executable process pipeline (one sweep stage) — the campaign
|
||||
/// cell's methodology reference.
|
||||
|
||||
@@ -21,8 +21,8 @@ use aura_core::PrimitiveBuilder;
|
||||
use aura_analysis::{one_sided_p_laplace, MetricStats, MetricVocabulary, SplitMix64};
|
||||
use aura_backtest::{RunMetrics, RunReport};
|
||||
use aura_engine::{
|
||||
blueprint_from_json, blueprint_identity_json, expected_max_of_normals, FamilySelection,
|
||||
SelectionMode,
|
||||
blueprint_from_json, blueprint_identity_json, expected_max_of_normals, BlueprintDoc,
|
||||
CompositeData, FamilySelection, NodeData, SelectionMode,
|
||||
};
|
||||
use aura_research::{CampaignDoc, DocRef};
|
||||
|
||||
@@ -143,12 +143,26 @@ impl Registry {
|
||||
/// so a repeated write re-writes identical content. The registry does NOT
|
||||
/// verify `sha256(bytes) == hash` (no `sha2` dep here): the caller owns the
|
||||
/// hash, and reproduction's bit-identical metric compare is the integrity check.
|
||||
pub fn put_blueprint(&self, hash: &str, canonical_json: &str) -> Result<(), RegistryError> {
|
||||
/// Raw write — C29-gated callers go through [`Registry::put_blueprint`]; this
|
||||
/// stays for store-mechanics tests and pre-C29 store content.
|
||||
fn put_blueprint_unchecked(&self, hash: &str, canonical_json: &str) -> Result<(), RegistryError> {
|
||||
fs::create_dir_all(self.blueprints_dir())?;
|
||||
fs::write(self.blueprint_path(hash), canonical_json)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Write-once content-addressed put, C29-gated (#316): only a canonical
|
||||
/// blueprint whose root composite and every named nested composite carry
|
||||
/// a gate-passing doc enters the store. The gate guards the store
|
||||
/// boundary itself — verb, sugar, and any future caller alike — and the
|
||||
/// write path only: already-registered doc-less entries stay readable.
|
||||
pub fn put_blueprint(&self, hash: &str, canonical_json: &str) -> Result<(), RegistryError> {
|
||||
let doc: BlueprintDoc = serde_json::from_str(canonical_json)
|
||||
.map_err(RegistryError::MalformedBlueprint)?;
|
||||
gate_composite_docs(&doc.blueprint)?;
|
||||
self.put_blueprint_unchecked(hash, canonical_json)
|
||||
}
|
||||
|
||||
/// Read a stored blueprint by content id; `Ok(None)` if absent — the same
|
||||
/// treat-as-empty discipline `load` applies to a missing runs store.
|
||||
pub fn get_blueprint(&self, hash: &str) -> Result<Option<String>, RegistryError> {
|
||||
@@ -259,6 +273,25 @@ impl Registry {
|
||||
}
|
||||
}
|
||||
|
||||
/// C29 walk (#316): the root and every named nested composite must carry a
|
||||
/// gate-passing doc. `doc: None` refuses exactly like `Some("")` — both are
|
||||
/// the Empty fault ("carries no doc").
|
||||
fn gate_composite_docs(c: &CompositeData) -> Result<(), RegistryError> {
|
||||
let fault = match c.doc.as_deref() {
|
||||
None => Some(aura_core::DocGateFault::Empty),
|
||||
Some(d) => aura_core::doc_gate(&c.name, d).err(),
|
||||
};
|
||||
if let Some(fault) = fault {
|
||||
return Err(RegistryError::UndescribedComposite { name: c.name.clone(), fault });
|
||||
}
|
||||
for n in &c.nodes {
|
||||
if let NodeData::Composite(nested) = n {
|
||||
gate_composite_docs(nested)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Referential-validation findings for a campaign document. By-identifier
|
||||
/// and Display-free (the CLI phrases them).
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
@@ -897,6 +930,14 @@ pub enum RegistryError {
|
||||
NonRMetric(String),
|
||||
/// A generalization was asked for with fewer than two instruments.
|
||||
TooFewInstruments(usize),
|
||||
/// C29 register seam (#316): the canonical bytes offered to the blueprint
|
||||
/// store do not parse as a canonical blueprint — the gate cannot certify
|
||||
/// what it cannot read.
|
||||
MalformedBlueprint(serde_json::Error),
|
||||
/// C29 register seam (#316): a root or named nested composite entering
|
||||
/// the store without a gate-passing doc. Registered artifacts are never
|
||||
/// retroactively invalidated — the gate guards the write path only.
|
||||
UndescribedComposite { name: String, fault: aura_core::DocGateFault },
|
||||
}
|
||||
|
||||
impl fmt::Display for RegistryError {
|
||||
@@ -920,6 +961,21 @@ impl fmt::Display for RegistryError {
|
||||
f,
|
||||
"a generalization needs >= 2 instruments, got {n}"
|
||||
),
|
||||
RegistryError::MalformedBlueprint(e) => {
|
||||
write!(f, "blueprint: not a canonical blueprint: {e}")
|
||||
}
|
||||
RegistryError::UndescribedComposite { name, fault } => match fault {
|
||||
aura_core::DocGateFault::Empty => write!(
|
||||
f,
|
||||
"blueprint: composite `{name}` carries no doc — a registered \
|
||||
composite describes itself (C29); set .doc(\"...\") before register"
|
||||
),
|
||||
aura_core::DocGateFault::RestatesName => write!(
|
||||
f,
|
||||
"blueprint: composite `{name}` has a doc that merely restates \
|
||||
its name — a registered composite describes itself (C29)"
|
||||
),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1070,13 +1126,50 @@ mod tests {
|
||||
fn blueprint_store_round_trips_by_content_id() {
|
||||
let reg = Registry::open(temp_family_dir("blueprint_store_round_trip"));
|
||||
let canonical = r#"{"format_version":1,"input_roles":[],"nodes":[],"edges":[]}"#;
|
||||
reg.put_blueprint("deadbeef", canonical).expect("put");
|
||||
reg.put_blueprint_unchecked("deadbeef", canonical).expect("put");
|
||||
// exact bytes come back, keyed by content id
|
||||
assert_eq!(reg.get_blueprint("deadbeef").expect("get"), Some(canonical.to_string()));
|
||||
// an absent id is Ok(None), not an error (treat-as-empty discipline)
|
||||
assert_eq!(reg.get_blueprint("never-written").expect("get"), None);
|
||||
}
|
||||
|
||||
/// C29 (#316): the store refuses a doc-less root (None and "" alike),
|
||||
/// a name-restating doc, and admits a described composite. The raw
|
||||
/// unchecked path still round-trips pre-C29 doc-less bytes — registered
|
||||
/// artifacts are never retroactively invalidated.
|
||||
#[test]
|
||||
fn blueprint_store_gates_docs_at_the_write_boundary() {
|
||||
let reg = Registry::open(temp_family_dir("blueprint_store_doc_gate"));
|
||||
let docless = r#"{"format_version":1,"blueprint":{"name":"s","nodes":[]}}"#;
|
||||
let e = reg.put_blueprint("h1", docless).expect_err("doc-less root must refuse");
|
||||
assert!(
|
||||
e.to_string().contains("composite `s` carries no doc"),
|
||||
"prose names the composite and the rule: {e}"
|
||||
);
|
||||
let restated = r#"{"format_version":1,"blueprint":{"name":"echo","doc":"Echo","nodes":[]}}"#;
|
||||
let e = reg.put_blueprint("h2", restated).expect_err("restated doc must refuse");
|
||||
assert!(e.to_string().contains("merely restates"), "prose names the rule: {e}");
|
||||
let described = r#"{"format_version":1,"blueprint":{"name":"s","doc":"a described fixture blueprint","nodes":[]}}"#;
|
||||
reg.put_blueprint("h3", described).expect("described root registers");
|
||||
assert_eq!(reg.get_blueprint("h3").expect("get"), Some(described.to_string()));
|
||||
// never-retroactive witness: a pre-C29 doc-less entry stays readable
|
||||
reg.put_blueprint_unchecked("h4", docless).expect("raw write");
|
||||
assert_eq!(reg.get_blueprint("h4").expect("get"), Some(docless.to_string()));
|
||||
}
|
||||
|
||||
/// A doc-less named NESTED composite refuses even when the root is
|
||||
/// described — the walk covers every named nested composite (C29).
|
||||
#[test]
|
||||
fn blueprint_store_gates_nested_composite_docs() {
|
||||
let reg = Registry::open(temp_family_dir("blueprint_store_nested_doc_gate"));
|
||||
let nested_docless = r#"{"format_version":1,"blueprint":{"name":"root","doc":"a described root","nodes":[{"composite":{"name":"inner","nodes":[]}}]}}"#;
|
||||
let e = reg.put_blueprint("h1", nested_docless).expect_err("doc-less nested must refuse");
|
||||
assert!(
|
||||
e.to_string().contains("composite `inner` carries no doc"),
|
||||
"prose names the nested composite: {e}"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn document_stores_round_trip_by_content_id() {
|
||||
let reg = Registry::open(temp_family_dir("document_store_round_trip"));
|
||||
@@ -1810,7 +1903,8 @@ mod tests {
|
||||
vec![],
|
||||
vec![],
|
||||
vec![],
|
||||
);
|
||||
)
|
||||
.with_doc("fixture composite for the registry seeder test");
|
||||
let space = composite.param_space();
|
||||
let real = space.first().expect("fixture composite has an open param");
|
||||
let real_name = real.name.clone();
|
||||
@@ -1941,6 +2035,7 @@ mod tests {
|
||||
vec![],
|
||||
vec![],
|
||||
)
|
||||
.with_doc("fixture composite for the registry seeder test")
|
||||
}
|
||||
|
||||
/// A second, identity-distinct fixture: same shape over the zero-arg
|
||||
@@ -1955,6 +2050,7 @@ mod tests {
|
||||
vec![],
|
||||
vec![],
|
||||
)
|
||||
.with_doc("fixture composite for the registry seeder test")
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -2095,7 +2191,8 @@ mod tests {
|
||||
vec![],
|
||||
vec![],
|
||||
vec![],
|
||||
);
|
||||
)
|
||||
.with_doc("fixture composite for the registry seeder test");
|
||||
let (_json_b, content_b, identity_twin) = seed_blueprint(®, &twin);
|
||||
assert_eq!(identity, identity_twin, "twins must share one identity id");
|
||||
assert_ne!(content_a, content_b, "twins must have distinct content ids");
|
||||
@@ -2150,7 +2247,8 @@ mod tests {
|
||||
vec![],
|
||||
vec![],
|
||||
vec![],
|
||||
);
|
||||
)
|
||||
.with_doc("fixture composite for the registry seeder test");
|
||||
assert!(composite.param_space().is_empty(), "fixture is fully bound");
|
||||
let bound = composite.bound_param_space();
|
||||
let bound_param = bound.first().expect("fixture has one bound param");
|
||||
@@ -2243,7 +2341,8 @@ mod tests {
|
||||
vec![],
|
||||
vec![],
|
||||
vec![],
|
||||
);
|
||||
)
|
||||
.with_doc("fixture composite for the registry seeder test");
|
||||
let space = composite.param_space();
|
||||
assert_eq!(space.len(), 2, "fixture exposes two open params");
|
||||
let covered = space[0].name.clone(); // e.g. "b1.scale"
|
||||
@@ -2316,7 +2415,8 @@ mod tests {
|
||||
source: Some(aura_core::ScalarKind::F64),
|
||||
}],
|
||||
vec![],
|
||||
);
|
||||
)
|
||||
.with_doc("fixture composite for the registry seeder test");
|
||||
let space = composite.param_space();
|
||||
let axis = space.first().expect("fixture has an open param").name.clone();
|
||||
let blueprint_json = blueprint_to_json(&composite).expect("serializes");
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use aura_core::{Scalar, ScalarKind};
|
||||
use aura_core::{doc_gate, DocGateFault, Scalar, ScalarKind};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// The one accepted document format version.
|
||||
@@ -840,6 +840,9 @@ pub enum DocFault {
|
||||
UnknownTap { index: usize, tap: String },
|
||||
UnknownBindingColumn { role: String, column: String },
|
||||
ProcessRefMustBeContentId,
|
||||
/// C29 (#316): a document's optional top-level `description` is present
|
||||
/// but fails the shape gate. Absent is never a fault.
|
||||
BadDescription { subject: String, fault: DocGateFault },
|
||||
}
|
||||
|
||||
fn is_known_metric(name: &str) -> bool {
|
||||
@@ -850,6 +853,11 @@ fn is_known_metric(name: &str) -> bool {
|
||||
/// Returns ALL findings, not the first.
|
||||
pub fn validate_process(doc: &ProcessDoc) -> Vec<DocFault> {
|
||||
let mut faults = Vec::new();
|
||||
if let Some(desc) = &doc.description
|
||||
&& let Err(fault) = doc_gate(&doc.name, desc)
|
||||
{
|
||||
faults.push(DocFault::BadDescription { subject: doc.name.clone(), fault });
|
||||
}
|
||||
if doc.pipeline.is_empty() {
|
||||
faults.push(DocFault::EmptyPipeline);
|
||||
}
|
||||
@@ -913,6 +921,11 @@ pub fn validate_process(doc: &ProcessDoc) -> Vec<DocFault> {
|
||||
/// Returns ALL findings, not the first.
|
||||
pub fn validate_campaign(doc: &CampaignDoc) -> Vec<DocFault> {
|
||||
let mut faults = Vec::new();
|
||||
if let Some(desc) = &doc.description
|
||||
&& let Err(fault) = doc_gate(&doc.name, desc)
|
||||
{
|
||||
faults.push(DocFault::BadDescription { subject: doc.name.clone(), fault });
|
||||
}
|
||||
if doc.data.instruments.is_empty() {
|
||||
faults.push(DocFault::EmptyInstruments);
|
||||
}
|
||||
@@ -1900,6 +1913,56 @@ mod tests {
|
||||
assert_eq!(validate_process(&doc), Vec::new());
|
||||
}
|
||||
|
||||
/// C29 (#316): a present document description passes the shape gate;
|
||||
/// absent is never a fault. Both fault arms fire and carry the doc name.
|
||||
#[test]
|
||||
fn document_description_gate_flags_empty_and_restatement() {
|
||||
let base = r#"{"format_version":1,"kind":"process","name":"p","pipeline":[{"block":"std::generalize","metric":"sqn"}]}"#;
|
||||
let mut doc: ProcessDoc = serde_json::from_str(base).expect("fixture parses");
|
||||
assert!(validate_process(&doc).is_empty(), "no description → no fault");
|
||||
doc.description = Some(" ".into());
|
||||
assert!(matches!(
|
||||
validate_process(&doc).as_slice(),
|
||||
[DocFault::BadDescription { subject, fault: DocGateFault::Empty }] if subject == "p"
|
||||
));
|
||||
doc.description = Some("P".into());
|
||||
assert!(matches!(
|
||||
validate_process(&doc).as_slice(),
|
||||
[DocFault::BadDescription { fault: DocGateFault::RestatesName, .. }]
|
||||
));
|
||||
doc.description = Some("walk-forward validation methodology".into());
|
||||
assert!(validate_process(&doc).is_empty(), "described → clean");
|
||||
}
|
||||
|
||||
/// The campaign side takes the identical gate, exercised over the
|
||||
/// crate's existing valid campaign fixture (`CAMPAIGN_FIXTURE`, which
|
||||
/// validates clean — pinned by `validate_campaign_accepts_a_valid_risk_section`).
|
||||
#[test]
|
||||
fn campaign_description_gate_mirrors_process() {
|
||||
let mut doc: CampaignDoc = serde_json::from_str(CAMPAIGN_FIXTURE).unwrap();
|
||||
assert!(validate_campaign(&doc).is_empty(), "fixture validates clean");
|
||||
doc.description = Some("".into());
|
||||
assert!(matches!(
|
||||
validate_campaign(&doc).as_slice(),
|
||||
[DocFault::BadDescription { fault: DocGateFault::Empty, .. }]
|
||||
));
|
||||
doc.description = Some("GER40 walk-forward over the momentum stack".into());
|
||||
assert!(validate_campaign(&doc).is_empty(), "described → clean");
|
||||
}
|
||||
|
||||
/// C29 (#316): a present description participates in the content id
|
||||
/// (absent ⇒ byte-identical is pinned by the existing round-trips).
|
||||
#[test]
|
||||
fn document_description_participates_in_the_content_id() {
|
||||
let base = r#"{"format_version":1,"kind":"process","name":"p","pipeline":[{"block":"std::generalize","metric":"sqn"}]}"#;
|
||||
let bare: ProcessDoc = serde_json::from_str(base).unwrap();
|
||||
let mut described: ProcessDoc = serde_json::from_str(base).unwrap();
|
||||
described.description = Some("walk-forward validation methodology".into());
|
||||
let bare_id = content_id_of(&process_to_json(&bare));
|
||||
let described_id = content_id_of(&process_to_json(&described));
|
||||
assert_ne!(bare_id, described_id, "description must move the content id");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn validate_process_accepts_the_fixture_and_reports_each_fault() {
|
||||
let ok = parse_process(PROCESS_FIXTURE).unwrap();
|
||||
|
||||
@@ -782,6 +782,7 @@ pub fn reopen_all(signal: Composite, overrides: &[String]) -> Composite {
|
||||
fn r_breakout_signal(channel: Option<i64>) -> Composite {
|
||||
use aura_std::{Delay, Gt, Latch};
|
||||
let mut g = GraphBuilder::new("r_breakout_signal");
|
||||
g.doc("rolling-extreme breakout latched into a long/short signal");
|
||||
let delay = g.add(Delay::builder().bind("lag", Scalar::i64(1)));
|
||||
let mut mx_b = RollingMax::builder().named("channel_hi");
|
||||
let mut mn_b = RollingMin::builder().named("channel_lo");
|
||||
@@ -827,6 +828,7 @@ fn r_breakout_signal(channel: Option<i64>) -> Composite {
|
||||
fn r_meanrev_signal(window: Option<i64>, band_k: Option<f64>) -> Composite {
|
||||
use aura_std::{Add, Ema, Gt, Latch, Mul, Scale, Sqrt};
|
||||
let mut g = GraphBuilder::new("r_meanrev_signal");
|
||||
g.doc("EMA deviation against a volatility band, latched into a mean-reversion signal");
|
||||
let (mut mean_b, mut var_b) =
|
||||
(Ema::builder().named("mean_window"), Ema::builder().named("var_window"));
|
||||
if let Some(n) = window {
|
||||
@@ -891,6 +893,7 @@ fn r_meanrev_signal(window: Option<i64>, band_k: Option<f64>) -> Composite {
|
||||
fn r_channel_signal(channel: Option<i64>) -> Composite {
|
||||
use aura_std::{Delay, Gt, Latch};
|
||||
let mut g = GraphBuilder::new("hl_channel");
|
||||
g.doc("prior high/low channel breaks latched into a directional signal");
|
||||
let delay_hi = g.add(Delay::builder().named("prev_high").bind("lag", Scalar::i64(1)));
|
||||
let delay_lo = g.add(Delay::builder().named("prev_low").bind("lag", Scalar::i64(1)));
|
||||
let mut mx_b = RollingMax::builder().named("channel_hi");
|
||||
|
||||
Reference in New Issue
Block a user