Skip to content

Latest commit

 

History

History
15 lines (12 loc) · 11.4 KB

060-validator.md

File metadata and controls

15 lines (12 loc) · 11.4 KB

SPEC 060 — Dataflow validator

Status: DRAFT. Module: src/aisle/harness/validate.py. CLI: harness validate <graph.yaml> (CON-8). The most leveraged component: its error messages are the research agent's learning signal.

  • VAL-1: Loads a dora dataflow YAML + all manifests; resolves every node id to a manifest (unresolved id = error MANIFEST_MISSING).
  • VAL-2: Checks, each with a stable error code: NODE_ID_DUPLICATE, INPUT_NO_PRODUCER, SCHEMA_MISMATCH, RATE_INCOMPATIBLE (warning), EMBODIMENT_MISMATCH, ORACLE_LEAK, MOTION_UNGATED, EVAL_MISSING_FOR_MOTION, INSTALL_MISSING, PATH_MANIFEST_MISMATCH, SOURCE_INVALID, CLOCK_DROPPED, CLOCK_SOURCE_INVALID, CLOCK_PATH_INCOMPLETE, CLOCK_CYCLE, CLOCK_COMMIT_COUNT, DIALOGUE_GOAL_LEAK. DIALOGUE_GOAL_LEAK (ADR-32 §1): in a graph where any node declares AISLE_TASK_TIER: T4 (the same graph-attested declaration pattern as VAL-8's rung), episode_goal MUST reach only verifier-* nodes — the TC-7 goal carries the FINAL corrected target, and the T4 policy must learn its task from dialogue — and every node declaring the T4 tier MUST consume a human_msg input; without the rule the blinding is advisory, the same failure VAL-6 exists to prevent for oracle_state. Acceptance: tests/unit/test_validator.py::test_expert_t4_is_good, ::test_expert_t4_with_goal_routed_to_policy_is_rejected, plus the two dialogue_goal_leak_* bad-corpus graphs (VAL-7). The CLOCK_* errors enforce ADR-30 for every attested/acceptance/campaign simulation: every node with a FORWARD-edge path to a bridge command/reset input participates in the run-to-quiescence turn (ADR-30 §1.1 — measurement taps with no forward path, e.g. the trace recorder or a held-out verifier, are exempt by construction, and CLOCK_PATH_INCOMPLETE names any forward-path node that does not participate); every is_clock edge MUST have a positive explicit queue with queue_policy: backpressure and come from the bridge clock or a validated participant; the FORWARD-edge graph MUST be acyclic except across the bridge's turn boundary, and every cycle MUST contain at least one turn_edge: episodic input (ADR-30 §1.3 — the TC-6/TC-7 request-reply and action loops in every real graph are legal exactly through their episodic declarations, and CLOCK_CYCLE names a cycle whose every edge is forward); and exactly one terminal commit MUST return to the bridge (CLOCK_COMMIT_COUNT). A latest-wins/drop-oldest state edge cannot satisfy participation merely because the node uses it as a cadence. CLOCK_* validation is unconditional for every validated graph containing a simulation bridge; free-run and bring-up graphs are non-attesting and run directly without passing through the validator. INSTALL_MISSING: a node whose manifest source is pip:<distribution> and the distribution is not installed in the current environment — the graph is schema-valid but cannot launch (the dominant H1 composition failure, 24/40 attempts: analysis/h1/h1_findings.md). The hint MUST name an installed, embodiment-compatible registry alternative fully covering the missing node's provides when one exists — never an alternative that would fail the next compile. PATH_MANIFEST_MISMATCH: dora launches the graph node's path, not the manifest's source — a node whose resolved path does not match its manifest's source executes unvetted code under an approved id (issue #36; observed live in H3 campaign 2, where skill code ran under the curated s1-expert identity). For path sources there is exactly ONE base — the graph's own directory (what dora resolves against): (graph_dir/path) MUST resolve to the root-relative source; graphs staged elsewhere (instrumented run copies, live-swap staging) MUST carry absolute paths, and harness swap absolutizes from the original graph's directory before validation and dora node add (a second-base fallback would let a tmpdir-staged replacement validate against one file and launch another). For pip: sources, path MUST be the manifest source verbatim or the bare NORMALIZED distribution name (the _pip_dist contract: case-insensitive scheme, extras/specifiers stripped). Nodes without a path are dora's own launch error, not this check's. SOURCE_INVALID: a manifest whose path-form source is not a root-relative reference resolving to a REGULAR FILE contained by the root — absolute paths, ../ traversal, symlinks escaping the root, and directories all fail (PR #63 review: root / source is not containment); source-derived checks are skipped for that node, and INSTALL_MISSING alternative hints exclude such manifests (a ghost-path alternative would fail the NEXT compile). Unknown source schemes and empty-or-whitespace pip: distributions are CAP-1 territory: capability.schema.json patterns source as ^(pip:\s*[^:\s][^:]*|[^:]+)$, so they surface at registry lint and the validate-time registry screen (GRAPH_INVALID naming the manifest), never as silent passes.
  • VAL-3: Output JSON: {ok, errors:[{code, edge|node, detail..., hint}], warnings:[...], dist_state:{...}}. Every error MUST include a hint naming a registry capability or concrete fix. Exit 0 iff ok. dist_state (ADR-24 D5) maps each registry-referenced pip distribution (PEP 503 canonical name) to its installed version or null — a labeled NON-ATTESTING diagnostic explaining INSTALL_MISSING/launchability verdicts; attestation is tools/env_hash.py's (HAR-2).
  • VAL-4: SCHEMA_MISMATCH uses the CAP-2 vocabulary; unknown schema names are their own error (SCHEMA_UNKNOWN), never silently passed.
  • VAL-5: MOTION_UNGATED: every path terminating in a bridge joint_cmd/gripper_cmd input MUST traverse the budget-guard node (SPEC 080). The validator REWRITES nothing; it only rejects (rewiring is the composer's job).
  • VAL-6: ORACLE_LEAK: oracle_state may only be consumed by nodes whose manifest id starts verifier-.
  • VAL-7: Golden-corpus tests: tests/fixtures/graphs/bad/*.yaml — ≥20 deliberately broken graphs, one per failure mode incl. the expert_t0 node-id typo from the design doc; tests/fixtures/graphs/good/*.yaml — ≥3 valid graphs incl. graphs/expert_t0.yaml. Every agent-discovered failure class MUST be added to the corpus in the same PR that handles it.
  • VAL-8: PERCEPTION_RUNG_VIOLATION: a graph declaring perception rung L1 MUST NOT route the bridge's poses topic to any node, and one declaring L2 MUST route neither poses nor seg_overhead. At L2, depth_overhead remains permitted for TC-9's same-stamp metric reconstruction; identity remains RGB-derived, so ordinary sensor depth is not a privilege downgrade. Without this check the ladder is advisory — a graph could quietly keep consuming ground-truth pose while its results were reported as L1, which is the same failure VAL-6 exists to prevent for oracle_state, and the reason the rung is declared in the graph where the graph hash attests it (TC-9). The error MUST name the offending edge (VAL-3) and its hint MUST name the estimated-pose path, so the fix is one edit. The rung is read from the env key AISLE_PERCEPTION on the node that provides sim_bridge, defaulting to L0 when it declares none; a declaration on any OTHER node is an error, because dora passes env to that node's process alone, so such a rung reaches nobody at runtime — the validator would forbid poses graph-wide while the bridge stayed at its default, published ground truth and rendered no segmentation (an L1 run starved of every pose source). In a future multi-bridge graph, every bridge's effective rung participates in conflict detection: an omitted key contributes that process's runtime default L0, so one explicit L1 bridge plus one omitted/default-L0 bridge MUST be rejected as conflicting rather than applying L1 graph-wide. The forbidden topics are the BRIDGE's: an estimated pose published on a port named poses by some other node is exactly what L1 asks for, so the check matches on producer identity and not on port name alone. What the rung does NOT claim is recorded in ADR-28 — oracle_state stays available to verifier-* nodes at every rung (VAL-6), because a verifier that could not see privileged state could not judge. Historically: env is a field dora's dataflow schema already defines and passes to the node process, whereas the bare perception: key of this requirement's pre-implementation wording is not, so a graph carrying it would not launch. A graph that declares no rung is L0, where poses is the sanctioned pose source — every pre-TC-9 graph is in that class and MUST continue to validate unchanged. The store scene currently supports only L0: its graspables use item ids such as slot#2 and bin#category, while the L1 and L2 estimated-pose consumers query the desk med-name namespace, so a bridge declaring AISLE_SCENE: store with either estimated rung MUST be rejected before launch. That compatibility error's hint MUST name both the supported L0 path and implementing the missing store-namespace estimator; the bridge's matching config-time refusal remains defense in depth. The rung MUST be reported even when the same edge carries a SCHEMA_* error, inheriting VAL-6's no-masking rule: a leak that surfaces only once the graph is otherwise clean is a leak that ships. It MUST equally be reported for a consumer with NO manifest, inheriting VAL-6's manifest-less fallback for the same reason: the rung binds the GRAPH, not the registry, so a hand-added or harness-injected node is not exempt. Because an unrecognized rung forbids nothing and so silently PASSES the check, the reader MUST refuse rather than guess: the value is normalized (trimmed, upper-cased, matching the bridge so validator and runtime cannot disagree about the same graph text), a value outside L0|L1|L2 is an error (a PRESENT but blank value is unknown, not absent), and two bridge processes with DIFFERENT effective rungs is an error — node order in a dataflow YAML is arbitrary, so a graph with two rungs attests neither. On any such refusal the strictest rung is assumed, so forbidden edges are still named in the same report. Forbidden-edge hints MUST NOT offer lowering the rung as a remedy: these messages are the research agent's learning signal and the rung is self-declared in the graph the agent authors, so naming the downgrade would be a one-token way to silence the error without changing what the run measured. Each rung's remedy names only topics that rung permits (VAL-2: never an alternative that fails the next compile).

Acceptance: tests/unit/test_validator.py::test_bad_corpus_all_rejected_with_expected_codes (VAL-1..7), with golden failures for every CLOCK_* code and a good complete lockstep graph; ::test_good_corpus_passes, ::test_hints_nonempty (VAL-3), ::test_perception_rung_l1_forbids_ground_truth_poses, ::test_perception_rung_default_l0_allows_ground_truth_poses, ::test_perception_rung_l1_allows_estimated_pose_path, ::test_store_rejects_unsupported_estimated_perception_rungs_at_validation, ::test_store_allows_supported_l0_perception_at_validation, ::test_perception_rung_violation_not_hidden_by_schema_error, ::test_perception_rung_table_matches_tc9, ::test_perception_rung_unknown_value_is_refused_not_ignored, ::test_perception_rung_whitespace_is_normalized, ::test_perception_rung_conflicting_declarations_are_refused, ::test_perception_rung_binds_nodes_without_manifests, ::test_perception_rung_grants_verifiers_no_exemption, ::test_perception_rung_blank_declaration_is_not_absent, ::test_perception_rung_forbids_only_the_bridges_ground_truth (VAL-8). All unit-marked: the validator MUST NOT import genesis or dora runtime.