Bootstrap repo: State Hub registration, agent docs, TEN-WP-0001/0002

statehub register + repo-seed template scaffold (CLAUDE.md, .claude/rules/,
registry/). INTENT.md and SCOPE.md rewritten from the generated stub to
match net-kingdom's ratified tenant-engine-boundary-contract_v0.1.md
(Purpose, Responsibility Boundary, Non-Goals). topic_slug corrected from
the auto-assigned custodian default to netkingdom, matching key-cape and
user-engine.

TEN-WP-0001 (bootstrap) complete: files reviewed/refined, stack decided
(Python 3.12 + FastAPI, matching qonto-assistant's convention), first real
workplan seeded.

TEN-WP-0002 drafted: service skeleton, domain model (tenant/grouping/
capability-role/plan-grant), storage layer, and the three boundary-contract
API surfaces (cache-read for key-cape, live-lookup for flex-auth with an
explicit fail-closed requirement, write API behind a WriteAuthorizer seam
since real flex-auth integration is a declared non-goal for this pass).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
tegwick 2026-07-23 21:56:07 +02:00
parent cbb8057c38
commit 9370348d54
21 changed files with 1067 additions and 0 deletions

20
.claude/rules/agents.md Normal file
View file

@ -0,0 +1,20 @@
## Kaizen Agents
Specialized agent personas available on demand via the state-hub MCP.
**Discover:** `list_kaizen_agents()` — returns all agents with name, description, category
**Load:** `get_kaizen_agent("tdd-workflow")` — returns full instructions; read and follow them
Common agents:
| Agent | Category | When to use |
|-------|----------|-------------|
| `tdd-workflow` | testing | Step-by-step TDD8 workflow for any feature |
| `code-refactoring` | quality | Code quality analysis and safe refactoring |
| `test-maintenance` | testing | Diagnose and fix failing tests |
| `requirements-engineering` | process | Prevent interface/mock mismatches upfront |
| `keepaTodofile` | process | Maintain TODO.md during work |
| `project-management` | process | Track status, determine next steps |
| `datamodel-optimization` | quality | Optimize dataclasses and data structures |
All 17 agents: call `list_kaizen_agents()` for the full list.

View file

@ -0,0 +1,25 @@
## Architecture
Small headless service, modeled on `qonto-assistant`'s layout (same fleet
convention). Layers:
- `domain/` — tenant, grouping, capability-role, and plan-grant models; pure,
no framework dependency.
- `store/` — persistence for tenant records and the role/plan grant audit
trail. Starts in-memory/SQLite for early workplans; production backend TBD.
- `api/` — three surfaces per the boundary contract: a cache-read API
(`key-cape` calls at token issuance), a live-lookup API (`flex-auth` calls
synchronously for high-stakes decisions — must fail closed, never open),
and a write API (grant/revoke/plan mutations, authorization-gated by
`flex-auth`, not self-authorized).
- `guardrail/` — reserved namespace, not implemented yet (spend limits,
entity/action counts).
Full ownership boundary and API contract:
`net-kingdom/canon/standards/tenant-engine-boundary-contract_v0.1.md`.
Claim/carrying mechanism this service implements:
`net-kingdom/canon/standards/iam-profile_v0.3.md` ("Tenant Roles" section).
## Quick Reference
`~/state-hub/mcp_server/TOOLS.md` — MCP tool reference

View file

@ -0,0 +1,50 @@
# Credential and access routing
**Audience:** Codex, Claude Code, Grok, and custodian agents that call **llm-connect**
for inference. Run this check **before** requesting secrets, API keys, SSH access,
login tokens, or database passwords — in any repo, not only `ops-warden`.
ops-warden **issues SSH certificates only** (`warden sign`, `cert_command`). Every
other credential need belongs to another subsystem. **Do not** message
`ops-warden` on State Hub expecting a secret value; the reply is a pointer, not a key.
### Lookup (do this first)
```bash
warden route find "<describe your need>" --json
warden route show <catalog-id> --json
```
Requires the `warden` CLI from `~/ops-warden` (`uv tool install .` or `uv run warden`).
| Agent runtime | How to orient |
| --- | --- |
| **Codex / Grok** (shell, HTTP State Hub) | `warden route` commands above; inbox `to_agent=repo-seed` is for coordination, not secret vending |
| **Claude Code** (MCP when available) | `get_domain_summary("custodian")` for workplans; **still** use `warden route` for credential ownership |
| **llm-connect** (inference service) | Never put secret retrieval in prompts; route custody to OpenBao/operator paths surfaced by `warden route` |
### Quick routing table
| I need… | Owner | ops-warden executes? |
| --- | --- | --- |
| SSH cert (`adm`/`agt`/`atm`) | ops-warden | **Yes**`warden sign` |
| API key, DB password, provider token | OpenBao (`railiance-platform`) | No — route only |
| Login / OIDC / MFA | key-cape / Keycloak | No — route only |
| Authorization decision | flex-auth | No — route only |
| activity-core → issue-core emission | activity-core + issue-core | No — `warden route show activity-core-issue-sink` |
| SSH tunnel | ops-bridge (+ `cert_command` from warden) | No — route only |
### Anti-patterns (do not do these)
- `POST /messages/` to `ops-warden` asking for `ISSUE_CORE_API_KEY`, `OPENROUTER_API_KEY`, etc.
- Inventing `warden secret`, `warden login`, `warden bao`, `warden tunnel` — they do not exist
- Pasting secrets into Git, State Hub, workplans, logs, or chat
### Other capabilities (reuse-surface)
Non-credential capabilities are usually discovered through **reuse-surface** federation
(`reuse-surface` registry / `capability.*` indexes). Credential routing is inlined in
every repo's agent instructions because it is high-frequency, high-risk, and easy to
get wrong.
**Canon:** `~/ops-warden/wiki/CredentialRouting.md` · catalog `~/ops-warden/registry/routing/catalog.yaml`

