docs: inventory Hub and platform access boundaries for root integration
Some checks failed
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / pytest-smoke (push) Failing after 4s

Assistant: codex
Assistant-Model: gpt-6-astra
Assistant-Session: 01a0e715-b802-70f0-a8fa-590d9ee673a5
This commit is contained in:
tegwick 2026-09-28 10:52:57 +02:00
parent e5b67b391d
commit 1182b637c7
5 changed files with 8275 additions and 5 deletions

View file

@ -17,7 +17,7 @@
| workplan | HUB-WP-0007 | finished | — | workplans/HUB-WP-0007-workload-projection-transport.md |
| workplan | HUB-WP-0008 | finished | — | workplans/HUB-WP-0008-legacy-message-identity-reconciliation.md |
| workplan | HUB-WP-0009 | proposed | — | workplans/HUB-WP-0009-extension-conformance-gaps.md |
| workplan | HUB-WP-0012 | proposed | — | workplans/HUB-WP-0012-netkingdom-platform-root-access.md |
| workplan | HUB-WP-0012 | active | — | workplans/HUB-WP-0012-netkingdom-platform-root-access.md |
| task | HUB-WP-0001-T01 | done | — | workplans/HUB-WP-0001-statehub-bootstrap.md |
| task | HUB-WP-0001-T02 | done | — | workplans/HUB-WP-0001-statehub-bootstrap.md |
| task | HUB-WP-0001-T03 | done | — | workplans/HUB-WP-0001-statehub-bootstrap.md |
@ -62,7 +62,7 @@
| task | HUB-WP-0009-T02 | todo | — | workplans/HUB-WP-0009-extension-conformance-gaps.md |
| task | HUB-WP-0009-T03 | todo | — | workplans/HUB-WP-0009-extension-conformance-gaps.md |
| task | HUB-WP-0009-T04 | todo | — | workplans/HUB-WP-0009-extension-conformance-gaps.md |
| task | HUB-WP-0012-T01 | todo | — | workplans/HUB-WP-0012-netkingdom-platform-root-access.md |
| task | HUB-WP-0012-T01 | progress | — | workplans/HUB-WP-0012-netkingdom-platform-root-access.md |
| task | HUB-WP-0012-T02 | todo | — | workplans/HUB-WP-0012-netkingdom-platform-root-access.md |
| task | HUB-WP-0012-T03 | todo | — | workplans/HUB-WP-0012-netkingdom-platform-root-access.md |
| task | HUB-WP-0012-T04 | todo | — | workplans/HUB-WP-0012-netkingdom-platform-root-access.md |

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,339 @@
# Platform-root access inventory — 2026-09-28
This is the coverage baseline for HUB-WP-0012-T01, not an access grant or a
passing security test. It enumerates 161 Hub source surfaces (88 runtime route
registrations, 42 embedded router operations, 31 MCP tools), 39 observed cluster
namespaces and nine additional extension/native-management boundaries. All 250
observed Deployment/StatefulSet/DaemonSet/CronJob/Service/Ingress objects map to
exactly one namespace row. Scaled-down revisions and legacy workloads remain
listed so they cannot become unnoticed rollback bypasses.
The [machine-readable inventory](platform-access-inventory.json) is authoritative
for this snapshot. Rows inherit audience, actor/target tenant, action/resource
mapping, enforcement point and test owner from their named profile. Platform
rows additionally identify responsible repositories and exact Kubernetes objects.
Audience candidates and ownership inferred from deployment names are explicitly
pending owner confirmation; none establishes an effective platform-root grant.
## Scope and reproducibility
Hub source revision is recorded in the JSON. Runtime routes are constructed
locally without running startup, sending requests or connecting to a database.
The optional inbox router is included separately. Compatibility aliases and
FastAPI built-in documentation endpoints are included even when absent from
OpenAPI; disabled compatibility groups still belong in the coverage contract.
Embedded factories are scanned with their default prefixes and include optional
operations: host mounting and feature flags determine effective deployment paths.
MCP extraction asserts coverage against CORE_TOOL_NAMES and records its HTTP calls.
Cluster collection used the explicit railiance01 kubeconfig and metadata-only
projection of six resource kinds. No Secret, environment value, mounted file,
service-account token or authentication credential was collected. This is not a
scan of every CRD, Pod/Job, host process, external provider or tenant application
endpoint. Native execution and external-control rows keep those inventory gaps
visible. Root administration of tenant infrastructure is distinct from an
unreviewed grant to its business data.
Run from hub-core:
```sh
PYTHONDONTWRITEBYTECODE=1 .venv/bin/python tools/build_access_inventory.py \
--inventory docs/platform-access-inventory.json --check
```
Omit `--check` to refresh source rows after intentional changes, then review the
diff. Cluster metadata is a dated reviewed input, not silently refreshed by this
command. The checker detects source drift, missing profile/test references and
missing/duplicate cluster-object mappings. It does not test authorization. Actual
allow/deny cases are all marked `not-run`; implementation tasks must supply the
client fixtures, isolated mutations, independent readbacks and live receipts.
## Findings that affect implementation
1. **Documentation routes have overlapping handlers.** GET `/docs` and `/openapi.json`
each register both FastAPI's built-in handler and a compatibility alias.
Protecting only the compatibility handler leaves another dispatch path.
T04 must test effective routing, including HEAD and slash normalization.
2. **MCP is not synonymous with the standalone runtime.** Many tools call
`/messages`, `/domains`, `/state/summary` and other host routes rather than
`/ports/...`. T04/T05 need an explicit backend/migration mapping and per-caller
authentication. A successful native-port test does not cover these tools.
3. **Embedded APIs are independent entry points.** The host owns authentication,
policy injection and any prefix overrides; an Ingress change cannot protect
a host that mounts the SDK elsewhere. Inventory actual consumer mounts before
freezing T01, using the retirement route/caller ledgers.
4. **39 namespaces do not mean 39 login surfaces.** Controllers, backing stores,
scaled-down revisions and the notice page should be managed through their
owner/Kubernetes path, not exposed as new human-facing services. Each owner
must split management and application audiences within its namespace row.
5. **Extensions/native administration still need owner evidence.** Ops Hub's
manifest names `service.ops-hub.http`, a framework API, console and CLI;
standalone live resolution is unproven. Fabric hosting is independently
blocked under RAIL-FAB-WP-0028. SSH, Kubernetes, GitOps, host jobs and provider
control planes need their own root entitlement receipts.
## Proposed acceptance cases
Every non-health surface runs ROOT, OTHER, INVALID, REVOKE, OUTAGE, BYPASS and
CALLER from the JSON; native privileged actions additionally run APPROVAL.
`/healthz` gets HEALTH instead of pretending anonymous probes should be denied.
These are test specifications, not completed tests:
| Case | Required observation |
| --- | --- |
| ROOT | Verified immutable root identity + current entitlement + AAL2 succeeds; independent readback and actor audit |
| OTHER | Ordinary user and tenant administrator cannot perform platform operations or learn unauthorized tenant data |
| INVALID | Anonymous, forged username/header, wrong audience/issuer and expired token rejected without side effect |
| REVOKE | Grant removal, account suspension and logout deny within the specified bound; current authority rechecked for privileged mutation |
| OUTAGE | Untrusted/unavailable policy or required audit cannot authorize mutation |
| BYPASS | Direct Service, aliases, MCP and embedded hosts enforce the same decision |
| CALLER | Named workload receives only its grant; spoofed sender, delegation and inherited root authority fail |
| APPROVAL | Full root entitlement does not skip action-specific confirmation/approval; use reversible or isolated targets |
| HEALTH | Anonymous liveness reveals no subject, tenant, dependency or business details |
## Hub surface register
The table lists every discovered source operation. JSON retains factory/handler,
source location, current gate observation and MCP target call expressions.
Duplicate runtime method/path rows are intentional separate registrations.
| Kind/profile | Operation | Source/handler |
| --- | --- | --- |
| runtime-http / hub-api | `GET /annotation-categories` | `annotation_categories` |
| runtime-http / hub-api | `GET /annotations` | `empty_collection` |
| runtime-http / hub-api | `GET /api-consumers` | `list_consumers` |
| runtime-http / hub-api | `GET /api/v2/annotation-categories` | `annotation_categories` |
| runtime-http / hub-api | `GET /api/v2/annotations` | `empty_collection` |
| runtime-http / hub-api | `GET /api/v2/api-consumers` | `list_consumers` |
| runtime-http / hub-api | `GET /api/v2/decision-records` | `empty_collection` |
| runtime-http / hub-api | `GET /api/v2/deployment-records` | `empty_collection` |
| runtime-http / hub-api | `GET /api/v2/docs` | `docs` |
| runtime-http / hub-api | `GET /api/v2/event-types` | `event_types` |
| runtime-http / hub-api | `GET /api/v2/hub-capability-manifests` | `list_manifests` |
| runtime-http / hub-api | `GET /api/v2/hub-registry` | `hub_registry` |
| runtime-http / hub-api | `GET /api/v2/hubs` | `list_hubs` |
| runtime-http / hub-api | `GET /api/v2/interaction-events` | `list_interactions` |
| runtime-http / hub-api | `GET /api/v2/openapi.json` | `openapi_json` |
| runtime-http / hub-api | `GET /api/v2/openapi.yaml` | `openapi_yaml` |
| runtime-http / hub-api | `GET /api/v2/outcome-signals` | `empty_collection` |
| runtime-http / hub-api | `GET /api/v2/policy-scopes` | `policy_scopes` |
| runtime-http / hub-api | `GET /api/v2/requirement-candidates` | `empty_collection` |
| runtime-http / hub-api | `GET /api/v2/widget-types` | `widget_types` |
| runtime-http / hub-api | `GET /api/v2/widgets` | `list_widgets` |
| runtime-http / hub-api | `GET /console` | `console` |
| runtime-http / hub-api | `GET /decision-records` | `empty_collection` |
| runtime-http / hub-api | `GET /deployment-records` | `empty_collection` |
| runtime-http / hub-api | `GET /docs/oauth2-redirect` | `swagger_ui_redirect` |
| runtime-http / hub-api | `GET /docs` | `swagger_ui_html` |
| runtime-http / hub-api | `GET /docs` | `docs` |
| runtime-http / hub-api | `GET /event-types` | `event_types` |
| runtime-http / minimal-health | `GET /healthz` | `healthz` |
| runtime-http / hub-api | `GET /hub-capability-manifests` | `list_manifests` |
| runtime-http / hub-api | `GET /hub-registry` | `hub_registry` |
| runtime-http / hub-api | `GET /hubs` | `list_hubs` |
| runtime-http / hub-api | `GET /interaction-events` | `list_interactions` |
| runtime-http / hub-api | `GET /openapi.json` | `openapi` |
| runtime-http / hub-api | `GET /openapi.json` | `openapi_json` |
| runtime-http / hub-api | `GET /openapi.yaml` | `openapi_yaml` |
| runtime-http / hub-api | `GET /outcome-signals` | `empty_collection` |
| runtime-http / hub-api | `GET /policy-scopes` | `policy_scopes` |
| runtime-http / hub-api | `GET /ports/messaging/messages` | `list_messages` |
| runtime-http / hub-api | `GET /ports/projections/repository-navigation/facets/{facet_kind}/{facet_value}` | `query_facet` |
| runtime-http / hub-api | `GET /ports/projections/repository-navigation/repositories` | `query_repositories` |
| runtime-http / hub-api | `GET /ports/projections/statehub-inbox` | `inbox` |
| runtime-http / hub-api | `GET /ports/projections/workloads/resolve` | `resolve_workload` |
| runtime-http / hub-api | `GET /ports/projections/workloads` | `query_workloads` |
| runtime-http / hub-api | `GET /ports/projections/{projection_id}` | `query_projection` |
| runtime-http / hub-api | `GET /ports/registry/registrations/{hub_slug}/audit` | `registration_audit` |
| runtime-http / hub-api | `GET /ports/registry/registrations/{hub_slug}` | `resolve_registration` |
| runtime-http / hub-api | `GET /readyz` | `readyz` |
| runtime-http / hub-api | `GET /redoc` | `redoc_html` |
| runtime-http / hub-api | `GET /requirement-candidates` | `empty_collection` |
| runtime-http / hub-api | `GET /widget-types` | `widget_types` |
| runtime-http / hub-api | `GET /widgets` | `list_widgets` |
| runtime-http / hub-api | `HEAD /docs/oauth2-redirect` | `swagger_ui_redirect` |
| runtime-http / hub-api | `HEAD /docs` | `swagger_ui_html` |
| runtime-http / hub-api | `HEAD /openapi.json` | `openapi` |
| runtime-http / hub-api | `HEAD /redoc` | `redoc_html` |
| runtime-http / hub-api | `PATCH /api/v2/hub-capability-manifests/{manifest_id}` | `patch_manifest` |
| runtime-http / hub-api | `PATCH /hub-capability-manifests/{manifest_id}` | `patch_manifest` |
| runtime-http / hub-api | `POST /annotations` | `accept_deferred` |
| runtime-http / hub-api | `POST /api-consumers/{consumer_id}/api-keys` | `create_key` |
| runtime-http / hub-api | `POST /api-consumers` | `create_consumer` |
| runtime-http / hub-api | `POST /api/v2/annotations` | `accept_deferred` |
| runtime-http / hub-api | `POST /api/v2/api-consumers/{consumer_id}/api-keys` | `create_key` |
| runtime-http / hub-api | `POST /api/v2/api-consumers` | `create_consumer` |
| runtime-http / hub-api | `POST /api/v2/decision-records` | `accept_deferred` |
| runtime-http / hub-api | `POST /api/v2/deployment-records` | `accept_deferred` |
| runtime-http / hub-api | `POST /api/v2/hub-capability-manifests/{manifest_id}/activate` | `activate_manifest` |
| runtime-http / hub-api | `POST /api/v2/hub-capability-manifests` | `create_manifest` |
| runtime-http / hub-api | `POST /api/v2/hubs` | `create_hub` |
| runtime-http / hub-api | `POST /api/v2/interaction-events` | `create_interaction` |
| runtime-http / hub-api | `POST /api/v2/outcome-signals` | `accept_deferred` |
| runtime-http / hub-api | `POST /api/v2/requirement-candidates` | `accept_deferred` |
| runtime-http / hub-api | `POST /api/v2/token` | `token` |
| runtime-http / hub-api | `POST /api/v2/widgets` | `create_widget` |
| runtime-http / hub-api | `POST /decision-records` | `accept_deferred` |
| runtime-http / hub-api | `POST /deployment-records` | `accept_deferred` |
| runtime-http / hub-api | `POST /hub-capability-manifests/{manifest_id}/activate` | `activate_manifest` |
| runtime-http / hub-api | `POST /hub-capability-manifests` | `create_manifest` |
| runtime-http / hub-api | `POST /hubs` | `create_hub` |
| runtime-http / hub-api | `POST /interaction-events` | `create_interaction` |
| runtime-http / hub-api | `POST /outcome-signals` | `accept_deferred` |
| runtime-http / hub-api | `POST /ports/events/interaction` | `append_interaction` |
| runtime-http / hub-api | `POST /ports/events/progress` | `append_progress` |
| runtime-http / hub-api | `POST /ports/messaging/messages` | `send_message` |
| runtime-http / hub-api | `POST /ports/registry/registrations` | `register_extension` |
| runtime-http / hub-api | `POST /requirement-candidates` | `accept_deferred` |
| runtime-http / hub-api | `POST /token` | `token` |
| runtime-http / hub-api | `POST /widgets` | `create_widget` |
| mcp / mcp-client | `accept_capability_request` | `hub_core/mcp/server.py` |
| mcp / mcp-client | `append_progress` | `hub_core/mcp/server.py` |
| mcp / mcp-client | `check_repo_doi` | `hub_core/mcp/server.py` |
| mcp / mcp-client | `get_alerts` | `hub_core/mcp/server.py` |
| mcp / mcp-client | `get_capability_request` | `hub_core/mcp/server.py` |
| mcp / mcp-client | `get_doi_summary` | `hub_core/mcp/server.py` |
| mcp / mcp-client | `get_domain` | `hub_core/mcp/server.py` |
| mcp / mcp-client | `get_domain_summary` | `hub_core/mcp/server.py` |
| mcp / mcp-client | `get_gdpr_report` | `hub_core/mcp/server.py` |
| mcp / mcp-client | `get_messages` | `hub_core/mcp/server.py` |
| mcp / mcp-client | `get_repository_navigation_facet` | `hub_core/mcp/server.py` |
| mcp / mcp-client | `get_risks` | `hub_core/mcp/server.py` |
| mcp / mcp-client | `get_state_summary` | `hub_core/mcp/server.py` |
| mcp / mcp-client | `ingest_tpsc_tool` | `hub_core/mcp/server.py` |
| mcp / mcp-client | `list_capabilities` | `hub_core/mcp/server.py` |
| mcp / mcp-client | `list_capability_requests` | `hub_core/mcp/server.py` |
| mcp / mcp-client | `list_domain_repos` | `hub_core/mcp/server.py` |
| mcp / mcp-client | `list_domains` | `hub_core/mcp/server.py` |
| mcp / mcp-client | `list_services` | `hub_core/mcp/server.py` |
| mcp / mcp-client | `mark_message_read` | `hub_core/mcp/server.py` |
| mcp / mcp-client | `query_repository_navigation` | `hub_core/mcp/server.py` |
| mcp / mcp-client | `query_workloads` | `hub_core/mcp/server.py` |
| mcp / mcp-client | `register_capability` | `hub_core/mcp/server.py` |
| mcp / mcp-client | `register_repo` | `hub_core/mcp/server.py` |
| mcp / mcp-client | `register_service` | `hub_core/mcp/server.py` |
| mcp / mcp-client | `reply_to_message` | `hub_core/mcp/server.py` |
| mcp / mcp-client | `request_capability` | `hub_core/mcp/server.py` |
| mcp / mcp-client | `resolve_workload_reference` | `hub_core/mcp/server.py` |
| mcp / mcp-client | `send_message` | `hub_core/mcp/server.py` |
| mcp / mcp-client | `update_capability_request_status` | `hub_core/mcp/server.py` |
| mcp / mcp-client | `update_repo_path` | `hub_core/mcp/server.py` |
| embedded-http / embedded-host | `GET /capability-catalog/` | `create_capability_catalog_router` |
| embedded-http / embedded-host | `PATCH /capability-catalog/{entry_id}` | `create_capability_catalog_router` |
| embedded-http / embedded-host | `POST /capability-catalog/` | `create_capability_catalog_router` |
| embedded-http / embedded-host | `GET /capability-requests/` | `create_capability_request_read_router` |
| embedded-http / embedded-host | `GET /capability-requests/{request_id}` | `create_capability_request_read_router` |
| embedded-http / embedded-host | `PATCH /capability-requests/{request_id}` | `create_capability_request_write_router` |
| embedded-http / embedded-host | `PATCH /capability-requests/{request_id}/status` | `create_capability_request_write_router` |
| embedded-http / embedded-host | `POST /capability-requests/` | `create_capability_request_write_router` |
| embedded-http / embedded-host | `POST /capability-requests/{request_id}/accept` | `create_capability_request_write_router` |
| embedded-http / embedded-host | `POST /capability-requests/{request_id}/dispute` | `create_capability_request_write_router` |
| embedded-http / embedded-host | `POST /capability-requests/{request_id}/reroute` | `create_capability_request_write_router` |
| embedded-http / embedded-host | `GET /domains/` | `create_domains_router` |
| embedded-http / embedded-host | `GET /domains/{slug}` | `create_domains_router` |
| embedded-http / embedded-host | `PATCH /domains/{slug}` | `create_domains_router` |
| embedded-http / embedded-host | `PATCH /domains/{slug}/archive` | `create_domains_router` |
| embedded-http / embedded-host | `PATCH /domains/{slug}/rename` | `create_domains_router` |
| embedded-http / embedded-host | `POST /domains/` | `create_domains_router` |
| embedded-http / embedded-host | `GET /messages/` | `create_messages_router` |
| embedded-http / embedded-host | `GET /messages/thread/{thread_id}` | `create_messages_router` |
| embedded-http / embedded-host | `PATCH /messages/{message_id}/archive` | `create_messages_router` |
| embedded-http / embedded-host | `PATCH /messages/{message_id}/read` | `create_messages_router` |
| embedded-http / embedded-host | `POST /messages/` | `create_messages_router` |
| embedded-http / embedded-host | `POST /messages/{message_id}/reply` | `create_messages_router` |
| embedded-http / embedded-host | `GET /policy/{name}` | `create_policy_router` |
| embedded-http / embedded-host | `PUT /policy/{name}` | `create_policy_router` |
| embedded-http / embedded-host | `GET /progress/` | `create_progress_router` |
| embedded-http / embedded-host | `GET /progress/alerts` | `create_progress_router` |
| embedded-http / embedded-host | `GET /progress/risks` | `create_progress_router` |
| embedded-http / embedded-host | `POST /progress/` | `create_progress_router` |
| embedded-http / embedded-host | `GET /repos/` | `create_repos_router` |
| embedded-http / embedded-host | `GET /repos/by-fingerprint` | `create_repos_router` |
| embedded-http / embedded-host | `GET /repos/by-remote` | `create_repos_router` |
| embedded-http / embedded-host | `GET /repos/{slug}` | `create_repos_router` |
| embedded-http / embedded-host | `PATCH /repos/{slug}` | `create_repos_router` |
| embedded-http / embedded-host | `POST /repos/` | `create_repos_router` |
| embedded-http / embedded-host | `POST /repos/{slug}/paths` | `create_repos_router` |
| embedded-http / embedded-host | `GET /tpsc/catalog/` | `create_tpsc_router` |
| embedded-http / embedded-host | `GET /tpsc/catalog/{slug}` | `create_tpsc_router` |
| embedded-http / embedded-host | `GET /tpsc/report/gdpr` | `create_tpsc_router` |
| embedded-http / embedded-host | `GET /tpsc/snapshots/` | `create_tpsc_router` |
| embedded-http / embedded-host | `POST /tpsc/catalog/` | `create_tpsc_router` |
| embedded-http / embedded-host | `POST /tpsc/ingest/` | `create_tpsc_router` |
## Platform and extension register
All rows require owner review and root acceptance. The JSON contains exact
object names, hosts, desired/ready replicas and service types for cluster rows.
This mapping identifies accountable review destinations, not completed review.
| Boundary | Owner / test owner | Coverage |
| --- | --- | --- |
| `namespace:activity-core` | activity-core | 22 observed objects |
| `namespace:approval-engine` | approval-engine | 2 observed objects |
| `namespace:argocd` | railiance-platform / railiance-enablement | 8 observed objects |
| `namespace:audit-core` | audit-core | 3 observed objects |
| `namespace:bao-notice` | railiance-platform | 4 observed objects |
| `namespace:canned-prompts` | rapp-canned-prompts | 2 observed objects |
| `namespace:cert-manager` | railiance-platform | 6 observed objects |
| `namespace:cnpg-system` | rapp-postgres | 2 observed objects |
| `namespace:core-hub` | hub-core / rapp-core-hub / repo-manager | 6 observed objects |
| `namespace:coulomb` | railiance-platform (probe owner to confirm) | 3 observed objects |
| `namespace:coulomb-social` | coulomb-social | 3 observed objects |
| `namespace:databases` | rapp-postgres / railiance-platform | 18 observed objects |
| `namespace:default` | railiance-platform | 1 observed objects |
| `namespace:email-connect` | email-connect | 2 observed objects |
| `namespace:external-secrets` | railiance-platform | 5 observed objects |
| `namespace:flex-auth` | flex-auth | 12 observed objects |
| `namespace:forgejo` | railiance-forge / railiance-platform | 6 observed objects |
| `namespace:informed-decision` | informed-decision | 4 observed objects |
| `namespace:inter-hub` | prj-state-hub-retirement / railiance-platform | 2 observed objects |
| `namespace:issue-core` | issue-core | 2 observed objects |
| `namespace:knative-serving` | rail-knative / railiance-platform | 11 observed objects |
| `namespace:kourier-system` | rail-knative / railiance-platform | 3 observed objects |
| `namespace:kube-system` | rail-kubernetes / railiance-platform | 10 observed objects |
| `namespace:mfa` | net-kingdom / key-cape | 7 observed objects |
| `namespace:openbao` | rapp-openbao / railiance-platform | 8 observed objects |
| `namespace:platform-pg-drill` | rapp-postgres | 2 observed objects |
| `namespace:policy-nexus` | policy-nexus | 4 observed objects |
| `namespace:rapp-qonto` | rapp-qonto | 28 observed objects |
| `namespace:rapp-qonto-egress` | rapp-qonto | 2 observed objects |
| `namespace:rein-aharness` | rein-aharness | 1 observed objects |
| `namespace:reuse` | reuse-surface | 7 observed objects |
| `namespace:sbom-nexus` | sbom-nexus | 2 observed objects |
| `namespace:sso` | net-kingdom / key-cape | 15 observed objects |
| `namespace:state-hub` | state-hub | 4 observed objects |
| `namespace:target-revenue` | target-revenue | 6 observed objects |
| `namespace:telemetry` | rapp-telemetry / railiance-platform | 12 observed objects |
| `namespace:tenant-engine` | tenant-engine | 2 observed objects |
| `namespace:user-engine` | user-engine | 9 observed objects |
| `namespace:vergabe-demo-company` | vergabe-demo-company | 4 observed objects |
| `management:ops-hub` | ops-hub | service.ops-hub.http, framework /api/v2, ops-console, ops-bootstrap |
| `management:financial-fabric` | fin-hub / railiance-fabric | financial graph owner API and projection/export |
| `management:repo-manager` | repo-manager | registry/work projections, governed CLI/Git mutations, Forgejo-backed publisher |
| `management:kubernetes` | rail-kubernetes / railiance-platform | realm:kubernetes/railiance01; API, RBAC, nodes, workload lifecycle |
| `management:ssh-tunnels` | ops-warden / ops-bridge | railiance01 SSH certificate/principal and named tunnels |
| `management:gitops-deploy` | railiance-platform / railiance-enablement | ArgoCD Core CLI, repo authorization, per-workload release/rollback |
| `management:secrets-engine` | secrets-engine / railiance-platform | credential issue/rotate/revoke and approval-bound OpenBao operations |
| `management:host-jobs` | railiance-platform / activity-core | host systemd timers, cron, backup/restore and DR execution |
| `management:external-control` | railiance-platform / net-kingdom | DNS, registrar, hosting, object storage and off-cluster recovery administration |
## Remaining T01 decisions
- NetKingdom/User Engine: verify root `(iss, sub)` and the entitlement mapping;
confirm registered audiences, MFA journey and measurable revocation bounds.
- flex-auth/Tenant Engine: ratify action/resource names, authoritative fact
checks, cross-tenant root administration and decision/audit obligations.
- Hub/extension owners: map effective embedded mounts, legacy MCP destinations,
active extension discovery and public/health exceptions; resolve duplicate docs.
- Railiance owners: confirm namespace ownership, enumerate per-service audiences
and endpoint catalogs, CRD/Job/host/provider management paths, and the native
SSH/Kubernetes/GitOps grants. Unknown surfaces cannot be marked passed.
- All reviewers: approve the versioned profile and supply executable enforcement
fixtures/receipts. Until then T01 remains in progress and public exposure stays
gated. No new workplan or live configuration change was made by this inventory.
Related evidence: [access blueprint](netkingdom-access-blueprint.md),
[HUB-WP-0012](../workplans/HUB-WP-0012-netkingdom-platform-root-access.md),
`ops-hub/registry/hub-extension/v0.1.0/ops-hub.extension.json`, retirement project
`inventory/routes.yaml` and `inventory/jobs-callers-ops.yaml`.

