Skip to content

Latest commit

 

History

History
97 lines (68 loc) · 4.9 KB

File metadata and controls

97 lines (68 loc) · 4.9 KB

Forking this into your governance pipeline

The gate is built to be embedded in front of an Edit/Write (or a pre-commit hook). You supply, at most, four things: your SHACL shapes, a SHACL-conformance backend, a phase-completion backend, and which mutation types need an owner. Every one has a degrade-open default, so a partial wiring still runs.

1. Point it at your SHACL shapes

from owl_sop_enforcement_gate import OntologyEditGate

gate = OntologyEditGate(
    mode="enforce",
    shapes_graph_path="ontology/my_shapes.shacl.ttl",   # your shapes; omit to skip SHACL
)

With the optional shacl extra installed (pip install -e ".[shacl]"), the bundled reference_shacl_check runs a real pyshacl validation against those shapes. With it absent, the check returns "skipped" and the edit is allowed (logged) — your CI never breaks for lack of a validator.

2. Wire a real SHACL-conformance backend (the shacl_check seam)

If you have your own SHACL toolkit (or want to reuse a project-wide validator), inject it. The seam is (data_path, shapes_path) -> result-with-.status:

def my_shacl_check(data_path, shapes_path=None):
    report = my_toolkit.validate(data_path, shapes_path)
    if report.conforms:
        return ShaclResult(status="ok")
    return ShaclResult(status="violations",
                       violation_count=report.count,
                       detail=report.text)

gate = OntologyEditGate(mode="enforce",
                        shapes_graph_path="ontology/my_shapes.shacl.ttl",
                        shacl_check=my_shacl_check)

Return status="skipped" (or "error") to degrade open for an edit your toolkit can't process — the gate will allow it and log that conformance was not run. A separate OWL/SHACL governance toolkit repo is the natural home for the real validator + reasoner; this gate only needs its yes/no answer.

3. Wire a real phase-completion backend (the phase_check seam)

The seam is (change_set_id, mutation_type) -> (all_complete, missing_phases):

def my_phase_check(change_set_id, mutation_type):
    required = my_tracker.required_phases_for(mutation_type)
    missing = [p for p in required if not my_tracker.is_complete(change_set_id, p)]
    return (len(missing) == 0, missing)

gate = OntologyEditGate(mode="enforce", phase_check=my_phase_check)

A dedicated phase / step tracker repo (mapping each mutation type to its prerequisite governance steps and tracking their completion per change-set) is the natural backend here. The reference impl reports everything complete, so the gate is a no-op on this axis until you wire a tracker in.

4. Declare your owner-gated mutations

gate = OntologyEditGate(
    mode="enforce",
    owner_required_mutations={"new_class", "delete_class", "rename_property"},
)

Those mutation types now require a responsible_owner in addition to the verbatim rationale (the rest require only the rationale). Customize the path→mutation mapping itself with mutation_detector= if your surfaces are richer than .ttl/.json.

5. (Optional) Surface blocked edits to an advisor / notifier

def sink(kind, detail):
    if kind == "edit_blocked":
        notify_channel(detail)         # your Slack/webhook/etc.

gate = OntologyEditGate(mode="enforce", event_sink=sink)

Events emitted: edit_allowed, edit_blocked, edit_overridden, phase_check_error. If you omit event_sink, the gate makes a best-effort probe for an importable advisor module and is otherwise a silent no-op — it never hard-imports anything.

6. Run it as a pre-commit / pre-tool-use hook

OSEG_GATE_MODE=enforce \
OSEG_SHAPES_GRAPH=ontology/my_shapes.shacl.ttl \
OSEG_OWNER_REQUIRED_MUTATIONS=new_class,delete_class \
  owl-sop-gate '{"file_path": "ontology/core.ttl", "verbatim_response": "...", "responsible_owner": "..."}'

Exit 0 = allowed (or advisory/override); exit 2 = blocked in enforce mode (the conventional pre-commit reject code). Env vars: OSEG_GATE_MODE, OSEG_SHAPES_GRAPH, OSEG_OWNER_REQUIRED_MUTATIONS, OSEG_OVERRIDE (+ OSEG_OVERRIDE_REASON), and OSEG_TOOL_INPUT (the JSON envelope, as an alternative to argv[1]).

The adoption ramp

  1. Ship with mode="advisory" — every finding is logged, nothing is blocked. Watch your logs.
  2. Flip to mode="enforce" once the noise is gone. Now bad edits are rejected.
  3. Keep OSEG_OVERRIDE=1 (with a reason) for the genuine exception — it is allowed and loudly logged, never silent.

What NOT to change

  • Don't make evaluate() raise or exit — its purity is what makes the gate testable; all control flow belongs in enforce().
  • Don't make a degraded seam ("skipped" / a broken phase_check) block — BLOCK must require a real finding, or the gate becomes the thing that breaks minimal CI.
  • Don't drop the substance floor to a presence check — "ok" passing is the failure mode the gate exists to prevent.