View file

@ -0,0 +1,42 @@
## First Session Protocol
Triggered when `get_domain_summary("infotech")` shows **no workplans**.
The project is registered but work has not yet been structured.
**Step 1 — Read, don't write**
- `~/the-custodian/canon/projects/infotech/project_charter_v0.1.md` — purpose, scope
- `~/the-custodian/canon/projects/infotech/roadmap_v0.1.md` — planned phases
- Scan repo root: README, directory structure, existing code or docs
**Step 2 — Survey in-progress work**
Look for TODOs, open branches, half-finished files. Note done vs. started but incomplete.
**Step 3 — Propose workplans to Bernd**
Propose 13 workplans — each a coherent strand, weeks to months, anchored to a
roadmap phase. **Wait for approval before creating.**
**Step 4 — Write the workplan file; fix-consistency registers it (ADR-001)**
```
workplans/REPO-WP-NNNN-<slug>.md ← write this, commit it
```
Then register by running the consistency check — do **not** call
`create_workplan`/`create_task` yourself; manual registration duplicates what
C-06 creates from the file:
```bash
statehub fix-consistency --repo repo-seed
```
C-06 creates the hub workplan + tasks and writes `state_hub_workstream_id`
(legacy frontmatter name — holds the workplan UUID) and `state_hub_task_id`
back into the file.
**Step 5 — Record the setup**
```
add_progress_event(
summary="First session: structured infotech into N workplans, M tasks",
event_type="milestone",
topic_id="cee7bedf-2b48-46ef-8601-006474f2ad7a",
detail={"workplans": [...], "tasks_created": M}
)
```
<!-- Delete or archive this file once past first session -->

View file

@ -0,0 +1,18 @@
## Repo boundary
This repo owns **tenant-as-an-entity facts** only: tenant existence,
onboarding grouping, capability roles, plan/subscription assignment, and
(reserved) guardrail/quota policy. It does not own:
- Identity, authentication, MFA, token issuance → `key-cape` / Keycloak
- Tenant identifier format and role vocabulary → NetKingdom canon
(`iam-profile_v0.3.md`, ADR-0013, ADR-0014) — this repo consumes it,
never redefines it
- Users, profiles, memberships → `user-engine` (shared key: `tenant_id` only)
- Authorization decisions → `flex-auth` (this repo is a data source it
queries, never the decision maker)
- Pricing-model / plan term definitions → `adaptive-pricing`
- Payment processing → not yet identified as owned by any repo
- Runtime secret custody → OpenBao / `secrets-engine`
Full contract: `net-kingdom/canon/standards/tenant-engine-boundary-contract_v0.1.md`.

View file

@ -0,0 +1,6 @@
**Purpose:** Canonical owner of tenant-as-an-entity facts for NetKingdom: existence, onboarding grouping, capability roles, and plan/subscription assignment.
**Domain:** infotech
**Repo slug:** tenant-engine
**Workplan prefix:** `TEN-WP-`
**Topic:** netkingdom

View file

@ -0,0 +1,92 @@
## Session Protocol
Dev Hub (State Hub API): http://127.0.0.1:8000
MCP server name in `~/.claude.json`: `dev-hub`
**Step 1 — Orient**
Read the offline-safe brief first — it works without a live hub connection:
```bash
cat .custodian-brief.md
```
Then call the MCP tool for richer cross-domain context when MCP tools are exposed:
```
get_domain_summary("infotech")
```
If MCP tools are unavailable in the current agent session, use the REST API:
```bash
curl -s "http://127.0.0.1:8000/state/summary" | python3 -m json.tool
```
If the hub is offline: `cd ~/state-hub && make api`
**Step 2 — Check inbox**
With MCP tools:
```
get_messages(to_agent="repo-seed", unread_only=True)
```
Mark read with `mark_message_read(message_id)`. Reply or act on coordination
requests before proceeding.
Without MCP tools:
```bash
curl -s "http://127.0.0.1:8000/messages/?to_agent=repo-seed&unread_only=true" \
| python3 -m json.tool
curl -s -X PATCH "http://127.0.0.1:8000/messages/<id>/read" \
-H "Content-Type: application/json" -d '{}'
```
**Step 3 — Scan workplans**
```bash
ls workplans/
```
For each file with `status: ready`, `active`, or `blocked`, note pending
`wait`/`todo`/`progress` tasks.
**Step 4 — Present brief**
1. **Active workplans** for `infotech` — title, task counts, blocking decisions
2. **Pending tasks** from `workplans/` + any `[repo:repo-seed]` hub tasks
3. **Goal guidance** — if `goal_guidance` in summary:
- `needs_workplan`: surface as top action — *"Repo goal '{title}' has no workplan yet"*
- `alignment_warnings`: flag if active work is not aligned with current goal
4. **Suggested next action** — highest-priority open item
5. **SBOM status** — flag if `last_sbom_at` is unset for this repo
If no workplans: follow First Session Protocol (`first-session.md`).
**During work:** `record_decision()` · `add_progress_event()` · `resolve_decision()`
> State Hub is a *read model*. **Never register workplans or tasks by hand**
> (`create_workplan`, `create_task`) — write the workplan file in `workplans/`
> and run `fix-consistency`; C-06 registers the workplan and tasks and writes
> IDs back into the file. Manual registration creates duplicates when
> fix-consistency runs. Work structure belongs in repo files (ADR-001).
>
> Legacy: `create_workstream` and `/workstreams/` remain as metered aliases —
> see `workplan-convention.md` (compatibility footnote).
**Session close:**
With MCP tools:
```
add_progress_event(summary="...", topic_id="cee7bedf-2b48-46ef-8601-006474f2ad7a", workplan_id="<uuid>")
```
Without MCP tools:
```bash
curl -s -X POST http://127.0.0.1:8000/progress/ \
-H "Content-Type: application/json" \
-d '{"topic_id":"cee7bedf-2b48-46ef-8601-006474f2ad7a","workplan_id":"<uuid>","event_type":"note","summary":"what changed","author":"codex"}'
```
If workplan files were modified, ensure the local copy is up to date first,
then sync from the repo checkout:
```bash
git pull --ff-only
statehub fix-consistency
```
For repos where implementation runs on a remote machine (e.g. CoulombCore),
use the pull-before-fix mode from any shell with the State Hub CLI:
```bash
statehub fix-consistency --repo repo-seed --remote
```
**C-15** (DB task ahead of file) is normal in multi-machine workflows — writeback
will sync the file to match DB. **C-16** (repo behind remote) blocks all writes
until you pull — intentional to prevent clobbering remote progress.

