Adapt USER-WP-0021 and USER-WP-0023 to published policy-nexus contracts
IAM Profile v0.3 and Tenancy Posture v0.1 are now live on policy.coulomb.social. Close the portal expansion workplan against those documents, keep the flex-auth live A2 probe waiting, and forward optional tenant_roles to flex-auth without authorizing from them locally.
This commit is contained in:
parent
166788228d
commit
f762161d84
13 changed files with 109 additions and 10 deletions
|
|
@ -4,7 +4,7 @@ description: >
|
||||||
tenant, membership, profile, lifecycle, and evidence-facing context.
|
tenant, membership, profile, lifecycle, and evidence-facing context.
|
||||||
status: candidate
|
status: candidate
|
||||||
owner: codex
|
owner: codex
|
||||||
updated: "2026-06-05"
|
updated: "2026-08-19"
|
||||||
|
|
||||||
implements:
|
implements:
|
||||||
- identity-canon conceptual model as an implementation-facing domain facade
|
- identity-canon conceptual model as an implementation-facing domain facade
|
||||||
|
|
@ -25,9 +25,10 @@ produces:
|
||||||
- Access Grant or grant-like membership fact
|
- Access Grant or grant-like membership fact
|
||||||
|
|
||||||
consumes:
|
consumes:
|
||||||
- NetKingdom IAM Profile claims
|
- NetKingdom IAM Profile v0.3 claims
|
||||||
- verified issuer and subject identifiers
|
- verified issuer and subject identifiers
|
||||||
- assurance and principal type claims
|
- assurance and principal type claims
|
||||||
|
- optional tenant_roles forwarded to flex-auth only
|
||||||
- authorization decisions and obligations
|
- authorization decisions and obligations
|
||||||
- policy, control, review, exception, and evidence references
|
- policy, control, review, exception, and evidence references
|
||||||
- lifecycle task references from downstream task systems
|
- lifecycle task references from downstream task systems
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,14 @@
|
||||||
|
|
||||||
Status: caller side implemented and deployed; live proof pending flex-auth A2 promotion (FLEX-WP-0015-T02).
|
Status: caller side implemented and deployed; live proof pending flex-auth A2 promotion (FLEX-WP-0015-T02).
|
||||||
|
|
||||||
|
Governing published policy (2026-08-19):
|
||||||
|
|
||||||
|
- IAM Profile v0.3 Service Account Flow — audience-scoped, rotating
|
||||||
|
workload identity:
|
||||||
|
`https://policy.coulomb.social/standards/iam-profile/v0.3/`
|
||||||
|
- Tenancy Posture v0.1 A2 — inbound caller choke point:
|
||||||
|
`https://policy.coulomb.social/standards/tenancy-posture/v0.1/`
|
||||||
|
|
||||||
user-engine calls `flex-auth-user-engine` with a projected Kubernetes
|
user-engine calls `flex-auth-user-engine` with a projected Kubernetes
|
||||||
ServiceAccount token whose audience is exactly `flex-auth`. The adapter reads
|
ServiceAccount token whose audience is exactly `flex-auth`. The adapter reads
|
||||||
the token file for every decision so hourly projection rotation requires no
|
the token file for every decision so hourly projection rotation requires no
|
||||||
|
|
@ -77,7 +85,9 @@ BODY='{"actor":{"issuer":"https://kc.coulomb.social","subject":"probe",
|
||||||
3. **user-engine cannot represent another protected system.** Re-run step 1
|
3. **user-engine cannot represent another protected system.** Re-run step 1
|
||||||
with `resource.system` set to `tenant-engine`. Expect a refusal from the
|
with `resource.system` set to `tenant-engine`. Expect a refusal from the
|
||||||
binding, not an allow: the token binds `user-engine` to
|
binding, not an allow: the token binds `user-engine` to
|
||||||
`system:serviceaccount:user-engine:user-engine` only.
|
`system:serviceaccount:user-engine:user-engine` only. That binding is the
|
||||||
|
A2 choke point named by Tenancy Posture v0.1; publication does not change
|
||||||
|
the remaining operator sequence.
|
||||||
|
|
||||||
Record all three results, the flex-auth digest they ran against, and the
|
Record all three results, the flex-auth digest they ran against, and the
|
||||||
user-engine digest, in USER-WP-0023-T03 before marking it done. A pass on
|
user-engine digest, in USER-WP-0023-T03 before marking it done. A pass on
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,16 @@ See also the cross-repo assessment in the net-kingdom repo:
|
||||||
`/home/worsch/net-kingdom/docs/user-engine-netkingdom-integration-assessment.md`
|
`/home/worsch/net-kingdom/docs/user-engine-netkingdom-integration-assessment.md`
|
||||||
(for current intent/scope fit, gaps, and recommendations as of 2026-06-03).
|
(for current intent/scope fit, gaps, and recommendations as of 2026-06-03).
|
||||||
|
|
||||||
|
Published governing contracts (policy-nexus, 2026-08-19):
|
||||||
|
|
||||||
|
- IAM Profile v0.3: `https://policy.coulomb.social/standards/iam-profile/v0.3/`
|
||||||
|
- Tenancy Posture v0.1: `https://policy.coulomb.social/standards/tenancy-posture/v0.1/`
|
||||||
|
- NetKingdom architecture: `https://policy.coulomb.social/architecture/net-kingdom/v0.1/`
|
||||||
|
|
||||||
|
The user-engine boundary contract remains accepted in net-kingdom
|
||||||
|
(`canon/standards/user-engine-boundary-contract_v0.1.md`) and is marked
|
||||||
|
`publish` in the policy-nexus ledger, but it is not yet on the site.
|
||||||
|
|
||||||
## Required Ports
|
## Required Ports
|
||||||
|
|
||||||
| Port | Direction | Purpose |
|
| Port | Direction | Purpose |
|
||||||
|
|
@ -55,6 +65,7 @@ actor:
|
||||||
tenant: string
|
tenant: string
|
||||||
principal_type: human | service | agent
|
principal_type: human | service | agent
|
||||||
roles: [string]
|
roles: [string]
|
||||||
|
tenant_roles: [string]
|
||||||
groups: [string]
|
groups: [string]
|
||||||
scopes: [string]
|
scopes: [string]
|
||||||
assurance: object
|
assurance: object
|
||||||
|
|
|
||||||
|
|
@ -46,4 +46,7 @@ as externally complete. Compensation is idempotent and operator-visible.
|
||||||
Enterprise SAML/OIDC federation, SCIM and corporate directory synchronization
|
Enterprise SAML/OIDC federation, SCIM and corporate directory synchronization
|
||||||
remain provider adapters. Their external ownership metadata and identity links
|
remain provider adapters. Their external ownership metadata and identity links
|
||||||
must fit the existing neutral boundaries rather than becoming user-engine
|
must fit the existing neutral boundaries rather than becoming user-engine
|
||||||
domain dependencies.
|
domain dependencies. The published target is IAM Profile v0.3 Keycloak
|
||||||
|
expanded mode
|
||||||
|
(`https://policy.coulomb.social/standards/iam-profile/v0.3/`); a dedicated
|
||||||
|
workplan is created only when a tenant demands those integrations.
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,7 @@ class VerifiedIdentityClaimsAdapter:
|
||||||
principal_type=PrincipalType(str(claims["principal_type"])),
|
principal_type=PrincipalType(str(claims["principal_type"])),
|
||||||
audience=audience,
|
audience=audience,
|
||||||
roles=_strings(claims.get("roles", ())),
|
roles=_strings(claims.get("roles", ())),
|
||||||
|
tenant_roles=_strings(claims.get("tenant_roles", ())),
|
||||||
groups=_strings(claims.get("groups", ())),
|
groups=_strings(claims.get("groups", ())),
|
||||||
scopes=_strings(scopes),
|
scopes=_strings(scopes),
|
||||||
assurance=dict(claims.get("assurance", {})),
|
assurance=dict(claims.get("assurance", {})),
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,7 @@ class FlexAuthHTTPAdapter:
|
||||||
"attributes": {
|
"attributes": {
|
||||||
"issuer": request.actor.issuer,
|
"issuer": request.actor.issuer,
|
||||||
"roles": list(request.actor.roles),
|
"roles": list(request.actor.roles),
|
||||||
|
"tenant_roles": list(request.actor.tenant_roles),
|
||||||
"groups": list(request.actor.groups),
|
"groups": list(request.actor.groups),
|
||||||
"scopes": list(request.actor.scopes),
|
"scopes": list(request.actor.scopes),
|
||||||
"assurance": dict(request.actor.assurance),
|
"assurance": dict(request.actor.assurance),
|
||||||
|
|
|
||||||
|
|
@ -210,6 +210,7 @@ class Actor:
|
||||||
principal_type: PrincipalType
|
principal_type: PrincipalType
|
||||||
audience: tuple[str, ...]
|
audience: tuple[str, ...]
|
||||||
roles: tuple[str, ...] = ()
|
roles: tuple[str, ...] = ()
|
||||||
|
tenant_roles: tuple[str, ...] = ()
|
||||||
groups: tuple[str, ...] = ()
|
groups: tuple[str, ...] = ()
|
||||||
scopes: tuple[str, ...] = ()
|
scopes: tuple[str, ...] = ()
|
||||||
assurance: Mapping[str, Any] = field(default_factory=dict)
|
assurance: Mapping[str, Any] = field(default_factory=dict)
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,7 @@ class FixtureIdentityClaimsAdapter:
|
||||||
principal_type=PrincipalType(str(claims["principal_type"])),
|
principal_type=PrincipalType(str(claims["principal_type"])),
|
||||||
audience=tuple(_as_tuple(claims.get("aud", ()))),
|
audience=tuple(_as_tuple(claims.get("aud", ()))),
|
||||||
roles=tuple(_as_tuple(claims.get("roles", ()))),
|
roles=tuple(_as_tuple(claims.get("roles", ()))),
|
||||||
|
tenant_roles=tuple(_as_tuple(claims.get("tenant_roles", ()))),
|
||||||
groups=tuple(_as_tuple(claims.get("groups", ()))),
|
groups=tuple(_as_tuple(claims.get("groups", ()))),
|
||||||
scopes=tuple(_as_tuple(scopes)),
|
scopes=tuple(_as_tuple(scopes)),
|
||||||
assurance=dict(claims.get("assurance", {})),
|
assurance=dict(claims.get("assurance", {})),
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,7 @@ class PlatformAdapterTests(unittest.TestCase):
|
||||||
request = json.loads(call.call_args.args[0].data)
|
request = json.loads(call.call_args.args[0].data)
|
||||||
self.assertEqual(request["resource"]["system"], "user-engine")
|
self.assertEqual(request["resource"]["system"], "user-engine")
|
||||||
self.assertEqual(request["context"]["self"], True)
|
self.assertEqual(request["context"]["self"], True)
|
||||||
|
self.assertEqual(request["subject"]["attributes"]["tenant_roles"], ["CUS"])
|
||||||
|
|
||||||
def test_flex_auth_fails_closed_when_unavailable(self):
|
def test_flex_auth_fails_closed_when_unavailable(self):
|
||||||
with patch("user_engine.adapters.flex_auth.urlopen", side_effect=URLError("down")):
|
with patch("user_engine.adapters.flex_auth.urlopen", side_effect=URLError("down")):
|
||||||
|
|
@ -155,7 +156,7 @@ def _request():
|
||||||
actor = Actor(
|
actor = Actor(
|
||||||
issuer="https://issuer", subject="subject-1", tenant="tenant-a",
|
issuer="https://issuer", subject="subject-1", tenant="tenant-a",
|
||||||
principal_type=PrincipalType.HUMAN, audience=("user-engine",),
|
principal_type=PrincipalType.HUMAN, audience=("user-engine",),
|
||||||
roles=("tenant-admin",),
|
roles=("tenant-admin",), tenant_roles=("CUS",),
|
||||||
)
|
)
|
||||||
return AuthorizationRequest(
|
return AuthorizationRequest(
|
||||||
actor=actor, resource_type="user-engine:user", resource_id="user-1",
|
actor=actor, resource_type="user-engine:user", resource_id="user-1",
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,12 @@ class VerifiedIdentityClaimsAdapterTests(unittest.TestCase):
|
||||||
actor = self.adapter.normalize(self.claims)
|
actor = self.adapter.normalize(self.claims)
|
||||||
self.assertEqual("person-1", actor.subject)
|
self.assertEqual("person-1", actor.subject)
|
||||||
self.assertEqual(("tenant-admin",), actor.roles)
|
self.assertEqual(("tenant-admin",), actor.roles)
|
||||||
|
self.assertEqual((), actor.tenant_roles)
|
||||||
|
|
||||||
|
def test_optional_tenant_roles_are_preserved_without_local_authorization(self):
|
||||||
|
actor = self.adapter.normalize({**self.claims, "tenant_roles": ["CUS", "VEN"]})
|
||||||
|
self.assertEqual(("CUS", "VEN"), actor.tenant_roles)
|
||||||
|
self.assertEqual(("tenant-admin",), actor.roles)
|
||||||
|
|
||||||
def test_rejects_wrong_issuer_and_audience(self):
|
def test_rejects_wrong_issuer_and_audience(self):
|
||||||
with self.assertRaises(ValidationError):
|
with self.assertRaises(ValidationError):
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,9 @@ Date: 2026-05-22
|
||||||
Related product docs: `../INTENT.md`, `ProductRequirementsDocument.md`
|
Related product docs: `../INTENT.md`, `ProductRequirementsDocument.md`
|
||||||
Primary platform references:
|
Primary platform references:
|
||||||
|
|
||||||
- `/home/worsch/net-kingdom/canon/standards/iam-profile_v0.2.md`
|
- `https://policy.coulomb.social/standards/iam-profile/v0.3/` (source: `/home/worsch/net-kingdom/canon/standards/iam-profile_v0.3.md`; v0.2 is superseded)
|
||||||
|
- `https://policy.coulomb.social/standards/tenancy-posture/v0.1/`
|
||||||
|
- `https://policy.coulomb.social/architecture/net-kingdom/v0.1/`
|
||||||
- `/home/worsch/net-kingdom/docs/platform-identity-security-architecture.md`
|
- `/home/worsch/net-kingdom/docs/platform-identity-security-architecture.md`
|
||||||
- `/home/worsch/net-kingdom/docs/responsibility-map.md`
|
- `/home/worsch/net-kingdom/docs/responsibility-map.md`
|
||||||
- `/home/worsch/key-cape/SCOPE.md`
|
- `/home/worsch/key-cape/SCOPE.md`
|
||||||
|
|
@ -63,10 +65,17 @@ by flex-auth:
|
||||||
- `iss`, `sub`, `aud`, `tenant`, `principal_type`
|
- `iss`, `sub`, `aud`, `tenant`, `principal_type`
|
||||||
- `groups`, `roles`, `scope` or `scp`
|
- `groups`, `roles`, `scope` or `scp`
|
||||||
- `assurance`
|
- `assurance`
|
||||||
|
- optional `tenant_roles` (IAM Profile v0.3 cached tenant capability roles;
|
||||||
|
forwarded to flex-auth, never used as a local authorization decision;
|
||||||
|
privileged or `aal2`-class actions must re-query `tenant-engine`)
|
||||||
- optional human display/contact claims such as `preferred_username`, `email`,
|
- optional human display/contact claims such as `preferred_username`, `email`,
|
||||||
and `name`
|
and `name`
|
||||||
- optional agent/delegation claims for agentic use cases
|
- optional agent/delegation claims for agentic use cases
|
||||||
|
|
||||||
|
A tenant's onboarding grouping (ADR-0013) and its capability roles (ADR-0014)
|
||||||
|
are independent. The identifier's grouping segment is historical after a
|
||||||
|
reclassification; `tenant_roles` is not encoded in the identifier.
|
||||||
|
|
||||||
`user-engine` must treat `iss + sub` as the stable identity link key. Email,
|
`user-engine` must treat `iss + sub` as the stable identity link key. Email,
|
||||||
username, or display name must never be primary keys.
|
username, or display name must never be primary keys.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,11 +4,11 @@ type: workplan
|
||||||
title: "Expand user-engine portal beyond the proven Binky MVP"
|
title: "Expand user-engine portal beyond the proven Binky MVP"
|
||||||
domain: communication
|
domain: communication
|
||||||
repo: user-engine
|
repo: user-engine
|
||||||
status: active
|
status: finished
|
||||||
owner: codex
|
owner: codex
|
||||||
topic_slug: netkingdom
|
topic_slug: netkingdom
|
||||||
created: "2026-07-30"
|
created: "2026-07-30"
|
||||||
updated: "2026-08-16"
|
updated: "2026-08-19"
|
||||||
depends_on:
|
depends_on:
|
||||||
- USER-WP-0020
|
- USER-WP-0020
|
||||||
- TEN-WP-0005
|
- TEN-WP-0005
|
||||||
|
|
@ -190,7 +190,7 @@ deployed and wants its own permission.
|
||||||
|
|
||||||
```task
|
```task
|
||||||
id: USER-WP-0021-T05
|
id: USER-WP-0021-T05
|
||||||
status: wait
|
status: done
|
||||||
priority: low
|
priority: low
|
||||||
state_hub_task_id: "05046780-9625-47c2-8caf-f57e9239c603"
|
state_hub_task_id: "05046780-9625-47c2-8caf-f57e9239c603"
|
||||||
```
|
```
|
||||||
|
|
@ -199,6 +199,24 @@ When a tenant requires SAML/OIDC federation, SCIM, directory synchronization,
|
||||||
just-in-time provisioning, or customer-owned lifecycle, create a dedicated
|
just-in-time provisioning, or customer-owned lifecycle, create a dedicated
|
||||||
cross-repository plan against the provider-neutral contracts.
|
cross-repository plan against the provider-neutral contracts.
|
||||||
|
|
||||||
|
2026-08-19 policy-nexus adaptation: no tenant has demanded those
|
||||||
|
integrations, so this workplan does not spawn one. The trigger now points
|
||||||
|
at published contracts rather than unpublished drafts:
|
||||||
|
|
||||||
|
- IAM Profile v0.3 at
|
||||||
|
`https://policy.coulomb.social/standards/iam-profile/v0.3/` — Keycloak
|
||||||
|
expanded mode remains the interchangeable enterprise-federation
|
||||||
|
implementation; grouping and `tenant_roles` are independent axes
|
||||||
|
(ADR-0013 / ADR-0014). Existing v0.2 claim handling stays conformant.
|
||||||
|
- Tenancy Posture v0.1 at
|
||||||
|
`https://policy.coulomb.social/standards/tenancy-posture/v0.1/`.
|
||||||
|
- NetKingdom architecture draft-2 at
|
||||||
|
`https://policy.coulomb.social/architecture/net-kingdom/v0.1/`.
|
||||||
|
|
||||||
|
user-engine still does not own federation, SCIM, or directory
|
||||||
|
synchronization. A new workplan is the right vehicle if a tenant later
|
||||||
|
requires them. This workplan's product-expansion scope is complete.
|
||||||
|
|
||||||
## Production rollout (2026-08-08)
|
## Production rollout (2026-08-08)
|
||||||
|
|
||||||
Commit `8229c6d` passed 115 tests with three external-provider skips, was
|
Commit `8229c6d` passed 115 tests with three external-provider skips, was
|
||||||
|
|
@ -283,3 +301,17 @@ instead of receiving the documented bounded automatic retry. The query now
|
||||||
selects every undelivered, non-dead-letter event; regression coverage checks
|
selects every undelivered, non-dead-letter event; regression coverage checks
|
||||||
that failed rows are not filtered out. The full suite passes 133 tests with
|
that failed rows are not filtered out. The full suite passes 133 tests with
|
||||||
three external-provider skips.
|
three external-provider skips.
|
||||||
|
|
||||||
|
## Residuals after close (2026-08-19)
|
||||||
|
|
||||||
|
- Live tenant-lifecycle probe from a user-engine pod (GET / PATCH / retire /
|
||||||
|
reactivate on a disposable tenant) remains owed with USER-WP-0023-T03.
|
||||||
|
Contract tests cannot see a digest pin rollback.
|
||||||
|
- Outbox transport activation still waits on governed OpenBao event/mail
|
||||||
|
tokens and transactional SMTP credentials.
|
||||||
|
- Optional `tenant_roles` is now forwarded to flex-auth. Privileged
|
||||||
|
live re-query against tenant-engine is required only if a future policy
|
||||||
|
treats those roles as `aal2`-class input; flex-auth currently has no
|
||||||
|
such policy.
|
||||||
|
- A new workplan is required if a tenant demands federation, SCIM, or
|
||||||
|
directory synchronization.
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ status: active
|
||||||
owner: codex
|
owner: codex
|
||||||
topic_slug: netkingdom
|
topic_slug: netkingdom
|
||||||
created: "2026-08-18"
|
created: "2026-08-18"
|
||||||
updated: "2026-08-18"
|
updated: "2026-08-19"
|
||||||
state_hub_workstream_id: "014d0886-b690-4860-8337-c718e440f678"
|
state_hub_workstream_id: "014d0886-b690-4860-8337-c718e440f678"
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -80,4 +80,26 @@ This task stays `wait` on FLEX-WP-0015-T02 promotion through FLEX-WP-0011,
|
||||||
and on an operator shell with cluster credentials, which agent sessions in
|
and on an operator shell with cluster credentials, which agent sessions in
|
||||||
this repo do not hold.
|
this repo do not hold.
|
||||||
|
|
||||||
|
2026-08-19 policy-nexus adaptation: the published contracts now name the
|
||||||
|
same split this workplan already implemented.
|
||||||
|
|
||||||
|
- IAM Profile v0.3 Service Account Flow
|
||||||
|
(`https://policy.coulomb.social/standards/iam-profile/v0.3/`) requires
|
||||||
|
audience-scoped, rotating workload credentials delivered through the
|
||||||
|
credential-management standard — that is the projected ServiceAccount
|
||||||
|
token we already send. The optional `tenant_roles` claim is forwarded
|
||||||
|
to flex-auth as a subject attribute and is never used as a local
|
||||||
|
authorization decision.
|
||||||
|
- Tenancy Posture v0.1
|
||||||
|
(`https://policy.coulomb.social/standards/tenancy-posture/v0.1/`) is the
|
||||||
|
public A2 target T03 is waiting to prove: a single inbound choke point
|
||||||
|
that refuses an unbound caller. Our half of that vector is deployed;
|
||||||
|
flex-auth's running digest is still A0.
|
||||||
|
- NetKingdom architecture draft-2
|
||||||
|
(`https://policy.coulomb.social/architecture/net-kingdom/v0.1/`) lists
|
||||||
|
both documents in chapter 9.
|
||||||
|
|
||||||
|
The workplan stays `active` because T03's live proof is still owed. That
|
||||||
|
proof is not unblocked by publication.
|
||||||
|
|
||||||
Contract: `docs/flex-auth-caller-identity.md`.
|
Contract: `docs/flex-auth-caller-identity.md`.
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue