Assistant: codex Assistant-Model: gpt-6-astra Assistant-Session: 01a0e747-8f27-7242-8df8-8bc44f88c929
10 KiB
Hub access profile 1.0.0 — implementation candidate
HUB-WP-0012 source implementation, 2026-09-28. Owner review and live acceptance remain open. This profile does not grant platform access or enable public exposure.
Runtime behavior
HUB_CORE_ACCESS_MODE=auto enables enforcement whenever HUB_CORE_ENV is not
development or test. enforce also enables it locally. development is
rejected in other environments. Do not deploy this candidate as a routine
upgrade: the default production factory has no admitted owner adapters yet and
returns 401 for missing credentials and 503 for credential-bearing requests.
The current deployed image and its release configuration have not been changed.
Without explicit browser composition, only exact GET /healthz is public, returning {"status":"ok"}. The shared
ASGI boundary protects docs, readiness, native ports, projections, compatibility
aliases and subsequently attached routes. Unknown method/route/handler combinations,
WebSockets, mounts without admission, and slash redirects without catalog entries
fail closed. Clients must use exact paths. Catalog admission does not override
compatibility feature flags or single-writer/read-only gates. Legacy bearer checks
remain in the development lane; production enforcement never falls back to them.
hub_core/security/routes.json is the candidate action catalog. It names each
runtime method, path template and handler, with a stable action per handler/method.
It is packaged in the wheel and tested against actual route construction. Source
inventory generation does not auto-admit a new route. Duplicate docs handlers
remain separately inventoried; the boundary selects the first effective route.
These technical action names require flex-auth/owner review before policy delivery.
Optional browser composition adds four exact /auth/* protocol routes and the
hub.browser.session action; login initiation is public, callback state is
browser-bound, and session access requires current root authority. See the
browser integration contract.
Composition and trust
A host composes create_app(access_controller=AccessController(...)) with:
OIDCVerifier: an explicitly trusted HTTPS issuer and Hub audience, an ownedhttpx.AsyncClient, RS256 discovery/JWKS, token and assurance lifetime at most 300 seconds, a 60-second key cache, and unknown-key rotation refresh (rate limited to once per second). Access tokens requireat+jwtor the KeyCapetyp: Bearerpayload marker. ID tokens, local issuers/profiles, weak keys, AAL0 and delegated agents are refused. Strict integer NumericDates and assurance time are required.FactSource.resolve: an owner implementation still owed that queries authoritative account, root entitlement, actor tenant and target tenant state. Its result binds issuer, subject, actor tenant, evidence reference and permitted producer addresses; its age may not exceed five seconds, including after policy and audit finish. No token role or incoming tenant/identity header supplies facts.FlexPolicy: a dedicated HTTPS Hub PDP, exact admitted ServiceAccount principal, a rotating projected caller-token file and an owner-delivered trusted public-key set. The token is reread on every call and is separate from the end-user token. Remote/v1/keysresponses never establish their own trust. Current/previous keys can coexist in the mounted trust file; removing a key takes effect next call.AuditCoreSink: implemented against the owner eight-field ingestion contract, returning only after an operational-custody probe and explicit durable acceptance. Every allow must reach this sink before handler execution; a failed sink blocks reads as well as writes. Authorization receipts sayauthorized, not “operation completed.” Domain commit/outcome audit remains a separate concern. Native durable and implemented SQL compatibility mutations have a transaction-linked outcome outbox; arbitrary embedded-host and external mutations remain outside that slice.- The root's existing immutable issuer and subject, supplied after owner resolution. No username, email, first-login promotion or generic role establishes root.
Do not implement these missing adapters as a constant allow, in-memory audit sink,
or an assertion copied from token claims. Tests use synthetic owners explicitly.
The current CLI deliberately provides no fixture adapter or production bypass.
An explicit SecuritySettings/owner-facts composition now owns the HTTP client
and closes it with the runtime. Audit custody is probed per append. Real owner-facts
admission and independent owner probes remain T02–T04 work;
dependency readiness now reports recent verified operations; see the
owner integration review.
For this candidate all Hub resources are explicitly platform-owned. Other target tenants are refused. Root requires AAL2/3, active account and tenants, current root entitlement, and a fresh policy allow for every action. Workloads receive no root shortcut: a policy grant and current authoritative facts are always required. Real workload admission remains unproven. Cross-tenant owner administration and Phase 2 storage/query isolation are not implemented by this classification.
Policy interoperability and failure handling
The PDP request carries actor, target tenant, action, concrete resource path, assurance, root entitlement, authoritative evidence reference, and a digest of HTTP method/path/query/body. Client bodies and bearer tokens are not sent to the PDP or audit sink. Body size is bounded at 1 MiB with a ten-second receive deadline. The controller's identity/facts/ policy/audit chain has a ten-second timeout; individual HTTP calls have three seconds. A separate refusal-audit attempt is bounded at three seconds.
The verifier requires Ed25519 signing, the submitted request digest, matching
request ID and structured actor/action/resource/tenant/context, enforced caller
provenance, policy version/digest, a decision age below 30 seconds and a valid
allow lifetime. Decision.valid_until is a required finite epoch deadline, derived
from the earliest signed allow expiry, caller expiry and decision-time freshness
limit. The controller checks it before audit and again after durable acceptance,
before returning authority to HTTP, browser or direct SDK callers. Expiry during
custody returns 503 policy_decision_expired; an authorization receipt is not
permission to execute after expiry. Custom policy adapters must supply this field
from verified provenance, never a fresh deadline invented at receipt time.
It never caches decisions. Every unimplemented obligation and
non-allow/non-deny effect fails closed; approval requirements cannot be waived.
A malformed/untrusted/unavailable decision returns 503, a verified denial 403,
and invalid authentication 401. Responses are no-store and do not expose backend
exceptions. There is no local allow fallback.
Interoperability tests retain flex-auth's real Go-signed fixture, tampered pair,
public test key and original submitted request. Go encoding/json emits struct
fields in declaration order and map keys in sorted order. The verifier retains
wire order and HTML escaping for signatures and reproduces the request structs
for submitted_request_digest. This is not RFC 8785. Duplicate JSON keys and
non-integer decision numbers are outside this candidate profile and fail closed;
a reordered envelope also fails signature verification. Agree broader canonical
encoding with flex-auth before expanding this profile.
Producers, MCP and embedded hosts
from_address, from_agent and author, when present in a top-level JSON command,
must match the live owner's producer-address set. Native events get a reserved
payload._hub_access record containing verified actor/tenant/correlation identity;
client assertions under that key are overwritten. Domain subject_refs remain
business data, never proof of origin. Compatibility event implementations and
external publishers still need owner acceptance of equivalent attribution.
Embedded hosts can install AccessBoundary and an explicit host route catalog.
Tests prove the common seam on an embedded host; each real host's mounted paths,
features and routers still require inventory and admission. Do not treat the
standalone runtime catalog as admission for every embedded API.
MCP hosts may supply token_provider, a callable resolving a Hub-audience
credential from the current invocation, with require_credentials=True. Credentials
are never retained on the server; requests do not follow redirects when carrying
one. trailing_slash=False targets the standalone runtime's native paths. The
standalone production MCP supports only explicit single-principal stdio credentials;
network transports require an authenticated host composition. The standalone
profile advertises four mapped runtime tools; legacy operations remain embedded-only.
Many legacy tools target APIs the standalone Hub does not implement. They remain
unsupported, not silently translated or authorized. Cross-audience delegation
remains open. Browser PKCE sessions/logout are implemented locally; live browser/MCP caller
admission remains open. See the MCP composition contract.
Remaining release gates
- T01: cross-owner contract review, concrete policy vocabulary, effective host and service-route expansion; the 250-object snapshot is not full route discovery.
- T02: immutable root binding, registered audience/redirects, PKCE/MFA/recovery, admitted live facts adapters, attended login/logout and revocation receipts.
- T03: dedicated Hub policy and fact provenance review; authenticated deployment, credential/key custody, real Audit Core sender admission and native rotation probes.
- T04–T05: deploy the admitted composition, complete dependency health probes and all client/extension migrations, domain outcome audit, legacy lane rollback and full root journeys.
- T06–T08: every platform/Railiance receipt, separate public-enable approval and later role/delegation/tenant isolation. No milestone is closed by local fixtures.