Skip to content

Commit 3c1555b

Browse files
committed
ogar-from-schema: DO-arm lift → SPO emitter, proven end-to-end
Closes the lift→triples seam for the behavioral arm: the DO-arm ActionDef (D-HIRO-DO) flows through the canonical ogar-emitter to SPO triples — the surface the RBAC / query layers consume. tests/do_arm_emit.rs (new integration test, ogar-emitter as dev-dep): - KnowledgeItem TTL → into_action_def → emit_action_def: asserts the §4 field mapping survives as ogar:ActionDef / ogar:actionPredicate (execute) / ogar:actionObjectClass (ogit-automation/mars_node_template) / LifecycleTrigger kausal / actuator-verb decorators. - lossless-DO holds END-TO-END: body_source is None, so the emitter produces NO ogar:actionBody triple — a body triple would mean bytes leaked into the IR. Test fails if any do. ogar-emitter is an acyclic dev-dep (it depends on ogar-vocab/-ontology, never on ogar-from-schema). 24 lib + 2 integration tests green, clippy-clean. Docs: D-HIRO-DO ledger row notes the proven emit path (+ D-EMIT dep). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EYvNjD8M8LMNYbRy3gq2FP
1 parent 59be3b2 commit 3c1555b

3 files changed

Lines changed: 103 additions & 1 deletion

File tree

crates/ogar-from-schema/Cargo.toml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,3 +29,10 @@ roxmltree = { version = "0.20", optional = true }
2929
# When the TTL surface grows (Wikidata-shaped TTL, full RDF/XML, OWL
3030
# imports), swap in `oxttl` / `oxrdf` without touching the
3131
# Class/Attribute target shape this crate produces.
32+
33+
[dev-dependencies]
34+
# Used only by tests/do_arm_emit.rs to prove the DO-arm lift's output
35+
# (`ogar_vocab::ActionDef`) flows through the canonical SPO emitter
36+
# end-to-end. Acyclic: ogar-emitter depends on ogar-vocab/-ontology,
37+
# never on this crate.
38+
ogar-emitter = { path = "../ogar-emitter" }
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
//! End-to-end integration: the DO-arm lift reaches the SPO emitter.
2+
//!
3+
//! Proves the full behavioral-arm path closes —
4+
//!
5+
//! ```text
6+
//! OGIT Automation TTL → ttl::parse_file → EntityDecl
7+
//! → do_arm::into_action_def → ogar_vocab::ActionDef
8+
//! → ogar_emitter::TripleEmitter::emit_action_def → Vec<Triple>
9+
//! ```
10+
//!
11+
//! i.e. the `KnowledgeItem` → `ActionDef` contract template (D‑HIRO‑DO)
12+
//! emits as canonical `ogar:ActionDef` SPO triples carrying the §4 field
13+
//! mapping, with the lossless‑DO invariant preserved across the whole path
14+
//! (no body inlined, so no `ogar:actionBody` triple is produced at the schema
15+
//! level). This is the lift→triples seam the RBAC / query layers consume.
16+
17+
use ogar_emitter::TripleEmitter;
18+
use ogar_from_schema::do_arm::into_action_def;
19+
use ogar_from_schema::ttl::parse_file;
20+
use ogar_from_schema::TtlDeclaration;
21+
22+
const KNOWLEDGE_ITEM_TTL: &str =
23+
include_str!("../../../vocab/imports/ogit/NTO/Automation/entities/KnowledgeItem.ttl");
24+
25+
fn obj(triples: &[ogar_emitter::Triple], subject: &str, predicate: &str) -> Option<String> {
26+
triples
27+
.iter()
28+
.find(|t| t.subject == subject && t.predicate == predicate)
29+
.map(|t| t.object.clone())
30+
}
31+
32+
#[test]
33+
fn knowledge_item_lifts_and_emits_as_action_def_triples() {
34+
// Lift.
35+
let TtlDeclaration::Entity(ki) = parse_file(KNOWLEDGE_ITEM_TTL).expect("parses") else {
36+
panic!("expected entity");
37+
};
38+
let def = into_action_def(&ki).expect("KnowledgeItem → ActionDef");
39+
let id = def.identity.clone();
40+
assert_eq!(id, "ogit-automation/knowledge_item::action_def::execute");
41+
42+
// Emit.
43+
let triples = TripleEmitter::emit_action_def(&def);
44+
45+
// Type triple — the def IS an ogar:ActionDef on the SPO surface.
46+
assert_eq!(obj(&triples, &id, "rdf:type").as_deref(), Some("ogar:ActionDef"));
47+
48+
// The §4 field mapping survives the lift→emit path verbatim.
49+
assert_eq!(
50+
obj(&triples, &id, "ogar:actionPredicate").as_deref(),
51+
Some("execute")
52+
);
53+
assert_eq!(
54+
obj(&triples, &id, "ogar:actionObjectClass").as_deref(),
55+
Some("ogit-automation/mars_node_template")
56+
);
57+
58+
// The `contains Trigger` → LifecycleTrigger kausal emits its triple(s).
59+
assert!(
60+
triples
61+
.iter()
62+
.any(|t| t.subject == id && t.predicate.starts_with("ogar:") && t.object == "Trigger"),
63+
"LifecycleTrigger event `Trigger` missing from emitted triples: {triples:?}"
64+
);
65+
66+
// The §4 actuator verbs ride as ogar:actionDecorator provenance.
67+
for verb in ["relates", "contains", "uses"] {
68+
assert!(
69+
triples
70+
.iter()
71+
.any(|t| t.subject == id
72+
&& t.predicate == "ogar:actionDecorator"
73+
&& t.object == verb),
74+
"decorator `{verb}` missing from emitted triples"
75+
);
76+
}
77+
}
78+
79+
#[test]
80+
fn lossless_do_survives_the_emit_path() {
81+
// The lossless-DO invariant must hold END-TO-END, not just at the lift:
82+
// since `body_source` is None (the body is pointed-to, never inlined),
83+
// the emitter must NOT produce an `ogar:actionBody` triple. A body triple
84+
// here would mean some bytes leaked into the IR.
85+
let TtlDeclaration::Entity(ki) = parse_file(KNOWLEDGE_ITEM_TTL).expect("parses") else {
86+
panic!("expected entity");
87+
};
88+
let def = into_action_def(&ki).expect("ActionDef");
89+
let triples = TripleEmitter::emit_action_def(&def);
90+
91+
assert!(
92+
!triples.iter().any(|t| t.predicate == "ogar:actionBody"),
93+
"lossless-DO violated across emit: a body triple was produced ({triples:?})"
94+
);
95+
}