View file

@ -0,0 +1,131 @@
#!/usr/bin/env python3
"""Inventory source surfaces without network calls, database access or credentials.
Run with hub-core's runtime environment. --check detects source-surface drift;
it is not an authorization conformance test. Platform snapshot is reviewed input.
"""
import argparse
import ast
import inspect
import json
from pathlib import Path
import sys
def discover(root):
sys.path.insert(0, str(root))
from hub_core.runtime.app import create_app
from hub_core.runtime.config import RuntimeSettings
from hub_core.runtime.inbox_projection import create_inbox_projection_router
rows = []
def routes(router):
for route in router.routes:
if hasattr(route, 'original_router'):
yield from routes(route.original_router)
elif hasattr(route, 'methods'):
yield route
else:
raise ValueError(f'Uninventoried route type: {type(route).__name__}')
# Construction only: no lifespan/startup and no requests or DB connection.
app = create_app(settings=RuntimeSettings())
for route in [*routes(app), *routes(create_inbox_projection_router())]:
endpoint = route.endpoint
source = inspect.getsource(endpoint)
module = endpoint.__module__
gate = ('shared-bearer' if '_protected(' in source or module.endswith('inbox_projection')
else 'no-identity-check-in-handler')
for method in sorted(route.methods):
rows.append(dict(id=f'http:{method}:{route.path}:{module}.{endpoint.__name__}', kind='runtime-http',
method=method, path=route.path, profile=('minimal-health' if route.path == '/healthz' else 'hub-api'),
current_gate=gate, source=module,
conditional=module.endswith('inbox_projection'),
handler=endpoint.__name__))
verbs = {'get', 'post', 'patch', 'put', 'delete', 'head', 'options'}
for path in sorted((root / 'hub_core/routers').glob('*.py')):
tree = ast.parse(path.read_text())
for factory in tree.body:
if not isinstance(factory, ast.FunctionDef) or not factory.name.startswith('create_'):
continue
prefix = ''
for node in ast.walk(factory):
if isinstance(node, ast.Call) and isinstance(node.func, ast.Name) and node.func.id == 'APIRouter':
for kw in node.keywords:
if kw.arg == 'prefix':
if isinstance(kw.value, ast.Constant):
prefix = kw.value.value
elif isinstance(kw.value, ast.Name):
defaults = dict(zip([a.arg for a in factory.args.kwonlyargs], factory.args.kw_defaults))
prefix = ast.literal_eval(defaults[kw.value.id])
else:
raise ValueError('Unresolved SDK prefix')
for node in ast.walk(factory):
if not isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
continue
for dec in node.decorator_list:
if isinstance(dec, ast.Call) and isinstance(dec.func, ast.Attribute) and dec.func.attr in verbs:
route_path = prefix + ast.literal_eval(dec.args[0])
method = dec.func.attr.upper()
rows.append(dict(id=f'sdk:{factory.name}:{method}:{route_path}',
kind='embedded-http', method=method, path=route_path,
profile='embedded-host', factory=factory.name,
source=str(path.relative_to(root)), line=node.lineno,
current_gate='host-injected; not established by inventory',
conditional=True))
path = root / 'hub_core/mcp/server.py'
tree = ast.parse(path.read_text())
expected = None
for node in tree.body:
if isinstance(node, ast.Assign) and any(isinstance(t, ast.Name) and t.id == 'CORE_TOOL_NAMES' for t in node.targets):
expected = set(ast.literal_eval(node.value.args[0]))
for node in ast.walk(tree):
if not isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
continue
for dec in node.decorator_list:
if isinstance(dec, ast.Call) and isinstance(dec.func, ast.Name) and dec.func.id == 'register':
name = ast.literal_eval(dec.args[0])
calls = []
for call in ast.walk(node):
if isinstance(call, ast.Call) and isinstance(call.func, ast.Attribute) and call.func.attr in {'_get','_post','_patch','_put','_delete'}:
calls.append(dict(method=call.func.attr[1:].upper(), path_expression=ast.unparse(call.args[0])))
rows.append(dict(id=f'mcp:{name}', kind='mcp', tool=name, profile='mcp-client',
source=str(path.relative_to(root)), line=node.lineno,
target_calls=calls, current_gate='no per-user credential forwarding in base wrapper'))
assert expected == {r['tool'] for r in rows if r['kind'] == 'mcp'}
assert len(rows) == len({r['id'] for r in rows}), 'Duplicate surface identity'
return sorted(rows, key=lambda r:r['id'])
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--root', type=Path, default=Path(__file__).resolve().parents[1])
parser.add_argument('--inventory', type=Path, required=True)
parser.add_argument('--check', action='store_true')
args = parser.parse_args()
data = json.loads(args.inventory.read_text())
found = discover(args.root)
if args.check:
assert data['hub_surfaces'] == found, 'Source inventory drift; regenerate and review'
else:
data['hub_surfaces'] = found
args.inventory.write_text(json.dumps(data, indent=2)+'\n')
cases = data['acceptance_cases']
profiles = data['profiles']
for row in data['hub_surfaces'] + data['platform_surfaces']:
profile = profiles[row['profile']]
assert all(profile.get(k) for k in ('audience','actor_tenant','target_tenant','action_resource_rule','enforcement','test_owner','cases'))
assert all(c in cases for c in profile['cases'])
objects = data['cluster_snapshot']
mapped = [x for r in data['platform_surfaces'] for x in r.get('objects',[])]
keys = lambda xs: sorted((x['kind'],x['namespace'],x['name']) for x in xs)
assert keys(mapped) == keys(objects), 'Cluster object coverage mismatch or duplicates'
assert all(r.get('owner') and r.get('review_status') for r in data['platform_surfaces'])
print(f"{len(found)} Hub surfaces; {len(data['platform_surfaces'])} platform rows; {len(objects)} cluster objects; inventory checks pass")
if __name__ == '__main__':
main()

View file

@ -4,7 +4,7 @@ type: workplan
title: "NetKingdom identity and tenant integration: platform-root first"
domain: infotech
repo: hub-core
status: proposed
status: active
flavor: implementation
owner: codex
topic_slug: infotech
@ -43,7 +43,7 @@ existing retirement and rollout plans retain their tasks. Core Hub receives no
new product feature work. This planning session does not implement or activate
grants, enroll factors, deploy policies, expose services or retire State Hub.
Status is proposed because cross-owner policy, root identity binding and live
Inventory work is active. Cross-owner policy, root identity binding and live
acceptance are not yet reviewed. The user has selected the root-first scope;
there is no need to reopen that product decision. Dependencies below are
per-task sequencing, not a blanket wait for every related workplan to finish.
@ -52,7 +52,7 @@ per-task sequencing, not a blanket wait for every related workplan to finish.
```task
id: HUB-WP-0012-T01
status: todo
status: progress
priority: high
state_hub_task_id: "204f4fb0-e240-5558-8790-5985517b85e0"
```
@ -70,6 +70,16 @@ Done when no published route/tool or active platform surface lacks a row and
owner, contract reviewers' decisions are recorded, and M1 success/deny cases
are executable specifications. Unknown/disputed rows remain visible blockers.
2026-09-28: the [access inventory](../docs/platform-access-inventory.md) now
enumerates 161 Hub source surfaces and 48 platform/extension boundaries, covering
250 observed cluster objects. Its machine-readable profiles specify owners,
audiences, actor/target tenants, enforcement and acceptance cases; the checker
detects source drift and incomplete object mappings. Duplicate docs handlers,
legacy MCP targets and unresolved native/extension paths are explicit findings.
Owner review, effective host/per-service route expansion, policy vocabulary and
authenticated acceptance remain open, so T01 is `progress`, not `done`. No
platform-root login or enforcement test is claimed by inventory validation.
## T02 — Bind platform-root identity, login, tenant and revocation
```task