Implement durable store contract and registration roadmap

This commit is contained in:
2026-06-15 16:33:24 +02:00
parent 05596146c8
commit 2c94b40fc4
16 changed files with 1906 additions and 472 deletions

View File

@@ -9,7 +9,9 @@ make test
See `docs/development.md`, `docs/configuration.md`, `docs/contracts.md`,
`docs/canon-mapping.md`, `docs/canon-interface-card.yaml`,
`docs/evidence-gap-examples.md`, `docs/family-dataspace-onboarding.md`,
`docs/examples.md`, `docs/scenarios.md`,
`docs/netkingdom-registration-onboarding-vision.md`,
`docs/postgres-durable-store-consumer-requirements.md`, `docs/examples.md`,
`docs/scenarios.md`,
`docs/operability.md`, `docs/release.md`, `docs/ui-contracts.md`,
`docs/identity-domain-naming-decision.md`, and `docs/final-assessment.md`
for implementation boundaries, contracts, canon mappings, examples, and release

View File

@@ -42,7 +42,8 @@ application catalogs, projections, evidence references, audit, and events.
- policy, control, access-review, exception, and organization source-of-truth
ownership;
- runtime secret custody;
- UI implementation;
- UI implementation in the current MVP; optional registration and access
management UI work is proposed separately under `USER-WP-0014`;
- full SCIM server or enterprise directory replacement in the initial product.
## Boundary Rule
@@ -56,5 +57,7 @@ truth.
## Current Planning
Implementation work is tracked in `workplans/USER-WP-0001` through
`USER-WP-0006`.
Implementation and planning work is tracked in `workplans/USER-WP-0001`
through `USER-WP-0015`. `USER-WP-0010` through `USER-WP-0015` are proposed
future workplans for NetKingdom registration, prepared accounts, hats/access
profiles, onboarding journeys, optional UI, and security conformance.

View File

@@ -89,6 +89,24 @@ without emitting outbox events.
Local audit records may be exported as identity-canon `Evidence Source`
references. Durable platform audit custody remains outside user-engine.
## Durable Store Contract
`UserEngineService` depends on the `UserEngineStore` protocol, not the
in-memory adapter's concrete collections. Store implementations must expose
schema readiness, logical record accessors, audit-log reads, pending-outbox
reads, adapter-neutral record counts, and a `transaction` context for atomic
mutations.
Mutating writes happen after validation and authorization, inside the store
transaction. Domain changes, local mutation audit records, and outbox events
must commit or roll back together. Authorization-denial audit records must
remain durable without outbox events, including when a denial occurs inside a
composed mutation that rolls back other writes.
Postgres-specific connection handling, SQL, locks, credentials, tenant
isolation primitives, backup, restore, and platform observability remain
adapter or provider concerns outside the domain service.
## Migration Contract
The isolated store exposes `SCHEMA_VERSION = 0001_initial` and a `migrate`

View File

