diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e7ac781..18f76cb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -39,6 +39,26 @@ jobs: run: | python -m goblin.validate --data samples/goblin-sample.ttl + - name: Generate guidance governance module (RDFLib) + run: | + python tools/generate_guidance_governance_module.py + + - name: Verify generated guidance module is committed + run: | + python tools/generate_guidance_governance_module.py --check + + - name: Validate SHACL on guidance governance sample + run: | + python -m goblin.validate \ + --data samples/guidance-governance-sample.ttl \ + --shapes goblin-guidance-shapes.ttl + + - name: Enforce guidance conflict gate + run: | + python tools/report_guidance_conflicts.py \ + --data samples/guidance-governance-sample.ttl \ + --ontology goblin-guidance-ontology.ttl + - name: Lint DOT (render to SVG) run: | dot -Tsvg goblin-map.dot -o /tmp/goblin-map.svg @@ -47,4 +67,3 @@ jobs: run: | curl -I --fail https://service.tib.eu/webvowl/ - diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 906c5f9..f8b10b4 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -6,6 +6,7 @@ Thank you for helping improve the Goblin ontology and visualizations. - Problem instances MUST use `ui:ProblemClass` (or a subclass) and SHOULD have a `ui:goblinScore` in [0,1]. - UI-closure problems MUST include `ui:goblinScore` (enforced by SHACL). - Dimensions SHOULD include `ui:dimensionIndex` and `ui:energyRelevanceScore`. +- Guidance requirements MUST use normalized topic + modality + provenance fields (see `goblin-guidance-ontology.ttl` and `goblin-guidance-shapes.ttl`). - Use `ui:hasScoreComponent` to attach per-dimension components (`ui:ScoreComponent`) with `ui:componentValue` in [0,1]. - Use `ui:WeightAssignment` with `ui:forDimension` and `ui:hasWeight` for weight vectors. Prefer one canonical assignment (e.g. `ui:DefaultWeights`). - Optional relations between problem classes: @@ -16,6 +17,7 @@ Thank you for helping improve the Goblin ontology and visualizations. ## Validation All changes are checked in CI via SHACL (pySHACL). Pushes/PRs must pass. +Guidance-governance modules are generated via RDFLib; do not hand-edit generated module files. ## Visualization outputs The pipeline generates: @@ -27,10 +29,11 @@ The pipeline generates: ## Contribution workflow 1. Fork and branch from `main`. 2. Update `goblin-ontology.ttl` (and `goblin-shapes.ttl` if adding constraints). -3. Run local checks if possible (`tools/validate_shapes.py`). -4. Open a PR with a summary of ontology changes. +3. If guidance-governance terms/shapes change, update `tools/generate_guidance_governance_module.py` and regenerate: + - `python tools/generate_guidance_governance_module.py` +4. Run local checks if possible (`tools/validate_shapes.py` plus guidance sample validation). +5. Open a PR with a summary of ontology changes. ## Versioning We tag semantic versions for ontology releases and maintain a `CHANGELOG.md`. - diff --git a/README.md b/README.md index 9d6e5dd..cc974c7 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,8 @@ This bundle contains: - `goblin-ontology.ttl` — OWL ontology defining Goblin score (SCIG), problem classes, dimensions, and example domains. - `goblin-shapes.ttl` — SHACL shapes constraining Goblin score usage. +- `goblin-guidance-ontology.ttl` — OWL module for normalized guidance requirements (topics/modality/provenance). +- `goblin-guidance-shapes.ttl` — SHACL module constraining guidance requirement instances. - `goblin-map.dot` — Graphviz DOT file for a "Goblin map" of common domains. - `goblin-agent-lim42.md` — lim42-ready agent spec for computing Goblin scores. - `goblin-energy-gradient.md` — energy gradient interpretation with simple flow equations. @@ -19,6 +21,14 @@ Then validate data against the bundled shapes (after installation): python -m goblin.validate --data samples/goblin-sample.ttl ``` +Guidance-governance module: + +```bash +python tools/generate_guidance_governance_module.py +python -m goblin.validate --data samples/guidance-governance-sample.ttl --shapes goblin-guidance-shapes.ttl +python tools/report_guidance_conflicts.py --data samples/guidance-governance-sample.ttl --ontology goblin-guidance-ontology.ttl +``` + ## Meme / Usage Because "SCIG" is overloaded in Google (subcutaneous immunoglobulin), we use the meme name: @@ -68,4 +78,3 @@ Use the existing lim42 agent prompt (`goblin-agent-lim42.md`) and weights to go - [SVG](docs/goblin-map.svg) — scalable version from the DOT source (`goblin-map.dot`). - To produce a PNG locally (not checked into the repo), run `dot -Tpng goblin-map.dot -o docs/goblin-map.png`. - diff --git a/docs/guidance_governance_additions_2026-03-07.md b/docs/guidance_governance_additions_2026-03-07.md new file mode 100644 index 0000000..a41e4db --- /dev/null +++ b/docs/guidance_governance_additions_2026-03-07.md @@ -0,0 +1,21 @@ +# Guidance Governance Additions (2026-03-07) + +Based on real failures encountered in cross-repo agent guidance work, Goblin now includes baseline support for guidance-governance modeling and drift checks: + +1. RDFLib-generated ontology module: `goblin-guidance-ontology.ttl` +2. RDFLib-generated SHACL module: `goblin-guidance-shapes.ttl` +3. Sample governed data: `samples/guidance-governance-sample.ttl` +4. Conflict drift report tool: `tools/report_guidance_conflicts.py` +5. CI enforcement for generation, SHACL validation, and conflict gate. + +## Why this was added + +- We observed policy drift when guidance was split across model-driven and hand-maintained sources. +- We observed semantic overlap and conflicting clauses hidden by prose. +- We observed conflict-prone merges in high-churn policy files. + +## Immediate next candidates + +- Add explicit precedence/authority modeling (for conflict resolution beyond polarity checks). +- Add owner metadata for each requirement topic and enforcement policy. +- Add baseline-vs-head conflict growth checks against repository history for real guidance datasets. diff --git a/docs/guidance_model_pathology_2026-03-07.md b/docs/guidance_model_pathology_2026-03-07.md new file mode 100644 index 0000000..5bf9fad --- /dev/null +++ b/docs/guidance_model_pathology_2026-03-07.md @@ -0,0 +1,55 @@ +# Guidance Model Pathology Note (2026-03-07) + +## Context + +Observed while tightening agent-governance gates in `eudorus`, but applicable to Goblin-style guidance systems. + +## Pathology + +A recurrent failure mode appears when guidance is partly model-driven and partly hand-maintained prose: + +1. Policy clauses are duplicated across multiple artifacts. +2. Semantic coupling is implicit (inferred from wording), not explicit in a canonical model. +3. Projection/generation covers only a subset of guidance surfaces. +4. CI validates syntax/format but not cross-artifact semantic overlap. + +This yields "goblin-class" drift: +- overlapping or contradictory norms, +- frequent merge conflicts in high-churn policy files, +- partial refactors leaving dead or inconsistent logic paths, +- policy intent ambiguity during enforcement. + +## Triggering Example + +- Introduced stricter ontology-exemption governance and typo-only semantics. +- Concurrent branch evolution changed adjacent exemption/authenticity policy text. +- A code path introduced richer change-status parsing but still used synthetic status in enforcement. +- Result: merge conflict + semantic ambiguity + inconsistent enforcement intent. + +## Hypothesis + +The root issue is under-modeled guidance dimensions, not just bad merge hygiene. + +Missing/weak dimensions usually include: +- topic (normalized policy domain), +- modality (`must`, `must_not`, `should`, `should_not`), +- scope/target surface, +- precedence/authority, +- enforcement level, +- provenance and ownership. + +## Recommended Controls + +1. Maintain a canonical normalized requirement model for guidance artifacts. +2. Project host-specific guidance from model where possible. +3. Add drift gates: + - fail on conflict-risk growth relative to base, + - fail on mixed-modality drift for key topics. +4. Require explicit concept IDs for policy clauses to reduce inferred coupling. + +## Follow-up Work for Goblin + +- Add a first-class guidance ontology/schema for policy dimensions. +- Add deterministic extraction from markdown/yaml guidance into that schema. +- Add CI checks for topic-level modality conflicts and precedence violations. +- Build a dashboard/report for conflict hotspots by topic and source file. diff --git a/goblin-guidance-ontology.ttl b/goblin-guidance-ontology.ttl new file mode 100644 index 0000000..8d69607 --- /dev/null +++ b/goblin-guidance-ontology.ttl @@ -0,0 +1,68 @@ +@prefix owl: . +@prefix rdfs: . +@prefix ui: . +@prefix xsd: . + +ui:Must a ui:NormativeModality ; + rdfs:label "must" . + +ui:MustNot a ui:NormativeModality ; + rdfs:label "must_not" . + +ui:Policy a ui:NormativeModality ; + rdfs:label "policy" . + +ui:Should a ui:NormativeModality ; + rdfs:label "should" . + +ui:ShouldNot a ui:NormativeModality ; + rdfs:label "should_not" . + +ui:Topic_Branching a ui:GuidanceTopic ; + rdfs:label "branching" . + +ui:Topic_GitHubAuth a ui:GuidanceTopic ; + rdfs:label "github.auth" . + +ui:Topic_OntologyGovernance a ui:GuidanceTopic ; + rdfs:label "ontology.governance" . + +ui:Topic_QueuePrioritization a ui:GuidanceTopic ; + rdfs:label "queue.prioritization" . + +ui:enforcementLevel a owl:DatatypeProperty ; + rdfs:label "enforcement level" ; + rdfs:domain ui:GuidanceRequirement ; + rdfs:range xsd:string . + +ui:hasGuidanceTopic a owl:ObjectProperty ; + rdfs:label "has guidance topic" ; + rdfs:domain ui:GuidanceRequirement ; + rdfs:range ui:GuidanceTopic . + +ui:hasModality a owl:ObjectProperty ; + rdfs:label "has modality" ; + rdfs:domain ui:GuidanceRequirement ; + rdfs:range ui:NormativeModality . + +ui:sourceArtifact a owl:DatatypeProperty ; + rdfs:label "source artifact" ; + rdfs:domain ui:GuidanceRequirement ; + rdfs:range xsd:string . + +ui:sourceLine a owl:DatatypeProperty ; + rdfs:label "source line" ; + rdfs:domain ui:GuidanceRequirement ; + rdfs:range xsd:integer . + +ui:GuidanceRequirement a owl:Class ; + rdfs:label "Guidance Requirement" ; + rdfs:comment "Normalized governance requirement extracted from guidance artifacts." . + +ui:GuidanceTopic a owl:Class ; + rdfs:label "Guidance Topic" ; + rdfs:comment "Normalized topic for cross-artifact drift checks." . + +ui:NormativeModality a owl:Class ; + rdfs:label "Normative Modality" . + diff --git a/goblin-guidance-shapes.ttl b/goblin-guidance-shapes.ttl new file mode 100644 index 0000000..bab277f --- /dev/null +++ b/goblin-guidance-shapes.ttl @@ -0,0 +1,53 @@ +@prefix rdf: . +@prefix sh: . +@prefix ui: . +@prefix uiSh: . +@prefix xsd: . + +uiSh:GuidanceRequirementShape a sh:NodeShape ; + sh:property uiSh:Prop_enforcementLevel, + uiSh:Prop_hasGuidanceTopic, + uiSh:Prop_hasModality, + uiSh:Prop_sourceArtifact, + uiSh:Prop_sourceLine ; + sh:targetClass ui:GuidanceRequirement . + +uiSh:In_hasModality rdf:first ui:Must ; + rdf:rest uiSh:In_hasModality_0 . + +uiSh:In_hasModality_0 rdf:first ui:MustNot ; + rdf:rest uiSh:In_hasModality_1 . + +uiSh:In_hasModality_1 rdf:first ui:Should ; + rdf:rest uiSh:In_hasModality_2 . + +uiSh:In_hasModality_2 rdf:first ui:ShouldNot ; + rdf:rest uiSh:In_hasModality_3 . + +uiSh:In_hasModality_3 rdf:first ui:Policy ; + rdf:rest () . + +uiSh:Prop_enforcementLevel sh:datatype xsd:string ; + sh:message "GuidanceRequirement MUST define enforcementLevel." ; + sh:minCount 1 ; + sh:path ui:enforcementLevel . + +uiSh:Prop_hasGuidanceTopic sh:message "GuidanceRequirement MUST define hasGuidanceTopic." ; + sh:minCount 1 ; + sh:path ui:hasGuidanceTopic . + +uiSh:Prop_hasModality sh:in uiSh:In_hasModality ; + sh:message "GuidanceRequirement MUST define hasModality." ; + sh:minCount 1 ; + sh:path ui:hasModality . + +uiSh:Prop_sourceArtifact sh:datatype xsd:string ; + sh:message "GuidanceRequirement MUST define sourceArtifact." ; + sh:minCount 1 ; + sh:path ui:sourceArtifact . + +uiSh:Prop_sourceLine sh:datatype xsd:integer ; + sh:message "GuidanceRequirement MUST define sourceLine." ; + sh:minCount 1 ; + sh:path ui:sourceLine . + diff --git a/samples/guidance-governance-sample.ttl b/samples/guidance-governance-sample.ttl new file mode 100644 index 0000000..21d5b65 --- /dev/null +++ b/samples/guidance-governance-sample.ttl @@ -0,0 +1,24 @@ +@prefix ui: . +@prefix ex: . +@prefix xsd: . + +ex:req1 a ui:GuidanceRequirement ; + ui:hasGuidanceTopic ui:Topic_GitHubAuth ; + ui:hasModality ui:Must ; + ui:sourceArtifact "AGENTS.md" ; + ui:sourceLine "42"^^xsd:integer ; + ui:enforcementLevel "hard" . + +ex:req2 a ui:GuidanceRequirement ; + ui:hasGuidanceTopic ui:Topic_OntologyGovernance ; + ui:hasModality ui:Must ; + ui:sourceArtifact "AGENTS.md" ; + ui:sourceLine "355"^^xsd:integer ; + ui:enforcementLevel "hard" . + +ex:req3 a ui:GuidanceRequirement ; + ui:hasGuidanceTopic ui:Topic_QueuePrioritization ; + ui:hasModality ui:Should ; + ui:sourceArtifact "AGENTS.md" ; + ui:sourceLine "30"^^xsd:integer ; + ui:enforcementLevel "soft" . diff --git a/tools/generate_guidance_governance_module.py b/tools/generate_guidance_governance_module.py new file mode 100644 index 0000000..ff718fd --- /dev/null +++ b/tools/generate_guidance_governance_module.py @@ -0,0 +1,193 @@ +#!/usr/bin/env python3 +"""Generate guidance-governance ontology and SHACL module files via RDFLib.""" + +from __future__ import annotations + +import argparse +from pathlib import Path + +from rdflib import Graph, Literal, Namespace +from rdflib.namespace import OWL, RDF, RDFS, SH, XSD + + +REPO_ROOT = Path(__file__).resolve().parent.parent +ONTOLOGY_PATH = REPO_ROOT / "goblin-guidance-ontology.ttl" +SHAPES_PATH = REPO_ROOT / "goblin-guidance-shapes.ttl" + +UI = Namespace("https://nkllon.com/ui#") +UISH = Namespace("https://nkllon.com/ui/shapes#goblin#") + + +def _ontology_graph() -> Graph: + g = Graph() + g.bind("ui", UI) + g.bind("owl", OWL) + g.bind("rdfs", RDFS) + g.bind("xsd", XSD) + + g.add((UI.GuidanceRequirement, RDF.type, OWL.Class)) + g.add((UI.GuidanceRequirement, RDFS.label, Literal("Guidance Requirement"))) + g.add( + ( + UI.GuidanceRequirement, + RDFS.comment, + Literal("Normalized governance requirement extracted from guidance artifacts."), + ) + ) + + g.add((UI.GuidanceTopic, RDF.type, OWL.Class)) + g.add((UI.GuidanceTopic, RDFS.label, Literal("Guidance Topic"))) + g.add((UI.GuidanceTopic, RDFS.comment, Literal("Normalized topic for cross-artifact drift checks."))) + + g.add((UI.NormativeModality, RDF.type, OWL.Class)) + g.add((UI.NormativeModality, RDFS.label, Literal("Normative Modality"))) + + for iri, label in ( + (UI.Must, "must"), + (UI.MustNot, "must_not"), + (UI.Should, "should"), + (UI.ShouldNot, "should_not"), + (UI.Policy, "policy"), + ): + g.add((iri, RDF.type, UI.NormativeModality)) + g.add((iri, RDFS.label, Literal(label))) + + for iri, label in ( + (UI.Topic_GitHubAuth, "github.auth"), + (UI.Topic_OntologyGovernance, "ontology.governance"), + (UI.Topic_QueuePrioritization, "queue.prioritization"), + (UI.Topic_Branching, "branching"), + ): + g.add((iri, RDF.type, UI.GuidanceTopic)) + g.add((iri, RDFS.label, Literal(label))) + + g.add((UI.hasGuidanceTopic, RDF.type, OWL.ObjectProperty)) + g.add((UI.hasGuidanceTopic, RDFS.label, Literal("has guidance topic"))) + g.add((UI.hasGuidanceTopic, RDFS.domain, UI.GuidanceRequirement)) + g.add((UI.hasGuidanceTopic, RDFS.range, UI.GuidanceTopic)) + + g.add((UI.hasModality, RDF.type, OWL.ObjectProperty)) + g.add((UI.hasModality, RDFS.label, Literal("has modality"))) + g.add((UI.hasModality, RDFS.domain, UI.GuidanceRequirement)) + g.add((UI.hasModality, RDFS.range, UI.NormativeModality)) + + g.add((UI.sourceArtifact, RDF.type, OWL.DatatypeProperty)) + g.add((UI.sourceArtifact, RDFS.label, Literal("source artifact"))) + g.add((UI.sourceArtifact, RDFS.domain, UI.GuidanceRequirement)) + g.add((UI.sourceArtifact, RDFS.range, XSD.string)) + + g.add((UI.sourceLine, RDF.type, OWL.DatatypeProperty)) + g.add((UI.sourceLine, RDFS.label, Literal("source line"))) + g.add((UI.sourceLine, RDFS.domain, UI.GuidanceRequirement)) + g.add((UI.sourceLine, RDFS.range, XSD.integer)) + + g.add((UI.enforcementLevel, RDF.type, OWL.DatatypeProperty)) + g.add((UI.enforcementLevel, RDFS.label, Literal("enforcement level"))) + g.add((UI.enforcementLevel, RDFS.domain, UI.GuidanceRequirement)) + g.add((UI.enforcementLevel, RDFS.range, XSD.string)) + + return g + + +def _shapes_graph() -> Graph: + g = Graph() + g.bind("ui", UI) + g.bind("sh", SH) + g.bind("xsd", XSD) + g.bind("uiSh", UISH) + + shape = UISH.GuidanceRequirementShape + g.add((shape, RDF.type, SH.NodeShape)) + g.add((shape, SH.targetClass, UI.GuidanceRequirement)) + + def _property(path, min_count=1, datatype=None, in_values=None, msg=None): + node = UISH[f"Prop_{path.split('#')[-1]}"] + g.add((shape, SH.property, node)) + g.add((node, SH.path, path)) + g.add((node, SH.minCount, Literal(min_count, datatype=XSD.integer))) + if datatype is not None: + g.add((node, SH.datatype, datatype)) + if in_values is not None: + collection = UISH[f"In_{path.split('#')[-1]}"] + g.add((node, SH["in"], collection)) + # Build rdf list manually + head = collection + for index, value in enumerate(in_values): + nxt = UISH[f"In_{path.split('#')[-1]}_{index}"] + g.add((head, RDF.first, value)) + if index == len(in_values) - 1: + g.add((head, RDF.rest, RDF.nil)) + else: + g.add((head, RDF.rest, nxt)) + head = nxt + if msg: + g.add((node, SH.message, Literal(msg))) + + _property( + UI.hasGuidanceTopic, + msg="GuidanceRequirement MUST define hasGuidanceTopic.", + ) + _property( + UI.hasModality, + in_values=(UI.Must, UI.MustNot, UI.Should, UI.ShouldNot, UI.Policy), + msg="GuidanceRequirement MUST define hasModality.", + ) + _property( + UI.sourceArtifact, + datatype=XSD.string, + msg="GuidanceRequirement MUST define sourceArtifact.", + ) + _property( + UI.sourceLine, + datatype=XSD.integer, + msg="GuidanceRequirement MUST define sourceLine.", + ) + _property( + UI.enforcementLevel, + datatype=XSD.string, + msg="GuidanceRequirement MUST define enforcementLevel.", + ) + + return g + + +def _serialize(g: Graph) -> str: + text = g.serialize(format="turtle") + assert isinstance(text, str) + return text + + +def _write(path: Path, content: str, check: bool) -> int: + if check: + if not path.exists(): + return 1 + return 0 if path.read_text(encoding="utf-8") == content else 1 + path.write_text(content, encoding="utf-8") + return 0 + + +def main() -> int: + parser = argparse.ArgumentParser() + parser.add_argument("--check", action="store_true") + args = parser.parse_args() + + ontology = _serialize(_ontology_graph()) + shapes = _serialize(_shapes_graph()) + + rc = 0 + rc |= _write(ONTOLOGY_PATH, ontology, args.check) + rc |= _write(SHAPES_PATH, shapes, args.check) + + if args.check and rc != 0: + print("FAIL: guidance governance module files are out of date.") + print("Run: python3 tools/generate_guidance_governance_module.py") + return 1 + + if not args.check: + print(f"Wrote {ONTOLOGY_PATH.name}") + print(f"Wrote {SHAPES_PATH.name}") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tools/report_guidance_conflicts.py b/tools/report_guidance_conflicts.py new file mode 100644 index 0000000..d607d7d --- /dev/null +++ b/tools/report_guidance_conflicts.py @@ -0,0 +1,91 @@ +#!/usr/bin/env python3 +"""Report mixed-modality conflicts for guidance requirements.""" + +from __future__ import annotations + +import argparse +import json +from pathlib import Path + +from rdflib import Graph, Namespace +from rdflib.namespace import RDF, RDFS + + +UI = Namespace("https://nkllon.com/ui#") +POSITIVE = {str(UI.Must), str(UI.Should)} +NEGATIVE = {str(UI.MustNot), str(UI.ShouldNot)} + + +def _topic_label(g: Graph, iri) -> str: + label = g.value(iri, RDFS.label) + return str(label) if label else str(iri) + + +def _collect(data_path: Path, ontology_path: Path | None = None) -> dict[str, set[str]]: + g = Graph() + if ontology_path and ontology_path.exists(): + g.parse(ontology_path, format="turtle") + g.parse(data_path, format="turtle") + by_topic: dict[str, set[str]] = {} + + for req in g.subjects(RDF.type, UI.GuidanceRequirement): + topic = g.value(req, UI.hasGuidanceTopic) + modality = g.value(req, UI.hasModality) + if topic is None or modality is None: + continue + topic_name = _topic_label(g, topic) + by_topic.setdefault(topic_name, set()).add(str(modality)) + + return by_topic + + +def main() -> int: + parser = argparse.ArgumentParser() + parser.add_argument("--data", type=Path, required=True) + parser.add_argument("--base-data", type=Path) + parser.add_argument("--ontology", type=Path) + parser.add_argument("--key-topic", action="append", default=["github.auth", "ontology.governance"]) + parser.add_argument("--json", action="store_true") + args = parser.parse_args() + + current = _collect(args.data, args.ontology) + current_conflicts = [] + for topic, modalities in sorted(current.items()): + if modalities.intersection(POSITIVE) and modalities.intersection(NEGATIVE): + current_conflicts.append(topic) + + base_conflicts: list[str] = [] + if args.base_data and args.base_data.exists(): + base = _collect(args.base_data, args.ontology) + for topic, modalities in sorted(base.items()): + if modalities.intersection(POSITIVE) and modalities.intersection(NEGATIVE): + base_conflicts.append(topic) + + payload = { + "current_conflicts": current_conflicts, + "base_conflicts": base_conflicts, + "current_count": len(current_conflicts), + "base_count": len(base_conflicts), + "key_topic_conflicts": sorted(set(args.key_topic).intersection(current_conflicts)), + } + + if args.json: + print(json.dumps(payload, indent=2, sort_keys=True)) + else: + print("Current conflicts:", payload["current_count"]) + if current_conflicts: + print("Topics:", ", ".join(current_conflicts)) + if args.base_data: + print("Base conflicts:", payload["base_count"]) + if payload["key_topic_conflicts"]: + print("Key-topic conflicts:", ", ".join(payload["key_topic_conflicts"])) + + if payload["key_topic_conflicts"]: + return 1 + if args.base_data and payload["current_count"] > payload["base_count"]: + return 1 + return 0 + + +if __name__ == "__main__": + raise SystemExit(main())