fix(aura-cli): --folds renders the registry roster; op-lists refuse unknown keys

Two fold-selector/op-script surface-honesty bugs from the 2026-07-24
fieldtests, both RED-first.

--folds (#332): graph introspect --folds rendered the aura-std FoldKind
table — capitalized ids (Mean) and no record entry — while aura run
--tap resolves labels against the fold-registry roster (lowercase,
record included). The discovery surface the --tap help points at thus
misdescribed the accepted input twice. It now renders
FoldRegistry::core().roster() (label + doc per entry); the pinned
FoldKind-rendering test was rewritten to pin the roster form (8 rows,
no capitalized id leaks, record present).

Unknown op-list keys (#326): a typo'd key in an op-list element
("params" for "bind") was silently dropped and the wrong graph built
with zero signal, contradicting the closed-vocabulary posture (C25).
OpDoc now carries serde deny_unknown_fields; the refusal names the
offending key. Exit class is 1, not the issue's suggested 2: op-lists
arrive on stdin, and the pinned #175 attribution principle classes
stdin-content faults as runtime — the new refusal fires from that same
deserialize. Blast radius verified: all 21 committed *.ops.json under
fieldtests/ still build cleanly.

closes #332
closes #326
This commit is contained in:
2026-07-24 16:12:12 +02:00
parent 73ad87b08a
commit 8dbca82756
3 changed files with 42 additions and 12 deletions
+9 -4
View File
@@ -48,7 +48,7 @@ Node types and their ports: aura graph introspect --vocabulary | --node <T>"#;
/// values are the typed `Scalar` form (`{"I64":2}`); `kind` the capitalized
/// `ScalarKind` form (`"F64"`) — both the #155 representations.
#[derive(Debug, Deserialize)]
#[serde(tag = "op", rename_all = "lowercase")]
#[serde(tag = "op", rename_all = "lowercase", deny_unknown_fields)]
enum OpDoc {
Source { role: String, kind: ScalarKind },
Input { role: String },
@@ -285,9 +285,14 @@ pub fn introspect_cmd(cmd: crate::GraphIntrospectCmd, env: &aura_runner::project
}
} else if cmd.folds {
// The fold vocabulary binds at graph-declared taps (C27), so the
// graph introspect namespace is its discovery surface (#315).
for f in aura_std::fold_vocabulary() {
println!("{:<7} binds {:<9} out {:<9}{}", f.id, f.binds, f.out, f.doc);
// graph introspect namespace is its discovery surface (#315). #332:
// this renders the fold-REGISTRY roster — the same roster `aura run
// --tap TAP=FOLD` resolves labels against (aura-runner's layered
// `FoldRegistry`) — not the aura-std `FoldKind` table: labels here
// must be exactly what `--tap` accepts (lowercase), including the
// `record` entry that has no `FoldKind` counterpart.
for (label, doc) in aura_runner::FoldRegistry::core().roster() {
println!("{label:<7}{doc}");
}
} else if let Some(type_id) = cmd.node.as_deref() {
match introspect_node(type_id, env) {