catalog domain is over-modelled — collapse ServiceDefinition to name + description + interfaces #1

Closed
opened 2026-05-20 07:36:37 +00:00 by timur · 2 comments
Owner

Problem

crates/hero_service/schemas/catalog/catalog.oschema models a ServiceDefinition that re-implements service.toml instead of just referencing it. Today's shape:

ServiceDefinition = {
    sid:          str               # ← auto-injected, never declare (hero_skills#275)
    name:         str @index
    repo:         str @index
    display:      str
    description:  str
    category:     ServiceCategory   # ← redeclares herolib_core::base::Category (drift!)
    status:       ServiceStatus
    version:      str
    binaries:     [BinaryDef]       # ← redeclares ServiceToml's `binaries`
    schema_files: [str]
    tags:         [str]
    created_at:   otime             # ← auto-injected, never declare
    updated_at:   otime             # ← auto-injected, never declare
}

Plus five supporting enums and two embedded structs (SocketDef, BinaryDef) that redeclare the closed-set enums and structs that already live in herolib_core::baseKind, Category, Protocol, SockType, Binary, Socket, ServiceMeta, ServiceToml. The local enums have already drifted from canon (e.g. BinaryKind adds app; ServiceCategory is a different set than Category).

Why it's wrong

The hero_service catalog isn't supposed to be a parallel registry of binaries and sockets — service.toml already is that, and it ships with every service binary. The catalog only needs enough to locate a service; everything else can be read from its service.toml on demand.

Proposed shape

A minimal ServiceDefinition:

Interface = "server" | "admin" | "cli" | "web"   # which surfaces a service exposes

# A Hero service tracked by the catalog [rootobject]
ServiceDefinition = {
    name:        str @index   # canonical service name, e.g. "hero_proc"
    description: str          # one-paragraph description
    interfaces:  [Interface]  # which surfaces this service exposes
}

That's it. sid/created_at/updated_at auto-injected by the generator. Everything else (repo, binaries, sockets, version, category, …) lives in the service's own service.toml and is read at query time.

With this collapse:

  • No redeclared enums/structs — issue hero_rpc#72 is satisfied without even needing the use-import language feature here (though that feature is still valuable for other consumers).
  • No drift surface between ServiceCategory and herolib_core::base::Category.
  • service.toml stays the single source of truth.

What to do

  1. Replace crates/hero_service/schemas/catalog/catalog.oschema with the minimal shape above (plus whatever service ServiceCatalog { … } methods the admin UI needs — get_by_interface(...), get_by_name(...), etc.).
  2. Regenerate crates/hero_service/src/catalog/types_generated.rs — should be ~20 lines: one ServiceDefinition struct and one Interface enum.
  3. Update seed data / fixtures to match the new shape.
  4. Update admin UI templates that referenced repo / binaries / category to either read from service.toml at request time or drop the columns.

Acceptance

  • catalog.oschema declares no types that overlap with herolib_core::base.
  • ServiceDefinition is ≤4 user-visible fields (name, description, interfaces, optional tags).
  • crates/hero_service builds clean.
  • hero_service_admin lists services correctly (reading service.toml for live metadata).

Refs

  • Parent: lhumina_code/hero_rpc#72 (which was framed as "stop regenerating ServiceToml types" — collapsing the catalog is the cleaner fix).
  • Related: lhumina_code/hero_skills#275 (auto-injected fields skill bug — applies to the sid/created_at/updated_at lines in current catalog.oschema).
  • Original bootstrap (where the over-modelled shape came from): commit ed0a1e1 in this repo, refs hero_skills#261.
