From 197f5eeca0ba040d802438a94b60051758edda5a Mon Sep 17 00:00:00 2001 From: Noah Gift Date: Sat, 19 Sep 2026 14:25:52 +0200 Subject: [PATCH 1/6] =?UTF-8?q?PMAT-3529:=20=CE=A3=20carries=20the=20contr?= =?UTF-8?q?act=20schema's=20metadata=20block,=20and=20entity.properties=20?= =?UTF-8?q?becomes=20:=20=E2=80=94=20the=20two=20defects?= =?UTF-8?q?=20apex=20measured=20on=20pv=200.68.1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Both were reported with file and stderr by the apex session (APEX-001 EV-19/EV-21) and both blocked a real corpus. 1. ONE BINARY, TWO SUBCOMMANDS, OPPOSITE VERDICTS ON THE SAME BYTES. `pv validate` REQUIRES metadata.{version, description} on a contract; contracts/ontology.yaml IS a contract; the Σ schema is deny_unknown_fields and refused the block — measured on apex: `pv validate` rc 0 and `pv lint --gate sigma` exit 3 over the same file, "unknown field `metadata` … at line 26". Σ now carries `metadata` as OPAQUE YAML: no Σ rule reads a field of it, it joins READABLE_KEYS so `readers` must still claim it, and the anti-decoration rule is answered rather than weakened ("the contract schema reads it, not Σ"). Two unit rows and a CLI row where the same fixture file passes `pv validate` AND `pv lint --gate sigma`; a third asserts that dropping the `readers` entry brings the exit 3 back, naming the key. 2. A SHAPE OVER entity.properties HAD NO PREDICATE TO CONSTRAIN. extract:pv-contract emitted ont:entityType and ont:entityRef from `entity:` and nothing else, so a contract carrying the properties of the thing it is a contract FOR — apex's nine researcher degrees of freedom, which live in the contract because the contract IS the pre-registration and EV-6 hashes it — had every `minCount: 1` fire for the wrong reason: a shape failing because the extractor was silent, the vacuity R-2 exists to end. `entity.properties.: ` now becomes `:` on the contract node (`study:scale "linear"`). The namespace is the ENTITY TYPE, never ont:, because two entity types may both carry a `scale` and one predicate for both would let a shape over one constrain the other. A contract with no `entity.type` gets no property triples — there is no namespace, and inventing one would be an inference. Nested mappings and sequences are not emitted at v1alpha1: the subset has no path expressions, so a predicate no shape could reach is decoration. All three rules have a test. Fixtures are apex's own falsifiers (EV-21 FALSIFY-ONT-021/021b), run through the CLI: entity-props-ok PASSES (a build that emits no property triples fails it on minCount), entity-props-bad-value FAILS naming `ont:study/scale` and the value, entity-props-undeclared-key FAILS on `closed` naming `ont:study/subgroup`. The shape in them is the header apex will write: targetClass ont:Contract, closed: true, ignoredProperties for the contract's own keys. Also: a lexical violation now names its PATH in the message (`ont:study/scale: quadratic is not one of […]`), not only in the result's path field — a shape with nine properties used to produce nine lines a reader could not tell apart. `in`, `pattern`, `minLength`, `maxLength`. Measured on this tree: `pv lint contracts --gate sigma` Pass, `--gate shapes` Pass (1731 focus nodes), `pv extract contracts --check` 0, aprender-contracts lib 1614/1614, ont_entity_properties 6/6, ont2b_sigma_gate 11/11, ont4_relations_gate 11/11, ont4b_shapes_gate 8/8, clippy -D warnings clean. Pmat-Ticket: PMAT-3529 Co-Authored-By: Claude Opus 5 (1M context) --- ...er-contracts-cli-ont-entity-properties.cmd | 1 + .../tests/ont_entity_properties.rs | 180 ++++++++++++++++++ .../src/ontology/extract/pv_contract.rs | 111 ++++++++++- .../aprender-contracts/src/ontology/shapes.rs | 11 +- .../aprender-contracts/src/ontology/sigma.rs | 53 +++++- docs/roadmaps/entries/PMAT-3529.yaml | 19 ++ docs/roadmaps/roadmap.yaml | 19 ++ scripts/tree_reader_tests.txt | 1 + .../contracts/lint-baseline.json | 5 + .../contracts/ontology.yaml | 21 ++ .../contracts/study-shape-v1.yaml | 27 +++ .../contracts/lint-baseline.json | 5 + .../entity-props-ok/contracts/ontology.yaml | 21 ++ .../contracts/study-shape-v1.yaml | 27 +++ .../contracts/lint-baseline.json | 5 + .../contracts/ontology.yaml | 21 ++ .../contracts/study-shape-v1.yaml | 28 +++ 17 files changed, 549 insertions(+), 6 deletions(-) create mode 100644 ci/explicit-test-commands.d/341-aprender-contracts-cli-ont-entity-properties.cmd create mode 100644 crates/aprender-contracts-cli/tests/ont_entity_properties.rs create mode 100644 docs/roadmaps/entries/PMAT-3529.yaml create mode 100644 tests/fixtures/ont/entity-props-bad-value/contracts/lint-baseline.json create mode 100644 tests/fixtures/ont/entity-props-bad-value/contracts/ontology.yaml create mode 100644 tests/fixtures/ont/entity-props-bad-value/contracts/study-shape-v1.yaml create mode 100644 tests/fixtures/ont/entity-props-ok/contracts/lint-baseline.json create mode 100644 tests/fixtures/ont/entity-props-ok/contracts/ontology.yaml create mode 100644 tests/fixtures/ont/entity-props-ok/contracts/study-shape-v1.yaml create mode 100644 tests/fixtures/ont/entity-props-undeclared-key/contracts/lint-baseline.json create mode 100644 tests/fixtures/ont/entity-props-undeclared-key/contracts/ontology.yaml create mode 100644 tests/fixtures/ont/entity-props-undeclared-key/contracts/study-shape-v1.yaml diff --git a/ci/explicit-test-commands.d/341-aprender-contracts-cli-ont-entity-properties.cmd b/ci/explicit-test-commands.d/341-aprender-contracts-cli-ont-entity-properties.cmd new file mode 100644 index 0000000000..14f09b4f5c --- /dev/null +++ b/ci/explicit-test-commands.d/341-aprender-contracts-cli-ont-entity-properties.cmd @@ -0,0 +1 @@ +cargo test -p aprender-contracts-cli --test ont_entity_properties diff --git a/crates/aprender-contracts-cli/tests/ont_entity_properties.rs b/crates/aprender-contracts-cli/tests/ont_entity_properties.rs new file mode 100644 index 0000000000..35c4f0e09f --- /dev/null +++ b/crates/aprender-contracts-cli/tests/ont_entity_properties.rs @@ -0,0 +1,180 @@ +//! PMAT-3529 — the two defects apex measured on `pv 0.68.1`, on the CLI. +//! +//! 1. Σ carries the CONTRACT schema's `metadata:` block. `pv validate` requires `metadata.{version, +//! description}` on a contract and `contracts/ontology.yaml` IS a contract, so before this one file could +//! not satisfy both readers: `pv validate` rc 0 and `pv lint --gate sigma` exit 3 over the same bytes. +//! 2. `entity.properties.` becomes `:` on the contract node, so a `shape:` can constrain the +//! entity's own properties — apex's nine pre-registered degrees of freedom, which live in the contract +//! because the contract IS the pre-registration (APEX-001 EV-6 hashes it). +//! +//! The three fixtures are apex's EV-21 falsifiers: conforming, a value outside `in:`, and a tenth key under +//! `closed: true`. DISCRIMINATION: `entity-props-ok/` must PASS (a build that emits no property triples fails +//! it on `minCount`), and the two others must FAIL naming the PROPERTY (a build that emits them under `ont:` +//! or drops the path from the message fails those). + +use std::path::{Path, PathBuf}; +use std::process::Command; + +fn pv_bin() -> PathBuf { + PathBuf::from(env!("CARGO_BIN_EXE_pv")) +} + +struct Run { + code: i32, + stdout: String, + stderr: String, +} + +fn pv_in(cwd: &Path, args: &[&str]) -> Run { + let out = Command::new(pv_bin()) + .current_dir(cwd) + .args(args) + .output() + .expect("failed to spawn pv"); + Run { + code: out.status.code().unwrap_or(-1), + stdout: String::from_utf8_lossy(&out.stdout).into_owned(), + stderr: String::from_utf8_lossy(&out.stderr).into_owned(), + } +} + +fn show(r: &Run) -> String { + format!( + "exit {}\n--- stdout\n{}\n--- stderr\n{}", + r.code, r.stdout, r.stderr + ) +} + +fn fixture(name: &str) -> PathBuf { + Path::new(env!("CARGO_MANIFEST_DIR")) + .join("../../tests/fixtures/ont") + .join(name) +} + +fn json_of(r: &Run) -> serde_json::Value { + serde_json::from_str(&r.stdout).unwrap_or_else(|e| panic!("stdout is JSON: {e}\n{}", show(r))) +} + +fn gate(name: &str, which: &str) -> Run { + pv_in( + &fixture(name), + &["lint", "contracts", "--gate", which, "--format", "json"], + ) +} + +fn messages(v: &serde_json::Value) -> Vec { + v["findings"] + .as_array() + .map(|a| { + a.iter() + .filter_map(|f| f["message"].as_str().map(ToString::to_string)) + .collect() + }) + .unwrap_or_default() +} + +#[test] +fn the_sigma_gate_accepts_the_contract_schemas_metadata_block_that_validate_requires() { + // The same file, both readers: this is the contradiction the ticket exists to end. + let dir = fixture("entity-props-ok"); + let sigma = gate("entity-props-ok", "sigma"); + assert_eq!( + sigma.code, + 0, + "the sigma gate reads Σ with metadata\n{}", + show(&sigma) + ); + let validate = pv_in(&dir, &["validate", "contracts/ontology.yaml"]); + assert_eq!( + validate.code, + 0, + "pv validate accepts the same file\n{}", + show(&validate) + ); +} + +#[test] +fn an_unclaimed_metadata_block_is_still_refused_by_name() { + // The anti-decoration rule is answered, not weakened: drop the `readers` entry and Σ is malformed again. + let scratch = tempfile::tempdir().expect("scratch"); + let src = fixture("entity-props-ok").join("contracts"); + let dst = scratch.path().join("contracts"); + std::fs::create_dir_all(&dst).expect("mkdir"); + for entry in std::fs::read_dir(&src).expect("read fixture").flatten() { + let name = entry.file_name(); + let text = std::fs::read_to_string(entry.path()).expect("read"); + let text = if name == "ontology.yaml" { + text.replace( + " metadata: schema/types.rs (the contract schema, not Σ)\n", + "", + ) + } else { + text + }; + std::fs::write(dst.join(name), text).expect("write"); + } + let r = pv_in( + scratch.path(), + &["lint", "contracts", "--gate", "sigma", "--format", "json"], + ); + assert_eq!(r.code, 3, "a Σ key nothing claims is exit 3\n{}", show(&r)); + assert!( + r.stderr.contains("metadata") && r.stderr.contains("no reader"), + "the refusal names the key\n{}", + show(&r) + ); +} + +#[test] +fn a_conforming_study_contract_passes_with_its_own_properties_as_focus_predicates() { + let r = gate("entity-props-ok", "shapes"); + assert_eq!(r.code, 0, "{}", show(&r)); + let v = json_of(&r); + assert_eq!(v["verdict"], "Pass", "{}", show(&r)); + assert_eq!(v["focus_nodes_n"], 1, "{}", show(&r)); + assert_eq!(v["violations"], 0, "{}", show(&r)); +} + +#[test] +fn a_value_outside_the_in_list_fails_naming_the_property_and_the_value() { + // apex EV-21 FALSIFY-ONT-021. + let r = gate("entity-props-bad-value", "shapes"); + assert_eq!(r.code, 1, "{}", show(&r)); + let v = json_of(&r); + assert_eq!(v["verdict"], "Fail", "{}", show(&r)); + let msgs = messages(&v); + assert!( + msgs.iter().any(|m| m.contains("ont:study/scale") + && m.contains("quadratic") + && m.contains("is not one of")), + "the violation names the property and the value: {msgs:?}" + ); +} + +#[test] +fn a_property_the_shape_does_not_declare_fails_on_closed_naming_it() { + // apex EV-21 FALSIFY-ONT-021b. + let r = gate("entity-props-undeclared-key", "shapes"); + assert_eq!(r.code, 1, "{}", show(&r)); + let v = json_of(&r); + let msgs = messages(&v); + assert!( + msgs.iter() + .any(|m| m.contains("(closed)") && m.contains("ont:study/subgroup")), + "the violation names the undeclared property: {msgs:?}" + ); +} + +#[test] +fn the_properties_are_namespaced_by_the_entity_type_and_land_in_the_extraction() { + let dir = fixture("entity-props-ok"); + let r = pv_in(&dir, &["extract", "contracts"]); + assert_eq!(r.code, 0, "{}", show(&r)); + let nt = std::fs::read_to_string(dir.join("contracts/contracts.nt")).expect("contracts.nt"); + assert!(nt.contains("/study/scale> \"linear\""), "{nt}"); + assert!(nt.contains("/study/vintage> \"2026-09-12\""), "{nt}"); + // Never under ont:, which would let a shape over one entity type constrain another's `scale`. + assert!(!nt.contains("/v1alpha1/scale>"), "{nt}"); + let _ = std::fs::remove_file(dir.join("contracts/contracts.nt")); + let _ = std::fs::remove_file(dir.join("contracts/shapes.ttl")); +} diff --git a/crates/aprender-contracts/src/ontology/extract/pv_contract.rs b/crates/aprender-contracts/src/ontology/extract/pv_contract.rs index 72669c2888..46dca8f79d 100644 --- a/crates/aprender-contracts/src/ontology/extract/pv_contract.rs +++ b/crates/aprender-contracts/src/ontology/extract/pv_contract.rs @@ -71,12 +71,14 @@ pub fn extract_one(g: &mut Graph, stem: &str, file: &str, doc: &serde_yaml::Valu g.insert(s.clone(), ont("evidenceLevel"), Term::string(level)); } if let Some(entity) = doc.get("entity") { - if let Some(t) = scalar(entity.get("type")) { + let entity_type = scalar(entity.get("type")); + if let Some(t) = entity_type.clone() { g.insert(s.clone(), ont("entityType"), Term::string(t)); } if let Some(r) = scalar(entity.get("ref")) { g.insert(s.clone(), ont("entityRef"), Term::string(r)); } + emit_entity_properties(g, &s, entity_type.as_deref(), entity); } if let Some(rel) = doc.get("relations").and_then(serde_yaml::Value::as_mapping) { for (role, targets) in rel { @@ -93,6 +95,46 @@ pub fn extract_one(g: &mut Graph, stem: &str, file: &str, doc: &serde_yaml::Valu } } +/// §4.2 `entity: {type, ref, properties}` — the entity's OWN properties, as `:` on the contract node. +/// +/// A contract may carry the properties of the thing it is a contract FOR, and for a pre-registration the +/// contract IS the thing: apex's nine researcher degrees of freedom live at `entity.properties` because +/// APEX-001 EV-6 hashes the contract, and moving them to a sidecar document would change what is locked. +/// Before this they were read by nothing, so a `shape:` over them had no predicate to constrain and every +/// `minCount: 1` fired for the wrong reason — a shape failing because the extractor was silent, which is the +/// vacuity R-2 exists to end (measured on apex, 2026-09-19). +/// +/// The predicate is namespaced by the ENTITY TYPE (`study:scale`, not `ont:scale`): two entity types may both +/// carry a `scale`, and one predicate for both would let a shape over one constrain the other. A contract with +/// no `entity.type` gets no property triples — there is no namespace to put them in, and inventing one would +/// be an inference. Nested mappings and sequences are not emitted at v1alpha1: the subset has no path +/// expressions, so a predicate no shape could reach is decoration. +fn emit_entity_properties( + g: &mut Graph, + subject: &str, + entity_type: Option<&str>, + entity: &serde_yaml::Value, +) { + let (Some(ty), Some(props)) = ( + entity_type, + entity + .get("properties") + .and_then(serde_yaml::Value::as_mapping), + ) else { + return; + }; + for (key, value) in props { + let (Some(key), Some(v)) = (key.as_str(), scalar(Some(value))) else { + continue; + }; + g.insert( + subject.to_string(), + crate::ontology::shapes::expand(&format!("{ty}:{key}")), + Term::string(v), + ); + } +} + /// A YAML scalar as a string: strings as they are, numbers and booleans by their YAML spelling. Mappings and /// sequences are not scalars and produce no triple. fn scalar(v: Option<&serde_yaml::Value>) -> Option { @@ -134,6 +176,73 @@ mod tests { assert!(!g.to_ntriples().contains("_:")); } + #[test] + fn an_entitys_own_properties_become_typed_predicates_on_the_contract_node() { + // apex EV-21: the nine degrees of freedom live in the contract, because the contract IS the + // pre-registration. `study:scale` &c. so a shape can constrain them by path. + let doc: serde_yaml::Value = serde_yaml::from_str( + "entity:\n type: study\n ref: study-shape-v1\n properties:\n scale: linear\n vintage: '2026-09-12'\n bins: 7\n logged: true\n nested: {a: 1}\n listed: [a, b]\n", + ) + .expect("yaml"); + let mut g = Graph::new(); + extract_one( + &mut g, + "study-shape-v1", + "contracts/study-shape-v1.yaml", + &doc, + ); + let s = iri("contract", "study-shape-v1"); + let p = |k: &str| crate::ontology::shapes::expand(&format!("study:{k}")); + assert_eq!( + g.objects(&s, &p("scale"))[0] + .as_literal() + .expect("literal") + .0, + "linear" + ); + assert_eq!( + g.objects(&s, &p("vintage"))[0] + .as_literal() + .expect("literal") + .0, + "2026-09-12" + ); + assert_eq!( + g.objects(&s, &p("bins"))[0] + .as_literal() + .expect("literal") + .0, + "7" + ); + assert_eq!( + g.objects(&s, &p("logged"))[0] + .as_literal() + .expect("literal") + .0, + "true" + ); + // Not emitted at v1alpha1: the subset has no path expressions, so these would be unreachable. + assert!(g.objects(&s, &p("nested")).is_empty()); + assert!(g.objects(&s, &p("listed")).is_empty()); + // The namespace is the ENTITY TYPE, never ont: — two types may both carry `scale`. + assert!(g.objects(&s, &ont("scale")).is_empty()); + } + + #[test] + fn properties_without_an_entity_type_are_not_guessed_into_a_namespace() { + let doc: serde_yaml::Value = + serde_yaml::from_str("entity:\n ref: x\n properties:\n scale: linear\n") + .expect("yaml"); + let mut g = Graph::new(); + extract_one(&mut g, "a", "contracts/a.yaml", &doc); + let s = iri("contract", "a"); + assert!( + g.predicates_of(&s).iter().all(|p| !p.ends_with("scale")), + "{:?}", + g.to_ntriples() + ); + } + #[test] fn two_extractions_of_a_fixture_corpus_are_byte_identical() { let dir = std::path::Path::new(env!("CARGO_MANIFEST_DIR")) diff --git a/crates/aprender-contracts/src/ontology/shapes.rs b/crates/aprender-contracts/src/ontology/shapes.rs index 5fe6432513..a4c8ea5e39 100644 --- a/crates/aprender-contracts/src/ontology/shapes.rs +++ b/crates/aprender-contracts/src/ontology/shapes.rs @@ -578,11 +578,14 @@ fn check_lexical( if let Some(allowed) = &p.r#in { let ok = allowed.iter().any(|a| a == lexical || expand(a) == lexical); if !ok { + // The PATH is in the message, not only in the result's `path` field: a reader who gets one line + // ("quadratic is not one of …") cannot act on it without being told which property said it, and a + // shape with nine properties produces nine indistinguishable lines (measured on apex's EV-21). push( p.severity, path, "in", - format!("{lexical} is not one of {allowed:?}"), + format!("{}: {lexical} is not one of {allowed:?}", short(&p.path)), ); } } @@ -592,7 +595,7 @@ fn check_lexical( p.severity, path, "pattern", - format!("{lexical:?} does not match /{src}/"), + format!("{}: {lexical:?} does not match /{src}/", short(&p.path)), ); } } @@ -602,7 +605,7 @@ fn check_lexical( p.severity, path, "minLength", - format!("length {len} is below minLength"), + format!("{}: length {len} is below minLength", short(&p.path)), ); } if p.max_length.is_some_and(|m| len > m) { @@ -610,7 +613,7 @@ fn check_lexical( p.severity, path, "maxLength", - format!("length {len} is above maxLength"), + format!("{}: length {len} is above maxLength", short(&p.path)), ); } } diff --git a/crates/aprender-contracts/src/ontology/sigma.rs b/crates/aprender-contracts/src/ontology/sigma.rs index 0863db97bd..49cc5123aa 100644 --- a/crates/aprender-contracts/src/ontology/sigma.rs +++ b/crates/aprender-contracts/src/ontology/sigma.rs @@ -45,6 +45,19 @@ pub struct Sigma { /// Which reader claims each Σ key. The anti-decoration rule: a key nobody reads is refused (exit 3). #[serde(default)] pub readers: BTreeMap, + /// The CONTRACT schema's own block, carried here because Σ is a contract too. + /// + /// `pv validate` REQUIRES `metadata.{version, description}` on a contract, and this file is a contract — + /// so a corpus that keeps ONE `contracts/ontology.yaml` for both readers had `pv validate` at rc 0 and + /// `pv lint --gate sigma` at exit 3 over the same bytes (measured on apex 2026-09-19, `pv 0.68.1`: + /// ``unknown field `metadata` … at line 26``). One binary contradicting itself about one key is not a + /// corpus defect, so the schema admits the block. + /// + /// Σ does not INTERPRET it: the value is opaque YAML, no Σ rule reads a field of it, and `readers` must + /// still claim the key like every other — the anti-decoration rule is not weakened, it is answered + /// ("the contract schema reads it, not Σ"). + #[serde(default)] + pub metadata: Option, } #[derive(Debug, Clone, Deserialize, PartialEq, Eq)] @@ -174,7 +187,7 @@ impl fmt::Display for SigmaError { impl std::error::Error for SigmaError {} /// The Σ keys that must be claimed by a reader when they are present and non-empty. -pub const READABLE_KEYS: [&str; 8] = [ +pub const READABLE_KEYS: [&str; 9] = [ "concepts", "roles", "symbols", @@ -183,6 +196,8 @@ pub const READABLE_KEYS: [&str; 8] = [ "entity_types", "extractors", "not_expressible", + // The contract schema owns this one; Σ only carries it (see `Sigma::metadata`). + "metadata", ]; impl Sigma { @@ -289,6 +304,9 @@ impl Sigma { if !self.not_expressible.is_empty() { keys.insert("not_expressible"); } + if self.metadata.is_some() { + keys.insert("metadata"); + } keys } @@ -353,6 +371,39 @@ readers: assert!(s.check_integrity().is_ok(), "{:?}", s.check_integrity()); } + /// Σ is a CONTRACT, and `pv validate` requires `metadata.{version, description}` on a contract. Before this + /// the Σ schema refused the block outright, so one file could not satisfy both readers: apex measured + /// `pv validate` rc 0 and `pv lint --gate sigma` exit 3 over the same `contracts/ontology.yaml` + /// (2026-09-19, pv 0.68.1). The block parses, is opaque to every Σ rule, and still has to be claimed. + #[test] + fn sigma_carries_the_contract_schemas_own_metadata_block_and_reads_nothing_of_it() { + let y = good().replace( + "readers:\n", + "metadata:\n version: 1.0.0\n description: \"apex's entity-type registry\"\n references:\n - 'APEX-001 §5 row EV-19'\nreaders:\n metadata: schema/types.rs (the contract schema, not Σ)\n", + ); + let s = Sigma::from_yaml(&y).expect("Σ with metadata parses"); + assert!(s.metadata.is_some()); + assert!(s.check_integrity().is_ok(), "{:?}", s.check_integrity()); + // Opaque: no Σ rule reads a field of it, and the rest of Σ is unchanged by its presence. + let without = Sigma::from_yaml(good()).expect("Σ parses"); + assert_eq!(s.concepts, without.concepts); + assert_eq!(s.entity_types, without.entity_types); + } + + /// …and the anti-decoration rule is answered, not weakened: an unclaimed `metadata:` is still refused. + #[test] + fn an_unclaimed_metadata_block_is_refused_like_every_other_key() { + let y = good().replace( + "readers:\n", + "metadata:\n version: 1.0.0\n description: d\nreaders:\n", + ); + let s = Sigma::from_yaml(&y).expect("Σ parses"); + match s.check_integrity() { + Err(SigmaError::KeyWithoutReader { key }) => assert_eq!(key, "metadata"), + other => panic!("an unclaimed metadata: must be refused, got {other:?}"), + } + } + #[test] fn an_entity_type_naming_no_extractor_is_malformed() { let y = good().replace( diff --git a/docs/roadmaps/entries/PMAT-3529.yaml b/docs/roadmaps/entries/PMAT-3529.yaml new file mode 100644 index 0000000000..ffece33282 --- /dev/null +++ b/docs/roadmaps/entries/PMAT-3529.yaml @@ -0,0 +1,19 @@ +- id: PMAT-3529 + github_issue: 3529 + item_type: task + title: 'pv contradicts itself about metadata: (validate requires it, the sigma gate refuses it) and entity.properties is read by nothing — Σ accepts an opaque metadata block, extract:pv-contract emits entity.properties. as :, with apex''s two EV-21 falsifiers as fixtures' + status: planned + priority: medium + assigned_to: null + created: 2026-09-19T12:20:38Z + updated: 2026-09-19T12:20:38Z + spec: null + acceptance_criteria: [] + phases: [] + subtasks: [] + estimated_effort: null + labels: + - kind:code + - orch:fable + - orch-basis:state + notes: null diff --git a/docs/roadmaps/roadmap.yaml b/docs/roadmaps/roadmap.yaml index 0cfce5a915..8a76207166 100644 --- a/docs/roadmaps/roadmap.yaml +++ b/docs/roadmaps/roadmap.yaml @@ -18596,3 +18596,22 @@ roadmap: estimated_effort: null labels: [] notes: null +- id: PMAT-3529 + github_issue: 3529 + item_type: task + title: 'pv contradicts itself about metadata: (validate requires it, the sigma gate refuses it) and entity.properties is read by nothing — Σ accepts an opaque metadata block, extract:pv-contract emits entity.properties. as :, with apex''s two EV-21 falsifiers as fixtures' + status: planned + priority: medium + assigned_to: null + created: 2026-09-19T12:20:38Z + updated: 2026-09-19T12:20:38Z + spec: null + acceptance_criteria: [] + phases: [] + subtasks: [] + estimated_effort: null + labels: + - kind:code + - orch:fable + - orch-basis:state + notes: null diff --git a/scripts/tree_reader_tests.txt b/scripts/tree_reader_tests.txt index 99c7b42e98..9aa265d068 100644 --- a/scripts/tree_reader_tests.txt +++ b/scripts/tree_reader_tests.txt @@ -64,6 +64,7 @@ aprender-contracts-cli --test ont4_relations_gate aprender-contracts-cli --test ont4b_shapes_gate aprender-contracts-cli --test ont6_lint_verdict aprender-contracts-cli --test ont_extract_json +aprender-contracts-cli --test ont_entity_properties aprender-contracts-cli --test pv_surface_gate aprender-contracts-cli --test pvl_zero_contracts aprender-core --lib format::metadata_bounds_contract_falsify diff --git a/tests/fixtures/ont/entity-props-bad-value/contracts/lint-baseline.json b/tests/fixtures/ont/entity-props-bad-value/contracts/lint-baseline.json new file mode 100644 index 0000000000..f8a66767c5 --- /dev/null +++ b/tests/fixtures/ont/entity-props-bad-value/contracts/lint-baseline.json @@ -0,0 +1,5 @@ +{ + "armed_gates": ["validate"], + "armed_shapes": ["study-shape-v1"], + "ont": {} +} diff --git a/tests/fixtures/ont/entity-props-bad-value/contracts/ontology.yaml b/tests/fixtures/ont/entity-props-bad-value/contracts/ontology.yaml new file mode 100644 index 0000000000..cc88a2273e --- /dev/null +++ b/tests/fixtures/ont/entity-props-bad-value/contracts/ontology.yaml @@ -0,0 +1,21 @@ +schema: ont-sigma-v1 +metadata: + version: "1.0.0" + kind: pattern + created: '2026-09-19' + last_modified: '2026-09-19' + author: fixture + description: "the contract schema's own block, carried by Σ and read by neither Σ rule" + references: + - 'paiml/infra docs/specifications/paiml-ontology.md §4.1 (Σ) — and pv validate, which requires this block' +concepts: + Contract: {doc: a pv contract file} +entity_types: + - {name: study, extractor: pv_contract, implemented: true} +extractors: + - {name: pv_contract, reader: ontology/extract/pv_contract.rs, implemented: true} +readers: + concepts: ontology/sigma.rs + entity_types: lint/sigma_gate.rs + extractors: ontology/sigma.rs + metadata: schema/types.rs (the contract schema, not Σ) diff --git a/tests/fixtures/ont/entity-props-bad-value/contracts/study-shape-v1.yaml b/tests/fixtures/ont/entity-props-bad-value/contracts/study-shape-v1.yaml new file mode 100644 index 0000000000..4aea763937 --- /dev/null +++ b/tests/fixtures/ont/entity-props-bad-value/contracts/study-shape-v1.yaml @@ -0,0 +1,27 @@ +# apex EV-21 (FALSIFY-ONT-021/021b): the researcher degrees of freedom live in the contract, because the +# contract IS the pre-registration. The shape targets ont:Contract and constrains them by path. +name: study-shape +version: "1.0.0" +status: active +metadata: + version: "1.0.0" + kind: pattern + created: '2026-09-19' + last_modified: '2026-09-19' + author: fixture + description: a pre-registered study's closed degrees of freedom +entity: + type: study + ref: study-shape-v1 + properties: + scale: quadratic + denominator: population-100k + vintage: '2026-09-12' +shape: + targetClass: ont:Contract + closed: true + ignoredProperties: [ont:id, ont:file, ont:name, ont:version, ont:status, ont:kind, ont:entityType, ont:entityRef] + properties: + - {path: study:scale, minCount: 1, maxCount: 1, in: [linear, log10, log2, sqrt, logit]} + - {path: study:denominator, minCount: 1, maxCount: 1, in: [population-100k, count]} + - {path: study:vintage, minCount: 1, maxCount: 1, pattern: "^[0-9]{4}-[0-9]{2}-[0-9]{2}$"} diff --git a/tests/fixtures/ont/entity-props-ok/contracts/lint-baseline.json b/tests/fixtures/ont/entity-props-ok/contracts/lint-baseline.json new file mode 100644 index 0000000000..f8a66767c5 --- /dev/null +++ b/tests/fixtures/ont/entity-props-ok/contracts/lint-baseline.json @@ -0,0 +1,5 @@ +{ + "armed_gates": ["validate"], + "armed_shapes": ["study-shape-v1"], + "ont": {} +} diff --git a/tests/fixtures/ont/entity-props-ok/contracts/ontology.yaml b/tests/fixtures/ont/entity-props-ok/contracts/ontology.yaml new file mode 100644 index 0000000000..cc88a2273e --- /dev/null +++ b/tests/fixtures/ont/entity-props-ok/contracts/ontology.yaml @@ -0,0 +1,21 @@ +schema: ont-sigma-v1 +metadata: + version: "1.0.0" + kind: pattern + created: '2026-09-19' + last_modified: '2026-09-19' + author: fixture + description: "the contract schema's own block, carried by Σ and read by neither Σ rule" + references: + - 'paiml/infra docs/specifications/paiml-ontology.md §4.1 (Σ) — and pv validate, which requires this block' +concepts: + Contract: {doc: a pv contract file} +entity_types: + - {name: study, extractor: pv_contract, implemented: true} +extractors: + - {name: pv_contract, reader: ontology/extract/pv_contract.rs, implemented: true} +readers: + concepts: ontology/sigma.rs + entity_types: lint/sigma_gate.rs + extractors: ontology/sigma.rs + metadata: schema/types.rs (the contract schema, not Σ) diff --git a/tests/fixtures/ont/entity-props-ok/contracts/study-shape-v1.yaml b/tests/fixtures/ont/entity-props-ok/contracts/study-shape-v1.yaml new file mode 100644 index 0000000000..d881c41afe --- /dev/null +++ b/tests/fixtures/ont/entity-props-ok/contracts/study-shape-v1.yaml @@ -0,0 +1,27 @@ +# apex EV-21 (FALSIFY-ONT-021/021b): the researcher degrees of freedom live in the contract, because the +# contract IS the pre-registration. The shape targets ont:Contract and constrains them by path. +name: study-shape +version: "1.0.0" +status: active +metadata: + version: "1.0.0" + kind: pattern + created: '2026-09-19' + last_modified: '2026-09-19' + author: fixture + description: a pre-registered study's closed degrees of freedom +entity: + type: study + ref: study-shape-v1 + properties: + scale: linear + denominator: population-100k + vintage: '2026-09-12' +shape: + targetClass: ont:Contract + closed: true + ignoredProperties: [ont:id, ont:file, ont:name, ont:version, ont:status, ont:kind, ont:entityType, ont:entityRef] + properties: + - {path: study:scale, minCount: 1, maxCount: 1, in: [linear, log10, log2, sqrt, logit]} + - {path: study:denominator, minCount: 1, maxCount: 1, in: [population-100k, count]} + - {path: study:vintage, minCount: 1, maxCount: 1, pattern: "^[0-9]{4}-[0-9]{2}-[0-9]{2}$"} diff --git a/tests/fixtures/ont/entity-props-undeclared-key/contracts/lint-baseline.json b/tests/fixtures/ont/entity-props-undeclared-key/contracts/lint-baseline.json new file mode 100644 index 0000000000..f8a66767c5 --- /dev/null +++ b/tests/fixtures/ont/entity-props-undeclared-key/contracts/lint-baseline.json @@ -0,0 +1,5 @@ +{ + "armed_gates": ["validate"], + "armed_shapes": ["study-shape-v1"], + "ont": {} +} diff --git a/tests/fixtures/ont/entity-props-undeclared-key/contracts/ontology.yaml b/tests/fixtures/ont/entity-props-undeclared-key/contracts/ontology.yaml new file mode 100644 index 0000000000..cc88a2273e --- /dev/null +++ b/tests/fixtures/ont/entity-props-undeclared-key/contracts/ontology.yaml @@ -0,0 +1,21 @@ +schema: ont-sigma-v1 +metadata: + version: "1.0.0" + kind: pattern + created: '2026-09-19' + last_modified: '2026-09-19' + author: fixture + description: "the contract schema's own block, carried by Σ and read by neither Σ rule" + references: + - 'paiml/infra docs/specifications/paiml-ontology.md §4.1 (Σ) — and pv validate, which requires this block' +concepts: + Contract: {doc: a pv contract file} +entity_types: + - {name: study, extractor: pv_contract, implemented: true} +extractors: + - {name: pv_contract, reader: ontology/extract/pv_contract.rs, implemented: true} +readers: + concepts: ontology/sigma.rs + entity_types: lint/sigma_gate.rs + extractors: ontology/sigma.rs + metadata: schema/types.rs (the contract schema, not Σ) diff --git a/tests/fixtures/ont/entity-props-undeclared-key/contracts/study-shape-v1.yaml b/tests/fixtures/ont/entity-props-undeclared-key/contracts/study-shape-v1.yaml new file mode 100644 index 0000000000..6ec9faa22e --- /dev/null +++ b/tests/fixtures/ont/entity-props-undeclared-key/contracts/study-shape-v1.yaml @@ -0,0 +1,28 @@ +# apex EV-21 (FALSIFY-ONT-021/021b): the researcher degrees of freedom live in the contract, because the +# contract IS the pre-registration. The shape targets ont:Contract and constrains them by path. +name: study-shape +version: "1.0.0" +status: active +metadata: + version: "1.0.0" + kind: pattern + created: '2026-09-19' + last_modified: '2026-09-19' + author: fixture + description: a pre-registered study's closed degrees of freedom +entity: + type: study + ref: study-shape-v1 + properties: + scale: linear + denominator: population-100k + vintage: '2026-09-12' + subgroup: none +shape: + targetClass: ont:Contract + closed: true + ignoredProperties: [ont:id, ont:file, ont:name, ont:version, ont:status, ont:kind, ont:entityType, ont:entityRef] + properties: + - {path: study:scale, minCount: 1, maxCount: 1, in: [linear, log10, log2, sqrt, logit]} + - {path: study:denominator, minCount: 1, maxCount: 1, in: [population-100k, count]} + - {path: study:vintage, minCount: 1, maxCount: 1, pattern: "^[0-9]{4}-[0-9]{2}-[0-9]{2}$"} From d78612aa5620d517151b0a42256f836f39129e3e Mon Sep 17 00:00:00 2001 From: Noah Gift Date: Sat, 19 Sep 2026 14:37:15 +0200 Subject: [PATCH 2/6] PMAT-3529: the ticket names the message change too, and the acceptance criteria are written from the diff (Refs PMAT-3529) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Quorum round 1 was NOT AGREED: lane 1 (gemini-3.1-pro-high) raised two findings and one of them is right. RIGHT, and fixed here: the diff changed the shape gate's violation messages (the path now travels IN the message for in/pattern/minLength/maxLength) and the ticket did not say so, so the lane read a change nobody asked for. It is not a drive-by: without the path in the message apex's two EV-21 falsifiers cannot "FAIL naming study:", which is the whole point of the row, and a shape with nine properties emits nine lines a reader cannot tell apart. The ticket title now names it and the acceptance criteria say what each part must do — written from the diff, as they should have been before the first round. WRONG, and measured: the lane asserted (grounding "asserted", not "measured") that the CLI legs assert `ont:study/scale` while the extractor emits `study:scale`, "which will cause the test to fail". Both are the same IRI. `expand("study:scale")` is ONT_BASE + "study/scale" and `short()` renders any ONT_BASE IRI with the `ont:` prefix, so the message reads `ont:study/scale` — the repo-wide convention (`ont:model/parityReceipt`, `ont:tool/surprise`). The legs pass: `cargo test -p aprender-contracts-cli --test ont_entity_properties` 6/6, and the message on the fixture is, verbatim: ont:contract/study-shape-v1 violates shape `study-shape-v1` (in): ont:study/scale: quadratic is not one of ["linear", "log10", "log2", "sqrt", "logit"] No code change for that one; a claim that a green test will fail is refuted by running it. Pmat-Ticket: PMAT-3529 Co-Authored-By: Claude Opus 5 (1M context) --- docs/roadmaps/entries/PMAT-3529.yaml | 11 ++++++++--- docs/roadmaps/roadmap.yaml | 11 ++++++++--- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/docs/roadmaps/entries/PMAT-3529.yaml b/docs/roadmaps/entries/PMAT-3529.yaml index ffece33282..519508c5d6 100644 --- a/docs/roadmaps/entries/PMAT-3529.yaml +++ b/docs/roadmaps/entries/PMAT-3529.yaml @@ -1,14 +1,19 @@ - id: PMAT-3529 github_issue: 3529 item_type: task - title: 'pv contradicts itself about metadata: (validate requires it, the sigma gate refuses it) and entity.properties is read by nothing — Σ accepts an opaque metadata block, extract:pv-contract emits entity.properties. as :, with apex''s two EV-21 falsifiers as fixtures' + title: 'pv contradicts itself about metadata: (validate requires it, the sigma gate refuses it); entity.properties is read by nothing; and a lexical violation does not name its path — Σ accepts an opaque metadata block, extract:pv-contract emits entity.properties. as :, and in/pattern/minLength/maxLength name the property they fired on, with apex''s two EV-21 falsifiers as fixtures' status: planned priority: medium assigned_to: null created: 2026-09-19T12:20:38Z - updated: 2026-09-19T12:20:38Z + updated: 2026-09-19T12:36:52Z spec: null - acceptance_criteria: [] + acceptance_criteria: + - 'The SAME contracts/ontology.yaml passes `pv validate` AND `pv lint --gate sigma`: Σ carries `metadata` as opaque YAML (no Σ rule reads a field of it), it joins READABLE_KEYS so `readers:` must claim it, and removing that claim brings exit 3 back naming the key — measured by a CLI leg that runs both subcommands on one fixture.' + - 'entity.properties. becomes : on the contract node, so a shape over the entity''s own properties has focus predicates: apex''s EV-21 falsifiers through the CLI — entity-props-ok Pass, entity-props-bad-value Fail (in) naming the property and the value, entity-props-undeclared-key Fail (closed) naming the undeclared property.' + - 'The namespace is the entity TYPE, never ont:; no entity.type means no property triples; nested mappings and sequences are not emitted at v1alpha1 — each rule has its own unit test.' + - 'A lexical violation names its PATH in the message (in, pattern, minLength, maxLength), not only in the result''s path field. Without it the two falsifiers above cannot name the property at all, and a shape with nine properties emits nine lines a reader cannot tell apart.' + - 'No regression on the real corpus: pv lint contracts --gate sigma Pass, --gate shapes Pass over 1731 focus nodes, pv extract contracts --check 0, aprender-contracts lib green, ont2b_sigma_gate / ont4_relations_gate / ont4b_shapes_gate green, clippy -D warnings clean.' phases: [] subtasks: [] estimated_effort: null diff --git a/docs/roadmaps/roadmap.yaml b/docs/roadmaps/roadmap.yaml index 8a76207166..1a5c7f7967 100644 --- a/docs/roadmaps/roadmap.yaml +++ b/docs/roadmaps/roadmap.yaml @@ -18599,14 +18599,19 @@ roadmap: - id: PMAT-3529 github_issue: 3529 item_type: task - title: 'pv contradicts itself about metadata: (validate requires it, the sigma gate refuses it) and entity.properties is read by nothing — Σ accepts an opaque metadata block, extract:pv-contract emits entity.properties. as :, with apex''s two EV-21 falsifiers as fixtures' + title: 'pv contradicts itself about metadata: (validate requires it, the sigma gate refuses it); entity.properties is read by nothing; and a lexical violation does not name its path — Σ accepts an opaque metadata block, extract:pv-contract emits entity.properties. as :, and in/pattern/minLength/maxLength name the property they fired on, with apex''s two EV-21 falsifiers as fixtures' status: planned priority: medium assigned_to: null created: 2026-09-19T12:20:38Z - updated: 2026-09-19T12:20:38Z + updated: 2026-09-19T12:36:52Z spec: null - acceptance_criteria: [] + acceptance_criteria: + - 'The SAME contracts/ontology.yaml passes `pv validate` AND `pv lint --gate sigma`: Σ carries `metadata` as opaque YAML (no Σ rule reads a field of it), it joins READABLE_KEYS so `readers:` must claim it, and removing that claim brings exit 3 back naming the key — measured by a CLI leg that runs both subcommands on one fixture.' + - 'entity.properties. becomes : on the contract node, so a shape over the entity''s own properties has focus predicates: apex''s EV-21 falsifiers through the CLI — entity-props-ok Pass, entity-props-bad-value Fail (in) naming the property and the value, entity-props-undeclared-key Fail (closed) naming the undeclared property.' + - 'The namespace is the entity TYPE, never ont:; no entity.type means no property triples; nested mappings and sequences are not emitted at v1alpha1 — each rule has its own unit test.' + - 'A lexical violation names its PATH in the message (in, pattern, minLength, maxLength), not only in the result''s path field. Without it the two falsifiers above cannot name the property at all, and a shape with nine properties emits nine lines a reader cannot tell apart.' + - 'No regression on the real corpus: pv lint contracts --gate sigma Pass, --gate shapes Pass over 1731 focus nodes, pv extract contracts --check 0, aprender-contracts lib green, ont2b_sigma_gate / ont4_relations_gate / ont4b_shapes_gate green, clippy -D warnings clean.' phases: [] subtasks: [] estimated_effort: null From 8c7feee0dcb05d4b020ec0ef9635243d8d7bd895 Mon Sep 17 00:00:00 2001 From: Noah Gift Date: Sat, 19 Sep 2026 14:42:14 +0200 Subject: [PATCH 3/6] =?UTF-8?q?PMAT-3529:=20the=20acceptance=20criterion?= =?UTF-8?q?=20says=20which=20IRI,=20not=20which=20prefix=20=E2=80=94=20the?= =?UTF-8?q?=20sentence=20the=20lane=20read=20twice=20was=20mine=20(Refs=20?= =?UTF-8?q?PMAT-3529)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Round 2 was NOT AGREED and lane 1 (gemini-3.1-pro-high) raised the same objection as round 1, this time grounded in my own acceptance criterion: AC3 said "the namespace is the entity TYPE, never ont:", the CLI legs assert the message contains `ont:study/scale`, and the lane concluded that either the code violates the AC or the test must fail. The code is right and the sentence was wrong. Two different things were being called "namespace": - the PREDICATE IRI, which is ONT_BASE + "study/scale" — what `study:scale` expands to — and never ONT_BASE + "scale", so a shape over one entity type cannot constrain another type's `scale`. That is the rule, and the unit test asserts both directions. - the RENDERING, which is `short()`'s job and repo-wide: every ONT_BASE IRI prints with the `ont:` prefix, so the message reads `ont:study/scale` exactly as the model receipts already read `ont:model/parityReceipt` and the json row reads `ont:tool/surprise`. AC2 and AC3 now say the IRI in full and note that the rendering is separate and unchanged. No code change: a lane reading a criterion the way it is written is the criterion's defect, and changing `short()` to invert `expand()` would re-render every vocabulary predicate and focus node across the corpus and three other repos' expectations — a rendering change that large is its own row with its own case table, not a footnote to this one. Measured again at this head: ont_entity_properties 6/6, and the fixture's message verbatim — ont:contract/study-shape-v1 violates shape `study-shape-v1` (in): ont:study/scale: quadratic is not one of ["linear", "log10", "log2", "sqrt", "logit"] Pmat-Ticket: PMAT-3529 Co-Authored-By: Claude Opus 5 (1M context) --- docs/roadmaps/entries/PMAT-3529.yaml | 5 +++-- docs/roadmaps/roadmap.yaml | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/docs/roadmaps/entries/PMAT-3529.yaml b/docs/roadmaps/entries/PMAT-3529.yaml index 519508c5d6..0603569e65 100644 --- a/docs/roadmaps/entries/PMAT-3529.yaml +++ b/docs/roadmaps/entries/PMAT-3529.yaml @@ -10,8 +10,9 @@ spec: null acceptance_criteria: - 'The SAME contracts/ontology.yaml passes `pv validate` AND `pv lint --gate sigma`: Σ carries `metadata` as opaque YAML (no Σ rule reads a field of it), it joins READABLE_KEYS so `readers:` must claim it, and removing that claim brings exit 3 back naming the key — measured by a CLI leg that runs both subcommands on one fixture.' - - 'entity.properties. becomes : on the contract node, so a shape over the entity''s own properties has focus predicates: apex''s EV-21 falsifiers through the CLI — entity-props-ok Pass, entity-props-bad-value Fail (in) naming the property and the value, entity-props-undeclared-key Fail (closed) naming the undeclared property.' - - 'The namespace is the entity TYPE, never ont:; no entity.type means no property triples; nested mappings and sequences are not emitted at v1alpha1 — each rule has its own unit test.' + - 'entity.properties. becomes the predicate : (IRI: ONT_BASE + /) on the contract node, so a shape over the entity''s own properties has focus predicates: apex''s EV-21 falsifiers through the CLI — entity-props-ok Pass, entity-props-bad-value Fail (in) naming the property and the value, entity-props-undeclared-key Fail (closed) naming the undeclared property.' + - 'The predicate IRI carries the entity type as its vocabulary segment: entity.properties.scale of an entity typed `study` is https://ont.paiml.dev/v1alpha1/study/scale — which is what `study:scale` expands to — and NEVER the bare https://ont.paiml.dev/v1alpha1/scale, so a shape over one entity type cannot constrain another type''s `scale`. Both directions asserted in a unit test. (Rendering is unchanged and separate: `short()` prints every ONT_BASE IRI with the `ont:` prefix, so messages read `ont:study/scale`, exactly as they already read `ont:model/parityReceipt`.)' + - 'No entity.type means no property triples — there is no vocabulary segment and inventing one would be an inference; nested mappings and sequences are not emitted at v1alpha1, because the subset has no path expressions and a predicate no shape can reach is decoration. One unit test each.' - 'A lexical violation names its PATH in the message (in, pattern, minLength, maxLength), not only in the result''s path field. Without it the two falsifiers above cannot name the property at all, and a shape with nine properties emits nine lines a reader cannot tell apart.' - 'No regression on the real corpus: pv lint contracts --gate sigma Pass, --gate shapes Pass over 1731 focus nodes, pv extract contracts --check 0, aprender-contracts lib green, ont2b_sigma_gate / ont4_relations_gate / ont4b_shapes_gate green, clippy -D warnings clean.' phases: [] diff --git a/docs/roadmaps/roadmap.yaml b/docs/roadmaps/roadmap.yaml index 1a5c7f7967..f7f98eb62a 100644 --- a/docs/roadmaps/roadmap.yaml +++ b/docs/roadmaps/roadmap.yaml @@ -18608,8 +18608,9 @@ roadmap: spec: null acceptance_criteria: - 'The SAME contracts/ontology.yaml passes `pv validate` AND `pv lint --gate sigma`: Σ carries `metadata` as opaque YAML (no Σ rule reads a field of it), it joins READABLE_KEYS so `readers:` must claim it, and removing that claim brings exit 3 back naming the key — measured by a CLI leg that runs both subcommands on one fixture.' - - 'entity.properties. becomes : on the contract node, so a shape over the entity''s own properties has focus predicates: apex''s EV-21 falsifiers through the CLI — entity-props-ok Pass, entity-props-bad-value Fail (in) naming the property and the value, entity-props-undeclared-key Fail (closed) naming the undeclared property.' - - 'The namespace is the entity TYPE, never ont:; no entity.type means no property triples; nested mappings and sequences are not emitted at v1alpha1 — each rule has its own unit test.' + - 'entity.properties. becomes the predicate : (IRI: ONT_BASE + /) on the contract node, so a shape over the entity''s own properties has focus predicates: apex''s EV-21 falsifiers through the CLI — entity-props-ok Pass, entity-props-bad-value Fail (in) naming the property and the value, entity-props-undeclared-key Fail (closed) naming the undeclared property.' + - 'The predicate IRI carries the entity type as its vocabulary segment: entity.properties.scale of an entity typed `study` is https://ont.paiml.dev/v1alpha1/study/scale — which is what `study:scale` expands to — and NEVER the bare https://ont.paiml.dev/v1alpha1/scale, so a shape over one entity type cannot constrain another type''s `scale`. Both directions asserted in a unit test. (Rendering is unchanged and separate: `short()` prints every ONT_BASE IRI with the `ont:` prefix, so messages read `ont:study/scale`, exactly as they already read `ont:model/parityReceipt`.)' + - 'No entity.type means no property triples — there is no vocabulary segment and inventing one would be an inference; nested mappings and sequences are not emitted at v1alpha1, because the subset has no path expressions and a predicate no shape can reach is decoration. One unit test each.' - 'A lexical violation names its PATH in the message (in, pattern, minLength, maxLength), not only in the result''s path field. Without it the two falsifiers above cannot name the property at all, and a shape with nine properties emits nine lines a reader cannot tell apart.' - 'No regression on the real corpus: pv lint contracts --gate sigma Pass, --gate shapes Pass over 1731 focus nodes, pv extract contracts --check 0, aprender-contracts lib green, ont2b_sigma_gate / ont4_relations_gate / ont4b_shapes_gate green, clippy -D warnings clean.' phases: [] From 681b1b971a794bd2e1f5dc5879a65cb13b4d5aef Mon Sep 17 00:00:00 2001 From: Noah Gift Date: Sat, 19 Sep 2026 14:48:32 +0200 Subject: [PATCH 4/6] PMAT-3529: spell the entity predicate's IRI in one function and pin it to the prefixed form with a test (Refs PMAT-3529) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Round 3: lane 1 PASS, lane 3 PASS, lane 2 FAIL — and lane 2's finding is the same misreading lane 1 made in round 2, from the other side: it claimed `expand("study:scale")` returns the literal `"study:scale"` because `study:` is "not a registered prefix", so the graph would hold `` and the tests would fail. Measurably false — §3.6's prefix rule is that any UNREGISTERED `p:name` maps into `p/name`, which is exactly the fall-through arm of `expand`, and the tests pass with the N-Triples holding `…/v1alpha1/study/scale>`. But two independent readers taking the same line for a literal IRI is not two mistakes, it is one ambiguity: the shape writes `path: study:scale`, the extractor wrote `expand(&format!("{ty}:{key}"))`, and a reader had to know `expand`'s arms to see they are the same predicate. So the code now says it. `entity_predicate(ty, key)` spells `/` directly, and a unit test asserts it equals `expand("study:scale")` AND equals the full IRI AND is not `ont("scale")` — the equivalence is pinned by a failing test rather than by knowledge of the prefix rule. No behaviour changes: same triples, same messages, ont_entity_properties 6/6, pv_contract unit tests 5/5, clippy clean. Pmat-Ticket: PMAT-3529 Co-Authored-By: Claude Opus 5 (1M context) --- .../src/ontology/extract/pv_contract.rs | 31 ++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/crates/aprender-contracts/src/ontology/extract/pv_contract.rs b/crates/aprender-contracts/src/ontology/extract/pv_contract.rs index 46dca8f79d..93f9d1e935 100644 --- a/crates/aprender-contracts/src/ontology/extract/pv_contract.rs +++ b/crates/aprender-contracts/src/ontology/extract/pv_contract.rs @@ -129,12 +129,24 @@ fn emit_entity_properties( }; g.insert( subject.to_string(), - crate::ontology::shapes::expand(&format!("{ty}:{key}")), + entity_predicate(ty, key), Term::string(v), ); } } +/// The predicate IRI for one entity property: `/`. +/// +/// Spelled here rather than left to `expand(":")` because the equality is the whole rule and two +/// independent readers took the prefixed form for a literal IRI (quorum PMAT-3529, rounds 2 and 3). It IS what +/// `expand` produces — §3.6's prefix rule maps any unregistered `p:name` into `p/name` — and +/// [`tests::the_predicate_is_the_expansion_of_the_prefixed_form`] pins the two together, so a change to either +/// side is a failing test and not a silent divergence. +#[must_use] +pub fn entity_predicate(entity_type: &str, key: &str) -> String { + format!("{}{entity_type}/{key}", crate::ontology::rdf::ONT_BASE) +} + /// A YAML scalar as a string: strings as they are, numbers and booleans by their YAML spelling. Mappings and /// sequences are not scalars and produce no triple. fn scalar(v: Option<&serde_yaml::Value>) -> Option { @@ -176,6 +188,23 @@ mod tests { assert!(!g.to_ntriples().contains("_:")); } + /// The prefixed form and the IRI are the SAME predicate — §3.6's rule maps any unregistered `p:name` into + /// `p/name`. Pinned here because a shape writes `path: study:scale` while the extractor writes the + /// IRI, and a reader who does not know the rule sees two different things (quorum PMAT-3529, rounds 2/3). + #[test] + fn the_predicate_is_the_expansion_of_the_prefixed_form() { + assert_eq!( + entity_predicate("study", "scale"), + crate::ontology::shapes::expand("study:scale") + ); + assert_eq!( + entity_predicate("study", "scale"), + "https://ont.paiml.dev/v1alpha1/study/scale" + ); + // …and never the bare ont: local, which another entity type's `scale` would share. + assert_ne!(entity_predicate("study", "scale"), ont("scale")); + } + #[test] fn an_entitys_own_properties_become_typed_predicates_on_the_contract_node() { // apex EV-21: the nine degrees of freedom live in the contract, because the contract IS the From e1b93b8c2bf42ea5fe3a800fed7ca3b943bf09da Mon Sep 17 00:00:00 2001 From: Noah Gift Date: Sat, 19 Sep 2026 14:57:06 +0200 Subject: [PATCH 5/6] audit: quorum-PMAT-3529 round 4 AGREED 3/3 (Refs PMAT-3529) Rounds 1-3 were NOT AGREED and each finding is answered in the commit that followed it: the message change now named by the ticket and its acceptance criteria (r1), the acceptance criterion saying which IRI rather than which prefix (r2), and the predicate spelled in one function with a test pinning it to the prefixed form (r3). Two of the four findings were false and were refuted by running the tests; both false ones pointed at the same ambiguity, which is now gone. Pmat-Ticket: PMAT-3529 Co-Authored-By: Claude Opus 5 (1M context) --- docs/audits/quorum-PMAT-3529.json | 171 ++++++++++++++++++++++++++++++ 1 file changed, 171 insertions(+) create mode 100644 docs/audits/quorum-PMAT-3529.json diff --git a/docs/audits/quorum-PMAT-3529.json b/docs/audits/quorum-PMAT-3529.json new file mode 100644 index 0000000000..05f4c9d87d --- /dev/null +++ b/docs/audits/quorum-PMAT-3529.json @@ -0,0 +1,171 @@ +{ + "ticket": "PMAT-3529", + "base": "origin/main", + "base_resolved": "origin/main", + "base_note": "no origin/origin/main exists; judged against the local ref", + "head": "0b24cf2ba825b305b5db2893c49ed4d80c41c8a9", + "diff_sha256": "6303cc69057879198c2f8833233e2b0f8e46a18da6ee38f38bac1d3f697eed0a", + "width": 3, + "executor": "agy", + "prompt_mode": "inline", + "prompt_bytes": 38096, + "author": { + "model": "claude-opus-5", + "family": "claude", + "source": "flag" + }, + "agreed": true, + "lanes": [ + { + "lane": 1, + "status": "SUCCESS", + "verdict": "PASS", + "summary": "The diff meets all acceptance criteria defined in PMAT-3529. The Sigma schema now carries an opaque metadata block that must be claimed in readers. The extractor emits entity properties using the entity type as the namespace for the predicate IRI, correctly avoiding emissions when no entity type is present or for nested objects. Lexical violations have been updated to prefix their output messages with the path. All requirements are backed by new tests matching the behavior prescribed in the ticket.", + "findings": [ + { + "claim": "The SAME contracts/ontology.yaml passes pv validate AND pv lint --gate sigma, and removing claim brings exit 3 back", + "file": "crates/aprender-contracts-cli/tests/ont_entity_properties.rs", + "fix": "", + "grounding": "measured", + "line": 119 + }, + { + "claim": "entity.properties. becomes the predicate : (IRI: ONT_BASE + /) on the contract node", + "file": "crates/aprender-contracts/src/ontology/extract/pv_contract.rs", + "fix": "", + "grounding": "cited", + "line": 146 + }, + { + "claim": "The predicate IRI carries the entity type as its vocabulary segment and NEVER the bare ont:scale", + "file": "crates/aprender-contracts/src/ontology/extract/pv_contract.rs", + "fix": "", + "grounding": "measured", + "line": 204 + }, + { + "claim": "No entity.type means no property triples, and nested mappings/sequences are not emitted", + "file": "crates/aprender-contracts/src/ontology/extract/pv_contract.rs", + "fix": "", + "grounding": "measured", + "line": 268 + }, + { + "claim": "A lexical violation names its PATH in the message (in, pattern, minLength, maxLength)", + "file": "crates/aprender-contracts/src/ontology/shapes.rs", + "fix": "", + "grounding": "cited", + "line": 585 + } + ], + "raw_bytes": 4792, + "err_bytes": 100, + "envelope_status": "SUCCESS", + "verdict_source": "structured_output", + "grounding_check": "parity", + "model": "gemini-3.1-pro-high", + "model_measured": "gemini-3.1-pro-high", + "model_source": "measured", + "family": "gemini" + }, + { + "lane": 2, + "status": "SUCCESS", + "verdict": "PASS", + "summary": "The diff successfully implements all requirements of PMAT-3529. Sigma now carries the metadata block as opaque YAML, requiring it to be claimed in `readers` to satisfy the anti-decoration rule. Entity properties are extracted as `:` scoped correctly to avoid leaking across types, while nested mappings and sequences are safely ignored. Lexical violations (in, pattern, minLength, maxLength) now correctly prepend the property path to their error messages for clarity. All tests and falsifiers from the ticket have been correctly verified.", + "findings": [], + "raw_bytes": 3762, + "err_bytes": 100, + "envelope_status": "SUCCESS", + "verdict_source": "structured_output", + "grounding_check": "parity", + "model": "gemini-3.1-pro-low", + "model_measured": "gemini-3.1-pro-low", + "model_source": "measured", + "family": "gemini" + }, + { + "lane": 3, + "status": "SUCCESS", + "verdict": "PASS", + "summary": "The diff strictly satisfies all acceptance criteria of PMAT-3529:\n1. `Sigma` in `crates/aprender-contracts/src/ontology/sigma.rs` accepts an opaque `metadata` block and includes `\"metadata\"` in `READABLE_KEYS`, enforcing that `readers` must claim it (preserving the anti-decoration rule).\n2. `emit_entity_properties` in `crates/aprender-contracts/src/ontology/extract/pv_contract.rs` extracts `entity.properties.` into `/` triples on the contract node.\n3. `check_lexical` in `crates/aprender-contracts/src/ontology/shapes.rs` formats `in`, `pattern`, `minLength`, and `maxLength` violation messages with `short(&p.path)`.\n4. Integration CLI tests, unit tests, explicit test commands, tree reader test entries, and fixtures are all correctly added and matched against the ticket specification without weakening any gates or adding forbidden changes.", + "findings": [], + "raw_bytes": 3153, + "err_bytes": 0, + "envelope_status": "SUCCESS", + "verdict_source": "structured_output", + "grounding_check": "parity", + "model": "gemini-3.6-flash-low", + "model_measured": "gemini-3.6-flash-low", + "model_source": "measured", + "family": "gemini" + } + ], + "dissent": [], + "dedup": [ + { + "file": "crates/aprender-contracts-cli/tests/ont_entity_properties.rs", + "line": 119, + "lanes_agreeing": [ + 1 + ], + "claims": [ + "The SAME contracts/ontology.yaml passes pv validate AND pv lint --gate sigma, and removing claim brings exit 3 back" + ] + }, + { + "file": "crates/aprender-contracts/src/ontology/extract/pv_contract.rs", + "line": 146, + "lanes_agreeing": [ + 1 + ], + "claims": [ + "entity.properties. becomes the predicate : (IRI: ONT_BASE + /) on the contract node" + ] + }, + { + "file": "crates/aprender-contracts/src/ontology/extract/pv_contract.rs", + "line": 204, + "lanes_agreeing": [ + 1 + ], + "claims": [ + "The predicate IRI carries the entity type as its vocabulary segment and NEVER the bare ont:scale" + ] + }, + { + "file": "crates/aprender-contracts/src/ontology/extract/pv_contract.rs", + "line": 268, + "lanes_agreeing": [ + 1 + ], + "claims": [ + "No entity.type means no property triples, and nested mappings/sequences are not emitted" + ] + }, + { + "file": "crates/aprender-contracts/src/ontology/shapes.rs", + "line": 585, + "lanes_agreeing": [ + 1 + ], + "claims": [ + "A lexical violation names its PATH in the message (in, pattern, minLength, maxLength)" + ] + } + ], + "uncovered": [], + "coverage_source": "lanes", + "partial": false, + "partial_reasons": [], + "auto_merge": { + "checked": true, + "was_armed": false, + "disarmed": false, + "note": "auto-merge not armed" + }, + "lint": { + "ok": true, + "output": "receipt complete: kind=artifact lanes=3 author=claude-opus-5/claude" + } +} From f69f7514f1b9c1133b2e73db4fefd4c3e6e887f3 Mon Sep 17 00:00:00 2001 From: Noah Gift Date: Sat, 19 Sep 2026 15:31:48 +0200 Subject: [PATCH 6/6] audit: quorum-PMAT-3529 round 5 AGREED 3/3 on the rebased head e1b93b8c2 (Refs PMAT-3529) Pmat-Ticket: PMAT-3529 Co-Authored-By: Claude Fable 5.1 --- docs/audits/quorum-PMAT-3529.json | 97 ++++++++++++++++++++++--------- 1 file changed, 70 insertions(+), 27 deletions(-) diff --git a/docs/audits/quorum-PMAT-3529.json b/docs/audits/quorum-PMAT-3529.json index 05f4c9d87d..cd5d9f38e1 100644 --- a/docs/audits/quorum-PMAT-3529.json +++ b/docs/audits/quorum-PMAT-3529.json @@ -3,12 +3,12 @@ "base": "origin/main", "base_resolved": "origin/main", "base_note": "no origin/origin/main exists; judged against the local ref", - "head": "0b24cf2ba825b305b5db2893c49ed4d80c41c8a9", - "diff_sha256": "6303cc69057879198c2f8833233e2b0f8e46a18da6ee38f38bac1d3f697eed0a", + "head": "e1b93b8c2bf42ea5fe3a800fed7ca3b943bf09da", + "diff_sha256": "a347c04f008c3586f1968f45cc23587a45050392ace20ce9e44e28b12e80765e", "width": 3, "executor": "agy", "prompt_mode": "inline", - "prompt_bytes": 38096, + "prompt_bytes": 45561, "author": { "model": "claude-opus-5", "family": "claude", @@ -20,14 +20,14 @@ "lane": 1, "status": "SUCCESS", "verdict": "PASS", - "summary": "The diff meets all acceptance criteria defined in PMAT-3529. The Sigma schema now carries an opaque metadata block that must be claimed in readers. The extractor emits entity properties using the entity type as the namespace for the predicate IRI, correctly avoiding emissions when no entity type is present or for nested objects. Lexical violations have been updated to prefix their output messages with the path. All requirements are backed by new tests matching the behavior prescribed in the ticket.", + "summary": "The diff accurately matches the specifications in PMAT-3529. The sigma schema parses the opaque `metadata` block without applying its own rules to it, keeping `pv validate` and `pv lint` from contradicting each other. It includes tests verifying that an unclaimed metadata block still exits with a 3. In the pv_contract extractor, entity properties correctly use the `:` prefix to ensure scope isolation between different types, skipping any properties if `entity.type` is missing or the value is a nested mapping/sequence. Furthermore, lexical validation violations (in, pattern, minLength, maxLength) have been explicitly updated to prepend the `path` string on the error messages. All ticket requirements are backed by matching tests and fixtures.", "findings": [ { - "claim": "The SAME contracts/ontology.yaml passes pv validate AND pv lint --gate sigma, and removing claim brings exit 3 back", + "claim": "The SAME contracts/ontology.yaml passes pv validate AND pv lint --gate sigma, and removing claim brings exit 3 back naming the key", "file": "crates/aprender-contracts-cli/tests/ont_entity_properties.rs", "fix": "", - "grounding": "measured", - "line": 119 + "grounding": "cited", + "line": 77 }, { "claim": "entity.properties. becomes the predicate : (IRI: ONT_BASE + /) on the contract node", @@ -40,14 +40,14 @@ "claim": "The predicate IRI carries the entity type as its vocabulary segment and NEVER the bare ont:scale", "file": "crates/aprender-contracts/src/ontology/extract/pv_contract.rs", "fix": "", - "grounding": "measured", + "grounding": "cited", "line": 204 }, { "claim": "No entity.type means no property triples, and nested mappings/sequences are not emitted", "file": "crates/aprender-contracts/src/ontology/extract/pv_contract.rs", "fix": "", - "grounding": "measured", + "grounding": "cited", "line": 268 }, { @@ -58,8 +58,8 @@ "line": 585 } ], - "raw_bytes": 4792, - "err_bytes": 100, + "raw_bytes": 5389, + "err_bytes": 0, "envelope_status": "SUCCESS", "verdict_source": "structured_output", "grounding_check": "parity", @@ -73,9 +73,45 @@ "status": "SUCCESS", "verdict": "PASS", "summary": "The diff successfully implements all requirements of PMAT-3529. Sigma now carries the metadata block as opaque YAML, requiring it to be claimed in `readers` to satisfy the anti-decoration rule. Entity properties are extracted as `:` scoped correctly to avoid leaking across types, while nested mappings and sequences are safely ignored. Lexical violations (in, pattern, minLength, maxLength) now correctly prepend the property path to their error messages for clarity. All tests and falsifiers from the ticket have been correctly verified.", - "findings": [], - "raw_bytes": 3762, - "err_bytes": 100, + "findings": [ + { + "claim": "The SAME contracts/ontology.yaml passes `pv validate` AND `pv lint --gate sigma`", + "file": "crates/aprender-contracts-cli/tests/ont_entity_properties.rs", + "fix": "", + "grounding": "cited", + "line": 77 + }, + { + "claim": "entity.properties. becomes the predicate : (IRI: ONT_BASE + /) on the contract node", + "file": "crates/aprender-contracts/src/ontology/extract/pv_contract.rs", + "fix": "", + "grounding": "cited", + "line": 146 + }, + { + "claim": "The predicate IRI carries the entity type as its vocabulary segment and NEVER the bare ont:scale", + "file": "crates/aprender-contracts/src/ontology/extract/pv_contract.rs", + "fix": "", + "grounding": "cited", + "line": 204 + }, + { + "claim": "No entity.type means no property triples; nested mappings and sequences are not emitted", + "file": "crates/aprender-contracts/src/ontology/extract/pv_contract.rs", + "fix": "", + "grounding": "cited", + "line": 268 + }, + { + "claim": "A lexical violation names its PATH in the message (in, pattern, minLength, maxLength)", + "file": "crates/aprender-contracts/src/ontology/shapes.rs", + "fix": "", + "grounding": "cited", + "line": 585 + } + ], + "raw_bytes": 4857, + "err_bytes": 0, "envelope_status": "SUCCESS", "verdict_source": "structured_output", "grounding_check": "parity", @@ -88,9 +124,9 @@ "lane": 3, "status": "SUCCESS", "verdict": "PASS", - "summary": "The diff strictly satisfies all acceptance criteria of PMAT-3529:\n1. `Sigma` in `crates/aprender-contracts/src/ontology/sigma.rs` accepts an opaque `metadata` block and includes `\"metadata\"` in `READABLE_KEYS`, enforcing that `readers` must claim it (preserving the anti-decoration rule).\n2. `emit_entity_properties` in `crates/aprender-contracts/src/ontology/extract/pv_contract.rs` extracts `entity.properties.` into `/` triples on the contract node.\n3. `check_lexical` in `crates/aprender-contracts/src/ontology/shapes.rs` formats `in`, `pattern`, `minLength`, and `maxLength` violation messages with `short(&p.path)`.\n4. Integration CLI tests, unit tests, explicit test commands, tree reader test entries, and fixtures are all correctly added and matched against the ticket specification without weakening any gates or adding forbidden changes.", + "summary": "The diff accurately and completely implements all requirements of ticket PMAT-3529 with zero scope creep, no weakened gates, and fully backed test coverage:\n1. `Sigma` in `crates/aprender-contracts/src/ontology/sigma.rs` supports an optional, opaque `metadata` block and adds `\"metadata\"` to `READABLE_KEYS`. Unclaimed metadata blocks continue to trigger exit 3 (anti-decoration rule preserved).\n2. `emit_entity_properties` in `crates/aprender-contracts/src/ontology/extract/pv_contract.rs` extracts scalar values from `entity.properties.` into `/` triples. When no `entity.type` is present or for complex nested/listed structures, no triples are emitted.\n3. `check_lexical` in `crates/aprender-contracts/src/ontology/shapes.rs` updates error formatting for `in`, `pattern`, `minLength`, and `maxLength` violations to prefix the message with `short(&p.path)`.\n4. CLI integration test `ont_entity_properties.rs`, unit tests, explicit test command, tree reader script list, and EV-21 fixtures validate all specified falsifier scenarios and behavior.", "findings": [], - "raw_bytes": 3153, + "raw_bytes": 3541, "err_bytes": 0, "envelope_status": "SUCCESS", "verdict_source": "structured_output", @@ -105,19 +141,22 @@ "dedup": [ { "file": "crates/aprender-contracts-cli/tests/ont_entity_properties.rs", - "line": 119, + "line": 77, "lanes_agreeing": [ - 1 + 1, + 2 ], "claims": [ - "The SAME contracts/ontology.yaml passes pv validate AND pv lint --gate sigma, and removing claim brings exit 3 back" + "The SAME contracts/ontology.yaml passes `pv validate` AND `pv lint --gate sigma`", + "The SAME contracts/ontology.yaml passes pv validate AND pv lint --gate sigma, and removing claim brings exit 3 back naming the key" ] }, { "file": "crates/aprender-contracts/src/ontology/extract/pv_contract.rs", "line": 146, "lanes_agreeing": [ - 1 + 1, + 2 ], "claims": [ "entity.properties. becomes the predicate : (IRI: ONT_BASE + /) on the contract node" @@ -127,7 +166,8 @@ "file": "crates/aprender-contracts/src/ontology/extract/pv_contract.rs", "line": 204, "lanes_agreeing": [ - 1 + 1, + 2 ], "claims": [ "The predicate IRI carries the entity type as its vocabulary segment and NEVER the bare ont:scale" @@ -137,17 +177,20 @@ "file": "crates/aprender-contracts/src/ontology/extract/pv_contract.rs", "line": 268, "lanes_agreeing": [ - 1 + 1, + 2 ], "claims": [ - "No entity.type means no property triples, and nested mappings/sequences are not emitted" + "No entity.type means no property triples, and nested mappings/sequences are not emitted", + "No entity.type means no property triples; nested mappings and sequences are not emitted" ] }, { "file": "crates/aprender-contracts/src/ontology/shapes.rs", "line": 585, "lanes_agreeing": [ - 1 + 1, + 2 ], "claims": [ "A lexical violation names its PATH in the message (in, pattern, minLength, maxLength)" @@ -160,9 +203,9 @@ "partial_reasons": [], "auto_merge": { "checked": true, - "was_armed": false, - "disarmed": false, - "note": "auto-merge not armed" + "was_armed": true, + "disarmed": true, + "note": "auto-merge was armed from an earlier round; disarmed before lanes launched" }, "lint": { "ok": true,