Skip to content

feat(OMN-16882): theme instances + theme catalog — give a design-token value somewhere to live - #1608

Merged
github-actions[bot] merged 1 commit into
devfrom
jonah/omn-16882-phase-c-core-contracts
Aug 28, 2026
Merged

github-actions[bot] merged 1 commit into
devfrom
jonah/omn-16882-phase-c-core-contracts

Conversation

@jonahgabriel

@jonahgabriel jonahgabriel commented Aug 28, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Closes OMN-16882 (Phase C1 of epic OMN-16879, per omni_home/docs/plans/2026-08-27-omnidash-component-rework-plan.md §1.4.1 / §2.1 / Phase C item C1).

ModelRendererThemeContract is a schema: every token field is Field(...)-required under frozen=True, extra="forbid", and the class holds no values. scripts/emit_ts_types.py emits model_json_schema(). So the shipped pipeline had no way to carry a token value to a consumer, and "edit the Python class to change color_accent_primary" was never a workflow the code supported — there is no value in the class to edit.

This lands the instance layer the schema was always missing. It is purely additive; no existing model changes shape.

What lands

Object Role
ModelThemeInstance A schema-validated token value set plus the document header a lifecycle needs. theme_id / schema_version in the header are cross-checked against the embedded token set and fail closed on disagreement — a header that can drift from its body is worse than no header.
ModelThemeCatalogEntry One published, immutable revision: theme, revision, schema version, sha256:<hex> content digest, source path. The digest lives here, not on the instance, because an object cannot contain a hash that covers itself.
ModelThemeCatalog Every published revision, unique per (theme_id, instance_revision), resolved by exact revision — there is deliberately no latest.
ModelThemeActivation The active pointer: which surface renders which revision, carrying the digest it resolved and a monotonic activation_sequence.
utils/util_theme_catalog.py load / digest / build / activate / roll_back.
contracts/themes/<theme_id>/<revision>.yaml Instance documents for light / dark / warm, packaged data under the wheel's existing artifacts glob.

The three version axes are three fields

schema_version (token fields) · instance_revision (token values) · content digest (published bytes). Conflating any two is how a stale artifact passes on a matching version number, which is the precise failure §2.1 records. The digest is sha256 over the instance's canonical JSON (compute_canonical_hash), not over raw file bytes, so two surfaces loading one entry through different YAML writers still agree and formatting churn cannot read as a value change.

OD-2 resolved (plan §5.1)

Published revisions are a compiled artifact shipped with the library; the active pointer is runtime state. Revisions must be immutable, digested, and byte-reproducible offline so two surfaces agree on a digest with no service in the path — a registry projection cannot give that property to a build input. Which surface renders which revision is mutable, per-surface, and reported: that half is ModelThemeActivation. Splitting them is what makes "rollback moves a pointer and the digest proves the old bytes came back" mechanically true rather than asserted — revisions cannot move, so a restored pointer carrying the same digest bytes proves the old bytes returned rather than a fresh compile that happens to look similar.

Gates

Gate Evidence
GC.1 — a value change publishes, activates, and rolls back with no Python class edit test_a_token_value_changes_without_editing_a_python_class, test_rollback_moves_the_pointer_and_restores_the_original_digest
GC.2 — two loads of one entry report one digest test_digest_is_deterministic_across_independent_loads, test_digest_is_stable_across_a_serialize_reload_round_trip
C1(a) instances exist for light/dark/warm, validated in CI test_light_dark_warm_instances_exist_and_validate, test_every_shipped_instance_document_validates_against_the_schema
C1(b) three version axes are distinct fields test_instance_declares_schema_version_and_instance_revision_separately, test_a_value_change_moves_the_digest_but_not_the_schema_version
C1(d) a fresh TS mirror carries the schema test_emitter_registers_the_theme_schema_and_instance_layer, test_instance_json_schema_embeds_the_token_schema

dod_evidence

  • uv run pytest tests/unit/models/dashboard/ -q → 327 passed (26 new in test_theme_catalog.py).
  • uv run ruff format --check clean; uv run ruff check clean on every changed file.
  • uv run mypy src/ --strict → 14 errors, all pre-existing in unrelated files (backend_cache_redis, service_protocol_auditor, model_onex_container, …); zero in any file this PR touches.
  • Additive only: no existing model, validator, or emitter symbol changes shape.

