hub-core/docs/owner-facts-contract.md
tegwick 4153ad7305
Some checks failed
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / pytest-smoke (push) Failing after 2s
feat: consume account authority and refresh blocked dependencies
Assistant: codex
Assistant-Model: gpt-6.1-sol
Assistant-Session: 01a0e747-8f27-7242-8df8-8bc44f88c929
2026-10-04 12:12:09 +02:00

13 KiB

Platform authority facts: owner review contract

HUB-WP-0012 T01/T02, reviewed 2026-10-04. Hub-side composition and User Engine HTTP client are implemented; no real Hub caller admission or complete live account/tenant composition is claimed. The current client contract and remaining owner requirements are in the October 4 update; the earlier sections preserve the initial review.

Historical source review — 2026-09-28

  • 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. The follow-up User Engine commit 686da7c adds UserEngineService.lookup_account_authority: an authorized exact issuer/subject/tenant lookup that never provisions a missing identity, returns current global and scoped account status (missing scoped state is explicit), and separates caller from target. The new policy action is account.authority.read; existing generic read grants are not assumed sufficient. Its result includes source observation time, identity/account references and the policy decision reference, but no root entitlement or profile data.

This closes the read-only service primitive, not the wire contract or admitted Hub workload. USER-WP-0037 owns root-grant/mapping disposition and authenticated HTTP exposure/private acceptance. No database, live grant or credential changed.

Initial contract for owner disposition — 2026-09-28

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.

Initial composition — 2026-09-28

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.

Initial acceptance cases and remaining work — 2026-09-28

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.

Implemented account reader — 2026-10-04

The historical missing-endpoint assessment above is superseded by User Engine a9360842f84fae89857c2b1a298efc64d079ee7b, USER-WP-0037 and accepted NetKingdom ADR-0017 revision 2 (45a189a39167eef62709df0e857309f7ddc59029). UserEngineAccountReader implements the existing POST /api/v1/platform/account-authority/lookup wire contract. Its independently configured bearer maps to the fixed admitted workload at User Engine; it never forwards the human's Hub token or invents root entitlement.

A host injects a UserEngineAccountReader(base_url=..., token_file=Path(...), client=...) into PlatformFacts(accounts=..., tenants=reviewed_tenant_reader, account_tenant="platform", mapping_evidence="NK-ADR-0017:2"). The HTTP client must be lifecycle-owned, with trust_env=False and no ambient authorization or cookies (as in the existing security composition). The token file is dedicated to this User Engine caller, not the PDP/audit/browser credential. Nothing is automatically enabled by environment variables or this documentation example.

The reviewed custody adapter and its real service integration fixture require lookup tenant platform. This differs from User Engine's legacy operator context platform:root; neither is silently normalized by the reader. The canonical Hub actor/target remains tenant:platform. Before live enablement, owners must confirm the real account has the intended scoped account and that this exact mapping is admitted; a missing scoped account is inactive.

The response must bind issuer, subject and lookup tenant, identity/account/user references, decision reference, known global/scoped status, and timezone-aware observation time. root_grant:null means no entitlement. A non-null grant must match exact issuer/subject, scope_type:tenant, scope_id:platform, relation platform-root-custodian, owner net-kingdom-custody, source record nk-custody:platform-root, positive integer revision and valid timestamps. Unknown/expired/revoked or differently scoped observations fail closed.

Hub deliberately keeps its stricter five-second source age on every read/write, even though the canon permits up to fifteen minutes elsewhere. The earliest account/custody timestamp is retained, and the grant's absolute expiry travels through AccountObservation and LiveFacts to the final post-audit check. The reader trusts User Engine's authenticated observation; User Engine owns durable custody revision/rollback validation. A content digest identifies retained account/grant evidence but does not independently authenticate the publisher. No producer aliases are supplied by this contract, so the result has none.

Every call rereads a bounded workload credential, uses non-local HTTPS and a three-second total budget, limits the response to 64 KiB, rejects redirects, compressed responses and nonzero Age headers, and exposes sanitized failures. No caching or same-token fallback exists. User Engine's current environment-based receiver credential rotation still needs an owner rollout/revocation receipt.

Remaining dependencies — 2026-10-04

Hub tasks Owner and required artifact What unblocks Hub
0012-T01 net-kingdom, user-engine, tenant-engine, access-engine and platform surface owners: reviewed action/audience/tenant/enforcement matrix with disputed rows resolved Signed-off source/profile mapping; executable per-owner acceptance cases
0012-T02 user-engine USER-WP-0037-T03 + railiance-platform/rapp-user-engine: actual Hub workload identity, dedicated credential delivery/rotation reference, TLS origin and scoped account mapping Wire this implemented client to an admitted private endpoint and prove allow/deny/withdrawal/outage without owner payloads or credentials in evidence
0012-T02 tenant-engine + deployment owner: authenticated Hub caller binding for authoritative lifecycle read, canonical identifier/record version and observation time Implement TenantReader against that admitted response; do not treat actor query text or roles/live as workload authentication
0012-T02 net-kingdom/KeyCape + custody owner: actual immutable root identity, approved nonempty custody grant where intended, Hub audience/redirect registration and attended AAL2/recovery/revocation receipts Real positive root journey; the accepted empty custody source proves denial only
0012-T03 access-engine + platform owner: Hub protected-system policy, authenticated workload grant, pinned trusted public keys and credential delivery; audit-core + platform owner: Hub sender classes including hub.operation.committed, operational custody and rotation/failure receipts Compose real PDP/audit dependencies; existing FLEX-WP-0024/0025 source completion does not admit Hub
0012-T04 rapp-core-hub plus T02/T03 owners: pinned private image/migration/composition, network boundary, explicit owner-bound messaging/event producer aliases and rollback Execute real browser/API/MCP, direct-service bypass, revocation and audit-outage acceptance
0012-T05 ops-hub first, other inventoried extensions, activity-core/Repo Manager/inbox caller owners: per-audience identity/delegation/tenant contract and host enforcement Root and workload allow plus anonymous/non-root/wrong-audience/tenant denial with separate attribution
0012-T06 platform/Railiance owners after T04/T05: attended per-surface management and audit receipts M1 requires every inventory row, not a navigation link or broad provisioning decision
0012-T07 rapp-core-hub RAPPCOREHUB-WP-0002-T05 after M1: explicit public-enable authorization, TLS/bypass/rollback packet Package and enable only the reviewed public release
0012-T08 identity/tenant/policy owners after M1: roles, delegated principals and stored tenant provenance Phase 2 implementation can start without inferring tenant authority from legacy data

These requirements update the existing workplans; they are not messages claiming external owners have accepted new work. No control was relaxed or live credential retrieved in this review. Workplans 0006/0011 independently retain A5/A4 cutover and inbox source-feed/retention/reader-admission requirements.

Local verification: make ci-check covers normalization, transport refusal, rotation, withdrawal and post-audit expiry. To exercise the actual owner route and serializer with synthetic authority, run HUB_CORE_USER_ENGINE_SOURCE=/home/worsch/user-engine .venv/bin/python -m pytest -q tests/test_user_engine_owner_contract.py. The owner suite is explicit opt-in and does not contact a live endpoint.