@@ -0,0 +1,259 @@
# NetKingdom Registration And Onboarding Vision
Status: vision and proposed roadmap
Date: 2026-06-15
Related workplans: USER-WP-0010 through USER-WP-0015
## Purpose
NetKingdom needs a convenient way for people to register, receive a stable
NetKingdom identity, claim prepared rights, choose the role or "hat" they are
acting under, and enter services through guided onboarding journeys.
`user-engine` can support this as the identity-domain and user-domain system
behind a registration UI. It should not become the identity provider,
credential system, MFA provider, final authorization policy engine, or runtime
ACL enforcer. Instead, it should provide the domain model, orchestration
facades, profile and membership facts, identity context, audit/outbox events,
and optional UI/API contracts that NetKingdom IAM, authorization, and service
runtimes can consume.
## Product Vision
A new user should be able to arrive at NetKingdom, establish the required
identity factors, receive or claim a NetKingdom ID, and immediately see the
services, realms, groups, and assets they may access. If the user is expected
before registration, an administrator, tenant owner, family owner, or upstream
system should be able to prepare the account and rights in advance. During
registration, verified factors such as email, phone, postal address, or eID can
match those preparations and attach the waiting access package to the new
account.
The end state is:
```text
People register once with NetKingdom.
NetKingdom links verified factors and external identities to one canonical user.
Prepared rights can be claimed safely when factor evidence matches.
Users choose an active hat for the context they are entering.
Applications receive claims, profile projections, and membership facts.
Authorization systems evaluate ACLs and policy from explicit facts.
Subsystem onboarding is driven by events, welcome protocols, and journeys.
```
## Answer: Can user-engine Provide A UI?
Yes, but the UI should be an optional NetKingdom registration and onboarding
surface backed by user-engine service contracts. The repo's current intent is
"headless first" and "optional UI, not UI-driven". That means user-engine can
own a small registration UI or UI contract when it is the most convenient way
to operate the domain, as long as source-of-truth boundaries stay explicit:
- NetKingdom IAM verifies credentials and proofing factors.
- user-engine stores users, accounts, identity links, memberships, profiles,
prepared-account claims, role context, and onboarding state.
- Authorization systems make final access decisions.
- Service runtimes enforce ACLs for their own resources.
- Audit, evidence, and lifecycle systems receive exported records and events.
## Key Concepts
### NetKingdom ID
The NetKingdom ID should be a stable canonical identifier for the person in the
NetKingdom identity domain. It should not expose raw identity-provider
issuer/subject pairs. user-engine can mint or map this identifier through the
`User` record and `ExternalIdentity` links, while IAM continues to authenticate
the subject.
### Registration Session
A registration session is a short-lived onboarding workflow. It tracks the
actor, verified factor evidence, selected tenant or realm context, consent,
prepared-account matches, requested roles, and completion state. It should be
auditable and resumable without storing secret credential material.
### Identity Factors
Factors are evidence that help establish or link identity:
- email address;
- phone number;
- postal address;
- eID or government-backed identity;
- organization-issued invite;
- existing SSO identity;
- recovery or delegated caretaker evidence.
user-engine should store factor references, verification status, assurance
level, expiry, source system, and evidence references. It should not perform
the proofing itself unless a later adapter explicitly owns a local development
mock.
### Prepared Accounts And Rights
Prepared accounts allow NetKingdom to create pending user-account intent before
the person registers. A prepared account can contain:
- expected factor match rules;
- tenant, group, family, realm, service, or asset scope;
- initial account state;
- role or hat templates;
- profile defaults;
- customer-journey steps;
- approval requirements;
- expiry and revocation rules.
When a registering user proves the required factors, user-engine can link the
prepared account to the real user and convert prepared rights into explicit
memberships, profile values, and onboarding tasks.
### Hats, Roles, And Profiles
A "hat" is the active context a user chooses when entering NetKingdom or a
service. Examples include family owner, child, tenant admin, employee,
contractor, service operator, customer, vendor, or agent delegate.
In user-engine, hats should be represented through tenant-scoped memberships,
role labels, profile layers, and application projections. A user may have many
hats, but only a subset should be active in a given realm, service, or asset
context.
### Realms, Services, Assets, And ACLs
user-engine should efficiently manage identity-domain access facts for users
and groups against realms, services, and assets. It should not become the final
ACL enforcement engine. The recommended split is:
- user-engine owns user, group, membership, role, profile, and access-intent
facts;
- authorization systems evaluate policy and ACLs from those facts;
- services enforce decisions at runtime;
- user-engine exports identity context and claims-enrichment projections for
the active hat.
## Target Flows
### Self Registration
1. User opens the NetKingdom registration UI.
2. IAM verifies one or more required factors.
3. user-engine creates or resolves the canonical user and account.
4. user-engine links verified external identities and factor evidence.
5. user-engine evaluates prepared-account matches.
6. User accepts terms, chooses initial tenant or realm, and selects available
hats.
7. user-engine emits audit and outbox events for downstream onboarding.
8. Services receive identity context and claims projections.
### Prepared Account Claim
1. Admin, family owner, tenant admin, HR feed, service owner, or invite source
prepares an account package.
2. The package declares required factor matches and planned roles/profiles.
3. User registers and proves the required factors.
4. user-engine links the preparation to the canonical user.
5. Prepared memberships, profile defaults, and onboarding tasks are activated.
6. Any sensitive or privileged role waits for approval if policy requires it.
### Hat Selection
1. User signs in and sees available hats for the current tenant, realm, or
service.
2. User selects an active hat.
3. user-engine returns `identity_context` and a claims-enrichment projection
for that context.
4. IAM or a gateway issues service-facing claims.
5. Authorization and service runtimes evaluate ACLs and policy.
### Welcome Protocols
1. Registration or prepared-account claim emits onboarding events.
2. Journey definitions map events to subsystem steps.
3. Welcome protocols send the user to profile completion, family setup, tenant
selection, app tour, evidence collection, approval, or service activation.
4. Each subsystem reports completion, failure, or required manual follow-up.
## UI Surface
The first UI should be functional, quiet, and workflow-oriented. It should
support:
- registration start and resume;
- factor verification status;
- prepared-account claim review;
- terms and consent capture;
- role or hat selection;
- profile completion;
- welcome journey timeline;
- access request and pending approval status;
- administrator views for prepared accounts, invitations, groups, realms,
service bindings, and onboarding diagnostics.
The UI should call stable service/application APIs. It should not embed IAM,
authorization, proofing, or service-specific ACL logic in browser code.
## Domain Additions Needed
The current domain already has users, accounts, tenant accounts, external
identities, memberships, profiles, applications, catalogs, invitations, audit,
outbox, and identity context. The registration vision needs additional
concepts:
- `RegistrationSession`
- `NetKingdomIdentity` or public NetKingdom ID alias
- `IdentityFactor`
- `FactorVerification`
- `PreparedAccount`
- `PreparedEntitlement`
- `Hat` or active role context
- `Realm`
- `ServiceArea`
- `AssetScope`
- `AccessProfile`
- `AccessIntent`
- `OnboardingJourney`
- `WelcomeProtocol`
- `OnboardingTask`
These should be introduced incrementally through workplans rather than all at
once.
## Security And Governance
- Factor values must be minimized, normalized, and redacted in diagnostics.
- High-assurance factors such as eID should be represented by evidence
references and assurance metadata, not raw proofing payloads.
- Prepared rights must expire, be revocable, and show who prepared them.
- Privileged hats require explicit evidence, approval, or policy/control
references.
- Users should see why a prepared account or role is available before claiming
it.
- Access to realms, services, and assets must fail closed when tenant, hat, or
factor context is missing.
- All lifecycle transitions should be auditable and emit outbox events.
## Recommended Workplans
| Workplan | Title | Purpose |
| --- | --- | --- |
| USER-WP-0010 | Registration Identity And Factor Model | Add registration sessions, NetKingdom ID semantics, factor evidence, and verification adapter boundaries. |
| USER-WP-0011 | Prepared Accounts And Entitlement Claims | Allow accounts, roles, profiles, and journeys to be prepared before registration and claimed after factor match. |
| USER-WP-0012 | Hats, Realms, Services, Assets, And Access Profiles | Model active hats and access-control facts for users/groups across realms, services, and assets. |
| USER-WP-0013 | Onboarding Journeys And Welcome Protocols | Orchestrate subsystem welcome flows from registration, invitation, and prepared-account events. |
| USER-WP-0014 | Registration And Access Management UI | Build the optional UI/API surface for registration, factor status, prepared rights, hat selection, and admin setup. |
| USER-WP-0015 | Registration Scenario And Security Conformance | Add end-to-end scenarios, threat-oriented negative tests, redaction checks, and adapter conformance for the full flow. |
## First Milestone
The first useful milestone should not be the full UI. It should be a headless
registration facade and scenario tests:
```text
Start registration -> verify email through adapter evidence -> create
NetKingdom user/account -> claim a prepared tenant role -> choose active hat ->
return identity_context and claims projection -> emit onboarding events.
```
After that is stable, a thin UI can use the same facade without inventing its
own registration rules.

View File

@@ -1,7 +1,7 @@
# Postgres Durable Store Consumer Requirements
Status: requirements
Date: 2026-06-05
Status: requirements + store contract boundary
Date: 2026-06-15
Related workplan: USER-WP-0009
## Purpose
@@ -13,6 +13,12 @@ NetKingdom infrastructure repository provides a tenant-aware, security
integrated Postgres capability, and `user-engine` consumes that capability
through a durable store adapter.
The consumer-side contract is now represented in code by
`user_engine.ports.UserEngineStore`. The protocol is intentionally
adapter-neutral: it names the service behavior a durable store must satisfy
without adding a Postgres dependency or giving this repository ownership of
database provisioning.
## Consumer Story
As a `user-engine` consumer, I want the service to persist identity-domain
@@ -85,6 +91,22 @@ the isolated store:
- Support the same service-level exceptions for not found, conflict,
validation, and authorization-denied flows.
### Store Protocol Boundary
`UserEngineService` consumes the `UserEngineStore` protocol rather than local
in-memory collections. A future Postgres adapter must provide:
- Schema readiness through `schema_version`, `ready`, and `migrate`.
- A `transaction` context that makes each mutating write unit atomic.
- Logical read/write methods for users, accounts, tenant accounts, external
identities, memberships, applications, bindings, catalogs, family
invitations, and profile values.
- Audit and outbox append/read methods that preserve write order.
- Adapter-neutral record counts for diagnostics and operability snapshots.
Concrete tables, SQL, connection pools, and row locks remain adapter details.
Service and domain code should not depend on Postgres-specific concepts.
### Identity And Account Constraints
- `(issuer, subject)` must uniquely identify one external identity link.
@@ -157,6 +179,9 @@ the isolated store:
them to `ConflictError` where appropriate.
- Migration and outbox claiming should use explicit locking strategies that do
not require consumers to understand Postgres internals.
- Authorization-denial audit records must persist without outbox events even
when the denied operation occurs inside a composed transaction that rolls
back domain writes.
### Migration Requirements
@@ -253,10 +278,16 @@ A future Postgres adapter should pass conformance tests for:
## First Implementation Follow-Ups
After this requirements work is accepted, likely follow-up work should be:
The first consumer-side follow-up is complete: `UserEngineStore` defines the
adapter boundary and the in-memory store acts as the reference implementation
for service-level behavior.
Likely future follow-up work should be:
- Define the durable store protocol changes, if any.
- Add a Postgres adapter behind the existing store boundary.
- Add migration files for user-engine tables.
- Add provider-backed conformance tests for locking, uniqueness races,
migration readiness, outbox claiming, redacted diagnostics, and restore
validation.
- Add conformance tests that run against both in-memory and Postgres stores.
- Integrate the adapter with the future NetKingdom Postgres provider repo.