View file

@ -0,0 +1,29 @@
## Stack
- **Language:** Python 3.12 (matches `qonto-assistant`'s fleet convention for
small headless services)
- **Framework:** FastAPI, `pyproject.toml` + `Makefile` layout
- **Key deps:** State Hub ADR-001 workplans, `net-kingdom` canon (IAM Profile
v0.3, ADR-0013, ADR-0014, tenant-engine boundary contract)
## Dev Commands
```bash
# Orient (offline-safe)
cat .custodian-brief.md
cat INTENT.md
cat SCOPE.md
ls workplans/
# Once scaffolded (TEN-WP-0002)
make install-dev
make test
make lint
make run
# After workplan or registry edits — from this repo checkout
statehub fix-consistency
# Sanity-check markdown / registry edits
git diff --check
```

View file

@ -0,0 +1,55 @@
## Workplan Convention (ADR-001)
File location: `workplans/REPO-WP-NNNN-<slug>.md`
ID prefix: `REPO-WP-`
Work items originate as files in this repo **before** being registered in the hub.
Canonical workplan frontmatter statuses are:
`proposed`, `ready`, `active`, `blocked`, `backlog`, `finished`, `archived`.
Use `proposed` for a newly drafted plan, `ready` after review against current
repo state, and `finished` when implementation is complete. `stalled` and
`needs_review` are derived health labels, not stored statuses.
Closed workplans may be moved to `workplans/archived/` with a completion-date
prefix: `YYMMDD-REPO-WP-NNNN-<slug>.md`. The frontmatter id remains
unchanged; the prefix is only for quick visual reference.
Small opportunistic tasks discovered during another session use **Ad Hoc Tasks**:
`workplans/ADHOC-YYYY-MM-DD.md`, workplan slug `adhoc-YYYY-MM-DD`, and task ids
`ADHOC-YYYY-MM-DD-T01`, `T02`, etc. Use adhocs only for low-risk work completed
directly. Promote anything requiring analysis, design, approval, dependencies, or
multiple planned phases into a normal workplan.
Ecosystem todos from other agents arrive as `[repo:repo-seed]` hub tasks —
visible at session start. Pick one up by creating the workplan file, committing,
and running `statehub fix-consistency` — C-06 registers the workplan in the hub.
Never register by hand with `create_workplan` (legacy MCP alias: `create_workstream`).
Task blocks use this shape:
```task
id: REPO-WP-NNNN-T01
status: wait | todo | progress | done | cancel
priority: high | medium | low
state_hub_task_id: "<uuid>" # written by fix-consistency — do not edit
```
Status progression is `todo``progress``done`; use `wait` for waiting or
blocked work and `cancel` for stopped work.
Workplan frontmatter carries `state_hub_workstream_id` — a legacy field name
kept for compatibility; it holds the hub workplan UUID and is written by
fix-consistency. Do not edit or rename it.
### Legacy terminology (compatibility footnote)
**Workplan** is the fleet term — see
`the-custodian/canon/standards/workplan-terminology-fleet_v0.1.md`.
**Workplan** is legacy only: some API routes (`/workstreams/`), params
(`workstream_id`), MCP aliases (`create_workstream`), and the frontmatter field
above remain until `STATE-WP-0069` retires them via legacy-meter. Treat those
identifiers as workplan IDs. Prefer `GET /workplans/` and `workplan_id` in new
examples and scripts.
<!-- Ralph Loop rules and HEUREKA sequence: ~/.claude/CLAUDE.md — do not duplicate here -->

27
.custodian-brief.md Normal file
View file

@ -0,0 +1,27 @@
<!-- custodian-brief: generated by statehub register; fix-consistency may replace this file -->
# Custodian Brief - tenant-engine
**Project:** tenant-engine
**Domain:** infotech
**State Hub:** http://127.0.0.1:8000
**Topic ID:** `cee7bedf-2b48-46ef-8601-006474f2ad7a`
## Open Workplans
### Bootstrap State Hub integration
Workplan file: `workplans/TEN-WP-0001-statehub-bootstrap.md`
Open tasks:
- T01 - Review generated integration files
- T02 - Verify local developer workflow
- T03 - Seed first real workplan
## Session Start
1. Read `INTENT.md`, `SCOPE.md`, and `AGENTS.md`.
2. Check inbox: `GET /messages/?to_agent=tenant-engine&unread_only=true`.
3. Scan `workplans/`.
4. Update task statuses in workplan files as work progresses.
Last generated: 2026-07-23