## Problem `crates/hero_service/schemas/catalog/catalog.oschema` models a `ServiceDefinition` that **re-implements `service.toml`** instead of just referencing it. Today's shape: ```oschema ServiceDefinition = { sid: str # ← auto-injected, never declare (hero_skills#275) name: str @index repo: str @index display: str description: str category: ServiceCategory # ← redeclares herolib_core::base::Category (drift!) status: ServiceStatus version: str binaries: [BinaryDef] # ← redeclares ServiceToml's `binaries` schema_files: [str] tags: [str] created_at: otime # ← auto-injected, never declare updated_at: otime # ← auto-injected, never declare } ``` Plus five supporting enums and two embedded structs (`SocketDef`, `BinaryDef`) that **redeclare** the closed-set enums and structs that already live in [`herolib_core::base`](https://forge.ourworld.tf/lhumina_code/hero_lib/src/branch/development/crates/core/src/base/service.rs) — `Kind`, `Category`, `Protocol`, `SockType`, `Binary`, `Socket`, `ServiceMeta`, `ServiceToml`. The local enums have **already drifted** from canon (e.g. `BinaryKind` adds `app`; `ServiceCategory` is a different set than `Category`). ## Why it's wrong The `hero_service` catalog isn't supposed to be a parallel registry of binaries and sockets — `service.toml` already is that, and it ships with every service binary. The catalog only needs enough to **locate** a service; everything else can be read from its `service.toml` on demand. ## Proposed shape A minimal `ServiceDefinition`: ```oschema Interface = "server" | "admin" | "cli" | "web" # which surfaces a service exposes # A Hero service tracked by the catalog [rootobject] ServiceDefinition = { name: str @index # canonical service name, e.g. "hero_proc" description: str # one-paragraph description interfaces: [Interface] # which surfaces this service exposes } ``` That's it. `sid`/`created_at`/`updated_at` auto-injected by the generator. Everything else (`repo`, `binaries`, `sockets`, `version`, `category`, …) lives in the service's own `service.toml` and is read at query time. With this collapse: - No redeclared enums/structs — issue [hero_rpc#72](https://forge.ourworld.tf/lhumina_code/hero_rpc/issues/72) is satisfied without even needing the `use`-import language feature here (though that feature is still valuable for other consumers). - No drift surface between `ServiceCategory` and `herolib_core::base::Category`. - `service.toml` stays the single source of truth. ## What to do 1. Replace `crates/hero_service/schemas/catalog/catalog.oschema` with the minimal shape above (plus whatever `service ServiceCatalog { … }` methods the admin UI needs — `get_by_interface(...)`, `get_by_name(...)`, etc.). 2. Regenerate `crates/hero_service/src/catalog/types_generated.rs` — should be ~20 lines: one `ServiceDefinition` struct and one `Interface` enum. 3. Update seed data / fixtures to match the new shape. 4. Update admin UI templates that referenced `repo` / `binaries` / `category` to either read from `service.toml` at request time or drop the columns. ## Acceptance - `catalog.oschema` declares **no** types that overlap with `herolib_core::base`. - `ServiceDefinition` is ≤4 user-visible fields (`name`, `description`, `interfaces`, optional `tags`). - `crates/hero_service` builds clean. - `hero_service_admin` lists services correctly (reading `service.toml` for live metadata). ## Refs - Parent: https://forge.ourworld.tf/lhumina_code/hero_rpc/issues/72 (which was framed as "stop regenerating ServiceToml types" — collapsing the catalog is the cleaner fix). - Related: https://forge.ourworld.tf/lhumina_code/hero_skills/issues/275 (auto-injected fields skill bug — applies to the `sid`/`created_at`/`updated_at` lines in current `catalog.oschema`). - Original bootstrap (where the over-modelled shape came from): commit `ed0a1e1` in this repo, refs `hero_skills#261`.
Author
Owner

Expanded scope — make hero_service the meta-service that manages hero services

Original framing of this issue was "collapse the over-modelled catalog schema." That stands, but the bigger picture from chat:

hero_service is a template service today — mock it as if it's a real service to bootstrap, refactor, check, and verify hero_services, using hero_rpc and also hero_skills and AI where necessary.

So the catalog isn't just a data store; it's the public surface of an operational meta-service. Implementations will be mocked for this issue (canned responses + clearly-marked TODO stubs at every integration point), but the schema, RPC surface, and module wiring will be real.

Updated catalog.oschema (proposed)

# Surfaces a hero service exposes — used by the catalog to filter / route to UIs.
Interface = "server" | "admin" | "cli" | "web" | "rhai"

# Outcome of a meta-operation (bootstrap / refactor / check / verify).
OpStatus = "ok" | "warning" | "error"

# One diagnostic produced by a check or verify run.
Diagnostic = {
    severity: OpStatus    # "warning" or "error"; "ok" is unused here
    code:     str         # e.g. "missing-service-toml", "socket-path-drift"
    message:  str         # human-readable
    file:     str         # repo-relative path or "" if not file-scoped
}

# Result of any meta-op.
OpReport = {
    status:      OpStatus
    summary:     str
    diagnostics: [Diagnostic]
}

# A Hero service tracked by the catalog [rootobject]
ServiceDefinition = {
    name:        str @index   # canonical service name, e.g. "hero_proc"
    description: str          # one-paragraph description
    interfaces:  [Interface]  # which surfaces this service exposes
}

