Enterprise access control integration

This commit is contained in:
2026-05-04 15:32:54 +02:00
parent ffab98be10
commit 5ecb52aece
9 changed files with 838 additions and 3 deletions

View File

@@ -139,6 +139,22 @@ to future backend storage.
## Adapter Boundaries
Enterprise IAM integration is covered in
`docs/enterprise-access-control-integration.md`. In that architecture,
Markitect is the policy enforcement point for Markdown knowledge results, while
NetKingdom/key-cape-compatible OIDC supplies identity claims and external
policy engines can act as policy decision points.
Identity and directory integration use these provider-neutral boundaries:
- `IdentityClaimsAdapter` validates OIDC/JWT/SAML material and returns
normalized `EnterpriseIdentity`.
- `DirectoryGroupResolver` resolves group overage or stale directory claims
through SCIM/Graph/LDAP/Keycloak-style adapters.
- `EnterprisePolicyMapper` maps canonical enterprise roles, scopes, and groups
to `PolicySubject` labels, trust zones, and allowed actions.
- `DecisionLogStore` persists durable audit records for policy decisions.
Relationship policies use `RelationshipPolicyAdapter`:
```text

View File

@@ -0,0 +1,335 @@
# Enterprise Access-Control Integration
Date: 2026-05-04
## Purpose
This note explains how Markitect's access-control gateway should integrate with
enterprise IAM while respecting the local NetKingdom/key-cape direction.
The answer is yes: trust zones and access groups can map to canonical directory
group membership, but Markitect should not trust raw AD group names directly.
The enterprise shape should be:
```text
OIDC/SAML/SCIM identity and directory plane
-> canonical subject claims
-> policy map and/or policy decision point
-> Markitect policy gateway
-> filtered document/query/context results
```
Markitect is the policy enforcement point for Markdown knowledge results. It
should validate and normalize identity data through adapters, then call a policy
decision point through the existing policy interfaces.
## Canonical Enterprise Shape
The canonical enterprise pattern is the PEP/PDP/PIP/PAP split:
| Component | Markitect fit |
| --- | --- |
| PEP: policy enforcement point | `AccessPolicyGateway` at query, search, context, workflow, export, and assisted-prompt boundaries. |
| PDP: policy decision point | Local label policy for development; OpenFGA/SpiceDB, OPA/Rego, Cedar, or enterprise authorization service through adapters. |
| PIP: policy information point | NetKingdom/key-cape OIDC claims, directory group resolution, object labels, backend metadata, workflow context, and environment attributes. |
| PAP: policy administration point | Enterprise IAM/policy administration, plus versioned Markitect mapping files for labels, actions, trust zones, and emergency rules. |
This keeps Markitect small and auditable. It enforces decisions where Markdown
knowledge leaves a boundary, but it does not become the enterprise directory,
SSO provider, or policy administration system.
## Local Infrastructure Fit
The local canon already points the right way:
- NetKingdom SSO is the reference identity provider.
- Keycloak is the reference OIDC provider, with privacyIDEA-backed MFA.
- key-cape is the lightweight OIDC/profile-enforcement path for local,
sandbox, and bootstrap scenarios.
- Services should trust OIDC tokens, validate issuer/audience/signature/expiry,
and authorize from explicit roles/scopes.
Markitect should therefore consume the NetKingdom IAM profile rather than
create its own identity standard.
## Enterprise Reference Architecture
```mermaid
flowchart LR
AD["AD / LDAP / HRIS"] --> SCIM["SCIM / Directory Sync<br/>(PIP)"]
SCIM --> IdP["NetKingdom SSO<br/>Keycloak / key-cape-compatible OIDC"]
IdP --> Token["OIDC Access Token<br/>roles, scopes, groups, org, assurance"]
Token --> Mapper["Identity Claim Mapper"]
Mapper --> Subject["PolicySubject"]
Source["Markdown objects<br/>labels, paths, trust zones"] --> Object["PolicyObject"]
Subject --> Gateway["Markitect AccessPolicyGateway<br/>(PEP)"]
Object --> Gateway
Gateway --> PDP["Policy Decision Point<br/>local / OpenFGA / OPA / Cedar"]
PDP --> Decision["PolicyDecision + audit metadata"]
Decision --> Boundary["Filtered query/search/context results"]
```
### Identity Plane
OIDC should be the default authentication path for users and services. SAML may
remain relevant for enterprise federation, but Markitect should normalize both
into the same subject shape.
The canonical accepted claims should follow the NetKingdom IAM profile:
- `iss`
- `sub`
- `aud`
- `exp`
- `iat`
- `scope` or `scp`
- `preferred_username`
- `roles` or `realm_access.roles`
- recommended `groups`, `azp`, `email`, `name`
For humans, Authorization Code + PKCE is the right login flow. For services,
client credentials or workload identity should produce short-lived service
tokens. OAuth token exchange can support hub-to-hub delegation where a service
acts on behalf of a user.
### Directory And Group Plane
Enterprise directories remain authoritative for group membership. Provisioning
should use a standard such as SCIM where possible; AD/LDAP synchronization into
Keycloak is also reasonable when NetKingdom owns the identity plane.
Important design rule:
```text
directory groups -> canonical roles/scopes/trust labels -> Markitect subject
```
Avoid:
```text
raw AD group name -> direct Markitect privilege
```
Reasons:
- AD group names change and often encode organizational accidents.
- Token group claims can be too large for large organizations.
- Privilege should be expressed as app roles/scopes or an explicit mapping file.
- Group membership freshness must be visible in the decision trail.
For Microsoft Entra-style group claims, large tenants may hit token group
overage. Markitect should therefore support a group-resolution adapter rather
than assuming all groups are always present in the token.
### Policy Plane
The existing Markitect interfaces already provide the main policy boundary:
```text
AccessPolicyGateway.authorize(subject, action, object_id, context)
AccessPolicyGateway.filter_results(subject, action, results, context)
AccessPolicyGateway.explain_decision(decision_id)
```
The existing data models also fit enterprise needs:
- `PolicySubject`: identity, roles, groups/labels, allowed actions, attributes.
- `PolicyObject`: path, labels, trust zone, attributes.
- `PolicyDecision`: stable decision id, effect, reason, labels, trust zone,
metadata.
- `PolicyFilterResult`: filtered results, decisions, diagnostics, summary.
The adapter seams are also correct:
- `RelationshipPolicyAdapter` for OpenFGA/SpiceDB/Zanzibar-style checks.
- `RulePolicyAdapter` for OPA/Rego, Cedar, or other ABAC engines.
This follow-up adds the missing interface layer:
```text
OIDC token or SAML assertion -> verified EnterpriseIdentity -> PolicySubject
```
Concrete adapters must validate issuer, audience, expiry, signature, and
assurance metadata before any authorization decision is made. The core package
now exposes protocol/data boundaries for this without taking a dependency on
Keycloak, Entra, LDAP, SCIM, OpenFGA, OPA, or Cedar client libraries.
## Canonical Subject Mapping
Recommended normalized shape:
```yaml
subject:
id: "oidc:<issuer>#<sub>"
display_name: "Ada Lovelace"
principal_type: human | service
issuer: "https://sso.example.org/realms/netkingdom"
audience: ["markitect-tool"]
authorized_party: "markitect-cli"
roles: [viewer, operator]
scopes: [openid, profile, hub:read]
groups:
- /markitect/readers
- /platform/architecture
allowed_labels: [public, internal]
trust_zones: [public, internal]
assurance:
mfa: true
acr: "..."
amr: ["pwd", "otp"]
directory:
source: keycloak | entra | ldap | scim
refreshed_at: "2026-05-04T10:00:00Z"
```
The mapping file should translate enterprise groups and app roles to Markitect
labels and actions:
```yaml
id: markitect-enterprise-policy-map
issuer: https://sso.example.org/realms/netkingdom
audiences: [markitect-tool]
groups:
/markitect/readers:
allowed_labels: [public, internal]
actions: [query, search, read]
/markitect/stewards:
allowed_labels: [public, internal, restricted]
actions: [query, search, read, package, export]
roles:
admin:
allowed_labels: [public, internal, restricted]
actions: [query, search, read, package, export, policy-admin]
scopes:
markitect:read:
actions: [query, search, read]
trust_zones:
internal:
required_groups: [/markitect/readers]
```
## Data/Object Mapping
Markdown remains the source-friendly object labeling layer:
```yaml
---
policy:
labels: [internal]
trust_zone: internal
owner: team:platform-architecture
---
```
For enterprise environments, object metadata should eventually include:
- content labels/classification
- repository/path
- owning team or project
- business domain
- tenancy/org
- retention or export constraints
- provenance of label assignment
Path rules remain useful as a safety net, but document labels and backend
metadata should become authoritative for cached/context-package retrieval.
## Enforcement Points
Markitect should enforce policy at every point where knowledge leaves a
boundary:
- `mkt cache query`
- `mkt search`
- context package creation and activation
- workflow outputs
- assisted/LLM prompt assembly
- export/render steps
- backend APIs or future MCP resources
The highest-risk point is context assembly for agents. Before WP-0008 turns
context caches into agent memory, policy must be able to answer:
- who is activating the context package?
- which token or service account is acting?
- which labels/trust zones are included?
- was anything denied or redacted?
- can this package be reactivated later under the same policy assumptions?
## Interface Confirmation
The WP-0009 infrastructure is a good foundation. It already has:
- policy gateway protocol
- local label gateway
- explainable decisions
- filter-before-return behavior
- query/search integration
- relationship and rule adapter boundaries
This follow-up adds these provider-neutral interfaces in
`markitect_tool.policy.adapters`:
1. `IdentityClaimsAdapter`
- validates OIDC/JWT/SAML assertions
- normalizes NetKingdom/key-cape-compatible claims into
`EnterpriseIdentity`
2. `DirectoryGroupResolver`
- resolves group overage or stale claims
- supports SCIM/Graph/LDAP/Keycloak admin APIs behind adapters
- records freshness and source provenance
3. `EnterprisePolicyMapper`
- maps canonical groups, roles, scopes, and tenants to Markitect labels,
trust zones, actions, and object constraints
4. `DecisionLogStore`
- persists policy decisions for query/context/workflow runs
- records token hash, subject id, action, object id, policy version, result,
reason, and redaction status
5. workflow/runtime policy injection
- `subject_from_token`
- `policy_map`
- `required_assurance`
- `emergency_justification`
Concrete implementations remain future work. This is deliberate: key-cape and
NetKingdom should own identity issuance and profile compliance, while
Markitect owns normalization, policy envelopes, diagnostics, and enforcement at
knowledge boundaries.
## Recommended Direction
Do not make AD/LDAP/Entra groups a Markitect core dependency.
Instead:
1. Accept NetKingdom/key-cape-compatible OIDC tokens.
2. Normalize claims into `PolicySubject`.
3. Map enterprise groups/roles/scopes to Markitect labels/trust zones/actions.
4. Use the existing `AccessPolicyGateway` as the enforcement point.
5. Let OpenFGA/SpiceDB/OPA/Cedar attach through adapter protocols where a
deployment needs stronger central policy.
6. Persist decisions before using this for production agent memory or exports.
## Sources
- OpenID Connect Core 1.0: https://openid.net/specs/openid-connect-core-1_0.html
- OAuth 2.0 Token Exchange, RFC 8693: https://www.rfc-editor.org/rfc/rfc8693
- SCIM Protocol, RFC 7644: https://datatracker.ietf.org/doc/rfc7644
- NIST SP 800-162 ABAC guide: https://csrc.nist.gov/pubs/sp/800/162/upd2/final
- NIST glossary, Policy Enforcement Point:
https://csrc.nist.gov/glossary/term/policy_enforcement_point
- NIST glossary, Policy Decision Point:
https://csrc.nist.gov/glossary/term/policy_decision_point
- NIST glossary, Policy Information Point:
https://csrc.nist.gov/glossary/term/policy_information_point
- Keycloak Server Administration Guide: https://www.keycloak.org/docs/latest/server_admin/
- Microsoft Entra group claims: https://learn.microsoft.com/en-us/security/zero-trust/develop/configure-tokens-group-claims-app-roles
- OpenFGA concepts: https://openfga.dev/docs/concepts
- Open Policy Agent policy language: https://www.openpolicyagent.org/docs/policy-language
- Cedar policy language: https://docs.cedarpolicy.com/
- Local canon: `/home/worsch/the-custodian/canon/standards/iam-profile_v0.1.md`

View File

@@ -38,6 +38,7 @@ and descriptions mirror the operational view.
| `MKTT-WP-0005` | complete | done | `MKTT-WP-0003`, `MKTT-WP-0004` | Runtime context, form state, dynamic rules, workflow integration, and provider-neutral assessment boundary are complete. |
| `MKTT-WP-0011` | complete | done | `MKTT-WP-0003`; task-level triggers: `MKTT-WP-0010-T001`, `MKTT-WP-0010-T005` | Markdown dataflow workflow layer is complete: workflow standard, source collectors, binding model, deterministic steps, assisted boundary, safe outputs, CLI, docs, and examples. |
| `MKTT-WP-0009` | complete | done | `MKTT-WP-0006` | Access-controlled knowledge gateway is complete: local labels, trust zones, path rules, policy-aware cache query/search, decisions, diagnostics, and external adapter boundaries. |
| `MKTT-WP-0014` | P2 | todo | `MKTT-WP-0009` | Enterprise IAM access-control integration: NetKingdom/key-cape-compatible identity claims, directory group resolution, policy maps, durable decision logs, and external PDP examples. |
| `MKTT-WP-0012` | P3 | todo | `MKTT-WP-0004`, `MKTT-WP-0010`, `MKTT-WP-0011` | Future Quarkdown-inspired document function layer: reusable Markdown-native function calls over processors, references, contracts, workflows, and later assisted steps. |
| `MKTT-WP-0008` | P3 | todo | `MKTT-WP-0006`, `MKTT-WP-0007`, `MKTT-WP-0009` | Agent working-memory cache after backend and policy floor are available. |
@@ -74,6 +75,12 @@ operations deserve author-facing function syntax. It should remain optional and
capability-gated, especially before assisted, external, file, or network
functions are allowed.
`MKTT-WP-0014` captures enterprise IAM integration for the access-control
gateway. It should follow `MKTT-WP-0009` and can run before or alongside
security-sensitive context memory work. It does not block local `MKTT-WP-0008`
research, but it should gate production deployment of reactivatable agent
context packages in enterprise environments.
## State Hub Mirror
Native State Hub dependency edges should mirror the whole-workstream
@@ -100,6 +107,7 @@ dependencies:
- `MKTT-WP-0012 -> MKTT-WP-0004`
- `MKTT-WP-0012 -> MKTT-WP-0010`
- `MKTT-WP-0012 -> MKTT-WP-0011`
- `MKTT-WP-0014 -> MKTT-WP-0009`
- `MKTT-WP-0008 -> MKTT-WP-0006`
- `MKTT-WP-0008 -> MKTT-WP-0007`
- `MKTT-WP-0008 -> MKTT-WP-0009`

View File

@@ -219,8 +219,12 @@ def _local_label_policy_descriptor() -> ExtensionDescriptor:
examples=["examples/policy/local-label-policy.yaml"],
metadata={
"external_adapters": [
"IdentityClaimsAdapter",
"DirectoryGroupResolver",
"EnterprisePolicyMapper",
"RelationshipPolicyAdapter",
"RulePolicyAdapter",
"DecisionLogStore",
]
},
)

View File

@@ -1,6 +1,14 @@
"""Access policy gateways and adapter protocols."""
from markitect_tool.policy.adapters import (
DecisionLogStore,
DirectoryGroupResolution,
DirectoryGroupResolutionRequest,
DirectoryGroupResolver,
EnterpriseIdentity,
EnterprisePolicyMapRequest,
EnterprisePolicyMapper,
IdentityClaimsAdapter,
RelationshipPolicyAdapter,
RelationshipPolicyRequest,
RulePolicyAdapter,
@@ -23,6 +31,14 @@ __all__ = [
"LocalLabelPolicy",
"LocalLabelPolicyGateway",
"LocalPathPolicyRule",
"DecisionLogStore",
"DirectoryGroupResolution",
"DirectoryGroupResolutionRequest",
"DirectoryGroupResolver",
"EnterpriseIdentity",
"EnterprisePolicyMapRequest",
"EnterprisePolicyMapper",
"IdentityClaimsAdapter",
"PolicyDecision",
"PolicyFilterResult",
"PolicyObject",

View File

@@ -1,11 +1,168 @@
"""Protocol boundaries for external authorization engines."""
"""Protocol boundaries for external identity and authorization engines."""
from __future__ import annotations
from dataclasses import asdict, dataclass, field
from typing import Any, Protocol
from markitect_tool.policy.models import PolicyDecision
from markitect_tool.policy.models import PolicyDecision, PolicySubject
@dataclass(frozen=True)
class EnterpriseIdentity:
"""Verified enterprise identity claims normalized for policy mapping.
Concrete adapters are responsible for validating tokens or assertions before
returning this shape. Core Markitect only consumes the normalized identity.
"""
issuer: str
subject: str
identity_scheme: str = "oidc"
principal_type: str = "human"
audience: list[str] = field(default_factory=list)
authorized_party: str | None = None
preferred_username: str | None = None
roles: list[str] = field(default_factory=list)
scopes: list[str] = field(default_factory=list)
groups: list[str] = field(default_factory=list)
assurance: dict[str, Any] = field(default_factory=dict)
directory: dict[str, Any] = field(default_factory=dict)
claims: dict[str, Any] = field(default_factory=dict)
provenance: dict[str, Any] = field(default_factory=dict)
@property
def canonical_id(self) -> str:
return f"{self.identity_scheme}:{self.issuer}#{self.subject}"
def to_policy_subject(
self,
*,
allowed_labels: list[str] | None = None,
trust_zones: list[str] | None = None,
allowed_actions: list[str] | None = None,
attributes: dict[str, Any] | None = None,
) -> PolicySubject:
"""Convert verified claims into the subject shape used by gateways."""
return PolicySubject(
id=self.canonical_id,
allowed_labels=list(allowed_labels or []),
trust_zones=list(trust_zones or []),
roles=list(self.roles),
allowed_actions=list(allowed_actions or []),
attributes={
"issuer": self.issuer,
"subject": self.subject,
"identity_scheme": self.identity_scheme,
"principal_type": self.principal_type,
"audience": self.audience,
"authorized_party": self.authorized_party,
"preferred_username": self.preferred_username,
"scopes": self.scopes,
"groups": self.groups,
"assurance": self.assurance,
"directory": self.directory,
"identity_provenance": self.provenance,
}
| dict(attributes or {}),
)
def to_dict(self) -> dict[str, Any]:
data = asdict(self)
data["canonical_id"] = self.canonical_id
return _drop_empty(data)
class IdentityClaimsAdapter(Protocol):
"""Adapter boundary for OIDC/JWT/SAML verification and normalization."""
def verify(
self,
token_or_assertion: str | dict[str, Any],
*,
context: dict[str, Any] | None = None,
) -> EnterpriseIdentity | dict[str, Any]:
"""Validate identity material and return normalized enterprise claims."""
@dataclass(frozen=True)
class DirectoryGroupResolutionRequest:
"""Request for resolving group claims that are stale, partial, or overlarge."""
subject_id: str
issuer: str
groups: list[str] = field(default_factory=list)
claims: dict[str, Any] = field(default_factory=dict)
context: dict[str, Any] = field(default_factory=dict)
def to_dict(self) -> dict[str, Any]:
return _drop_empty(asdict(self))
@dataclass(frozen=True)
class DirectoryGroupResolution:
"""Directory group resolution result with freshness and source provenance."""
groups: list[str] = field(default_factory=list)
source: str | None = None
refreshed_at: str | None = None
overage: bool = False
metadata: dict[str, Any] = field(default_factory=dict)
def to_dict(self) -> dict[str, Any]:
return _drop_empty(asdict(self))
class DirectoryGroupResolver(Protocol):
"""Adapter boundary for SCIM/LDAP/Graph/Keycloak group resolution."""
def resolve(
self,
request: DirectoryGroupResolutionRequest,
) -> DirectoryGroupResolution | dict[str, Any]:
"""Return groups plus freshness/provenance metadata."""
@dataclass(frozen=True)
class EnterprisePolicyMapRequest:
"""Request to map enterprise claims onto Markitect policy vocabulary."""
identity: EnterpriseIdentity | dict[str, Any]
policy_map: dict[str, Any] = field(default_factory=dict)
groups: list[str] = field(default_factory=list)
context: dict[str, Any] = field(default_factory=dict)
def to_dict(self) -> dict[str, Any]:
data = asdict(self)
if isinstance(self.identity, EnterpriseIdentity):
data["identity"] = self.identity.to_dict()
return _drop_empty(data)
class EnterprisePolicyMapper(Protocol):
"""Adapter boundary for mapping IAM roles/groups/scopes to policy subjects."""
def map_subject(
self,
request: EnterprisePolicyMapRequest,
) -> PolicySubject | dict[str, Any]:
"""Return a gateway-ready subject with labels, zones, and actions."""
class DecisionLogStore(Protocol):
"""Persistent audit boundary for policy decisions."""
def record(
self,
decision: PolicyDecision,
*,
context: dict[str, Any] | None = None,
) -> str:
"""Persist a decision and return its durable audit id."""
def get(self, decision_id: str) -> dict[str, Any] | None:
"""Return one recorded decision when available."""
@dataclass(frozen=True)

View File

@@ -96,4 +96,5 @@ def test_builtin_policy_descriptor_exposes_cli_and_adapter_boundary():
"policy_filter",
}
assert "mkt policy check" in descriptor.cli["commands"]
assert "IdentityClaimsAdapter" in descriptor.metadata["external_adapters"]
assert "RelationshipPolicyAdapter" in descriptor.metadata["external_adapters"]

View File

@@ -4,7 +4,14 @@ from pathlib import Path
from click.testing import CliRunner
from markitect_tool.cli import main
from markitect_tool.policy import LocalLabelPolicy, LocalLabelPolicyGateway
from markitect_tool.policy import (
DirectoryGroupResolution,
DirectoryGroupResolutionRequest,
EnterpriseIdentity,
EnterprisePolicyMapRequest,
LocalLabelPolicy,
LocalLabelPolicyGateway,
)
POLICY_TEXT = """id: example-policy
@@ -187,6 +194,59 @@ def test_mkt_cache_query_filters_indexed_documents_by_policy(tmp_path: Path):
assert data["policy"]["denied"] == 1
def test_enterprise_identity_maps_to_policy_subject():
identity = EnterpriseIdentity(
issuer="https://sso.example.test/realms/netkingdom",
subject="user-123",
preferred_username="ada",
roles=["viewer"],
scopes=["markitect:read"],
groups=["/markitect/readers"],
assurance={"mfa": True},
directory={"source": "keycloak"},
)
subject = identity.to_policy_subject(
allowed_labels=["public", "internal"],
trust_zones=["public", "internal"],
allowed_actions=["query", "search"],
)
assert subject.id == "oidc:https://sso.example.test/realms/netkingdom#user-123"
assert subject.roles == ["viewer"]
assert subject.allowed_labels == ["public", "internal"]
assert subject.allowed_actions == ["query", "search"]
assert subject.attributes["issuer"] == "https://sso.example.test/realms/netkingdom"
assert subject.attributes["groups"] == ["/markitect/readers"]
assert subject.attributes["assurance"]["mfa"] is True
def test_enterprise_policy_adapter_requests_serialize_cleanly():
group_request = DirectoryGroupResolutionRequest(
subject_id="oidc:https://sso.example.test/realms/netkingdom#user-123",
issuer="https://sso.example.test/realms/netkingdom",
claims={"hasgroups": True},
)
group_result = DirectoryGroupResolution(
groups=["/markitect/readers"],
source="keycloak",
refreshed_at="2026-05-04T10:00:00Z",
overage=True,
)
map_request = EnterprisePolicyMapRequest(
identity=EnterpriseIdentity(
issuer="https://sso.example.test/realms/netkingdom",
subject="user-123",
),
policy_map={"groups": {"/markitect/readers": {"allowed_labels": ["internal"]}}},
groups=group_result.groups,
)
assert group_request.to_dict()["claims"] == {"hasgroups": True}
assert group_result.to_dict()["overage"] is True
assert map_request.to_dict()["identity"]["canonical_id"].endswith("#user-123")
def _policy_mapping() -> dict:
return {
"id": "example-policy",

View File

@@ -0,0 +1,238 @@
---
id: MKTT-WP-0014
type: workplan
title: "Enterprise IAM Access-Control Integration"
domain: markitect
status: todo
owner: markitect-tool
topic_slug: markitect
planning_priority: P2
planning_order: 82
depends_on_workplans:
- MKTT-WP-0009
related_workplans:
- MKTT-WP-0006
- MKTT-WP-0007
- MKTT-WP-0008
- MKTT-WP-0011
- MKTT-WP-0013
created: "2026-05-04"
updated: "2026-05-04"
state_hub_workstream_id: "86c22ccc-5f5a-4650-8495-76fe6c08e411"
---
# MKTT-WP-0014: Enterprise IAM Access-Control Integration
## Purpose
Turn the local access-control gateway into an enterprise-ready integration
surface without making Markitect an identity provider or hard-coding one
directory vendor.
Markitect should act as the policy enforcement point for Markdown knowledge
results. NetKingdom/key-cape-compatible SSO should supply identity claims.
External policy engines and enterprise directories should attach through
provider-neutral adapters.
## Background
`MKTT-WP-0009` implemented local labels, trust zones, path rules, query/search
filtering, explainable decisions, and relationship/rule policy adapter
boundaries. The enterprise follow-up research showed a clear canonical shape:
- OIDC/SAML for authentication and signed identity assertions.
- SCIM/LDAP/Graph/Keycloak admin APIs for directory and group information.
- PEP/PDP/PIP/PAP separation for authorization architecture.
- RBAC/ABAC/ReBAC policy models through mappable policy decision points.
- NetKingdom IAM profile as the local identity contract, with key-cape as the
preferred lightweight/bootstrap path.
Initial provider-neutral interfaces now exist in
`markitect_tool.policy.adapters`:
- `EnterpriseIdentity`
- `IdentityClaimsAdapter`
- `DirectoryGroupResolver`
- `EnterprisePolicyMapper`
- `DecisionLogStore`
Documentation: `docs/enterprise-access-control-integration.md`.
## Decision
Implement concrete enterprise integration as an optional extension track. Core
Markitect should keep accepting normalized `PolicySubject` and `PolicyObject`
models, while enterprise adapters handle token verification, group freshness,
claim mapping, durable decision logs, and external PDP calls.
Do not map raw AD/LDAP/Entra group names directly to Markitect privileges.
Always map:
```text
directory groups -> canonical roles/scopes/trust labels -> PolicySubject
```
## P14.1 - Define enterprise policy map schema
```task
id: MKTT-WP-0014-T001
status: todo
priority: high
state_hub_task_id: "1894c50f-95c3-4e1a-bd4f-388f7624ebd7"
```
Define the mapping file that translates enterprise groups, roles, scopes,
tenants, assurance levels, and emergency rules into Markitect labels, trust
zones, allowed actions, and object constraints.
Output: schema, examples, diagnostics, and tests.
## P14.2 - Implement NetKingdom/key-cape identity claims adapter
```task
id: MKTT-WP-0014-T002
status: todo
priority: high
state_hub_task_id: "8a177375-09b3-4898-a053-7601f82fcb29"
```
Implement an optional `IdentityClaimsAdapter` that consumes
NetKingdom/key-cape-compatible OIDC discovery and JWTs.
It must validate:
- issuer
- audience
- expiry and issued-at
- signature through JWKS
- authorized party/client id where required
- MFA/assurance claims for privileged actions
Output: adapter, fixtures, negative tests, and clear diagnostics.
## P14.3 - Implement enterprise subject mapper
```task
id: MKTT-WP-0014-T003
status: todo
priority: high
state_hub_task_id: "6861d4bc-1bb8-440d-bb9e-33e20c7feb55"
```
Implement `EnterprisePolicyMapper` over the policy map schema. It should map
verified identity claims and resolved groups into gateway-ready
`PolicySubject` objects.
Output: mapper, examples, and tests for roles, scopes, groups, trust zones,
tenancy, and emergency access.
## P14.4 - Add directory group resolution boundary
```task
id: MKTT-WP-0014-T004
status: todo
priority: medium
state_hub_task_id: "56d6bad6-d706-47b3-b321-1f0e870ecc0d"
```
Implement a provider-neutral group-resolution layer for claims that are stale,
partial, or too large for tokens. Start with a fake/test resolver and specify
adapter hooks for SCIM, Microsoft Graph, LDAP, and Keycloak.
Output: resolver contract, freshness metadata, overage handling, and tests.
## P14.5 - Persist decision logs
```task
id: MKTT-WP-0014-T005
status: todo
priority: high
state_hub_task_id: "f212662c-4ffc-4cac-ace2-a43777f4960c"
```
Implement a durable `DecisionLogStore` for policy decisions from query, search,
context packages, workflows, exports, and assisted prompt assembly.
Decision logs should record subject id, token hash, action, object id, policy
version, decision effect, reason, redaction status, and provenance.
Output: storage adapter, CLI inspection path, and tests.
## P14.6 - Add external PDP examples
```task
id: MKTT-WP-0014-T006
status: todo
priority: medium
state_hub_task_id: "573a198f-df0b-470a-b11c-9ac839c0845e"
```
Provide reference adapters or documented examples for:
- OpenFGA/SpiceDB-style relationship checks through
`RelationshipPolicyAdapter`
- OPA/Rego or Cedar-style rule checks through `RulePolicyAdapter`
Output: examples, adapter stubs, and policy request/decision fixtures.
## P14.7 - Integrate policy identity into workflows and context packages
```task
id: MKTT-WP-0014-T007
status: todo
priority: high
state_hub_task_id: "c4650304-0e2b-49c5-8569-e69907c08ccc"
```
Make workflow and future context-package execution accept explicit enterprise
identity and policy mapping configuration.
Required concepts:
- `subject_from_token`
- `policy_map`
- `required_assurance`
- `emergency_justification`
- decision-log sink
Output: workflow/context integration design, examples, and tests.
## P14.8 - Validate against NetKingdom IAM profile
```task
id: MKTT-WP-0014-T008
status: todo
priority: medium
state_hub_task_id: "0486e0c2-2cb9-4902-9a09-9ec729e9e79f"
```
Build conformance tests against the local IAM profile:
- required claims
- human Authorization Code + PKCE expectations
- service account claims
- local development issuer rejection in production mode
- emergency access audit requirements
Output: test fixtures and conformance checklist.
## Exit Criteria
- A NetKingdom/key-cape-compatible OIDC identity can be validated and mapped to
a `PolicySubject`.
- Enterprise groups, roles, scopes, trust zones, and labels are mapped through
a versioned policy map rather than raw directory names.
- Query, search, workflow, and context-package boundaries can enforce policy
and emit durable decision logs.
- Directory group overage and freshness are represented explicitly.
- OpenFGA/SpiceDB and OPA/Cedar-style PDP integrations can attach without
replacing Markitect's local policy gateway.
- The implementation remains optional and does not add enterprise IAM
dependencies to core Markdown parsing or deterministic processing.
## Notes
This workplan should be picked up before using Markitect context caches for
production agent memory in enterprise settings. It does not need to block local
research on `MKTT-WP-0008`, but it should gate production deployment of
reactivatable cross-document context packages.