View File

@@ -2,8 +2,10 @@
from __future__ import annotations
import copy
from contextlib import contextmanager
from dataclasses import dataclass, field
from typing import Iterable
from typing import Iterable, Iterator, Mapping, cast
from user_engine.domain import (
Account,
@@ -51,6 +53,10 @@ class InMemoryUserEngineStore:
] = field(default_factory=dict)
audit_records: list[AuditRecord] = field(default_factory=list)
outbox_events: list[OutboxEvent] = field(default_factory=list)
_transaction_depth: int = field(default=0, init=False, repr=False)
_transaction_snapshot: Mapping[str, object] | None = field(
default=None, init=False, repr=False
)
def migrate(self) -> None:
"""Apply the standalone schema migration manifest."""
@@ -60,15 +66,42 @@ class InMemoryUserEngineStore:
def ready(self) -> bool:
return self.schema_version == SCHEMA_VERSION
@contextmanager
def transaction(self) -> Iterator[None]:
"""Provide atomic in-memory mutation semantics for conformance tests."""
if self._transaction_depth == 0:
self._transaction_snapshot = self._snapshot()
self._transaction_depth += 1
try:
yield
except Exception:
if self._transaction_depth == 1 and self._transaction_snapshot is not None:
self._restore(self._transaction_snapshot)
raise
finally:
self._transaction_depth -= 1
if self._transaction_depth == 0:
self._transaction_snapshot = None
def save_user(self, user: User) -> None:
self.users[user.user_id] = user
def user(self, user_id: str) -> User | None:
return self.users.get(user_id)
def save_account(self, account: Account) -> None:
self.accounts[account.user_id] = account
def save_identity(self, identity: ExternalIdentity) -> None:
self.identities[identity.identity_key] = identity
def identities_for_user(self, user_id: str) -> tuple[ExternalIdentity, ...]:
return tuple(
identity
for identity in self.identities.values()
if identity.user_id == user_id
)
def save_tenant_account(self, account: TenantAccount) -> None:
self.tenant_accounts[(account.tenant, account.user_id)] = account
@@ -78,12 +111,24 @@ class InMemoryUserEngineStore:
def save_application(self, application: Application) -> None:
self.applications[application.application_id] = application
def application(self, application_id: str) -> Application | None:
return self.applications.get(application_id)
def save_binding(self, binding: ApplicationBinding) -> None:
self.bindings[binding.application_id] = binding
def binding(self, application_id: str) -> ApplicationBinding | None:
return self.bindings.get(application_id)
def save_catalog(self, catalog: Catalog) -> None:
self.catalogs[catalog.catalog_id] = catalog
def catalog(self, catalog_id: str) -> Catalog | None:
return self.catalogs.get(catalog_id)
def all_catalogs(self) -> tuple[Catalog, ...]:
return tuple(self.catalogs.values())
def save_family_invitation(self, invitation: FamilyInvitation) -> None:
self.family_invitations[invitation.invitation_id] = invitation
@@ -136,9 +181,67 @@ class InMemoryUserEngineStore:
def append_audit(self, record: AuditRecord) -> None:
self.audit_records.append(record)
def audit_log(self) -> tuple[AuditRecord, ...]:
return tuple(self.audit_records)
def append_outbox(self, event: OutboxEvent) -> None:
self.outbox_events.append(event)
def pending_outbox(self) -> tuple[OutboxEvent, ...]:
return tuple(self.outbox_events)
def record_counts(self) -> Mapping[str, int]:
return {
"users": len(self.users),
"accounts": len(self.accounts),
"tenant_accounts": len(self.tenant_accounts),
"memberships": len(self.memberships),
"applications": len(self.applications),
"catalogs": len(self.catalogs),
"family_invitations": len(self.family_invitations),
"profile_values": len(self.profile_values),
"audit_records": len(self.audit_records),
"pending_outbox_events": len(self.outbox_events),
}
def _snapshot(self) -> Mapping[str, object]:
return {
"users": copy.deepcopy(self.users),
"accounts": copy.deepcopy(self.accounts),
"identities": copy.deepcopy(self.identities),
"tenant_accounts": copy.deepcopy(self.tenant_accounts),
"memberships": copy.deepcopy(self.memberships),
"applications": copy.deepcopy(self.applications),
"bindings": copy.deepcopy(self.bindings),
"catalogs": copy.deepcopy(self.catalogs),
"family_invitations": copy.deepcopy(self.family_invitations),
"profile_values": copy.deepcopy(self.profile_values),
"audit_records": copy.deepcopy(self.audit_records),
"outbox_events": copy.deepcopy(self.outbox_events),
}
def _restore(self, snapshot: Mapping[str, object]) -> None:
snapshot_audit_records = cast(list[AuditRecord], snapshot["audit_records"])
denied_audit_records = [
record
for record in self.audit_records[len(snapshot_audit_records) :]
if record.summary == "authorization denied"
]
self.users = snapshot["users"] # type: ignore[assignment]
self.accounts = snapshot["accounts"] # type: ignore[assignment]
self.identities = snapshot["identities"] # type: ignore[assignment]
self.tenant_accounts = snapshot["tenant_accounts"] # type: ignore[assignment]
self.memberships = snapshot["memberships"] # type: ignore[assignment]
self.applications = snapshot["applications"] # type: ignore[assignment]
self.bindings = snapshot["bindings"] # type: ignore[assignment]
self.catalogs = snapshot["catalogs"] # type: ignore[assignment]
self.family_invitations = snapshot[
"family_invitations"
] # type: ignore[assignment]
self.profile_values = snapshot["profile_values"] # type: ignore[assignment]
self.audit_records = [*snapshot_audit_records, *denied_audit_records]
self.outbox_events = snapshot["outbox_events"] # type: ignore[assignment]
class LocalAuthorizationCheckPort:
"""Deterministic local authorization adapter.

View File

@@ -7,20 +7,141 @@ adapters without changing domain code.
from __future__ import annotations
from contextlib import AbstractContextManager
from typing import Any, Iterable, Mapping, Protocol
from user_engine.domain import (
Account,
Actor,
Application,
ApplicationBinding,
AuditRecord,
AuthorizationDecision,
AuthorizationRequest,
CanonEntityReference,
Catalog,
ExternalIdentity,
FamilyInvitation,
Membership,
OutboxEvent,
ProfileValue,
TenantAccount,
User,
)
class UserEngineStore(Protocol):
"""Durable persistence boundary for user-engine service behavior.
Implementations may be in-memory, Postgres-backed, or platform-provided,
but must preserve the same logical keys, readiness contract, and atomic
mutation semantics exposed here.
"""
schema_version: str | None
@property
def ready(self) -> bool:
"""Return whether the store is schema-compatible for service use."""
def migrate(self) -> None:
"""Apply or verify user-engine-owned schema migrations."""
def transaction(self) -> AbstractContextManager[None]:
"""Return a context manager for one atomic mutation unit."""
def save_user(self, user: User) -> None:
"""Create or replace a user record."""
def user(self, user_id: str) -> User | None:
"""Return a user by id."""
def save_account(self, account: Account) -> None:
"""Create or replace a primary account record."""
def user_account(self, user_id: str) -> Account | None:
"""Return the primary account for a user."""
def save_identity(self, identity: ExternalIdentity) -> None:
"""Create or replace an external identity link."""
def find_identity(self, issuer: str, subject: str) -> ExternalIdentity | None:
"""Return an external identity by issuer and subject."""
def identities_for_user(self, user_id: str) -> tuple[ExternalIdentity, ...]:
"""Return all external identities linked to a user."""
def save_tenant_account(self, account: TenantAccount) -> None:
"""Create or replace a tenant-scoped account record."""
def tenant_account(self, tenant: str, user_id: str) -> TenantAccount | None:
"""Return a tenant-scoped account record."""
def save_membership(self, membership: Membership) -> None:
"""Create or replace a membership fact."""
def memberships_for_user(
self, user_id: str, *, tenant: str | None = None
) -> tuple[Membership, ...]:
"""Return memberships for a user, optionally scoped to a tenant."""
def memberships_for_tenant(self, tenant: str) -> tuple[Membership, ...]:
"""Return memberships scoped to a tenant."""
def save_application(self, application: Application) -> None:
"""Create or replace an application registration."""
def application(self, application_id: str) -> Application | None:
"""Return an application by id."""
def save_binding(self, binding: ApplicationBinding) -> None:
"""Create or replace an application binding."""
def binding(self, application_id: str) -> ApplicationBinding | None:
"""Return an application binding by application id."""
def save_catalog(self, catalog: Catalog) -> None:
"""Create or replace a catalog."""
def catalog(self, catalog_id: str) -> Catalog | None:
"""Return a catalog by id."""
def all_catalogs(self) -> tuple[Catalog, ...]:
"""Return all catalogs."""
def save_family_invitation(self, invitation: FamilyInvitation) -> None:
"""Create or replace a family invitation."""
def family_invitation(self, invitation_id: str) -> FamilyInvitation | None:
"""Return a family invitation by id."""
def family_invitations_for_user(
self, user_id: str
) -> tuple[FamilyInvitation, ...]:
"""Return family invitations for a user."""
def save_profile_value(self, value: ProfileValue) -> None:
"""Create or replace a profile value."""
def values_for_user(self, user_id: str) -> tuple[ProfileValue, ...]:
"""Return profile values for a user."""
def append_audit(self, record: AuditRecord) -> None:
"""Append a local audit record."""
def audit_log(self) -> tuple[AuditRecord, ...]:
"""Return local audit records in write order."""
def append_outbox(self, event: OutboxEvent) -> None:
"""Append an outbox event."""
def pending_outbox(self) -> tuple[OutboxEvent, ...]:
"""Return pending outbox events in write order."""
def record_counts(self) -> Mapping[str, int]:
"""Return adapter-neutral record counts for diagnostics."""
class IdentityClaimsAdapter(Protocol):
"""Normalize verified identity claims into a user-engine actor."""

File diff suppressed because it is too large Load Diff

View File

@@ -1,11 +1,26 @@
import unittest
from typing import Any
from user_engine.domain import AuthorizationEffect, AuthorizationRequest
from user_engine.adapters.local import (
InMemoryUserEngineStore,
LocalAuthorizationCheckPort,
)
from user_engine.domain import (
AuthorizationEffect,
AuthorizationRequest,
OutboxEvent,
ProfileScope,
ProjectionType,
)
from user_engine.errors import AuthorizationDenied
from user_engine.service import UserEngineService
from user_engine.testing.fixtures import (
FixtureIdentityClaimsAdapter,
StaticAuthorizationCheckPort,
human_actor_claims,
sample_application,
sample_application_binding,
sample_catalog,
)
@@ -44,6 +59,127 @@ class PortFixtureTests(unittest.TestCase):
self.assertEqual(binding.oidc_client_id, "demo-client")
self.assertEqual(binding.protected_system_id, "user-engine.demo")
def test_user_engine_service_consumes_store_protocol(self):
store = _ProtocolOnlyStore(InMemoryUserEngineStore())
service = UserEngineService(
store=store,
identity_adapter=FixtureIdentityClaimsAdapter(),
authorization=StaticAuthorizationCheckPort(),
)
session = service.me(human_actor_claims(), correlation_id="corr-me")
service.register_application(
session.actor,
sample_application(),
binding=sample_application_binding(),
correlation_id="corr-app",
)
service.publish_catalog(
session.actor,
sample_catalog(),
correlation_id="corr-catalog",
)
service.set_profile_value(
session.actor,
session.user.user_id,
"demo.display_density",
"compact",
scope=ProfileScope.APPLICATION,
scope_id="app.demo",
application_id="app.demo",
correlation_id="corr-profile",
)
projection = service.projection(
session.actor,
session.user.user_id,
ProjectionType.APPLICATION_RUNTIME,
application_id="app.demo",
correlation_id="corr-projection",
)
self.assertEqual(projection.values["demo.display_density"], "compact")
self.assertTrue(service.operability_snapshot().ready)
def test_store_transaction_rolls_back_failed_mutation(self):
store = _FailingOutboxStore()
service = UserEngineService(
store=store,
identity_adapter=FixtureIdentityClaimsAdapter(),
authorization=StaticAuthorizationCheckPort(),
)
with self.assertRaises(RuntimeError):
service.me(human_actor_claims(), correlation_id="corr-fail")
self.assertEqual(store.record_counts()["users"], 0)
self.assertEqual(store.audit_log(), ())
self.assertEqual(store.pending_outbox(), ())
self.assertIsNone(
store.find_identity("https://issuer.example.test", "user-123")
)
def test_denial_audit_survives_outer_transaction_rollback(self):
store = InMemoryUserEngineStore()
service = UserEngineService(
store=store,
identity_adapter=FixtureIdentityClaimsAdapter(),
authorization=LocalAuthorizationCheckPort(
action_effects={"membership.write": AuthorizationEffect.DENY}
),
)
session = service.me(human_actor_claims(), correlation_id="corr-me")
before_audit = len(store.audit_log())
before_outbox = len(store.pending_outbox())
with self.assertRaises(AuthorizationDenied):
with store.transaction():
service.add_membership(
session.actor,
session.user.user_id,
tenant="tenant:coulomb",
scope_type="team",
scope_id="team:demo",
kind="member",
correlation_id="corr-denied-membership",
)
self.assertEqual(len(store.audit_log()), before_audit + 1)
self.assertEqual(store.audit_log()[-1].summary, "authorization denied")
self.assertEqual(len(store.pending_outbox()), before_outbox)
self.assertEqual(store.record_counts()["memberships"], 0)
class _ProtocolOnlyStore:
"""Proxy that fails if service code reaches for local collection fields."""
_blocked_fields = {
"accounts",
"applications",
"audit_records",
"bindings",
"catalogs",
"family_invitations",
"identities",
"memberships",
"outbox_events",
"profile_values",
"tenant_accounts",
"users",
}
def __init__(self, inner: InMemoryUserEngineStore) -> None:
self._inner = inner
def __getattr__(self, name: str) -> Any:
if name in self._blocked_fields:
raise AssertionError(f"service accessed concrete store field {name}")
return getattr(self._inner, name)
class _FailingOutboxStore(InMemoryUserEngineStore):
def append_outbox(self, event: OutboxEvent) -> None:
raise RuntimeError("outbox unavailable")
if __name__ == "__main__":
unittest.main()

View File

@@ -4,13 +4,13 @@ type: workplan
title: "Postgres Durable Store Consumer Requirements"
domain: netkingdom
repo: user-engine
status: proposed
status: finished
owner: codex
topic_slug: netkingdom
planning_priority: high
planning_order: 9
created: "2026-06-05"
updated: "2026-06-05"
updated: "2026-06-15"
depends_on:
- USER-WP-0007
state_hub_workstream_id: "b5c85993-4aa2-4a8d-98b6-d174ab1b4538"
@@ -22,9 +22,11 @@ state_hub_workstream_id: "b5c85993-4aa2-4a8d-98b6-d174ab1b4538"
Define, from the `user-engine` consumer perspective, what a durable
Postgres-backed store must provide before user-engine depends on it in
NetKingdom. This workplan is requirements-only: it should not implement the
Postgres adapter, provision databases, create tenant infrastructure, or choose
the final provider repository design.
NetKingdom. The 2026-06-15 review also identified and closed one missing
durable-store contract in this repository: `UserEngineService` now consumes an
adapter-neutral store protocol instead of the concrete in-memory store. This
workplan still does not implement the Postgres adapter, provision databases,
create tenant infrastructure, or choose the final provider repository design.
## Scope Direction
@@ -51,7 +53,7 @@ schema, migrations for its own tables, store semantics, and conformance tests.
```task
id: USER-WP-0009-T1
status: todo
status: done
priority: high
state_hub_task_id: "64c578e1-e2a1-48d4-8da9-659d4f881ef3"
```
@@ -64,7 +66,7 @@ schema version reporting.
```task
id: USER-WP-0009-T2
status: todo
status: done
priority: high
state_hub_task_id: "19cfd23e-8a87-416d-b948-c727e8c5a11c"
```
@@ -76,7 +78,7 @@ security, observability, backup/restore expectations, and acceptance tests.
```task
id: USER-WP-0009-T3
status: todo
status: done
priority: high
state_hub_task_id: "d3b388de-bb79-41d5-805e-d2def88ac926"
```
@@ -88,7 +90,7 @@ secrets, authorization, or audit-platform concerns.
```task
id: USER-WP-0009-T4
status: todo
status: done
priority: medium
state_hub_task_id: "d0e05af7-d777-4948-b072-79f1ffb9fc3a"
```
@@ -99,7 +101,7 @@ the isolated MVP without leaking Postgres concepts into domain code.
```task
id: USER-WP-0009-T5
status: todo
status: done
priority: medium
state_hub_task_id: "3c428960-be5b-411e-bd9b-7cba833abba8"
```
@@ -111,7 +113,7 @@ readiness, and redacted diagnostics.
```task
id: USER-WP-0009-T6
status: todo
status: done
priority: medium
state_hub_task_id: "d606094a-254c-46d5-9bb8-a3449ce61c2c"
```
@@ -133,10 +135,46 @@ expectations, encryption, and operational runbooks.
tests.
- The provider-repo boundary is explicit and avoids duplicating IAM, secrets,
authorization, audit-platform, or infrastructure ownership.
- `UserEngineService` depends on an adapter-neutral store protocol with
readiness, query, transaction, audit, outbox, and diagnostics semantics.
- No Postgres implementation code is added as part of this workplan.
## Expected Outputs
- `docs/postgres-durable-store-consumer-requirements.md`
- Store-boundary notes suitable for a future provider repo.
- `UserEngineStore` protocol and local-store conformance behavior.
- Follow-up implementation workplan inputs for a Postgres adapter.
## Implementation Notes
Implemented on 2026-06-15:
- Added `UserEngineStore` in `src/user_engine/ports.py` as the durable
persistence boundary for service behavior.
- Moved `UserEngineService` from the concrete in-memory store type to the
store protocol.
- Replaced service reads of local dict/list fields with protocol accessors for
users, identities, applications, bindings, catalogs, audit, outbox, and
diagnostics.
- Added store transaction boundaries around mutating writes so domain changes,
local audit records, and outbox events commit or roll back together.
- Kept authorization-denial audit records durable without emitting outbox
events, including when a denial happens inside a composed outer transaction.
- Extended `InMemoryUserEngineStore` as the reference adapter with query
helpers, record counts, pending outbox access, audit-log access, and nested
transaction rollback semantics.
- Added conformance tests for protocol-only store consumption, failed-mutation
rollback, and denial-audit persistence across rollback.
- Updated the durable-store and public contract docs to describe the new
adapter boundary.
- No Postgres adapter, database dependency, provisioning, credentials, or
infrastructure ownership was added.
Verification:
```text
make test
Ran 42 tests in 0.134s
OK
```

View File

@@ -0,0 +1,125 @@
---
id: USER-WP-0010
type: workplan
title: "Registration Identity And Factor Model"
domain: netkingdom
repo: user-engine
status: proposed
owner: codex
topic_slug: netkingdom
planning_priority: high
planning_order: 10
created: "2026-06-15"
updated: "2026-06-15"
depends_on:
- USER-WP-0007
- USER-WP-0009
state_hub_workstream_id: "0d53560b-2b9d-442b-9328-4b2ce5c5bdae"
---
# USER-WP-0010 - Registration Identity And Factor Model
## Goal
Define and implement the first headless registration domain slice for
NetKingdom users. The slice should let user-engine start and complete a
registration session, establish a stable NetKingdom ID, link verified external
identities, record factor evidence, and return identity context without
becoming an identity provider or factor-proofing service.
## Scope Direction
user-engine owns the registration-domain records and service facade. NetKingdom
IAM, identity providers, eID providers, mail/SMS proofing, credential
lifecycle, sessions, and tokens remain external adapter concerns.
## Non-Goals
- Do not implement password, passkey, session, MFA, SMS, email, or eID proofing
providers in user-engine.
- Do not issue OIDC/SAML tokens.
- Do not build the registration UI in this workplan.
- Do not implement prepared account claiming, access profiles, or onboarding
journeys beyond the hooks needed for later workplans.
## Tasks
```task
id: USER-WP-0010-T1
status: todo
priority: high
state_hub_task_id: "2a6c93de-e320-41e6-8930-7a4099c5757a"
```
Define NetKingdom ID semantics. Decide whether the public NetKingdom ID is the
existing `User.user_id`, an alias, or a separate mapped identifier. Document
stability, visibility, privacy, and migration expectations.
```task
id: USER-WP-0010-T2
status: todo
priority: high
state_hub_task_id: "31ddb44e-b7d1-406e-9114-78c5e7f92478"
```
Add registration session domain models and lifecycle states: started,
factor_pending, factor_verified, completed, abandoned, expired, and rejected.
```task
id: USER-WP-0010-T3
status: todo
priority: high
state_hub_task_id: "7441f064-eb49-4e66-8c1d-a2626aae020c"
```
Add identity factor and factor verification models for email, phone, postal
address, eID, invite, and SSO identity evidence. Store assurance metadata and
evidence references without storing secret proofing payloads.
```task
id: USER-WP-0010-T4
status: todo
priority: high
state_hub_task_id: "7057afda-d585-48cd-bac1-f0bd0f05fef5"
```
Create factor verification adapter ports. The adapters should accept external
proofing results and return normalized factor evidence for user-engine.
```task
id: USER-WP-0010-T5
status: todo
priority: high
state_hub_task_id: "f4f0da38-9810-45e7-ab4e-0619eb45b3c4"
```
Implement a headless registration facade for start, attach verified factor,
complete, abandon, and resume flows.
```task
id: USER-WP-0010-T6
status: todo
priority: medium
state_hub_task_id: "c29b31cd-f2b2-41b6-86ee-9c78470abf01"
```
Add audit, outbox, diagnostics, and redaction behavior for registration and
factor lifecycle transitions.
## Acceptance Criteria
- A caller can start and complete a headless registration flow from verified
factor evidence.
- Completed registration creates or resolves a stable NetKingdom user/account
and external identity links.
- Factor evidence is inspectable through safe metadata and evidence references,
not raw proofing secrets.
- Registration failure, expiry, and abandon states are auditable.
- No credential, token, or proofing provider ownership moves into user-engine.
## Expected Outputs
- Registration and factor domain models.
- Registration service facade.
- Factor verification adapter ports.
- Documentation and tests for the basic self-registration flow.

View File

@@ -0,0 +1,124 @@
---
id: USER-WP-0011
type: workplan
title: "Prepared Accounts And Entitlement Claims"
domain: netkingdom
repo: user-engine
status: proposed
owner: codex
topic_slug: netkingdom
planning_priority: high
planning_order: 11
created: "2026-06-15"
updated: "2026-06-15"
depends_on:
- USER-WP-0010
state_hub_workstream_id: "39ac9f87-c61d-42d8-a45f-bece4848ed47"
---
# USER-WP-0011 - Prepared Accounts And Entitlement Claims
## Goal
Allow NetKingdom operators, tenant admins, family owners, service owners, or
upstream systems to prepare account intent and access packages before the user
registers. When the user later proves matching factors, user-engine can attach
the prepared package to the canonical user and activate the right lifecycle
steps.
## Scope Direction
Prepared accounts are not credentials. They are pending user-domain facts:
expected factor matches, tenant or group references, planned memberships,
profile defaults, onboarding journey hints, approval gates, expiry, and audit
history.
## Non-Goals
- Do not create login credentials for users who have not registered.
- Do not bypass factor verification or approval policies.
- Do not make user-engine the source of truth for external organization, HR, or
directory records.
- Do not implement final authorization policy decisions.
## Tasks
```task
id: USER-WP-0011-T1
status: todo
priority: high
state_hub_task_id: "11508f77-170b-4b22-bfdc-115a69bfe4db"
```
Add prepared account and prepared entitlement models with status, expiry,
preparer identity, tenant/scope references, factor match requirements, and
audit metadata.
```task
id: USER-WP-0011-T2
status: todo
priority: high
state_hub_task_id: "86ca36d4-721b-48fe-8c0c-c6a1e6740d2f"
```
Implement create, update, revoke, expire, and list operations for prepared
accounts, guarded by the authorization port.
```task
id: USER-WP-0011-T3
status: todo
priority: high
state_hub_task_id: "fe5a08e8-1101-4cec-b02f-b2eee8928604"
```
Implement claim matching during registration. Match verified factor evidence to
prepared account requirements and produce explicit claim decisions.
```task
id: USER-WP-0011-T4
status: todo
priority: high
state_hub_task_id: "8aef6d9e-5e76-4e44-bf81-58049b22a25c"
```
Convert claimed prepared entitlements into user-engine-owned facts:
memberships, tenant accounts, profile defaults, application bindings, and
onboarding journey starts.
```task
id: USER-WP-0011-T5
status: todo
priority: medium
state_hub_task_id: "527519a1-48ed-45fc-a6fc-739986ae6303"
```
Add conflict and safety rules for duplicate prepared accounts, weak factor
matches, expired packages, privileged roles, and manual approval requirements.
```task
id: USER-WP-0011-T6
status: todo
priority: medium
state_hub_task_id: "9530c8d6-82af-4635-8af8-aa79c54be94d"
```
Add audit/outbox events and evidence references for preparation, claim,
activation, denial, expiry, and revocation.
## Acceptance Criteria
- A prepared account can be created before user registration without issuing
credentials.
- A registering user can claim prepared rights only when required factor
evidence matches.
- Claimed rights become explicit user-engine memberships, profile values,
tenant account state, and onboarding events.
- Expired, revoked, ambiguous, or privileged claims fail closed.
- Every preparation and claim decision is auditable.
## Expected Outputs
- Prepared account domain model.
- Prepared entitlement activation facade.
- Claim matching rules and tests.
- Documentation for account preparation boundaries.

View File

@@ -0,0 +1,118 @@
---
id: USER-WP-0012
type: workplan
title: "Hats, Realms, Services, Assets, And Access Profiles"
domain: netkingdom
repo: user-engine
status: proposed
owner: codex
topic_slug: netkingdom
planning_priority: high
planning_order: 12
created: "2026-06-15"
updated: "2026-06-15"
depends_on:
- USER-WP-0010
state_hub_workstream_id: "f3cf0d30-eb6b-4734-a0a3-5a755d4cf150"
---
# USER-WP-0012 - Hats, Realms, Services, Assets, And Access Profiles
## Goal
Model how users and groups wear different hats across NetKingdom realms,
services, and assets. Provide access-control facts, profile layers, and
claims-enrichment context that authorization systems and service runtimes can
consume without moving final policy decisions into user-engine.
## Scope Direction
user-engine owns the identity-domain representation of hats, memberships,
access profiles, and active context. Authorization engines own policy decisions
and protected services own runtime enforcement.
## Non-Goals
- Do not implement the final ACL enforcement engine.
- Do not define every service-specific permission in user-engine.
- Do not bypass the authorization port.
- Do not make browser/UI state the source of truth for active access context.
## Tasks
```task
id: USER-WP-0012-T1
status: todo
priority: high
state_hub_task_id: "b86f0072-e666-479b-9b90-96d4015bbfa0"
```
Define realm, service area, asset scope, access profile, group, and hat
vocabulary. Map each concept to current user-engine membership, profile, and
canon reference patterns.
```task
id: USER-WP-0012-T2
status: todo
priority: high
state_hub_task_id: "66117083-8e85-44e1-9a76-cfd10dd24d23"
```
Add hat selection and active context models. A user should be able to choose an
active hat for a tenant, realm, service, or asset context when allowed.
```task
id: USER-WP-0012-T3
status: todo
priority: high
state_hub_task_id: "1dffda4c-f979-480e-9d6d-12ec9576780d"
```
Implement access profile templates that combine memberships, factor assurance
requirements, profile defaults, and claims projection rules.
```task
id: USER-WP-0012-T4
status: todo
priority: high
state_hub_task_id: "b07494fe-f301-49e2-8ea8-267a4c5219ee"
```
Extend `identity_context` and claims-enrichment projections with active hat,
realm, service, asset, group, access profile, and evidence references.
```task
id: USER-WP-0012-T5
status: todo
priority: medium
state_hub_task_id: "c78e10c4-b245-4a83-a75d-4b46a6073fd2"
```
Add ports for exporting access-control facts to authorization engines or ACL
systems while preserving source-of-truth boundaries.
```task
id: USER-WP-0012-T6
status: todo
priority: medium
state_hub_task_id: "f9f32165-3a12-424e-a370-bb2ab8348c21"
```
Add tests for hat selection, cross-tenant denial, missing factor assurance,
group-derived access, service-specific projection, and redacted diagnostics.
## Acceptance Criteria
- Users can have multiple hats without collapsing them into one account state.
- Active hat context is explicit in identity context and projections.
- Access profile facts can be exported to authorization systems.
- Missing tenant, realm, service, asset, factor, or approval context fails
closed.
- Final policy and ACL enforcement remain outside user-engine.
## Expected Outputs
- Hat and access profile domain model.
- Active context service facade.
- Identity-context and claims projection updates.
- Access-control fact export tests.

View File

@@ -0,0 +1,107 @@
---
id: USER-WP-0013
type: workplan
title: "Onboarding Journeys And Welcome Protocols"
domain: netkingdom
repo: user-engine
status: proposed
owner: codex
topic_slug: netkingdom
planning_priority: medium
planning_order: 13
created: "2026-06-15"
updated: "2026-06-15"
depends_on:
- USER-WP-0011
- USER-WP-0012
state_hub_workstream_id: "1dc82dfd-be68-4585-b6c9-6d24aebd3e27"
---
# USER-WP-0013 - Onboarding Journeys And Welcome Protocols
## Goal
Create a journey layer that helps newly registered or newly entitled users
enter the right NetKingdom subsystems. Welcome protocols should be driven by
registration, prepared-account, invitation, role, profile, and access events.
## Scope Direction
user-engine owns journey state, task references, event correlation, and user
context. Delivery systems, protected services, help content, notification
channels, and external task systems remain adapters or downstream systems.
## Non-Goals
- Do not build a notification platform.
- Do not embed service-specific tours or support content in core domain code.
- Do not replace external workflow/task systems.
- Do not build the UI in this workplan.
## Tasks
```task
id: USER-WP-0013-T1
status: todo
priority: high
state_hub_task_id: "30ef8507-eebc-4b96-8aa6-c530bef05739"
```
Define onboarding journey, welcome protocol, journey step, task, and subsystem
handoff models.
```task
id: USER-WP-0013-T2
status: todo
priority: high
state_hub_task_id: "7c6e53d4-ff96-4036-a413-f04b4b73d266"
```
Add journey templates keyed by registration outcome, prepared entitlement,
tenant, realm, service, application, role, hat, and factor requirements.
```task
id: USER-WP-0013-T3
status: todo
priority: high
state_hub_task_id: "d9c2983a-45d1-4b1b-a416-63e180ca74b3"
```
Implement journey start, progress, complete, skip, fail, and resume operations
with authorization, audit, and outbox behavior.
```task
id: USER-WP-0013-T4
status: todo
priority: medium
state_hub_task_id: "7155c2eb-4e32-46f0-ad33-961784cb9a03"
```
Add adapter ports for notifications, task systems, support content, subsystem
welcome callbacks, and lifecycle task linking.
```task
id: USER-WP-0013-T5
status: todo
priority: medium
state_hub_task_id: "c5e42dd6-207a-4b1e-a0d8-35701e9f71bc"
```
Expose onboarding status through identity context, diagnostics, and optional UI
contracts.
## Acceptance Criteria
- Registration or prepared-account claim can start an onboarding journey.
- Journey state is resumable, auditable, and correlated with outbox events.
- Subsystem welcome steps are adapter-driven, not hard-coded into core
registration logic.
- Users and admins can inspect pending onboarding work and blocked steps.
- Missing subsystem callbacks produce explicit lifecycle gaps.
## Expected Outputs
- Onboarding journey domain model.
- Welcome protocol service facade.
- Adapter ports for notifications and subsystem handoff.
- Scenario tests for successful, blocked, and resumed onboarding.

View File

@@ -0,0 +1,125 @@
---
id: USER-WP-0014
type: workplan
title: "Registration And Access Management UI"
domain: netkingdom
repo: user-engine
status: proposed
owner: codex
topic_slug: netkingdom
planning_priority: medium
planning_order: 14
created: "2026-06-15"
updated: "2026-06-15"
depends_on:
- USER-WP-0010
- USER-WP-0011
- USER-WP-0012
- USER-WP-0013
state_hub_workstream_id: "011f7d20-5c9d-42a9-b7a3-b20a8ae9f557"
---
# USER-WP-0014 - Registration And Access Management UI
## Goal
Build an optional NetKingdom registration and access management UI backed by
user-engine APIs. The UI should make registration, factor status, prepared
rights, hat selection, profile completion, and onboarding journeys convenient
without hiding IAM, authorization, proofing, or service-runtime boundaries.
## Scope Direction
The UI is an operating surface over user-engine domain APIs. It should be thin,
workflow-oriented, and suitable for self-service users, tenant admins, family
owners, and operators.
## Non-Goals
- Do not implement credential entry, password reset, passkeys, MFA challenges,
or token issuance in the UI.
- Do not embed final authorization policy rules in frontend code.
- Do not replace service-specific admin consoles.
- Do not make UI state authoritative over domain records.
## Tasks
```task
id: USER-WP-0014-T1
status: todo
priority: high
state_hub_task_id: "983087e1-c512-419f-86a6-b954d0a1ab54"
```
Define UI information architecture for registration, factor status,
prepared-account claim, hat selection, profile completion, onboarding journey,
and admin setup views.
```task
id: USER-WP-0014-T2
status: todo
priority: high
state_hub_task_id: "0af5d8ef-0d1e-44bd-b807-bc40e87afef2"
```
Define UI API contracts or route handlers over the headless service facades.
Keep proofing, IAM, authorization, and notification calls behind adapters.
```task
id: USER-WP-0014-T3
status: todo
priority: high
state_hub_task_id: "a2e00aa3-5849-469c-a3a3-f4f5bd2df6c8"
```
Implement the self-service registration flow with resume, prepared rights
review, factor status, terms/consent, and completion states.
```task
id: USER-WP-0014-T4
status: todo
priority: medium
state_hub_task_id: "36d49049-cfe7-4f87-9a7f-78e37de9188a"
```
Implement hat selection and active access context views for realms, services,
groups, and assets.
```task
id: USER-WP-0014-T5
status: todo
priority: medium
state_hub_task_id: "e58038fc-6138-40cc-bb6b-4cbf7a8b0b87"
```
Implement admin views for prepared accounts, invitations, access profiles,
group membership, realms/services/assets, and onboarding diagnostics.
```task
id: USER-WP-0014-T6
status: todo
priority: medium
state_hub_task_id: "4de949d6-e330-41b2-87cf-9b9425f0f8be"
```
Add usability, accessibility, error-state, redaction, and mobile/desktop tests
for the registration and admin flows.
## Acceptance Criteria
- A new user can complete a registration flow through the UI using adapter
supplied factor evidence.
- A prepared account claim can be reviewed and accepted or denied through the
UI.
- Users can choose an active hat and see available realms/services without
exposing internal policy logic.
- Admins can prepare accounts and inspect onboarding state.
- The UI does not store or display secrets, raw proofing payloads, or hidden
authorization decisions.
## Expected Outputs
- Registration UI and API contract.
- Hat/access management UI views.
- Admin prepared-account and onboarding views.
- Frontend verification artifacts.

View File

@@ -0,0 +1,121 @@
---
id: USER-WP-0015
type: workplan
title: "Registration Scenario And Security Conformance"
domain: netkingdom
repo: user-engine
status: proposed
owner: codex
topic_slug: netkingdom
planning_priority: medium
planning_order: 15
created: "2026-06-15"
updated: "2026-06-15"
depends_on:
- USER-WP-0010
- USER-WP-0011
- USER-WP-0012
- USER-WP-0013
- USER-WP-0014
state_hub_workstream_id: "4f21e1c9-ad27-4ac9-888f-8f78c6abfb3b"
---
# USER-WP-0015 - Registration Scenario And Security Conformance
## Goal
Prove the full NetKingdom registration and onboarding model through executable
scenarios, security negative paths, redaction checks, adapter conformance, and
operability diagnostics.
## Scope Direction
This workplan turns the registration roadmap into a testable contract. It
should cover both headless APIs and the optional UI surface where present.
## Non-Goals
- Do not add new product surface unless a test exposes a missing contract.
- Do not assert provider-specific IAM, eID, SMS, email, or authorization engine
internals.
- Do not require production infrastructure for local conformance tests.
## Tasks
```task
id: USER-WP-0015-T1
status: todo
priority: high
state_hub_task_id: "5ca0a269-559d-4138-b702-9984a411f2ed"
```
Define the registration scenario matrix: self-registration, prepared account
claim, privileged role requiring approval, eID-backed assurance, family invite,
tenant admin invite, group access, and denied cross-tenant claim.
```task
id: USER-WP-0015-T2
status: todo
priority: high
state_hub_task_id: "6ee492b1-923f-4aa0-8e17-b69f522c4898"
```
Add end-to-end headless tests covering registration through identity context,
claims enrichment, active hat selection, and onboarding event emission.
```task
id: USER-WP-0015-T3
status: todo
priority: high
state_hub_task_id: "b813a88f-ced6-40ce-9a25-d1c666fb73c9"
```
Add security negative tests for weak factor evidence, duplicate identity links,
prepared-account hijack attempts, expired claims, missing tenant context,
privileged role escalation, and stale approvals.
```task
id: USER-WP-0015-T4
status: todo
priority: medium
state_hub_task_id: "5a03ac1a-1f8e-455b-8f75-691e8bdda286"
```
Add redaction and diagnostics tests for factor values, profile sensitivity,
prepared-account metadata, active hat context, and access-profile evidence.
```task
id: USER-WP-0015-T5
status: todo
priority: medium
state_hub_task_id: "fcf32b4d-d050-4989-bb05-844e0d13e548"
```
Add adapter conformance tests for factor verification, authorization checks,
access fact export, onboarding handoff, audit export, outbox replay, and
durable store behavior.
```task
id: USER-WP-0015-T6
status: todo
priority: medium
state_hub_task_id: "a7850784-3b86-453f-bbc7-1d53d0813f82"
```
Add UI flow tests once USER-WP-0014 exists: registration happy path, resume,
prepared rights review, hat selection, admin preparation, and blocked journey.
## Acceptance Criteria
- The main registration and onboarding journeys are executable as tests.
- Security negative paths fail closed and leave audit evidence.
- Sensitive factor and profile data is redacted from diagnostics and UI output.
- Adapter contracts are testable without production infrastructure.
- The registration UI, if implemented, is covered by workflow-level tests.
## Expected Outputs
- Registration scenario matrix.
- Headless and UI conformance tests.
- Security negative-path test suite.
- Adapter conformance harness for registration dependencies.