feat(engine, cli): op-script doc slot -- the vocabulary grows the C29 meaning line
Iteration 3 completion (tasks 5-6): the construction vocabulary gains the slot its own #125 scope-cut comment anticipated. Op::Doc { text } is the op-script twin of the builder's .doc(...) -- a closed, typed metadata construct (C25, no logic content); at most one per script, a second refuses via OpError::DuplicateDoc (the existing duplicate-fault family), rendered "a doc op may appear at most once". GraphSession threads the text through Composite::with_doc at finish, so op-built composites pass the C29 store gate like builder-built ones. The op-script register test runs on the described variant (SIGNAL_DOC_DESCRIBED) for both shapes; the doc-less script now pins the C29 refusal, and the E2E phase added the RestatesName op-script case. Full gates: cargo test --workspace green (98 suites, 0 failed); clippy -D warnings clean. refs #316
This commit is contained in:
@@ -42,6 +42,10 @@ pub enum Op {
|
||||
Tap { from: String, as_name: String },
|
||||
/// Fuse two or more sibling params into one public knob (#61).
|
||||
Gang { as_name: String, into: Vec<String> },
|
||||
/// Declare the composite's one-line meaning (C29, #316) — the op-script
|
||||
/// twin of the builder's `.doc(...)`. At most one per script; a second
|
||||
/// is a fault (a declarative script carries no last-wins ambiguity).
|
||||
Doc { text: String },
|
||||
}
|
||||
|
||||
/// A per-op construction fault, by-identifier so the cause names the op.
|
||||
@@ -92,6 +96,8 @@ pub enum OpError {
|
||||
/// A holistic finalize fault (totality / injectivity / unbound root role),
|
||||
/// wrapping the unchanged engine gate's `CompileError`.
|
||||
Incomplete(CompileError),
|
||||
/// A second `doc` op — the meaning line is declared at most once.
|
||||
DuplicateDoc,
|
||||
}
|
||||
|
||||
/// A per-op-fallible blueprint accumulator. Holds the same interior data a
|
||||
@@ -113,6 +119,7 @@ pub struct GraphSession<'v> {
|
||||
tap_names: HashSet<String>,
|
||||
coverage: HashMap<(usize, usize), usize>,
|
||||
gangs: Vec<Gang>,
|
||||
doc: Option<String>,
|
||||
}
|
||||
|
||||
impl<'v> GraphSession<'v> {
|
||||
@@ -135,6 +142,7 @@ impl<'v> GraphSession<'v> {
|
||||
tap_names: HashSet::new(),
|
||||
coverage: HashMap::new(),
|
||||
gangs: Vec::new(),
|
||||
doc: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -150,6 +158,13 @@ impl<'v> GraphSession<'v> {
|
||||
Op::Expose { from, as_name } => self.expose(from, as_name),
|
||||
Op::Tap { from, as_name } => self.tap(from, as_name),
|
||||
Op::Gang { as_name, into } => self.gang(as_name, into),
|
||||
Op::Doc { text } => {
|
||||
if self.doc.is_some() {
|
||||
return Err(OpError::DuplicateDoc);
|
||||
}
|
||||
self.doc = Some(text);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -424,13 +439,14 @@ impl<'v> GraphSession<'v> {
|
||||
.into_iter()
|
||||
.map(|(name, source, targets)| Role { name, targets, source })
|
||||
.collect();
|
||||
// No doc is attached here: the op-script vocabulary deliberately has no
|
||||
// doc-carrying surface yet (#125 scope cut) — an op-built composite
|
||||
// carries no authored rationale until that vocabulary grows a slot.
|
||||
let c = Composite::new(self.name, self.nodes, self.edges, roles, self.out)
|
||||
// The doc op (C29, #316) threads through `Composite::with_doc`.
|
||||
let mut c = Composite::new(self.name, self.nodes, self.edges, roles, self.out)
|
||||
.with_taps(self.taps)
|
||||
.with_gangs(self.gangs)
|
||||
.map_err(OpError::Incomplete)?;
|
||||
if let Some(t) = self.doc {
|
||||
c = c.with_doc(t);
|
||||
}
|
||||
check_param_namespace_injective(&c.param_space()).map_err(OpError::Incomplete)?;
|
||||
validate_wiring(c.nodes(), c.edges(), c.input_roles(), c.output(), c.taps()).map_err(|e| match e {
|
||||
CompileError::UnconnectedPort { node, slot } => OpError::UnconnectedPort {
|
||||
@@ -866,6 +882,32 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
/// C29 (#316): the doc op sets the composite's meaning line — the
|
||||
/// op-script twin of the builder's .doc(...).
|
||||
#[test]
|
||||
fn doc_op_sets_the_composite_doc() {
|
||||
let mut s = scaffold();
|
||||
s.apply(Op::Doc { text: "a described op-built composite".into() }).unwrap();
|
||||
s.apply(Op::Feed { role: "price".into(), into: vec!["fast.series".into(), "slow.series".into()] })
|
||||
.unwrap();
|
||||
s.apply(Op::Connect { from: "fast.value".into(), to: "sub.lhs".into() }).unwrap();
|
||||
s.apply(Op::Connect { from: "slow.value".into(), to: "sub.rhs".into() }).unwrap();
|
||||
s.apply(Op::Expose { from: "sub.value".into(), as_name: "out".into() }).unwrap();
|
||||
let c = s.finish().expect("finishes");
|
||||
assert_eq!(c.doc(), Some("a described op-built composite"));
|
||||
}
|
||||
|
||||
/// A second doc op is a fault, not a silent overwrite.
|
||||
#[test]
|
||||
fn duplicate_doc_op_is_refused() {
|
||||
let mut s = scaffold();
|
||||
s.apply(Op::Doc { text: "first".into() }).unwrap();
|
||||
assert_eq!(
|
||||
s.apply(Op::Doc { text: "second".into() }),
|
||||
Err(OpError::DuplicateDoc)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn finish_reports_incomplete_on_unwired_slot() {
|
||||
// a fully-named scaffold missing the sub.rhs connection -> holistic 0-arm
|
||||
|
||||
Reference in New Issue
Block a user