6
.gitignore vendored
View file

@ -162,3 +162,9 @@ cython_debug/
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
# state-hub: track .claude/rules
# Claude Code local state (track shared rules; ignore machine-specific files)
.claude/*
!.claude/rules/
!.claude/rules/*.md

26
.repo-classification.yaml Normal file
View file

@ -0,0 +1,26 @@
repo_classification:
standard: Repo Classification Standard
version: '1.0'
classified_at: '2026-07-23'
classified_by: agent
category: product
domain: infotech
secondary_domains:
- financials
capability_tags:
- identity
- access-control
- tenancy
- platform
- operations
business_stake:
- technology
- operations
- legal
- product
business_mechanics:
- control
- operation
notes: Canonical owner of tenant-as-an-entity facts for NetKingdom -- tenant
existence, onboarding grouping, capability roles, and plan/subscription
assignment. See net-kingdom canon/standards/tenant-engine-boundary-contract_v0.1.md.

189
AGENTS.md Normal file
View file

@ -0,0 +1,189 @@
# tenant-engine — Agent Instructions
## Repo Identity
**Purpose:** Canonical owner of tenant-as-an-entity facts for NetKingdom: existence, onboarding grouping, capability roles, and plan/subscription assignment.
**Domain:** infotech
**Repo slug:** tenant-engine
**Topic ID:** `cee7bedf-2b48-46ef-8601-006474f2ad7a`
**Workplan prefix:** `TEN-WP-`
---
## State Hub Integration
The Custodian State Hub tracks work across all domains. Interact via HTTP REST —
there is no MCP server for Codex agents.
| Context | URL |
|---------|-----|
| Local workstation | `http://127.0.0.1:8000` |
| Remote via tunnel | `http://127.0.0.1:18000` |
| Optional local edge relay | http://127.0.0.1:18080 |
When an operator has enabled the edge relay, set API_BASE to the relay URL.
Queueable writes return an explicit queued receipt if the central hub is
unreachable. Treat that as pending local evidence, then ask the operator to run
statehub outbox status/replay after connectivity returns.
### Orient at session start
```bash
# Offline brief — works without hub connection
cat .custodian-brief.md
# Active workplans for this domain
curl -s "http://127.0.0.1:8000/workplans/?topic_id=cee7bedf-2b48-46ef-8601-006474f2ad7a&status=active" \
| python3 -m json.tool
# Check inbox
curl -s "http://127.0.0.1:8000/messages/?to_agent=tenant-engine&unread_only=true" \
| python3 -m json.tool
```
Mark a message read:
```bash
curl -s -X PATCH "http://127.0.0.1:8000/messages/<id>/read" \
-H "Content-Type: application/json" -d '{}'
```
### Log progress (required at session close)
```bash
curl -s -X POST http://127.0.0.1:8000/progress/ \
-H "Content-Type: application/json" \
-d '{
"summary": "what was done",
"event_type": "note",
"author": "codex",
"workplan_id": "<uuid>",
"task_id": "<uuid>"
}'
```
Omit `workplan_id` / `task_id` when not applicable.
### Update task status
```bash
curl -s -X PATCH "http://127.0.0.1:8000/tasks/<task_id>" \
-H "Content-Type: application/json" \
-d '{"status": "progress"}'
# values: wait | todo | progress | done | cancel
```
### Flag a task for human review
```bash
curl -s -X PATCH "http://127.0.0.1:8000/tasks/<task_id>" \
-H "Content-Type: application/json" \
-d '{"needs_human": true, "intervention_note": "reason"}'
```
---
## Session Protocol
**Start:**
1. `cat .custodian-brief.md` — domain goal and open workplans (offline-safe)
2. Check inbox: `GET /messages/?to_agent=tenant-engine&unread_only=true`; mark read
3. Scan workplans: `ls workplans/` — note `status: ready`, `active`, or `blocked` files and open tasks
4. Check human-needed tasks: `GET /tasks/?needs_human=true`
**During work:**
- Update task statuses in workplan files as tasks progress
- Record significant decisions via `POST /decisions/`
**Close:**
1. Update workplan file task statuses to reflect progress
2. If finishing a workplan: hand off **residuals** as live work records first
(intake with `origin: residual` + `origin_ref: <WP-id>`, or a next workplan /
decision / engagement). Do not park leftovers only in prose or `SCOPE.md`.
Canon: `the-custodian/canon/standards/work-record-types_v0.1.md` § Residuals.
3. Log: `POST /progress/` with a summary of what changed (name handoff ids)
4. After workplan file changes, run:
```bash
statehub fix-consistency
```
Coding agents should run this directly; ask the operator only if the CLI or
State Hub API is unavailable. This syncs task status from files into the hub DB.
---
{CREDENTIAL_ROUTING}
<!-- REPO-AGENTS-EXTENSIONS -->
<!-- Append repo-specific agent instructions below this marker.
The state-hub template sync preserves content after this line. -->
---
## Workplan Convention (ADR-001)
Work items originate as files in this repo — not in the hub. The hub is a
read/cache/index layer that rebuilds from files.
**File location:** `workplans/TEN-WP-NNNN-<slug>.md`
**Archived location:** finished workplans may move to
`workplans/archived/YYMMDD-TEN-WP-NNNN-<slug>.md`. The `YYMMDD` prefix is
the completion/archive date; the frontmatter `id` does not change.
**Ad Hoc Tasks:** small opportunistic fixes discovered during a session use
`workplans/ADHOC-YYYY-MM-DD.md` with task ids `ADHOC-YYYY-MM-DD-T01`, etc. Use
this only for low-risk work completed directly; create a normal workplan for
anything needing analysis, design, approval, dependencies, or multiple phases.
**Frontmatter:**
```yaml
---
id: TEN-WP-NNNN
type: workplan
title: "..."
domain: infotech
repo: tenant-engine
status: proposed | ready | active | blocked | backlog | finished | archived
owner: codex
topic_slug: ...
created: "YYYY-MM-DD"
updated: "YYYY-MM-DD"
state_hub_workstream_id: "<uuid>" # fix-consistency — do not edit (legacy field name; workplan UUID)
---
```
Use `proposed` for a new draft, `ready` after review against current repo
state, and `finished` after implementation. `stalled` and `needs_review` are
derived health labels, not frontmatter statuses.
**Terminology:** workplan is the fleet term; `workstream` appears only in legacy
API/MCP/frontmatter bridges until `STATE-WP-0069` retires them — see
`the-custodian/canon/standards/workplan-terminology-fleet_v0.1.md`.
**Task block format** (one per `##` section):
```
## Task Title
` ` `task
id: TEN-WP-NNNN-T01
status: wait | todo | progress | done | cancel
priority: high | medium | low
state_hub_task_id: "<uuid>" # written by fix-consistency — do not edit
` ` `
Task description text.
```
Status progression: `todo``progress``done`; use `wait` for waiting/blocked work and `cancel` for stopped work.
**Residuals when finishing:** actionable leftovers become live work records
before `status: finished` — usually an intake (`origin: residual`,
`origin_ref: TEN-WP-NNNN`) or a spawned workplan. Residual is a *role*,
not a kind. Fleet list lives on State Hub, not in `SCOPE.md`.
To create a new workplan:
1. Write the file following the format above
2. Run `statehub fix-consistency` locally; ask the operator only if the CLI or
State Hub API is unavailable.

12
CLAUDE.md Normal file
View file

@ -0,0 +1,12 @@
# Repo Seed — Claude Code Instructions
@SCOPE.md
@.claude/rules/repo-identity.md
@.claude/rules/session-protocol.md
@.claude/rules/first-session.md
@.claude/rules/workplan-convention.md
@.claude/rules/stack-and-commands.md
@.claude/rules/architecture.md
@.claude/rules/repo-boundary.md
@.claude/rules/credential-routing.md
@.claude/rules/agents.md

131
INTENT.md Normal file
View file

@ -0,0 +1,131 @@
---
repo: tenant-engine
updated: "2026-07-23"
---
# INTENT
## Purpose
`tenant-engine` exists to be the single, canonical source of truth for what a
**tenant** is on the NetKingdom platform — its existence, its onboarding
grouping, its capability roles, and its plan/subscription assignment —
separate from who its *users* are (`user-engine`), who *authenticates* it
(`key-cape`/Keycloak), who *authorizes* actions against it (`flex-auth`), and
what it *pays* for a given plan (`adaptive-pricing`).
Full ownership boundary, formally: `net-kingdom/canon/standards/tenant-engine-boundary-contract_v0.1.md`.
## Why This Exists
No service in the fleet currently owns tenant-as-an-entity facts as a
queryable resource. `user-engine`'s own boundary contract explicitly scopes
it to *consuming* tenant identifiers and *storing records scoped by* a
tenant — not owning the tenant record itself. The IAM Profile defines the
*shape* of the `tenant` claim, not a live directory of which tenants exist,
what they're allowed to do, or what plan they're on.
That gap became concrete during Binky Hedgehog GmbH's onboarding as the
platform's first tenant outside `tenant:coulomb`
(`key-cape/workplans/KEY-WP-0004-binky-hedgehog-tenant-onboarding.md`): a
tenant's **capability roles** (`PLTF`/`IAM`/`VEN`/`CUS`, non-exclusive — a
tenant can hold several at once, and its roles can change independently of
how it was onboarded) needed a real owner, a real carrying mechanism, and a
real governance model (role grants are usually a consequence of a paid
plan). See `net-kingdom/docs/adr/ADR-0013-tenant-onboarding-grouping-taxonomy.md`
and `ADR-0014-tenant-capability-roles-and-tenant-engine-ownership.md`.
## Primary Utility
`tenant-engine` provides:
- **Tenant records**: existence, identifier, and onboarding grouping
(`trial`/`friendly`/`single`/`small`/`medium`/`large`/`enterprise`/
`consumer`/`family`/`community`/`association`/`agentic`, per ADR-0013).
- **Capability role grants**: `PLTF`/`IAM`/`VEN`/`CUS`, with an audited
grant/revoke trail linking most grants to a plan-assignment change.
- **Plan/subscription assignment**: which `adaptive-pricing` plan a tenant
currently holds, referenced by id — never a duplicated copy of plan terms.
- **A cache-read API** `key-cape` calls at token-issuance time to source the
optional `tenant_roles` token claim (IAM Profile v0.3).
- **A live-lookup API** `flex-auth` calls synchronously for privileged,
high-stakes decisions — the case where a cached claim isn't trustworthy
enough (a stale `VEN` grant surviving a plan cancellation is not an
acceptable risk for money-movement or credential-vending actions).
- Reserved, not yet implemented: **guardrail/quota policy** (spend limits,
entity/action counts) — `trial`-grouped tenants default to a **zero**
spend budget; design is future work.
## Intended Users
- `key-cape` / Keycloak, at token issuance time (cache-read API).
- `flex-auth`, at authorization-decision time (live-lookup API, and the
target of write-authorization for grant/revoke/plan mutations).
- Operators and tenant administrators, indirectly, through whatever admin
surface eventually consumes `tenant-engine`'s APIs (not yet built).
- `adaptive-pricing`, as the source `tenant-engine` references for plan
*definitions* (not the other way around).
## Strategic Role
`tenant-engine` sits **beside** `user-engine`, not inside it — a deliberate
bounded-context split, not an oversight. `user-engine`'s aggregate root is
the *person*; `tenant-engine`'s is the *tenant*. Consumers that only need a
fast, security-critical tenant-role lookup (`flex-auth` on every privileged
decision, `key-cape` on every token issuance) should never need to depend on
`user-engine`'s much larger surface (registration flows, factor models,
family dataspace onboarding) to get it. This matches the fleet's existing
convention of many small, single-purpose `-engine`/`-core` services rather
than one large one.
## Responsibility Boundary
`tenant-engine` owns tenant-as-an-entity facts. It never re-derives or
duplicates facts another system already owns.
### tenant-engine Owns
- Tenant existence, identifier, and onboarding grouping.
- Capability role grants and their audit trail.
- Plan/subscription assignment (by reference, not by copying plan terms).
- Reserved: guardrail/quota policy, once designed.
### Other Systems Own
| Concern | Owner |
| --- | --- |
| OIDC discovery, login, token issuance | `key-cape`, Keycloak |
| Tenant identifier format and role vocabulary | NetKingdom (IAM Profile, ADR-0013, ADR-0014) |
| Users, profiles, memberships | `user-engine` |
| Authorization decisions | `flex-auth` |
| Pricing-model / plan definitions | `adaptive-pricing` |
| Runtime secrets | OpenBao / `secrets-engine` |
| Payment processing | Not yet identified — `tenant-engine` records plan assignment, not payment execution |
## Non-Goals
- Not an identity provider. Never issues, verifies, or refreshes tokens.
- Not a policy decision point. Never makes an authorization decision itself
— it answers queries `flex-auth` issues.
- Not a billing processor. Records plan assignment, not payment execution.
- Not a pricing-model engine. References `adaptive-pricing` plan ids.
- Not the user/membership domain. Never stores user accounts or profiles.
## Governing Principle
This repository should stay focused on the purpose above. Work that changes
its authority, ownership boundaries, or operational promises should be
captured in a workplan before implementation, and should stay consistent
with `net-kingdom/canon/standards/tenant-engine-boundary-contract_v0.1.md`
if the two drift, the canon contract wins and this file should be corrected.
## Related
- `net-kingdom/canon/standards/tenant-engine-boundary-contract_v0.1.md` — the
formal ownership contract
- `net-kingdom/canon/standards/iam-profile_v0.3.md` — the `tenant_roles`
claim and carrying mechanism this service implements
- `net-kingdom/docs/adr/ADR-0013-tenant-onboarding-grouping-taxonomy.md`
- `net-kingdom/docs/adr/ADR-0014-tenant-capability-roles-and-tenant-engine-ownership.md`
- `key-cape/workplans/KEY-WP-0004-binky-hedgehog-tenant-onboarding.md` — the
first tenant this service will record

65
SCOPE.md Normal file
View file

@ -0,0 +1,65 @@
# SCOPE
> Lightweight boundary for agents and contributors. Full contract:
> `net-kingdom/canon/standards/tenant-engine-boundary-contract_v0.1.md`.
---
## One-liner
Canonical owner of tenant-as-an-entity facts for NetKingdom: existence,
onboarding grouping, capability roles, and plan/subscription assignment.
---
## Core Idea
`tenant-engine` answers "what is this tenant, what can it do, and what plan
is it on" for two consumers: `key-cape` (cached, at token issuance) and
`flex-auth` (live, for high-stakes decisions). It never stores users,
issues tokens, or makes authorization decisions.
---
## In Scope
- Tenant records: identifier, onboarding grouping (ADR-0013).
- Capability role grants/revocations: `PLTF`/`IAM`/`VEN`/`CUS` (ADR-0014),
audited, mostly plan-linked.
- Plan/subscription assignment, referenced by `adaptive-pricing` plan id.
- Cache-read API for `key-cape` (token-issuance-time `tenant_roles` claim
source).
- Live-lookup API for `flex-auth` (high-stakes, `aal2`-class decisions).
- Write API for grant/revoke/plan mutations, authorization-gated by
`flex-auth`.
- Reserved, not yet implemented: guardrail/quota policy (spend limits,
entity/action counts).
## Out of Scope
- Identity, authentication, MFA, token issuance — `key-cape`/Keycloak.
- Authorization decisions — `flex-auth` is the PDP; `tenant-engine` is a
data source it queries, never the other way around.
- Users, profiles, memberships — `user-engine`. `tenant_id` is the only key
shared between the two services.
- Pricing-model / plan term definitions — `adaptive-pricing`.
- Payment processing — not yet identified as owned by any repo.
- Runtime secret custody — OpenBao / `secrets-engine`.
- Irreversible operational decisions without human approval.
## Current State
- Status: bootstrapping. No implementation yet — see
`workplans/TEN-WP-0001-statehub-bootstrap.md` and the first real
implementation workplan it seeds.
- Ownership contract (`tenant-engine-boundary-contract_v0.1.md`) and the two
governing ADRs (0013, 0014) are ratified in `net-kingdom` and predate this
repo's code, by design.
## Getting Oriented
- Start with: `INTENT.md`
- Ownership contract: `net-kingdom/canon/standards/tenant-engine-boundary-contract_v0.1.md`
- Claim/carrying mechanism: `net-kingdom/canon/standards/iam-profile_v0.3.md`
- Agent instructions: `AGENTS.md`, `CLAUDE.md`
- Workplans: `workplans/`

12
registry/README.md Normal file
View file

@ -0,0 +1,12 @@
# Capability Registry
Markdown-first capability index for federation and reuse planning.
## Authoring
1. Copy a capability entry template (see reuse-surface `templates/capability-entry.template.md`).
2. Add the row to `indexes/capabilities.yaml`.
3. Run `reuse-surface validate` from a checkout with the CLI installed.
4. Merge to `main` and verify publish with `reuse-surface establish --publish-check`.
Federation contract: reuse-surface `docs/RegistryFederation.md`.

View file

View file

@ -0,0 +1,4 @@
version: 1
updated: '2026-07-23'
domain: infotech
capabilities: []

View file

@ -0,0 +1,79 @@
---
id: TEN-WP-0001
type: workplan
title: "Bootstrap State Hub integration"
domain: infotech
repo: tenant-engine
status: finished
owner: codex
topic_slug: netkingdom
created: "2026-07-23"
updated: "2026-07-23"
---
# Bootstrap State Hub integration
Canonical owner of tenant-as-an-entity facts for NetKingdom: existence, onboarding grouping, capability roles, and plan/subscription assignment.
## Review Generated Integration Files
```task
id: TEN-WP-0001-T01
status: done
priority: high
```
Review `INTENT.md`, `SCOPE.md`, `AGENTS.md`, and `.custodian-brief.md`.
Replace generated placeholders with repo-specific facts where needed.
**Done 2026-07-23:** `INTENT.md` and `SCOPE.md` rewritten from the generated
stub into the full Purpose/Responsibility-Boundary/Non-Goals shape matching
the ratified `net-kingdom/canon/standards/tenant-engine-boundary-contract_v0.1.md`
(Ownership Model + Source-of-Truth Matrix condensed into this repo's own
docs, not duplicated wholesale). `topic_slug` corrected from the
auto-assigned `custodian` default to `netkingdom`, matching `key-cape` and
`user-engine`'s convention. `.claude/rules/{repo-identity,architecture,repo-boundary,stack-and-commands}.md`
placeholders filled in (copied from `repo-seed`, the fleet's bootstrap
template). `AGENTS.md`/`.custodian-brief.md` left as generated — both carry
an explicit "state-hub template sync" marker meaning they're managed by
`fix-consistency`, not hand-edited.
## Verify Local Developer Workflow
```task
id: TEN-WP-0001-T02
status: done
priority: high
```
Identify the repo's install, test, lint, build, and run commands. Add or refine
those commands in the agent instructions so future coding sessions can verify
changes confidently.
**Done 2026-07-23:** Stack decided — Python 3.12 + FastAPI, matching
`qonto-assistant`'s fleet convention for small headless services (this repo
plays a structurally similar role: a narrow, security-adjacent API a couple
of other services call synchronously). `make install-dev`/`test`/`lint`/`run`
commands documented in `.claude/rules/stack-and-commands.md` ahead of the
actual `Makefile`/`pyproject.toml`, which land in `TEN-WP-0002`.
## Seed First Real Workplan
```task
id: TEN-WP-0001-T03
status: done
priority: medium
```
Create the first implementation workplan for the repository's most important
next change. After workplan file updates, run the sync locally from this repo
checkout:
```bash
statehub fix-consistency
```
**Done 2026-07-23:** `TEN-WP-0002-domain-model-and-scaffold.md` created —
service skeleton, domain model (tenant/grouping/role/plan-grant), and the
cache-read + live-lookup APIs per the boundary contract. Registered via
`statehub fix-consistency`.

View file

@ -0,0 +1,179 @@
---
id: TEN-WP-0002
type: workplan
title: "Service skeleton, domain model, and the three boundary-contract APIs"
domain: infotech
repo: tenant-engine
status: ready
owner: codex
topic_slug: netkingdom
created: "2026-07-23"
updated: "2026-07-23"
---
# Service skeleton, domain model, and the three boundary-contract APIs
First real implementation workplan. Builds the tenant-engine skeleton
against `net-kingdom/canon/standards/tenant-engine-boundary-contract_v0.1.md`:
tenant/grouping/capability-role/plan-grant domain model, an audited grant
trail, and the three API surfaces the contract defines — cache-read
(`key-cape`), live-lookup (`flex-auth`), and write (grant/revoke/plan
mutation). Guardrail/quota policy stays a reserved, unimplemented namespace
per ADR-0014 — not in scope here.
**Depends on:** `net-kingdom` canon — `iam-profile_v0.3.md`,
`tenant-engine-boundary-contract_v0.1.md`, `ADR-0013`, `ADR-0014` (all
ratified). **Non-goals:** payment processing, pricing-model definitions
(`adaptive-pricing`'s job), real `flex-auth` integration (the write/live
APIs get a policy-hook interface, not a working `flex-auth` client — that's
a follow-up once `flex-auth` has a reachable endpoint), guardrail/quota
enforcement.
## Task: Service skeleton
```task
id: TEN-WP-0002-T01
status: todo
priority: high
```
Python 3.12 + FastAPI, matching `qonto-assistant`'s layout convention:
`pyproject.toml`, `Makefile` (`install-dev`, `test`, `lint`, `run`), package
layout separating `domain/` (pure), `store/` (persistence), `api/` (FastAPI
routers), from day one — not refactored in later.
Done when: `make test` runs an empty/smoke suite; `make run` starts a bare
FastAPI app with a `/health` endpoint.
## Task: Domain model — tenant, grouping, capability role, plan grant
```task
id: TEN-WP-0002-T02
status: todo
priority: high
```
Pure domain types, no framework dependency:
- `Tenant`: id, identifier (`tenant:<grouping>:<name>` or reserved
`tenant:platform`/`tenant:coulomb`), grouping (ADR-0013 enum, nullable for
the two reserved identifiers).
- `CapabilityRole`: enum `PLTF`/`IAM`/`VEN`/`CUS`.
- `RoleGrant`: tenant_id, role, grant_reason (`plan_assignment` /
`manual_grant` / `platform_default`), plan_id (nullable), granted_by,
granted_at, revoked_at (nullable), correlation_id — the audited record
shape from the boundary contract's Tenant Role & Plan Grant Contract.
- `PlanAssignment`: tenant_id, plan_id (references an `adaptive-pricing`
plan id — stored as an opaque string, never resolved or duplicated
locally), assigned_at.
Validation rules encoded as domain invariants, not just API-layer checks:
- grouping must be one of ADR-0013's twelve values, or the tenant identifier
must be exactly `tenant:platform`/`tenant:coulomb` (grouping-less);
- a `trial`-grouped tenant may hold any role with `grant_reason:
platform_default` and no `plan_id` (ADR-0014) — this must be
representable, not blocked by a plan-required constraint;
- non-`trial` roles other than `platform_default` require a `plan_id` when
`grant_reason: plan_assignment`;
- revoking a grant sets `revoked_at`, never deletes the record (audit trail).
Done when: unit tests cover valid/invalid grouping values, the trial/
no-plan-required exception, and grant/revoke as append-only operations.
## Task: Storage layer
```task
id: TEN-WP-0002-T03
status: todo
priority: high
```
In-memory store behind a `TenantStore` protocol/interface (mirrors
`qonto_client.QontoClientProtocol`'s pattern in `qonto-assistant`), so a
real backend can be swapped in later without touching `domain/` or `api/`.
Every mutation emits a domain event (tenant created, role granted, role
revoked, plan assigned) per the boundary contract's Audit Correlation
Contract — the event bus itself can be a simple in-process list for now.
Done when: unit tests cover create/read/grant/revoke/assign-plan through the
store interface, plus event emission for each mutation.
## Task: Cache-read API (for key-cape)
```task
id: TEN-WP-0002-T04
status: todo
priority: high
```
`GET /tenants/{tenant_id}/roles` — returns current, non-revoked capability
roles for a tenant. This is the endpoint `key-cape` calls at token-issuance
time to source the cached `tenant_roles` claim (IAM Profile v0.3). No
authorization gate of its own beyond service-to-service auth (out of scope
here — see Non-Goals); this endpoint's whole purpose is to be cheap and
fast, per the boundary contract's performance model.
Done when: integration test hits the endpoint against the in-memory store
and returns the expected role set for a seeded tenant.
## Task: Live-lookup API (for flex-auth) — fail closed
```task
id: TEN-WP-0002-T05
status: todo
priority: high
```
`GET /tenants/{tenant_id}/roles/live` — same data as the cache-read
endpoint, but explicitly documented and tested as the path `flex-auth` must
call before authorizing `aal2`-class actions. The distinction from T04 is
operational intent (freshness guarantee, called synchronously on a
privileged-decision path), not payload shape.
Per the boundary contract's performance model: **fail closed, never open**.
If the store is unavailable, this endpoint must return an error response
that a policy caller would treat as "deny", not a default-allow or an empty
role list indistinguishable from "no roles granted".
Done when: a test simulates store unavailability and asserts the endpoint
signals failure distinctly from "zero roles", not silently as 200 + `[]`.
## Task: Write API — grant, revoke, assign-plan
```task
id: TEN-WP-0002-T06
status: todo
priority: medium
```
`POST /tenants` (create), `POST /tenants/{id}/roles/grant`,
`POST /tenants/{id}/roles/revoke`, `POST /tenants/{id}/plan` — all mutating
endpoints from the boundary contract's Write API. Per the contract,
`tenant-engine` does not self-authorize these writes; `flex-auth` is meant
to gate them. Since a reachable `flex-auth` integration is explicitly a
non-goal here, implement a `WriteAuthorizer` protocol/interface point (one
class, default-deny stub) so every mutation already flows through a single
seam — swapping in a real `flex-auth` client later touches one file, not
every endpoint.
Done when: unit tests confirm every write endpoint calls the
`WriteAuthorizer` seam and is denied by the default-deny stub; a
test-only authorizer override proves the seam actually gates the mutation
when swapped.
## Task: Closure review
```task
id: TEN-WP-0002-T07
status: todo
priority: low
```
Confirm T01T06 done; run `make test`/`make lint`; verify the three API
surfaces match the boundary contract's Ownership Model and Source-of-Truth
Matrix with no drift. Note follow-ups: real `flex-auth` `WriteAuthorizer`
integration, `key-cape` wiring to actually call the cache-read endpoint at
issuance, guardrail/quota policy design (ADR-0014's reserved item), and
Binky Hedgehog GmbH as the first real tenant record once `key-cape`'s
`KEY-WP-0004` reaches that point. Run `statehub fix-consistency`.