# Meta-domain — manages hero services.
service ServiceCatalog {
    version: "1.0.0"
    description: "Bootstrap, refactor, check and verify Hero services."

    # ── Queries over the catalog ───────────────────────────────
    list() -> [ServiceDefinition]
    get_by_name(name: str) -> [ServiceDefinition]
    list_by_interface(interface: Interface) -> [ServiceDefinition]

    # ── Operations on a hero service ───────────────────────────
    # Scaffold a fresh hero service via `hero_rpc` generator + skills guidance.
    bootstrap(name: str, description: str, interfaces: [Interface]) -> OpReport

    # AI-driven refactor against a natural-language prompt — uses herolib_ai
    # with the relevant skills loaded as system context.
    refactor(name: str, prompt: str) -> OpReport

    # Static validation: service.toml shape, socket conventions, schema hygiene,
    # build_lib targets, branching model. Reads skills as the rule source.
    check(name: str) -> OpReport

    # End-to-end verify: build, run --info / --info --json, smoke-test sockets.
    verify(name: str) -> OpReport
}

Notes against the schema:

  • No sid / created_at / updated_at declared — those are auto-injected per hero_skills#275.
  • No types overlap with herolib_core::base — closes the original hero_rpc#72 symptom.
  • OpReport / Diagnostic / OpStatus are owned by the catalog (op-specific), not service-catalog-of-services concepts; they belong here.

Integration points (mocked here, real later)

