hub-core/docs/owner-facts-contract.md

86 lines
5.3 KiB
Markdown
Raw Normal View History

# Platform authority facts: owner review contract
HUB-WP-0012 T01/T02, 2026-09-28. Proposed owner contract and implemented Hub-side
composition; **no admitted HTTP adapter or production authority source**.
## Reviewed source
- User Engine `b4cef6446bc10555b598ea74de1743bc9b03e7ba`:
`service.py::me` creates user/account/tenant-account/identity records for unknown
`(issuer, subject)`. It cannot be an authorization lookup. `identity_context`
reads linked identity/account and membership evidence without this provisioning,
but accepts an actor or target user ID, not an admitted Hub workload's lookup of
an arbitrary immutable identity. It does not establish Hub's root entitlement.
`PLATFORM_TENANT` is `platform:root`; token `platform-operator` roles are not
evidence of a live root grant.
- Tenant Engine `4ce89969c0cc0ab4ba111f1b4009aa137c94a68b`:
`GET /tenants/{tenant_id}` exposes lifecycle/version and accepts an `actor`
query for authorization. `/roles/live` returns tenant roles, not a person's
root entitlement. These route handlers do not authenticate a Hub workload.
An admitted authenticating gateway could supply that boundary, but no such
composition is established by these sources.
These are source findings, not probes of deployed configurations. No owner
repository, database, grant or credential was modified.
## Contract for owner disposition
| Requirement | Owning decision |
| --- | --- |
| Side-effect-free lookup by exact issuer and subject | User Engine: existing identity only; unknown identity denies without provisioning |
| Current account state and explicit root grant | User Engine + NetKingdom: identity link, global and scoped account state, grant source/revocation/version, independent of JWT roles and Hub root-subject configuration |
| Platform naming | Both owners: explicit reviewed mapping of User Engine scope to canonical `tenant:platform`; no string substitution inferred by Hub |
| Current tenant lifecycle | Tenant Engine: canonical identifier bound to immutable record and version |
| Caller authentication | Each owner: dedicated Hub workload audience/identity and narrowly scoped read authorization; no end-user bearer forwarding |
| Freshness and evidence | Each owner: source observation timestamp and durable evidence/version reference; no stale cache on failure |
| Producer aliases | Account/identity owner: explicit current bindings, or an empty set; no aliases derived from display names |
Owners must specify the actual route, request/response schema, authentication
mechanism and scoped grants before HTTP reader implementations can be written.
The Python types below are an internal normalization seam, **not a proposed
HTTP endpoint that already exists**. Missing/unknown/ambiguous results must raise
and deny; they must never synthesize an active account.
## Implemented composition
`hub_core.security.facts.PlatformFacts` implements `FactSource` with injected
`AccountReader` and `TenantReader`. The account reader returns a typed
`AccountObservation`; the tenant reader returns a `TenantObservation`. Readers
own authentication and wire verification. `account_active` must include the
current identity link, global account and required scoped-account validity; a
globally active account alone is insufficient. They receive identity references only,
not the end-user token. No concrete network reader or automatic environment
activation is provided.
The host supplies the reviewed account-tenant identifier and mapping evidence
reference explicitly. That mapping is not an entitlement grant. This candidate
admits only human actors in `tenant:platform`; workload and cross-tenant lookups
remain separately scoped work. The shared controller still verifies the exact
root issuer/subject before consulting this source.
Every request rereads both owners within one three-second budget. Results must
match exact identity/tenant bindings, contain strict booleans and nonempty evidence,
and be no older than five seconds or in the future. The result retains the oldest
source timestamp, so slow joining/policy/audit cannot refresh authority. Inactive
or withdrawn state remains a valid observation and is denied by the controller.
Failures expose only a sanitized 503 and never fall back to an earlier allow.
Cancellation propagates to the pending lookup.
The evidence join is SHA-256 over canonical JSON containing the account, tenant
and mapping references. Readers/owners must retain the referenced evidence for
reconstruction; this hash is a join identifier, not a signature or independent
proof of owner authenticity. No account payload or credential enters the join.
## Acceptance cases and remaining work
Local tests cover exact bindings, malformed state, stale/future observations,
oldest-timestamp preservation, bounded outages, repeated lookup, entitlement and
account suspension, tenant inactivity, and refusal before policy after withdrawal.
Fixtures implement the internal readers; they do not prove owner API acceptance.
T01/T02 remain open for owner disposition of the table above, implementation of
real authenticated readers, and isolated owner-source integration tests proving
unknown lookups create nothing. Private acceptance must then demonstrate root
allow, ordinary/forged identity denial, next-request revocation and owner outages
with real admitted callers. No public exposure follows from this contract.