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).
|
# CI smoke — package tests prove hub-core library health on push.
|
||||||
# Copy to: .forgejo/workflows/ci-smoke.yaml in consumer repos.
|
|
||||||
name: CI Smoke
|
name: CI Smoke
|
||||||
|
|
||||||
on:
|
on:
|
||||||
|
|
@ -20,10 +19,26 @@ jobs:
|
||||||
echo "runner=${RUNNER_NAME:-unknown}"
|
echo "runner=${RUNNER_NAME:-unknown}"
|
||||||
uname -a
|
uname -a
|
||||||
|
|
||||||
container-smoke:
|
pytest-smoke:
|
||||||
runs-on: ubuntu-latest
|
runs-on: self-hosted
|
||||||
steps:
|
steps:
|
||||||
- name: Routing probe (container label)
|
- name: Run hub-core pytest
|
||||||
run: |
|
run: |
|
||||||
set -eu
|
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
|
- product
|
||||||
business_mechanics:
|
business_mechanics:
|
||||||
- operation
|
- operation
|
||||||
notes: Reusable Python package (router factories, models, schemas) — the shared library
|
notes: >-
|
||||||
boundary between hubs. product-as-component.
|
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
|
# INTENT — hub-core
|
||||||
|
|
||||||
**Project:** `hub-core`
|
**Project:** `hub-core`
|
||||||
**Domain:** `inter_hub`
|
**Domain:** `infotech`
|
||||||
**Status:** Active extraction (CUST-WP-0025)
|
**Status:** Active — library anchor for hub ecosystem (`CUST-WP-0057`)
|
||||||
**Updated:** 2026-06-16
|
**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
|
## Why it exists
|
||||||
|
|
||||||
Custodian and helix_forge ecosystems need more than one hub-shaped service:
|
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.
|
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
|
`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
|
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
|
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 |
|
| Repo | Boundary |
|
||||||
|---|---|
|
|---|---|
|
||||||
| `state-hub` | Host dev-hub; imports hub-core factories; keeps workplan/task/decision logic |
|
| `state-hub` | Primary host — mounts router factories and MCP composition; owns workplans/tasks |
|
||||||
| `the-custodian` | Owns extraction boundary doc and CUST-WP-0025 workplan |
|
| `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 |
|
| `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__"]
|
__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.pagination import PageParams, apply_pagination
|
||||||
from hub_core.utils.paths import resolve_repo_path
|
from hub_core.utils.paths import resolve_repo_path
|
||||||
from hub_core.utils.routing import normalize_trailing_slash
|
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__ = [
|
__all__ = [
|
||||||
"PageParams",
|
"PageParams",
|
||||||
|
|
@ -9,4 +9,5 @@ __all__ = [
|
||||||
"normalize_trailing_slash",
|
"normalize_trailing_slash",
|
||||||
"resolve_repo_path",
|
"resolve_repo_path",
|
||||||
"slugify",
|
"slugify",
|
||||||
|
"slugify_or_default",
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -12,3 +12,19 @@ def slugify(value: str, *, max_length: int = 100) -> str:
|
||||||
if max_length < 1:
|
if max_length < 1:
|
||||||
raise ValueError("max_length must be >= 1")
|
raise ValueError("max_length must be >= 1")
|
||||||
return slug[:max_length].strip("-")
|
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]
|
[project]
|
||||||
name = "hub-core"
|
name = "hub-core"
|
||||||
version = "0.1.0"
|
version = "0.2.0"
|
||||||
description = "Reusable core primitives for FOS hubs"
|
description = "Reusable core primitives for FOS hubs"
|
||||||
requires-python = ">=3.12"
|
requires-python = ">=3.12"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
|
|
||||||
|
|
@ -68,8 +68,11 @@ availability:
|
||||||
- library import
|
- library import
|
||||||
relations:
|
relations:
|
||||||
depends_on: []
|
depends_on: []
|
||||||
supports: []
|
supports:
|
||||||
related_to: []
|
- capability.infotech.core-hub
|
||||||
|
related_to:
|
||||||
|
- capability.infotech.core-hub
|
||||||
|
- capability.statehub.workstream-coordinate
|
||||||
evidence:
|
evidence:
|
||||||
documentation:
|
documentation:
|
||||||
- README.md
|
- README.md
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
version: 1
|
version: 1
|
||||||
updated: '2026-07-06'
|
updated: '2026-07-09'
|
||||||
domain: inter_hub
|
domain: inter_hub
|
||||||
capabilities:
|
capabilities:
|
||||||
- id: capability.infotech.hub-core-library
|
- id: capability.infotech.hub-core-library
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,14 @@ import pytest
|
||||||
from sqlalchemy import select
|
from sqlalchemy import select
|
||||||
|
|
||||||
from hub_core.models.domain import Domain
|
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:
|
class RepoStub:
|
||||||
|
|
@ -19,6 +26,18 @@ def test_slugify_rejects_empty_slug() -> None:
|
||||||
slugify(" !!! ")
|
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:
|
def test_page_params_bounds() -> None:
|
||||||
assert PageParams(limit=10, offset=20).limit == 10
|
assert PageParams(limit=10, offset=20).limit == 10
|
||||||
with pytest.raises(ValueError, match="limit"):
|
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