docs/DISCOVERY-MAP.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,7 @@ two halves of a cell. ADR‑026 names the cascade that ties them.
208208
| D‑KNOWABLE | `KnowableFromStore` + `register_class_knowable_from`; `surrealql-hint`; **`vart-backend`** | G | CODED | `ogar-knowable-from/` (#25,#33,#43) | D‑IDENT |
209209
| D‑HINT | `schema_ddl_hint` loop closed — self‑describing registry via emit | G | CODED | (#33) | D‑SURREALQL, D‑KNOWABLE |
210210
| D‑ELIXIR | Elixir/HIRO SchemaSource scaffold (`gen_statem`→Rubicon) | G | CODED (scaffold) | `ogar-from-elixir/` | D‑VOCAB |
211-
| D‑HIRO‑DO | OGIT Automation → DO arm: `into_action_def` lifts `KnowledgeItem``ActionDef` (object_class←`relates`, kausal←`contains Trigger`, body **pointed‑to** not inlined — lossless‑DO §1); schema half of `PROBE‑OGAR‑DO‑ARM‑LIFT` green | G | CODED (schema half) | `ogar-from-schema/src/do_arm.rs` | D‑VOCAB, D‑TTL, D‑ELIXIR |
211+
| D‑HIRO‑DO | OGIT Automation → DO arm: `into_action_def` lifts `KnowledgeItem``ActionDef` (object_class←`relates`, kausal←`contains Trigger`, body **pointed‑to** not inlined — lossless‑DO §1); schema half of `PROBE‑OGAR‑DO‑ARM‑LIFT` green; lift→`emit_action_def`→SPO triples proven end‑to‑end (`tests/do_arm_emit.rs`, lossless‑DO holds across emit) | G | CODED (schema half) | `ogar-from-schema/src/do_arm.rs` | D‑VOCAB, D‑TTL, D‑ELIXIR, D‑EMIT |
212212
| D‑OSM | `ogar-from-osm-pbf` — Node/Way/Relation; quadkey NiblePath from resolved geometry | H | IDEA | (queued) | D‑VOCAB, `[per rt]` D‑OSM‑3 |
213213
| D‑PATTERN | `ogar-pattern` — recognition library + confidence (FMA‑D/FIBO/SKR/PROV‑O) | H | IDEA | (queued) | D‑TTL |
214214
| D‑ACTION | `ogar-actionable` — lifecycle → `ActionDef`/`KausalSpec` | H | IDEA | (queued) | D‑PATTERN |

0 commit comments

Comments
 (0)