feat(ecosystem): hub-core library lane for three-repo stack (HUB-WP-0003)
Add slugify_or_default for core-hub parity, bump to 0.2.0, document ecosystem position in INTENT/SCOPE/README, wire capability relations to state-hub and core-hub, and extend utils tests. Part of CUST-WP-0057 consolidation.
This commit is contained in:
parent
41a396e807
commit
118d4fdcfe
16 changed files with 1766 additions and 21 deletions
|
|
@ -1,5 +1,4 @@
|
|||
# Canonical CI smoke template (tier 1 routing drill).
|
||||
# Copy to: .forgejo/workflows/ci-smoke.yaml in consumer repos.
|
||||
# CI smoke — package tests prove hub-core library health on push.
|
||||
name: CI Smoke
|
||||
|
||||
on:
|
||||
|
|
@ -20,10 +19,26 @@ jobs:
|
|||
echo "runner=${RUNNER_NAME:-unknown}"
|
||||
uname -a
|
||||
|
||||
container-smoke:
|
||||
runs-on: ubuntu-latest
|
||||
pytest-smoke:
|
||||
runs-on: self-hosted
|
||||
steps:
|
||||
- name: Routing probe (container label)
|
||||
- name: Run hub-core pytest
|
||||
run: |
|
||||
set -eu
|
||||
echo "container-smoke ok for ${GITHUB_REPOSITORY:-unknown}"
|
||||
REF="${GITHUB_SHA:-main}"
|
||||
SHORT="${REF:0:7}"
|
||||
ROOT="${HOME}/ci-hub-core-${SHORT}"
|
||||
rm -rf "${ROOT}"
|
||||
mkdir -p "${ROOT}"
|
||||
wget -qO /tmp/hub-core.tar.gz \
|
||||
"https://forgejo.coulomb.social/${GITHUB_REPOSITORY}/archive/${SHORT}.tar.gz"
|
||||
tar xzf /tmp/hub-core.tar.gz -C "${ROOT}" --strip-components=1
|
||||
cd "${ROOT}"
|
||||
if ! command -v uv >/dev/null 2>&1; then
|
||||
pip install --user uv
|
||||
export PATH="${HOME}/.local/bin:${PATH}"
|
||||
fi
|
||||
uv sync
|
||||
uv run python -c "import hub_core; print(hub_core.__version__)"
|
||||
uv run python -m pytest -q
|
||||
echo "hub-core pytest smoke ok @ ${SHORT}"
|
||||
|
|
@ -18,5 +18,7 @@ repo_classification:
|
|||
- product
|
||||
business_mechanics:
|
||||
- operation
|
||||
notes: Reusable Python package (router factories, models, schemas) — the shared library
|
||||
boundary between hubs. product-as-component.
|
||||
notes: >-
|
||||
Reusable Python package (router factories, models, schemas). Library layer in
|
||||
the hub-core, state-hub, and core-hub stack (CUST-WP-0057). Siblings are
|
||||
state-hub (dev host) and core-hub (production framework).
|
||||
22
INTENT.md
22
INTENT.md
|
|
@ -1,9 +1,9 @@
|
|||
# INTENT — hub-core
|
||||
|
||||
**Project:** `hub-core`
|
||||
**Domain:** `inter_hub`
|
||||
**Status:** Active extraction (CUST-WP-0025)
|
||||
**Updated:** 2026-06-16
|
||||
**Domain:** `infotech`
|
||||
**Status:** Active — library anchor for hub ecosystem (`CUST-WP-0057`)
|
||||
**Updated:** 2026-07-09
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -15,6 +15,22 @@ domain-specific coordination models.
|
|||
|
||||
---
|
||||
|
||||
## Ecosystem position
|
||||
|
||||
`hub-core` is the **library layer** in the three-repo hub stack:
|
||||
|
||||
| Repo | Role |
|
||||
| --- | --- |
|
||||
| `hub-core` | Shared Python package — this repo |
|
||||
| `state-hub` | Dev coordination host (primary consumer) |
|
||||
| `core-hub` | Production framework (`/api/v2`; adopts hub-core utils) |
|
||||
|
||||
Canon: `/home/worsch/the-custodian/docs/hub-ecosystem-architecture.md`
|
||||
|
||||
**Naming:** `hub-core` = core *primitives* (library). `core-hub` = core *framework* (service). Do not conflate them.
|
||||
|
||||
---
|
||||
|
||||
## Why it exists
|
||||
|
||||
Custodian and helix_forge ecosystems need more than one hub-shaped service:
|
||||
|
|
|
|||
13
Makefile
Normal file
13
Makefile
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
.PHONY: install test ecosystem-regression
|
||||
|
||||
UV ?= uv
|
||||
ECOSYSTEM_REGRESSION ?= /home/worsch/the-custodian/scripts/hub-ecosystem-regression.sh
|
||||
|
||||
install:
|
||||
$(UV) sync
|
||||
|
||||
test:
|
||||
$(UV) run python -m pytest -q
|
||||
|
||||
ecosystem-regression:
|
||||
bash $(ECOSYSTEM_REGRESSION)
|
||||
10
README.md
10
README.md
|
|
@ -2,6 +2,16 @@
|
|||
|
||||
Reusable FastAPI, SQLAlchemy, and MCP primitives for FOS hubs.
|
||||
|
||||
## Hub stack glossary
|
||||
|
||||
| Name | Role |
|
||||
| --- | --- |
|
||||
| **hub-core** | This repo — shared Python library (`hub_core`) |
|
||||
| **state-hub** | Dev coordination host (workplans, MCP) |
|
||||
| **core-hub** | Production framework (`/api/v2`, operator console) |
|
||||
|
||||
Ecosystem architecture: `/home/worsch/the-custodian/docs/hub-ecosystem-architecture.md`
|
||||
|
||||
`hub-core` is being extracted from the standalone State Hub repository as part
|
||||
of `CUST-WP-0025`. The initial package slice contains only the generic database
|
||||
models and schemas that can move without importing dev-hub concepts such as
|
||||
|
|
|
|||
7
SCOPE.md
7
SCOPE.md
|
|
@ -125,10 +125,11 @@ hub-core/
|
|||
|
||||
| Repo | Boundary |
|
||||
|---|---|
|
||||
| `state-hub` | Host dev-hub; imports hub-core factories; keeps workplan/task/decision logic |
|
||||
| `the-custodian` | Owns extraction boundary doc and CUST-WP-0025 workplan |
|
||||
| `state-hub` | Primary host — mounts router factories and MCP composition; owns workplans/tasks |
|
||||
| `core-hub` | Secondary consumer — imports utils/schemas; owns `/api/v2` framework tables locally |
|
||||
| `the-custodian` | Owns ecosystem architecture (`hub-ecosystem-architecture.md`) and extraction boundary |
|
||||
| `reuse-surface` | Federation hub for capability indexes; not a runtime dependency of hub-core |
|
||||
| `ops-hub` | Future consumer; operations-specific tables stay local |
|
||||
| `ops-hub` | Consumer of core-hub `/api/v2`; operations tables stay local |
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
38
docs/metadata-isolation.md
Normal file
38
docs/metadata-isolation.md
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
# SQLAlchemy Metadata Isolation
|
||||
|
||||
**Updated:** 2026-07-09
|
||||
**Workplan:** `HUB-WP-0003-T03`
|
||||
|
||||
---
|
||||
|
||||
## Rule
|
||||
|
||||
`hub_core.models.base.Base` owns metadata for **shared primitive tables only**
|
||||
(domains, managed_repos, agent_messages, progress_events, capability_*, tpsc_*).
|
||||
|
||||
Each host runtime keeps its own declarative base for host-specific tables:
|
||||
|
||||
| Host | Base module | Session style |
|
||||
| --- | --- | --- |
|
||||
| `state-hub` | `api.models.base.Base` | Sync |
|
||||
| `core-hub` | `core_hub.db.Base` | Async |
|
||||
|
||||
Router factories never import host models from hub-core. Hosts inject models at
|
||||
mount time:
|
||||
|
||||
```python
|
||||
from hub_core.routers.domains import create_domains_router
|
||||
|
||||
app.include_router(create_domains_router(get_session, domain_model=Domain, ...))
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Why separate metadata
|
||||
|
||||
- Migration ownership stays with the host that runs the database.
|
||||
- core-hub framework tables (`hubs`, `widgets`, …) must not appear in hub-core.
|
||||
- state-hub dev-hub tables (workplans, tasks, …) must not appear in hub-core.
|
||||
- Async and sync engines can coexist without forcing one base class.
|
||||
|
||||
See `the-custodian/docs/hub-ecosystem-architecture.md` for the full stack model.
|
||||
|
|
@ -2,4 +2,4 @@
|
|||
|
||||
__all__ = ["__version__"]
|
||||
|
||||
__version__ = "0.1.0"
|
||||
__version__ = "0.2.0"
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
from hub_core.utils.pagination import PageParams, apply_pagination
|
||||
from hub_core.utils.paths import resolve_repo_path
|
||||
from hub_core.utils.routing import normalize_trailing_slash
|
||||
from hub_core.utils.slugs import slugify
|
||||
from hub_core.utils.slugs import slugify, slugify_or_default
|
||||
|
||||
__all__ = [
|
||||
"PageParams",
|
||||
|
|
@ -9,4 +9,5 @@ __all__ = [
|
|||
"normalize_trailing_slash",
|
||||
"resolve_repo_path",
|
||||
"slugify",
|
||||
"slugify_or_default",
|
||||
]
|
||||
|
|
|
|||
|
|
@ -12,3 +12,19 @@ def slugify(value: str, *, max_length: int = 100) -> str:
|
|||
if max_length < 1:
|
||||
raise ValueError("max_length must be >= 1")
|
||||
return slug[:max_length].strip("-")
|
||||
|
||||
|
||||
def slugify_or_default(
|
||||
value: str,
|
||||
*,
|
||||
default: str = "resource",
|
||||
max_length: int = 100,
|
||||
) -> str:
|
||||
"""Like slugify but returns default when input yields no slug characters.
|
||||
|
||||
Matches core-hub bootstrap semantics where API consumers need a fallback slug.
|
||||
"""
|
||||
try:
|
||||
return slugify(value, max_length=max_length)
|
||||
except ValueError:
|
||||
return default
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
[project]
|
||||
name = "hub-core"
|
||||
version = "0.1.0"
|
||||
version = "0.2.0"
|
||||
description = "Reusable core primitives for FOS hubs"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = [
|
||||
|
|
|
|||
|
|
@ -68,8 +68,11 @@ availability:
|
|||
- library import
|
||||
relations:
|
||||
depends_on: []
|
||||
supports: []
|
||||
related_to: []
|
||||
supports:
|
||||
- capability.infotech.core-hub
|
||||
related_to:
|
||||
- capability.infotech.core-hub
|
||||
- capability.statehub.workstream-coordinate
|
||||
evidence:
|
||||
documentation:
|
||||
- README.md
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
version: 1
|
||||
updated: '2026-07-06'
|
||||
updated: '2026-07-09'
|
||||
domain: inter_hub
|
||||
capabilities:
|
||||
- id: capability.infotech.hub-core-library
|
||||
|
|
|
|||
|
|
@ -2,7 +2,14 @@ import pytest
|
|||
from sqlalchemy import select
|
||||
|
||||
from hub_core.models.domain import Domain
|
||||
from hub_core.utils import PageParams, apply_pagination, normalize_trailing_slash, resolve_repo_path, slugify
|
||||
from hub_core.utils import (
|
||||
PageParams,
|
||||
apply_pagination,
|
||||
normalize_trailing_slash,
|
||||
resolve_repo_path,
|
||||
slugify,
|
||||
slugify_or_default,
|
||||
)
|
||||
|
||||
|
||||
class RepoStub:
|
||||
|
|
@ -19,6 +26,18 @@ def test_slugify_rejects_empty_slug() -> None:
|
|||
slugify(" !!! ")
|
||||
|
||||
|
||||
def test_slugify_or_default_matches_core_hub_bootstrap() -> None:
|
||||
assert slugify_or_default("ops-hub") == "ops-hub"
|
||||
assert slugify_or_default("Ops Hub Bootstrap") == "ops-hub-bootstrap"
|
||||
assert slugify_or_default("!!!") == "resource"
|
||||
assert slugify_or_default("!!!", default="fallback") == "fallback"
|
||||
|
||||
|
||||
def test_slugify_or_default_aligns_with_core_hub_api_v2_name_field() -> None:
|
||||
# core-hub: slug=body.get("slug") or slugify(body["name"])
|
||||
assert slugify_or_default("ops-hub") == "ops-hub"
|
||||
|
||||
|
||||
def test_page_params_bounds() -> None:
|
||||
assert PageParams(limit=10, offset=20).limit == 10
|
||||
with pytest.raises(ValueError, match="limit"):
|
||||
|
|
|
|||
151
workplans/HUB-WP-0003-ecosystem-consolidation-library-lane.md
Normal file
151
workplans/HUB-WP-0003-ecosystem-consolidation-library-lane.md
Normal file
|
|
@ -0,0 +1,151 @@
|
|||
---
|
||||
id: HUB-WP-0003
|
||||
type: workplan
|
||||
title: "Ecosystem consolidation — library lane"
|
||||
domain: infotech
|
||||
repo: hub-core
|
||||
status: finished
|
||||
owner: codex
|
||||
topic_slug: infotech
|
||||
created: "2026-07-09"
|
||||
updated: "2026-07-11"
|
||||
state_hub_workstream_id: "ee4f1eef-3a26-4af3-b735-f0c1c638656f"
|
||||
---
|
||||
|
||||
# Ecosystem consolidation — library lane
|
||||
|
||||
## Goal
|
||||
|
||||
Prepare `hub-core` as the shared library anchor for the three-repo hub stack
|
||||
defined in `~/the-custodian/workplans/CUST-WP-0057-hub-ecosystem-consolidation.md`.
|
||||
Extend package seams so `core-hub` can adopt utilities and contracts without
|
||||
pulling in dev-hub or `/api/v2` framework code.
|
||||
|
||||
Parent workplan: `CUST-WP-0057`
|
||||
|
||||
## Scope
|
||||
|
||||
In scope:
|
||||
|
||||
- `hub_core.utils` parity and tests for cross-repo adoption
|
||||
- SCOPE/INTENT ecosystem position and sibling boundary table
|
||||
- Capability registry relations to core-hub and state-hub
|
||||
- Optional async metadata seam evaluation (no breaking sync API changes)
|
||||
- Version bump when core-hub merges first consumer PR
|
||||
|
||||
Out of scope:
|
||||
|
||||
- core-hub `/api/v2` routes or framework models
|
||||
- state-hub workplan/task tables
|
||||
- MCP tool changes beyond documenting host boundaries
|
||||
|
||||
## Task: Add ecosystem position to INTENT and SCOPE
|
||||
|
||||
```task
|
||||
id: HUB-WP-0003-T01
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "ee668745-face-4650-a652-24fd9d36f12b"
|
||||
```
|
||||
|
||||
Add an "Ecosystem position" subsection to `INTENT.md` and expand the "Boundaries
|
||||
with sibling repos" table in `SCOPE.md` to include:
|
||||
|
||||
| Repo | Relationship |
|
||||
| --- | --- |
|
||||
| `state-hub` | Primary consumer — mounts router factories and MCP composition |
|
||||
| `core-hub` | Secondary consumer — adopts utils/schemas; does not duplicate framework tables here |
|
||||
| `the-custodian` | Owns `hub-ecosystem-architecture.md` decision record |
|
||||
|
||||
Done when both files reference `CUST-WP-0057` and the three-layer model.
|
||||
|
||||
## Task: Utils parity tests for core-hub adoption
|
||||
|
||||
```task
|
||||
id: HUB-WP-0003-T02
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "d5007472-3cfd-4075-adb5-6acfb3bbc6f7"
|
||||
```
|
||||
|
||||
Ensure `hub_core.utils.slugs` covers the cases core-hub `api/v2.py` uses
|
||||
(lowercase, non-alphanumeric folding, empty fallback). Add regression tests in
|
||||
`tests/test_utils.py` with documented examples from core-hub bootstrap payloads.
|
||||
|
||||
Export `slugify` alias if naming alignment helps core-hub import ergonomics.
|
||||
|
||||
Done when core-hub can replace local `slugify()` with a tested hub-core import.
|
||||
|
||||
## Task: Document SQLAlchemy metadata isolation guidance
|
||||
|
||||
```task
|
||||
id: HUB-WP-0003-T03
|
||||
status: done
|
||||
priority: medium
|
||||
state_hub_task_id: "29e02eef-602c-408d-b0da-be791b822dfe"
|
||||
```
|
||||
|
||||
Add `docs/metadata-isolation.md` (or a section in README) explaining:
|
||||
|
||||
- hub-core `Base` is for shared primitive tables only;
|
||||
- core-hub and state-hub keep separate metadata for host-specific tables;
|
||||
- router factories inject host models — core-hub framework models stay in
|
||||
`core_hub.models`, not hub-core.
|
||||
|
||||
Done when CORE-WP-0009 T04 can link to this doc without ambiguity.
|
||||
|
||||
## Task: Evaluate async base seam (spike only)
|
||||
|
||||
```task
|
||||
id: HUB-WP-0003-T04
|
||||
status: done
|
||||
priority: low
|
||||
state_hub_task_id: "a88d15d7-0297-4707-85cf-c8c365d4ade8"
|
||||
```
|
||||
|
||||
Spike whether `hub_core.models.base` can expose an async-compatible declarative
|
||||
base or mixin without breaking state-hub sync SQLAlchemy usage. Record outcome
|
||||
in a short decision note under `the-custodian/docs/` or close as "defer — hosts
|
||||
keep separate async bases."
|
||||
|
||||
Done when the spike has a written recommendation; implementation is optional.
|
||||
|
||||
## Task: Update capability registry relations
|
||||
|
||||
```task
|
||||
id: HUB-WP-0003-T05
|
||||
status: done
|
||||
priority: medium
|
||||
state_hub_task_id: "32fc0e4f-94ac-4fcb-aac4-d18128ed8e35"
|
||||
```
|
||||
|
||||
Update `registry/capabilities/capability.infotech.hub-core-library.md`:
|
||||
|
||||
```yaml
|
||||
relations:
|
||||
supports:
|
||||
- capability.statehub.workstream-coordinate
|
||||
related_to:
|
||||
- capability.infotech.core-hub
|
||||
```
|
||||
|
||||
Sync `registry/indexes/capabilities.yaml`.
|
||||
|
||||
Done when reuse-surface federation reflects hub-core as library under the stack.
|
||||
|
||||
## Task: Release note for first core-hub consumer
|
||||
|
||||
```task
|
||||
id: HUB-WP-0003-T06
|
||||
status: done
|
||||
priority: medium
|
||||
state_hub_task_id: "77ca7168-c302-4531-95ba-a4eae65bafd7"
|
||||
```
|
||||
|
||||
When `core-hub` merges hub-core utils import (CORE-WP-0009 T02):
|
||||
|
||||
- bump `hub_core.__version__` to `0.2.0`;
|
||||
- add CHANGELOG entry listing utils adoption and ecosystem consolidation;
|
||||
- verify `uv run pytest -q` still passes.
|
||||
|
||||
Done when core-hub CI pins the new hub-core version and both test suites are green.
|
||||
Loading…
Add table
Add a link
Reference in a new issue