Skip to content

chore(scripts): relocate topic-parity scripts from omni_home [OMN-9286] - #1352

Merged
jonahgabriel merged 4 commits into
mainfrom
jonah/omn-9286-relocate-topic-scripts
Apr 20, 2026
Merged

jonahgabriel merged 4 commits into
mainfrom
jonah/omn-9286-relocate-topic-scripts

Conversation

@jonahgabriel

@jonahgabriel jonahgabriel commented Apr 20, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Relocates two topic-parity scripts out of omni_home/scripts/ (which is blocked by the no-functional-code pre-commit hook) into omnibase_infra/scripts/, following the OMN-4922 pattern.

  • scripts/check-topic-parity.py (OMN-4963)
  • scripts/sync-topic-registry.py (OMN-4962)

Both were pre-existing violations (merged 2026-03-13 via PRs #50/#51 in omni_home) blocking unrelated docs-only PRs from running pre-commit run --all-files cleanly.

Changes

  • Copy both scripts into omnibase_infra/scripts/, preserving shebang + exec bits
  • Replace module-level global state with an OMNI_HOME env var (fail-fast via _default_omni_home(), with Path(__file__).resolve().parents[2] as fallback) plus a frozen ModelTopicParityPaths Pydantic model
  • Add SPDX headers (per omnibase_infra/CLAUDE.md SPDX policy)
  • Fix 5 pre-existing PLW0603 (global-statement) violations in check-topic-parity.py via the path-model refactor
  • Fix 7 pre-existing mypy --strict missing-type-arg violations in sync-topic-registry.py by parameterizing dict → dict[str, str]
  • Add tests/scripts/test_topic_parity_scripts.py with 6 smoke tests (shebang, SPDX, argparse, env-var resolution)

Companion PR (to land after this merges)

A follow-up omni_home PR will:

  1. git rm both scripts from omni_home/scripts/
  2. Update .github/workflows/topic-parity.yml to invoke ./omnibase_infra/scripts/...

Sequenced to avoid a mid-flight CI break: this PR lands first so the new paths exist; the omni_home deletion + workflow repoint ships as one commit.

Test plan

  • uv run pytest tests/scripts/test_topic_parity_scripts.py -v — 6/6 passing
  • uv run ruff check scripts/check-topic-parity.py scripts/sync-topic-registry.py — clean
  • uv run mypy --strict scripts/check-topic-parity.py scripts/sync-topic-registry.py — clean
  • pre-commit run --files ... — all hooks pass including SPDX + AI-slop
  • Functional smoke: OMNI_HOME=/Users/jonah/Code/omni_home uv run python scripts/{check,sync}-... --check runs with expected output
  • CI green on this branch

Summary by CodeRabbit

  • Chores

    • Added two CLI tools: a topic-parity validator to verify consumer topic lists against the canonical registry, and a sync tool to generate/update deterministic topic definitions from the registry.
  • Tests

    • Added end-to-end tests for the new CLIs covering help/flags, error handling, registry/schema validation, parity/drift detection, and deterministic output validation.

omni_home/scripts/ is blocked by the no-functional-code pre-commit hook,
which rejects any .py/.sh file in that directory. Two pre-existing scripts
(check-topic-parity.py, sync-topic-registry.py — PRs #50/#51, 2026-03-13)
violated this and were blocking unrelated docs-only PRs. Relocating to
omnibase_infra/scripts/ per the OMN-4922 pattern (pull-all.sh).

Changes:
* Copy both scripts to omnibase_infra/scripts/ preserving exec bits
* Replace module-level global state with OMNI_HOME env var + ModelTopicParityPaths
* Add SPDX headers and satisfy mypy --strict + ruff (5 pre-existing PLW0603
  + 7 missing-type-arg violations fixed in the move)
* Add tests/scripts/test_topic_parity_scripts.py covering shebang, SPDX,
  argparse surface, and OMNI_HOME resolution

Companion omni_home PR will delete the originals and repoint the CI
workflow (.github/workflows/topic-parity.yml) at the new location.
@github-actions
github-actions Bot enabled auto-merge April 20, 2026 12:39
@coderabbitai

coderabbitai Bot commented Apr 20, 2026 •

Copy link
Copy Markdown

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 9b488936-f283-494a-abe4-afeb7997dc1f

📥 Commits

Reviewing files that changed from the base of the PR and between 0798251 and 08e4838.

📒 Files selected for processing (1)
  • tests/scripts/test_topic_parity_scripts.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/scripts/test_topic_parity_scripts.py

📝 Walkthrough

Walkthrough

Adds two CLI Python tools: one validates topic parity between the canonical topic_registry.yaml and TypeScript consumer lists; the other generates deterministic TypeScript topic constants from the registry. Includes pytest tests exercising CLI flags, error handling, and generation/check behaviors.

Changes

Cohort / File(s) Summary
Topic Validation Script
scripts/check-topic-parity.py
New CLI: resolves OMNI_HOME/--omni-home, loads topic_registry.yaml, parses omnidash/shared/topics.ts and consumer arrays (READ_MODEL_TOPICS, EXPECTED_TOPICS), resolves constants and spread expressions, compares sets, prints diagnostics, and returns exit codes 0/1/2.
Topic Registry Sync Script
scripts/sync-topic-registry.py
New CLI: resolves OMNI_HOME/paths, loads and validates topic_registry.yaml, derives stable constant names (uses topic_base_constant or event_type), generates deterministic TypeScript block (escaped JSDoc), and supports --check, --write, --dry-run with marker-based replace/insert logic.
Tests for Scripts
tests/scripts/test_topic_parity_scripts.py
New pytest module: verifies shebang/SPDX header, argparse options, behavior when OMNI_HOME is missing, YAML validation errors, missing consumer arrays, parity mismatch reporting, and sync-topic-registry --dry-run JSDoc escaping using synthetic omni_home fixtures and subprocess runs.

Sequence Diagram(s)

sequenceDiagram
    participant CLI as CLI (check-topic-parity)
    participant Registry as Registry (topic_registry.yaml)
    participant TopicsTS as TopicsTS (omnidash/shared/topics.ts)
    participant Consumers as Consumers (omnidash/server/*.ts)
    participant Comparator as Comparator

    CLI->>Registry: load & validate YAML
    CLI->>TopicsTS: parse exported topic constants
    CLI->>Consumers: parse READ_MODEL_TOPICS & EXPECTED_TOPICS
    CLI->>CLI: resolve constants & spread expressions
    CLI->>Comparator: compare registry vs consumer sets
    Comparator-->>CLI: return result (OK or TOPIC PARITY FAILURE)
Loading
sequenceDiagram
    participant CLI as CLI (sync-topic-registry)
    participant Registry as Registry (topic_registry.yaml)
    participant Generator as Generator
    participant TopicsTS as TopicsTS (omnidash/shared/topics.ts)
    participant FS as FileSystem

    CLI->>Registry: load & validate YAML
    CLI->>Generator: derive constant names & generate block
    CLI->>TopicsTS: locate BEGIN/END marker or insertion point
    CLI->>FS: (--check) compare / (--write) replace-or-append / (--dry-run) print
    FS-->>CLI: report result (OK or DRIFT)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 I hopped through YAML and chased each string,

constants aligned—what tidy things!
I sniffed the spreads and parsed each dot,
parity checked, then twitched one spot.
A tidy registry—now let's hop!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 70.97% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically describes the main change: relocating topic-parity scripts from omni_home to omnibase_infra/scripts, with the ticket reference (OMN-9286) providing additional context.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch jonah/omn-9286-relocate-topic-scripts

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@scripts/check-topic-parity.py`:
- Around line 179-184: The current code prints a warning and returns an empty
set when an expected array (e.g., READ_MODEL_TOPICS or EXPECTED_TOPICS) is not
found (the branch checking "if not match"), which lets CI pass; change this to a
hard failure by replacing the warning+return with an immediate non-zero exit or
exception (e.g., call sys.exit(1) or raise RuntimeError) so missing source
arrays cause the script to fail; reference the variables/locals in the branch
such as match, array_name, and file_path to locate and update the error
handling.
- Around line 254-310: The script only checks expected/read_model topics against
registry but not the reverse; add checks that any omniclaude topics in
registry_topics are actually referenced in EXPECTED_TOPICS and
READ_MODEL_TOPICS. After the existing parity checks (after computing
expected_not_in_registry and read_model_not_in_registry), compute
registry_not_in_expected = registry_topics - expected_topics and
registry_not_in_read_model = registry_topics - read_model_topics, filter each
set to omniclaude topics (e.g., ".omniclaude." and optional ".evt." if you want
to limit to events), and append descriptive error messages to errors for any
missing registry topics so the script returns non‑zero when registry entries are
not wired into EXPECTED_TOPICS/READ_MODEL_TOPICS. Ensure you use the same error
formatting style (errors.append and sorted iteration) as the surrounding checks.
- Around line 117-125: The YAML loader assumes the parsed `data` is a mapping
and will raise AttributeError for non-dict roots; after `data =
yaml.safe_load(f)` add a guard that checks `isinstance(data, dict)` (or
truthiness plus mapping type) and if it's not a dict print an error to stderr
(similar message as the missing topics case) and exit with code 2, then continue
to read `topics = data.get("topics", [])` and return the set comprehension
`{entry["topic"] for entry in topics}` as before.

In `@scripts/sync-topic-registry.py`:
- Around line 75-83: The code assumes yaml.safe_load(path) returns a mapping and
calls data.get("topics"), which will raise AttributeError for scalars, lists, or
None; update the logic after yaml.safe_load to validate that data is a
dict/mapping (i.e., isinstance(data, dict)) before accessing .get, and if it's
not a mapping print the same error (e.g., "ERROR: No topics found in {path}") to
stderr and sys.exit(2); reference the variables and call sites yaml.safe_load,
data, and topics to locate and guard the access.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: e7b24f62-2d51-417b-bffe-979048e9c3c0

📥 Commits

Reviewing files that changed from the base of the PR and between 7c1f4f1 and 86bacaf.

📒 Files selected for processing (3)
  • scripts/check-topic-parity.py
  • scripts/sync-topic-registry.py
  • tests/scripts/test_topic_parity_scripts.py

Comment thread scripts/check-topic-parity.py
Comment thread scripts/check-topic-parity.py
Comment thread scripts/check-topic-parity.py
Comment thread scripts/sync-topic-registry.py
…cripts

Four findings from the CodeRabbit review on PR #1352, all legitimate
correctness improvements to pre-existing behavior that's now in-scope
because we're already touching these files.

- CR #1, #4: yaml.safe_load may return None or a scalar; guard with
  isinstance check and fail fast with type-of-value in the message.
- CR #2 (MAJOR): missing top-level subscription arrays (READ_MODEL_TOPICS,
  EXPECTED_TOPICS) were a warning + silent pass. A rename or deletion of
  either array would silently succeed — exactly the breakage this gate
  exists to catch. Add required=True kwarg on top-level calls; recursive
  spread lookups still fall back to topics.ts with a warning.
- CR #3 (MAJOR): the parity check only walked consumer -> registry. A
  newly-declared registry topic that was never wired into READ_MODEL_TOPICS
  or EXPECTED_TOPICS passed the gate. Add a reverse check that every
  registry omniclaude evt topic is covered by both consumer arrays.

Tests: four new unit tests cover required-array failure, non-dict registry
rejection (both scripts), and reverse-parity failure. All 10 tests pass.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@scripts/sync-topic-registry.py`:
- Around line 85-90: The load_registry function currently only checks topics for
emptiness; add validation that each item in topics is a dict and contains a
'topic' key plus at least one of 'event_type' or 'topic_base_constant' (and
verify keys are strings as needed). For any invalid entry, print a clear error
to stderr including the path and the offending entry, then sys.exit(2). Keep the
existing topics return when all entries pass validation and reference the topics
variable and load_registry function when locating the code to change.
- Around line 140-142: The code appends raw description into a single-line JSDoc
which can be broken by embedded "*/" or newlines; update the generation to
sanitize and emit a safe multi-line JSDoc: when description is truthy, first
escape any occurrences of "*/" (e.g. replace with "*\/"), split the description
on newlines, then push a "/**" line, push each escaped line prefixed with " * "
into the lines list, and push the closing " */" before appending the "export
const {const_name} = '{topic}';" line so const_name/topic/lines are used to
locate the change.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 8f558d5a-8e85-4619-a9a8-3148b919a267

📥 Commits

Reviewing files that changed from the base of the PR and between 86bacaf and e275bc1.

📒 Files selected for processing (3)
  • scripts/check-topic-parity.py
  • scripts/sync-topic-registry.py
  • tests/scripts/test_topic_parity_scripts.py
🚧 Files skipped from review as they are similar to previous changes (2)
  • tests/scripts/test_topic_parity_scripts.py
  • scripts/check-topic-parity.py

Comment thread scripts/sync-topic-registry.py Outdated
Comment thread scripts/sync-topic-registry.py
Two follow-up CodeRabbit findings on the first fix commit:

- CR-minor: load_registry accepted any shape for topics entries; a dict
  missing 'topic' or both 'event_type'/'topic_base_constant' would raise
  a raw KeyError downstream instead of a structured exit-2 error with
  the offending index. Validate each entry's shape on load.

- CR-major: descriptions were injected verbatim into /** ... */ JSDoc.
  A description containing '*/' or a newline would break the generated
  TypeScript. Escape '*/' to '*\\/' and collapse newlines to spaces.

Tests: two new unit tests cover each case. All 12 tests pass.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
tests/scripts/test_topic_parity_scripts.py (1)

258-277: Isolate the READ_MODEL_TOPICS mismatch in this fixture.

This test claims to prove the consumer subscription check, but the fixture also omits the orphan topic from shared/topics.ts and EXPECTED_TOPICS. If the script reports those mismatches first, the test becomes order-dependent and no longer proves the READ_MODEL_TOPICS path.

Suggested fixture tweak
     (shared / "topics.ts").write_text(
-        "export const SUFFIX_OMNICLAUDE_COVERED = 'onex.evt.omniclaude.covered.v1';\n"
+        "export const SUFFIX_OMNICLAUDE_COVERED = 'onex.evt.omniclaude.covered.v1';\n"
+        "export const SUFFIX_OMNICLAUDE_ORPHAN = 'onex.evt.omniclaude.orphan.v1';\n"
     )
     (server / "read-model-consumer.ts").write_text(
         "const READ_MODEL_TOPICS: string[] = [SUFFIX_OMNICLAUDE_COVERED];\n"
     )
     (server / "event-bus-health-poller.ts").write_text(
-        "const EXPECTED_TOPICS: string[] = [SUFFIX_OMNICLAUDE_COVERED];\n"
+        "const EXPECTED_TOPICS: string[] = [SUFFIX_OMNICLAUDE_COVERED, SUFFIX_OMNICLAUDE_ORPHAN];\n"
     )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/scripts/test_topic_parity_scripts.py` around lines 258 - 277, The test
fixture hides the real target (READ_MODEL_TOPICS mismatch) by also omitting the
orphan topic from shared/topics.ts and EXPECTED_TOPICS; update the fixture so
shared/topics.ts exports both topic suffix constants (e.g., add a constant for
onex.evt.omniclaude.orphan.v1) and ensure the EXPECTED_TOPICS array (in
event-bus-health-poller.ts) includes both constants, leaving READ_MODEL_TOPICS
(in read-model-consumer.ts) unchanged so the test isolates the READ_MODEL_TOPICS
mismatch.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@tests/scripts/test_topic_parity_scripts.py`:
- Around line 240-247: The test currently only collects lines that start with
"/**" via jsdoc_lines and therefore never inspects a multi-line JSDoc block;
change the logic in tests/scripts/test_topic_parity_scripts.py to locate the
opening JSDoc line (use result.stdout.splitlines()), then collect the full block
by iterating from that index until the closing "*/" line is reached (or until a
line containing "*/"), then run assertions against the full joined block text
(e.g., ensure the block does not contain an unescaped "*/" and that it contains
the escaped "*\\/" sequence); reference the existing jsdoc_lines variable and
result.stdout when implementing the start-and-collect approach.

---

Nitpick comments:
In `@tests/scripts/test_topic_parity_scripts.py`:
- Around line 258-277: The test fixture hides the real target (READ_MODEL_TOPICS
mismatch) by also omitting the orphan topic from shared/topics.ts and
EXPECTED_TOPICS; update the fixture so shared/topics.ts exports both topic
suffix constants (e.g., add a constant for onex.evt.omniclaude.orphan.v1) and
ensure the EXPECTED_TOPICS array (in event-bus-health-poller.ts) includes both
constants, leaving READ_MODEL_TOPICS (in read-model-consumer.ts) unchanged so
the test isolates the READ_MODEL_TOPICS mismatch.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: bd1f32e1-b12a-4fe2-ac2e-4399f13b896b

📥 Commits

Reviewing files that changed from the base of the PR and between e275bc1 and 0798251.

📒 Files selected for processing (2)
  • scripts/sync-topic-registry.py
  • tests/scripts/test_topic_parity_scripts.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • scripts/sync-topic-registry.py

Comment thread tests/scripts/test_topic_parity_scripts.py Outdated
CodeRabbit flagged that the previous test only filtered lines starting
with /** and never inspected the full /** ... */ block body, making the
*/ check vacuous. Parse complete JSDoc blocks with a regex so the
assertion actually verifies the escape (and that newlines are
collapsed).
@github-actions
github-actions Bot added this pull request to the merge queue Apr 20, 2026
@jonahgabriel
jonahgabriel removed this pull request from the merge queue due to a manual request Apr 20, 2026
@jonahgabriel
jonahgabriel added this pull request to the merge queue Apr 20, 2026
@jonahgabriel

Copy link
Copy Markdown
Collaborator Author

Follow-up CR findings addressed in 0798251 (entry shape + JSDoc escape) and 08e4838 (stronger test assertion per CR review at 13:11Z). All 12 local tests passing. Re-running CR thread gate.

Merged via the queue into main with commit 009033e Apr 20, 2026
56 of 58 checks passed
@jonahgabriel
jonahgabriel deleted the jonah/omn-9286-relocate-topic-scripts branch April 20, 2026 13:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant