Assistant: codex Assistant-Model: gpt-6-astra Assistant-Session: 01a0e747-8f27-7242-8df8-8bc44f88c929
207 lines
13 KiB
Markdown
207 lines
13 KiB
Markdown
# HUB-WP-0012 owner integration review — 2026-09-28
|
|
|
|
The Hub foundation is committed as `3e38614`. This follow-up delivers an Audit
|
|
Core sender and runtime composition, and identifies the remaining source
|
|
contracts. These are owner review inputs, not evidence of deployed grants.
|
|
|
|
## Account, root entitlement and tenant facts
|
|
|
|
| Owner surface reviewed | Observed behavior | Required integration |
|
|
| --- | --- | --- |
|
|
| User Engine `web.py`, `service.py::me` | `/api/v1/me` resolves `(iss, sub)` but creates User/Account/ExternalIdentity records if absent | A side-effect-free authority lookup; Hub must not bootstrap identities to check access |
|
|
| User Engine `service.py` | `PLATFORM_TENANT = "platform:root"`; `platform-operator` is an actor role | Explicit mapping to IAM `tenant:platform` and the one immutable root principal; a role or string substitution is insufficient |
|
|
| User Engine tenant administration APIs | Human/edge-oriented routes; no reviewed Hub workload lookup for current root entitlement | Independently authenticated Hub workload, exact lookup scope and current entitlement provenance |
|
|
| Tenant Engine `/tenants/{tenant_id}` | Current lifecycle and record version, authorized `tenant.read` | Resolve immutable tenant ID versus canonical identifier; preserve owner version and lifecycle |
|
|
| Tenant Engine `/tenants/{tenant_id}/roles/live` | Live role state with `tenant.role.read.live`, caller supplies an `actor` query | Admit/authenticate the actual Hub workload and its actor assertion; do not mistake query text or a network path for caller authentication |
|
|
|
|
The accepted integration must return identity references, account status,
|
|
explicit current root entitlement, actor/target tenant status, observation times
|
|
and owner evidence/version references. Unknown identity is denied without
|
|
creating anything. Revocation must be visible on the next privileged read as
|
|
well as write; no cached token role supplies current entitlement.
|
|
|
|
`LiveFacts` is the Hub-side normalized result, not a wire endpoint invented for
|
|
an owner. Each source lookup must complete inside its timeout. The observation
|
|
age is measured from the actual source observation and cannot be reset after
|
|
slow downstream calls. All joined facts must still be at most five seconds old
|
|
when policy/audit finish. Missing, ambiguous, inactive or stale facts deny.
|
|
Producer aliases must come from an explicit owner binding to that identity.
|
|
|
|
T01/T02 require owner review of the lookup and root/tenant mapping before a
|
|
production `FactSource` is configured. The runtime will not load fixture facts,
|
|
query owner databases directly, forward Hub bearer tokens to other audiences,
|
|
or use `/me` as an account-provisioning side effect.
|
|
|
|
## Audit Core adapter and sender admission
|
|
|
|
`AuditCoreSink` implements the actual `docs/event-envelope.md` contract:
|
|
`POST /v1/events` with eight fields, a distinct rotating sender bearer, and an
|
|
`Idempotency-Key` matching the event ID. Source is exactly `hub-core`, tenant
|
|
exactly `tenant:platform`. Only a matching `202 accepted` or `200 duplicate`
|
|
with a nonempty archive reference counts as custody. Before each append,
|
|
`/readyz` must report `status=ok`, `durable=true`, and custody class
|
|
`operational` or its rollout alias `archive`. The entire append is bounded
|
|
at three seconds, uses TLS, and never follows redirects.
|
|
|
|
Allow is blocked until the archive accepts the authorization record. A lost
|
|
receipt blocks the business operation even if the attempt reached storage. This
|
|
is a pre-execution authorization journal, not proof that an operation committed.
|
|
There is no local success buffer or silent redaction. Domain transaction/outcome
|
|
atomicity and failure detection remain separate T03/T04 acceptance gates.
|
|
|
|
The exact verified signed decision is retained under `data.signed_decision` as
|
|
serialized JSON so another serialization of the archive cannot reorder its Go
|
|
struct fields. The verifier refuses secret-shaped field names before retention.
|
|
No end-user bearer, message body or command body is emitted. Independent tests
|
|
reverify the signed artifact after retrieval from the owner's receiver.
|
|
|
|
Proposed receiver registration (no credential values):
|
|
|
|
- Name/source: `hub-core`; allowed tenants: only `tenant:platform`.
|
|
- Write enabled, read disabled; distinct sender tokens with rotation overlap.
|
|
- `secret_policy=reject`; authorization record classes `hub.access.authorized`,
|
|
`hub.access.denied`, `hub.access.refused`.
|
|
- Load-bearing authorization evidence: execution requires receipt. Owner review
|
|
must settle exact emission-cadence/failure detection; no claim that this journal
|
|
provides complete domain mutation evidence or archive tamper evidence.
|
|
|
|
Credential routing: `warden route show audit-core-senders --json` identifies
|
|
`ops-mason`, subsystem OpenBao + audit-core, `warden_executes=false`, and currently
|
|
`resolvable=false`. This route points to registry custody; it does not prove an
|
|
admitted Hub sender or mint a credential. No secret was requested or retrieved.
|
|
|
|
## Runtime assembly
|
|
|
|
`SecuritySettings` uses these `HUB_CORE_SECURITY_` environment suffixes:
|
|
|
|
| Suffix | Value |
|
|
| --- | --- |
|
|
| `ISSUER`, `AUDIENCE`, `ROOT_SUBJECT` | Admitted HTTPS issuer, Hub audience, immutable root subject |
|
|
| `POLICY_URL`, `POLICY_CALLER` | Dedicated HTTPS PDP and exact admitted ServiceAccount principal |
|
|
| `POLICY_TOKEN_FILE`, `POLICY_KEYS_FILE` | Absolute projected caller-token and trusted public-key paths |
|
|
| `AUDIT_URL`, `AUDIT_TOKEN_FILE` | HTTPS receiver and separate absolute sender-token path |
|
|
|
|
A host calls `create_app(access_facts=reviewed_owner_adapter)` in enforcement
|
|
mode. It can alternatively supply an explicit `SecuritySettings` instance.
|
|
The runtime owns and closes the shared HTTP client; proxy environment variables
|
|
are not inherited. Partial/invalid composition is rejected. Without an explicit
|
|
facts adapter, the standalone app stays closed even if security variables exist.
|
|
No file-backed allowlist or dynamic arbitrary-module loader supplies authority.
|
|
|
|
## Evidence boundary
|
|
|
|
The opt-in `tests/test_audit_core_owner_contract.py` exercises the actual Audit
|
|
Core WSGI receiver and durable SQLite storage, including reopen, wrong-credential
|
|
and lost-receipt cases. A **test-only** readiness fixture reports operational
|
|
custody to exercise the production client's receipt checks; this is not native
|
|
operational custody. Unmodified development custody is independently refused.
|
|
A composed fixture journey verifies a real RSA JWT, fresh facts, an Ed25519
|
|
policy decision, receiver custody and native Hub write, followed by entitlement
|
|
withdrawal denial. Real root enrollment/login, service deployment, live key and
|
|
sender custody, and operational acceptance remain open.
|
|
|
|
## Browser composition (source candidate)
|
|
|
|
Pass `browser_settings=BrowserSettings(origin="https://hub.example",
|
|
client_id="hub-browser", client_secret_file=Path("/run/secrets/hub-oidc-client"))`
|
|
to `create_app`, alongside the enforced controller or authoritative-facts security
|
|
composition described above. Import `BrowserSettings` from
|
|
`hub_core.security.browser`. Browser configuration cannot activate a development
|
|
runtime or a missing controller. Its confidential-client credential is separate
|
|
from the policy/audit workload credentials and is reread at each code exchange.
|
|
|
|
Register the exact HTTPS origin plus `/auth/callback` with the admitted issuer.
|
|
The source candidate requires authorization code, S256 PKCE, RS256 ID tokens and
|
|
`client_secret_basic` discovery support. It requests fresh authentication and
|
|
AAL2; validated access-token assurance and current owner facts decide access.
|
|
Issuer registration, actual MFA behavior, audience mapping and live owner
|
|
acceptance remain deployment gates. The implementation follows the
|
|
[OIDC ID token validation contract](https://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation)
|
|
and [OAuth security best practices](https://www.rfc-editor.org/rfc/rfc9700.html).
|
|
|
|
- `GET /auth/login` initiates login; arbitrary query parameters are refused.
|
|
- `GET /auth/callback` consumes browser-bound state once, exchanges the code with
|
|
PKCE, checks signature/issuer/audience/nonce/authentication time/access binding,
|
|
and requires root authorization and audit custody before issuing a session.
|
|
- `GET /auth/session` rechecks authority and returns expiry and a CSRF token.
|
|
- `POST /auth/logout` requires the session, exact `Origin` and `X-Hub-CSRF`, then
|
|
destroys the local session even during identity/policy/audit outages.
|
|
|
|
Sessions contain access tokens only in server memory. Cookies carry random opaque
|
|
IDs with `Secure`, `HttpOnly`, `SameSite=Lax`, host-only scope and `/` path. Sessions
|
|
expire within five minutes or either token's expiry, whichever comes first;
|
|
there is no refresh-token renewal. Process restart/shutdown invalidates sessions.
|
|
Multiple workers need sticky routing or a separately reviewed shared session
|
|
store; this candidate does not provide distributed sessions. Pending logins and
|
|
sessions have bounded capacity. Production ingress must also rate-limit login.
|
|
|
|
All protected API requests recheck identity, current facts, signed policy and
|
|
durable audit. Cookie-authenticated writes additionally require exact `Origin`
|
|
and `X-Hub-CSRF`; browser clients obtain the latter from `/auth/session`. The
|
|
bundled Swagger UI does not automatically inject that header. Mixed cookies and
|
|
bearer credentials are rejected. Session expiry/logout is checked again after
|
|
authority awaits and before dispatch. Logout cannot cancel already executing
|
|
requests or log out the upstream identity provider; subsequent login requests
|
|
fresh authentication. Logout is local invalidation, not a durable audited
|
|
business mutation. Refused browser flows are recorded without codes/tokens/query
|
|
parameters; audit failure returns 503. Login initiation does not require authority.
|
|
|
|
The app must observe the configured HTTPS origin. Do not trust arbitrary
|
|
forwarded headers; configure an authenticated trusted proxy separately. Callback
|
|
query strings are cleared before response-time application access logging, but
|
|
reverse proxies and tracing collectors must independently suppress callback
|
|
queries, cookies and authorization headers. No public listener is enabled by
|
|
these source changes.
|
|
|
|
## MCP caller composition and backend mapping
|
|
|
|
The standalone CLI now registers only four tools with equivalent runtime APIs:
|
|
|
|
| MCP tool | Runtime GET route |
|
|
| --- | --- |
|
|
| `query_repository_navigation` | `/ports/projections/repository-navigation/repositories` |
|
|
| `get_repository_navigation_facet` | `/ports/projections/repository-navigation/facets/{facet_kind}/{facet_value}` |
|
|
| `query_workloads` | `/ports/projections/workloads` |
|
|
| `resolve_workload_reference` | `/ports/projections/workloads/resolve` |
|
|
|
|
These use exact paths without redirect-based slash normalization. The other 27
|
|
generic tools require embedded host APIs: state/domain orientation, message
|
|
read/write/reply, capabilities/requests, repo/DOI operations, service/TPSC and
|
|
legacy progress operations. They remain available in the SDK's default
|
|
`backend_profile="embedded"`; they are not advertised by the standalone runtime
|
|
profile. Native message/event commands are not equivalent replacements for legacy
|
|
thread, recipient, author or event semantics. The inventory records each tool's
|
|
backend profiles. Host-specific routing and caller admission remain T04/T05 work.
|
|
|
|
For a **private, single-principal stdio process**, an operator can project an
|
|
already admitted Hub-audience credential and run:
|
|
|
|
```sh
|
|
HUB_CORE_ENV=production hub-core mcp --transport stdio \
|
|
--api-base https://hub.example --token-file /run/secrets/hub-mcp-caller
|
|
```
|
|
|
|
The file is reread for every request, with bounded size and sanitized errors.
|
|
Missing, invalid or expired credentials cannot fall back to anonymous/shared-root
|
|
access. Expiry, issuer, audience and current grants are checked by the Hub API.
|
|
The CLI neither obtains nor refreshes credentials; the projecting owner controls
|
|
rotation. This example is a composition interface, not an issued grant or live
|
|
acceptance. Do not share this process between principals or use an operator/root
|
|
token as an automated workload identity.
|
|
|
|
Enforced CLI network transports refuse startup: a shared MCP endpoint needs a
|
|
host that authenticates callers. `--token-file` is rejected for every network
|
|
transport, including development. The host SDK composition supplies
|
|
`token_provider=current_invocation_hub_credential`, `require_credentials=True`,
|
|
and an explicit backend profile. The provider must resolve the authenticated
|
|
invocation's **Hub-audience** credential; forwarding an MCP-audience token or
|
|
putting a static root token in the callback is not an admitted composition.
|
|
The SDK does not implement token exchange, delegation or host authentication.
|
|
|
|
Authenticated outbound requests require non-local HTTPS, never follow redirects,
|
|
and ignore proxy environment variables. Tools never accept credentials as model
|
|
arguments. Facet path segments reject traversal/separator/query injection.
|
|
Tests exercise actual FastMCP tool invocation with distinct concurrent caller
|
|
contexts, file rotation/removal, route-catalog admission for all four runtime
|
|
tools, enforced backend denial and CLI transport restrictions. They do not claim
|
|
that an external MCP consumer or workload has been admitted.
|