Method Real impl will use
bootstrap hero_rpc_osis::build::OschemaBuilder + hero_skills/skills/* for the template
refactor herolib_ai::AiClient::from_env() + matching skill body as system prompt
check Rule packs from hero_skills (service.toml shape, socket conventions, hero_branching, hero_crates_best_practices_check)
verify cargo build, --info --json parse, socket connect, smoke tests

For this issue, each handler returns a clearly canned OpReport and a TODO comment naming the exact integration it would call. That gives the admin UI + SDK something to drive, without committing to a 2-week AI-orchestrator build right now.

Plan

  1. Replace schemas/catalog/catalog.oschema with the schema above.
  2. Regenerate crates/hero_service/src/catalog/types_generated.rs (should be small now — ServiceDefinition + Interface + OpStatus + Diagnostic + OpReport).
  3. Replace handlers in crates/hero_service_server/src/catalog/rpc.rs with mocked impls returning canned reports.
  4. cargo build --workspace clean.
  5. Branch issue-1-meta-service, push, open PR.
## Expanded scope — make `hero_service` the meta-service that *manages* hero services Original framing of this issue was "collapse the over-modelled catalog schema." That stands, but the bigger picture from chat: > `hero_service` is a template service today — mock it as if it's a real service to **bootstrap, refactor, check, and verify** hero_services, using `hero_rpc` and also `hero_skills` and AI where necessary. So the catalog isn't just a data store; it's the public surface of an *operational* meta-service. Implementations will be mocked for this issue (canned responses + clearly-marked TODO stubs at every integration point), but the schema, RPC surface, and module wiring will be real. ### Updated `catalog.oschema` (proposed) ```oschema # Surfaces a hero service exposes — used by the catalog to filter / route to UIs. Interface = "server" | "admin" | "cli" | "web" | "rhai" # Outcome of a meta-operation (bootstrap / refactor / check / verify). OpStatus = "ok" | "warning" | "error" # One diagnostic produced by a check or verify run. Diagnostic = { severity: OpStatus # "warning" or "error"; "ok" is unused here code: str # e.g. "missing-service-toml", "socket-path-drift" message: str # human-readable file: str # repo-relative path or "" if not file-scoped } # Result of any meta-op. OpReport = { status: OpStatus summary: str diagnostics: [Diagnostic] } # A Hero service tracked by the catalog [rootobject] ServiceDefinition = { name: str @index # canonical service name, e.g. "hero_proc" description: str # one-paragraph description interfaces: [Interface] # which surfaces this service exposes } # Meta-domain — manages hero services. service ServiceCatalog { version: "1.0.0" description: "Bootstrap, refactor, check and verify Hero services." # ── Queries over the catalog ─────────────────────────────── list() -> [ServiceDefinition] get_by_name(name: str) -> [ServiceDefinition] list_by_interface(interface: Interface) -> [ServiceDefinition] # ── Operations on a hero service ─────────────────────────── # Scaffold a fresh hero service via `hero_rpc` generator + skills guidance. bootstrap(name: str, description: str, interfaces: [Interface]) -> OpReport # AI-driven refactor against a natural-language prompt — uses herolib_ai # with the relevant skills loaded as system context. refactor(name: str, prompt: str) -> OpReport # Static validation: service.toml shape, socket conventions, schema hygiene, # build_lib targets, branching model. Reads skills as the rule source. check(name: str) -> OpReport # End-to-end verify: build, run --info / --info --json, smoke-test sockets. verify(name: str) -> OpReport } ``` Notes against the schema: - No `sid` / `created_at` / `updated_at` declared — those are auto-injected per [hero_skills#275](https://forge.ourworld.tf/lhumina_code/hero_skills/issues/275). - No types overlap with `herolib_core::base` — closes the original [hero_rpc#72](https://forge.ourworld.tf/lhumina_code/hero_rpc/issues/72) symptom. - `OpReport` / `Diagnostic` / `OpStatus` are owned by the catalog (op-specific), not service-catalog-of-services concepts; they belong here. ### Integration points (mocked here, real later) | Method | Real impl will use | |-------------|-----------------------------------------------------------------------------------| | `bootstrap` | `hero_rpc_osis::build::OschemaBuilder` + `hero_skills/skills/*` for the template | | `refactor` | `herolib_ai::AiClient::from_env()` + matching skill body as system prompt | | `check` | Rule packs from `hero_skills` (service.toml shape, socket conventions, hero_branching, hero_crates_best_practices_check) | | `verify` | `cargo build`, `--info --json` parse, socket connect, smoke tests | For this issue, each handler returns a clearly canned `OpReport` and a TODO comment naming the exact integration it would call. That gives the admin UI + SDK something to drive, without committing to a 2-week AI-orchestrator build right now. ### Plan 1. Replace `schemas/catalog/catalog.oschema` with the schema above. 2. Regenerate `crates/hero_service/src/catalog/types_generated.rs` (should be small now — `ServiceDefinition` + `Interface` + `OpStatus` + `Diagnostic` + `OpReport`). 3. Replace handlers in `crates/hero_service_server/src/catalog/rpc.rs` with mocked impls returning canned reports. 4. `cargo build --workspace` clean. 5. Branch `issue-1-meta-service`, push, open PR.
Author
Owner

Branch pushed: issue-1-meta-service.

Diff: 20 files, +1342 −1400. ServiceDefinition is now { name, description, interfaces: [Interface] } — auto-injected sid/created_at/updated_at, no ServiceToml mirroring. Closes hero_rpc#72 at the schema level.

RPC surface (catalog.oschema):

  • list / get_by_name / list_by_interface — real, backed by OsisCatalog.
  • bootstrap(name, description, interfaces) → OpReport — mocked. Real: hero_rpc_osis::build::OschemaBuilder + skills templates.
  • refactor(name, prompt) → OpReport — mocked. Real: herolib_ai::AiClient with skill bodies as system prompt.
  • check(name) → OpReport — mocked. Real: rule packs from hero_skills/skills/*.
  • verify(name) → OpReport — mocked. Real: cargo build + --info --json parse + socket connect.

Each handler returns a canned OpReport with a clear TODO[<op>] block in crates/hero_service_server/src/catalog/rpc.rs naming the exact crate / skill the production version will use.

Blocks on: hero_rpc#85 — generator rejects [rootobject] types without an explicit sid field. Cargo.tomls in this branch temporarily pin hero_rpc to issue-85-rootobj-sid-validation; revert to development after that lands.

Tests: cargo test --workspace clean (2 catalog CRUD tests pass).

Branch pushed: `issue-1-meta-service`. **Diff:** 20 files, +1342 −1400. `ServiceDefinition` is now `{ name, description, interfaces: [Interface] }` — auto-injected `sid`/`created_at`/`updated_at`, no `ServiceToml` mirroring. Closes [hero_rpc#72](https://forge.ourworld.tf/lhumina_code/hero_rpc/issues/72) at the schema level. **RPC surface (catalog.oschema):** - `list` / `get_by_name` / `list_by_interface` — real, backed by `OsisCatalog`. - `bootstrap(name, description, interfaces) → OpReport` — mocked. Real: `hero_rpc_osis::build::OschemaBuilder` + skills templates. - `refactor(name, prompt) → OpReport` — mocked. Real: `herolib_ai::AiClient` with skill bodies as system prompt. - `check(name) → OpReport` — mocked. Real: rule packs from `hero_skills/skills/*`. - `verify(name) → OpReport` — mocked. Real: `cargo build` + `--info --json` parse + socket connect. Each handler returns a canned `OpReport` with a clear `TODO[<op>]` block in `crates/hero_service_server/src/catalog/rpc.rs` naming the exact crate / skill the production version will use. **Blocks on:** [hero_rpc#85](https://forge.ourworld.tf/lhumina_code/hero_rpc/issues/85) — generator rejects `[rootobject]` types without an explicit `sid` field. `Cargo.toml`s in this branch temporarily pin `hero_rpc` to `issue-85-rootobj-sid-validation`; revert to `development` after that lands. **Tests:** `cargo test --workspace` clean (2 catalog CRUD tests pass).
timur closed this issue 2026-05-20 08:18:18 +00:00
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
lhumina_code/hero_service#1
No description provided.