owl-sop-enforcement-gate is a small, single-chokepoint gate over an ontology/config edit. The novel part is the ordering of four governance checks behind one mode switch, with the two heavy checks pulled out as injectable, degrade-open seams — not any single check.
| Module | Responsibility |
|---|---|
owl_sop_enforcement_gate/gate.py |
OntologyEditGate (the chokepoint): evaluate() (pure verdict) + enforce() (mode-aware action) + GateMode + GateBlocked + detect_mutation_type / is_ontology_path + the main() hook entry point. |
owl_sop_enforcement_gate/decision.py |
GateDecision — the frozen verdict value returned by evaluate(). |
owl_sop_enforcement_gate/seams.py |
The injectable seams + reference impls: ShaclCheck / reference_shacl_check, PhaseCheck / reference_phase_check, ShaclResult, and the inline is_substantive_verbatim. |
owl_sop_enforcement_gate/couplings.py |
Best-effort, never-hard-import couplings: try_emit_event (advisor event), try_substance_check (external linter). |
owl_sop_enforcement_gate/__init__.py |
Public API surface. |
examples/ontology/ |
A tiny generic ontology (core.ttl), one SHACL shape (core.shacl.ttl), one SPARQL competency question (competency_question.rq). |
evaluate(file_path, verbatim_response?, responsible_owner?, change_set_id, mutation_type?)
│
├─ mutation_type = explicit OR detect_mutation_type(file_path)
│ └─ None (not a gated surface) ⇒ GateDecision(allowed=True) ── no-op edit
│
├─ Finding 1: ontology edit? ─ shacl_check(data, shapes) ─┐
│ "violations" ⇒ BLOCK reason (+count) │ "skipped"/"error" ⇒ degrade open
│
├─ Finding 2: verbatim present? ── no ⇒ BLOCK reason ─────┤
├─ Finding 3: verbatim substantive? ── no ⇒ BLOCK reason ─┤ (≥50 non-ws chars; inline floor)
├─ Finding 2b: owner-gated mutation w/o owner ⇒ BLOCK ────┤
├─ Finding 4: phase_check(cs, mut) ⇒ (ok, missing) ───────┤ broken seam ⇒ degrade open
│ not ok ⇒ BLOCK reason (+missing phases) │
│ │
└────────────────────────────► GateDecision(allowed = no reasons, ...) ◄┘
enforce(...) = evaluate(...) then, by mode:
advisory ⇒ warn(stderr) + ALLOW | enforce ⇒ raise GateBlocked | override ⇒ log(stderr) + ALLOW
Two invariants make this a gate rather than a linter: the verdict is a pure value (evaluate never raises or exits — only enforce acts, and only it knows the mode), and BLOCK is reserved for a real finding (a degraded SHACL/phase seam allows, it never blocks on its own absence).
| Seam | How to use it |
|---|---|
shacl_check(data_path, shapes_path) -> ShaclResult |
Plug in a SHACL toolkit / pyshacl. Return an object with .status ("ok"/"violations"/"skipped"/"error") and, on violations, .violation_count / .detail. Defaults to the lazy, degrade-open reference_shacl_check. |
phase_check(change_set_id, mutation_type) -> (ok, missing) |
Plug in a governance phase tracker that maps a mutation to its prerequisite steps and checks each for completion. Defaults to the degrade-open reference_phase_check (all complete). |
mutation_detector(file_path) -> mutation_type | None |
Override the path→mutation mapping when your surfaces are richer than .ttl/.json. |
substance_check(text) -> bool |
Override the inline verbatim-substance floor. |
owner_required_mutations |
The mutation types that additionally require a responsible_owner. |
event_sink(kind, detail) -> None |
An advisor/notification sink for edit_allowed / edit_blocked / edit_overridden events. Best-effort; missing is a no-op. |
- The verdict is separate from the action.
evaluate()is a pure, side-effect-free function of its inputs;enforce()is the only thing that warns, raises, or overrides — so the gate is unit-testable in-process withoutsys.exit. - BLOCK requires a real finding. A SHACL seam with no validator returns
"skipped"; a broken phase seam is caught and treated as complete. The gate never blocks merely because a heavy dependency is absent. - Structural validity precedes metadata. SHACL non-conformance on an ontology edit is checked first — a structurally-invalid graph is rejected regardless of how complete the sign-off metadata is.
- Override is documented and loud. A bypass (env
OSEG_OVERRIDE=1oroverride=True) always prints its reason to stderr and emits anedit_overriddenevent; it is never silent. - Offline-exercisable. Zero third-party deps for the gate; the full flow (including the bundled reference SHACL seam's "skipped" path) runs on a bare Python 3.9 — see the tests.