generated from coulomb/repo-seed
Implement family dataspace onboarding
This commit is contained in:
@@ -11,6 +11,9 @@ HTTP or RPC adapters should preserve these operation names:
|
||||
`tenant_diagnostics`
|
||||
- `register_application`, `publish_catalog`
|
||||
- `set_profile_value`, `effective_profile`, `projection`, `identity_context`
|
||||
- `onboard_family_dataspace`, `invite_family_member`,
|
||||
`resend_family_invitation`, `revoke_family_invitation`,
|
||||
`accept_family_invitation`
|
||||
- `audit_records`, `outbox_events`
|
||||
|
||||
## Identity Context Contract
|
||||
@@ -36,6 +39,24 @@ policy, control, access-review, exception, and lifecycle task references belong
|
||||
to adapter contracts and remain non-owned unless a later workplan assigns
|
||||
source-of-truth responsibility to user-engine.
|
||||
|
||||
## Family Dataspace Onboarding Contract
|
||||
|
||||
`onboard_family_dataspace` is a convenience facade for personal-family
|
||||
identity-domain setup. It composes existing user, account, tenant-account,
|
||||
membership, application, catalog, profile, audit, outbox, projection, and
|
||||
identity-context operations.
|
||||
|
||||
The facade represents a family as a NetKingdom tenant plus a `family` scope. It
|
||||
does not provision the tenant, issue SSO tokens, own credentials, or implement
|
||||
the protected dataspace runtime. Family roles are scoped membership facts such
|
||||
as `owner`, `adult`, `child`, `guest`, and `delegated-caretaker`; authorization
|
||||
systems decide how those facts affect access.
|
||||
|
||||
Invitation acceptance requires already-verified claims. user-engine stores
|
||||
local invitation lifecycle, links the verified external identity, activates
|
||||
account state, and returns both `identity_context` and a
|
||||
`CLAIMS_ENRICHMENT` projection for SSO adapters.
|
||||
|
||||
## Error Taxonomy
|
||||
|
||||
- `ValidationError`: caller supplied an invalid shape, state transition, or
|
||||
|
||||
@@ -66,3 +66,40 @@ operation. Outbox consumers should treat `event_id` as the delivery id and
|
||||
for event in service.outbox_events():
|
||||
print(event.event_type, event.aggregate_id, event.correlation_id)
|
||||
```
|
||||
|
||||
## Onboard A Family Dataspace
|
||||
|
||||
```python
|
||||
from user_engine.domain import FamilyDataspaceRequest, FamilyMemberSpec, FamilyRole
|
||||
|
||||
owner = service.me(owner_claims, correlation_id="corr-owner")
|
||||
onboarding = service.onboard_family_dataspace(
|
||||
owner.actor,
|
||||
FamilyDataspaceRequest(
|
||||
tenant="tenant:worsch-family",
|
||||
family_scope_id="family:worsch",
|
||||
family_display_name="Worsch Family",
|
||||
application_id="app.personal-dataspace",
|
||||
oidc_client_id="personal-dataspace-client",
|
||||
protected_system_id="dataspace.personal.worsch",
|
||||
member_specs=(
|
||||
FamilyMemberSpec(
|
||||
primary_email="child@example.test",
|
||||
display_name="Child Member",
|
||||
role=FamilyRole.CHILD,
|
||||
),
|
||||
),
|
||||
),
|
||||
correlation_id="corr-family-onboard",
|
||||
)
|
||||
|
||||
accepted = service.accept_family_invitation(
|
||||
child_claims,
|
||||
onboarding.invitations[0].invitation.invitation_id,
|
||||
correlation_id="corr-child-accept",
|
||||
)
|
||||
```
|
||||
|
||||
`accepted.identity_context` is the canon-facing context for the SSO adapter.
|
||||
`accepted.claims_projection` is the application-visible profile projection for
|
||||
the personal dataspace.
|
||||
|
||||
120
docs/family-dataspace-onboarding.md
Normal file
120
docs/family-dataspace-onboarding.md
Normal file
@@ -0,0 +1,120 @@
|
||||
# Family Dataspace Onboarding
|
||||
|
||||
Status: implemented MVP facade
|
||||
Date: 2026-06-05
|
||||
Related workplan: USER-WP-0008
|
||||
|
||||
## Purpose
|
||||
|
||||
Family dataspace onboarding is the first concrete convenience use case for
|
||||
`user-engine` as a NetKingdom identity-domain integration layer. It lets a
|
||||
consumer represent a family as a tenant-scoped identity context, invite family
|
||||
members, bind a personal dataspace application, and produce SSO-ready identity
|
||||
context without making callers sequence low-level user, profile, membership,
|
||||
application, audit, and projection operations themselves.
|
||||
|
||||
## Model
|
||||
|
||||
| Use-case concept | user-engine representation | Source of truth |
|
||||
| --- | --- | --- |
|
||||
| Family | NetKingdom tenant plus `family` membership scope | NetKingdom tenant/organization infrastructure |
|
||||
| Family owner | `User`, `Account`, active `TenantAccount`, `family:owner` membership | user-engine for local facts |
|
||||
| Family member | invited `User`, `Account`, `TenantAccount`, `FamilyInvitation` | user-engine for local lifecycle |
|
||||
| SSO identity | linked `ExternalIdentity` from verified `(issuer, subject)` | NetKingdom IAM for authentication |
|
||||
| Family role | scoped `Membership.kind` such as `owner`, `adult`, `child`, `guest` | user-engine fact, authorization consumes it |
|
||||
| Personal dataspace | registered `Application` with `ApplicationBinding` | user-engine binding, external runtime owns app |
|
||||
| SSO claims input | `identity_context` plus `CLAIMS_ENRICHMENT` projection | user-engine read model, NetKingdom IAM consumes it |
|
||||
|
||||
## Public Flow
|
||||
|
||||
1. Resolve the owner through `me(...)` or pass an already-normalized actor.
|
||||
2. Call `onboard_family_dataspace(...)` with a `FamilyDataspaceRequest`.
|
||||
3. user-engine ensures the owner exists, registers the dataspace application,
|
||||
publishes a minimal dataspace catalog, assigns owner membership, creates
|
||||
pending member invitations, and returns identity context plus a
|
||||
claims-enrichment projection for SSO.
|
||||
4. Invited members accept through `accept_family_invitation(...)` using
|
||||
verified NetKingdom claims. user-engine links the external identity,
|
||||
activates account state, records audit/outbox events, and returns SSO-ready
|
||||
context for the member.
|
||||
5. Pending invitations can be resent or revoked through
|
||||
`resend_family_invitation(...)` and `revoke_family_invitation(...)`.
|
||||
|
||||
## Example
|
||||
|
||||
```python
|
||||
from user_engine.domain import FamilyDataspaceRequest, FamilyMemberSpec, FamilyRole
|
||||
|
||||
owner = service.me(owner_claims, correlation_id="corr-owner")
|
||||
onboarding = service.onboard_family_dataspace(
|
||||
owner.actor,
|
||||
FamilyDataspaceRequest(
|
||||
tenant="tenant:worsch-family",
|
||||
family_scope_id="family:worsch",
|
||||
family_display_name="Worsch Family",
|
||||
application_id="app.personal-dataspace",
|
||||
oidc_client_id="personal-dataspace-client",
|
||||
protected_system_id="dataspace.personal.worsch",
|
||||
member_specs=(
|
||||
FamilyMemberSpec(
|
||||
primary_email="child@example.test",
|
||||
display_name="Child Member",
|
||||
role=FamilyRole.CHILD,
|
||||
),
|
||||
),
|
||||
),
|
||||
correlation_id="corr-family-onboard",
|
||||
)
|
||||
|
||||
member = service.accept_family_invitation(
|
||||
member_claims,
|
||||
onboarding.invitations[0].invitation.invitation_id,
|
||||
correlation_id="corr-member-accept",
|
||||
)
|
||||
```
|
||||
|
||||
`onboarding.identity_context` and `member.identity_context` contain the
|
||||
canon-facing actor, user, account, authenticated subject, authorization
|
||||
principal, tenant, family group, membership, grant-like, and evidence
|
||||
references. `claims_projection` contains application-visible profile values
|
||||
such as the family display name and member display name.
|
||||
|
||||
## Boundary
|
||||
|
||||
user-engine does not issue tokens, manage credentials, run MFA, provision the
|
||||
family tenant, or implement the personal dataspace runtime. Those remain
|
||||
NetKingdom IAM, tenant, security, and application responsibilities.
|
||||
|
||||
Family roles are exported as scoped membership facts. The authorization port
|
||||
decides whether those facts allow an action.
|
||||
|
||||
Invitation tokens and proofing are deliberately adapter-owned. The MVP
|
||||
invitation record tracks local lifecycle state and assumes NetKingdom IAM has
|
||||
already verified claims before acceptance.
|
||||
|
||||
## Audit And Events
|
||||
|
||||
The facade emits high-level events in addition to the lower-level events from
|
||||
the operations it composes:
|
||||
|
||||
- `family_dataspace.onboarded`
|
||||
- `family_member.invited`
|
||||
- `family_invitation.resent`
|
||||
- `family_invitation.revoked`
|
||||
- `family_invitation.accepted`
|
||||
|
||||
Lower-level events such as `user.created`, `tenant_account.status_changed`,
|
||||
`membership.added`, `identity.linked`, `application.registered`,
|
||||
`catalog.published`, and `profile.value_set` remain visible for replay and
|
||||
traceability.
|
||||
|
||||
## Current MVP Limits
|
||||
|
||||
- Invitations are stored in the current store boundary and need durable-store
|
||||
backing before production use.
|
||||
- Invitation delivery, one-time token material, and proofing are external
|
||||
adapter responsibilities.
|
||||
- Membership revocation and historical role lifecycle are not yet fully
|
||||
modeled beyond invitation revoke and account status changes.
|
||||
- The default dataspace catalog is intentionally minimal and should evolve with
|
||||
real dataspace claims requirements.
|
||||
@@ -15,6 +15,7 @@ projection, audit, and event behavior testable without a UI.
|
||||
| sensitive_redaction | Sensitive values are redacted in runtime and claims-enrichment projections. |
|
||||
| audit_event_replay | Mutations carry audit records, outbox events, and correlation ids. |
|
||||
| identity_canon_context | Actor, user, account, authenticated subject, authorization principal, tenant, membership, grant-like facts, and evidence references stay distinguishable. |
|
||||
| family_dataspace_onboarding | A family tenant can register a personal dataspace, invite members, accept SSO identities, project claims context, and deny cross-family access. |
|
||||
|
||||
## Fixture Actors
|
||||
|
||||
|
||||
Reference in New Issue
Block a user