Phase C1 exit unblocks Phase 1B's theme binding; Phase 1A is unaffected and may run in parallel.

Evidence-Ticket: OMN-16882
Evidence-Source: OCC#7466

…n value somewhere to live

ModelRendererThemeContract is a SCHEMA: every token field is Field(...)-required
under frozen=True, extra="forbid", and the class holds no values. The emitter
emits JSON Schema. Nothing in the shipped pipeline could carry a token VALUE to
a consumer, so "edit the Python class to change color_accent_primary" was never
a workflow the code supported.

This lands the instance layer the schema was always missing.

- ModelThemeInstance — a schema-validated token value set plus the document
  header a lifecycle needs. Header (theme_id, schema_version) is cross-checked
  against the embedded token set and fails closed on disagreement.
- ModelThemeCatalogEntry / ModelThemeCatalog — published, immutable, digested
  revisions, unique per (theme_id, instance_revision), resolved by exact
  revision only (there is deliberately no "latest").
- ModelThemeActivation — the active pointer: which surface renders which
  revision, carrying the digest it resolved and a monotonic sequence.
- util_theme_catalog — load, digest, build, activate, roll back.
- Instance documents for light / dark / warm under
  src/omnibase_core/contracts/themes/<theme_id>/<revision>.yaml, already
  covered by the wheel's artifacts glob.
- Emitter registers the theme schema plus the whole instance layer.

Three version axes, three distinct fields: schema_version (token FIELDS),
instance_revision (token VALUES), content digest (published bytes). Conflating
any two is how a stale artifact passes on a matching version number.

OD-2 resolved: published revisions are packaged immutable data; only the
activation pointer is runtime state. That split is what makes rollback provable
— revisions cannot move, so a restored pointer carrying the same digest proves
the old bytes came back rather than a lookalike recompile.

Gates: GC.1 (value change -> publish -> activate -> rollback with no Python
class edit) and GC.2 (two loads of one entry report one digest) are covered by
tests/unit/models/dashboard/test_theme_catalog.py.
@coderabbitai

coderabbitai Bot commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

Next included review available in 8 seconds.

View limit details

Limit details: You’ve used the included review currently available. Your 131 included PR review attempts over the past 7 days set your current allowance at 1 review per hour.

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 0ef43652-b137-4543-8482-6d29e8ca0243

📥 Commits

Reviewing files that changed from the base of the PR and between 8c41a3c and c7d566d.

📒 Files selected for processing (12)
  • scripts/emit_ts_types.py
  • src/omnibase_core/contracts/themes/onex.theme.dark/1.0.0.yaml
  • src/omnibase_core/contracts/themes/onex.theme.light/1.0.0.yaml
  • src/omnibase_core/contracts/themes/onex.theme.warm/1.0.0.yaml
  • src/omnibase_core/models/dashboard/__init__.py
  • src/omnibase_core/models/dashboard/model_theme_activation.py
  • src/omnibase_core/models/dashboard/model_theme_catalog.py
  • src/omnibase_core/models/dashboard/model_theme_catalog_entry.py
  • src/omnibase_core/models/dashboard/model_theme_instance.py
  • src/omnibase_core/utils/util_theme_catalog.py
  • tests/unit/models/dashboard/test_dashboard_imports.py
  • tests/unit/models/dashboard/test_theme_catalog.py

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

@github-actions
github-actions Bot enabled auto-merge (squash) August 28, 2026 21:44
jonahgabriel added a commit to OmniNode-ai/onex_change_control that referenced this pull request Aug 28, 2026
…#7466)

* evidence: OCC companion pass 1 for OmniNode-ai/omnibase_core#1608

* evidence: OCC companion self-bind for #7466

* fix(OMN-16882): scope product proof to local done gate

* evidence(OMN-16882): add missing test_passes receipt

---------

Co-authored-by: node-occ-companion-effect <occ-companion-effect@omninode.ai>
Co-authored-by: jonahgabriel <jonah@omninode.ai>
@github-actions
github-actions Bot merged commit f3cb4d7 into dev Aug 28, 2026
313 of 354 checks passed
@github-actions
github-actions Bot deleted the jonah/omn-16882-phase-c-core-contracts branch August 28, 2026 22:55
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