enterprise/flex-auth integration layer

This commit is contained in:
2026-05-04 17:54:52 +02:00
parent e33f296bdb
commit 6cb3b7b172
17 changed files with 1240 additions and 23 deletions

View File

@@ -109,6 +109,19 @@ mkt cache query 'sections[heading=Decision]' \
--subject public-agent
```
Map NetKingdom/key-cape-style claims into a Markitect policy subject:
```text
mkt policy subject examples/policy/netkingdom-claims.yaml \
--policy-map examples/policy/enterprise-policy-map.yaml
```
Inspect a Markitect resource manifest intended for flex-auth registration:
```text
mkt policy resource-manifest examples/policy/flex-auth-resource-manifest.yaml
```
JSON and YAML outputs include:
- `policy`: mode, subject, action, allowed, denied, redacted, audit counts
@@ -155,6 +168,21 @@ Identity and directory integration use these provider-neutral boundaries:
to `PolicySubject` labels, trust zones, and allowed actions.
- `DecisionLogStore` persists durable audit records for policy decisions.
The Markitect-side enterprise helpers provide deterministic local
implementations:
- `NetKingdomIdentityClaimsAdapter` validates required IAM-profile claims,
issuer, audience, token lifetime, local-production issuer safety, roles, and
scopes for trusted claims or explicit JWT fixtures.
- `StaticDirectoryGroupResolver` records group overage/freshness for tests and
development.
- `EnterprisePolicyMap` and `LocalEnterprisePolicyMapper` translate groups,
roles, and scopes into `PolicySubject` labels, trust zones, and actions.
- `FlexAuthResourceManifest` describes Markitect knowledge resources that a
future flex-auth service can register.
- `LocalDecisionLogStore` is a JSONL development sink; durable enterprise audit
remains flex-auth scope.
Relationship policies use `RelationshipPolicyAdapter`:
```text

View File

@@ -155,6 +155,26 @@ 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.
The current Markitect-side implementation provides deterministic local
building blocks:
- `NetKingdomIdentityClaimsAdapter` validates required IAM-profile claims,
issuer, audience, token timestamps, roles/scopes, and production rejection of
local development issuers for already trusted claims or explicit JWT fixtures.
- `EnterprisePolicyMap` and `LocalEnterprisePolicyMapper` map groups, roles,
and scopes into `PolicySubject` labels, trust zones, actions, and diagnostic
attributes.
- `StaticDirectoryGroupResolver` models group freshness and overage without a
live directory dependency.
- `FlexAuthResourceManifest` describes Markitect knowledge resources for
future flex-auth registration.
- `LocalDecisionLogStore` provides a JSONL development/test sink for decision
records.
Live OIDC discovery, JWKS signature verification, directory synchronization,
central policy administration, and durable enterprise audit should be provided
by flex-auth/key-cape-facing adapters rather than Markitect core.
## Canonical Subject Mapping
Recommended normalized shape:
@@ -209,6 +229,13 @@ trust_zones:
required_groups: [/markitect/readers]
```
Command-line subject mapping:
```text
mkt policy subject examples/policy/netkingdom-claims.yaml \
--policy-map examples/policy/enterprise-policy-map.yaml
```
## Data/Object Mapping
Markdown remains the source-friendly object labeling layer:
@@ -222,6 +249,23 @@ policy:
---
```
A Markitect knowledge base can publish an explicit flex-auth resource manifest:
```yaml
id: markitect-example-knowledge-base
system: markitect-tool
actions: [read, query, search, package, export]
resources:
- id: knowledge-base:markitect-example
type: knowledge_base
- id: document:internal-note
type: document
parent: knowledge-base:markitect-example
path: examples/policy/private/internal-note.md
labels: [internal]
trust_zone: internal
```
For enterprise environments, object metadata should eventually include:
- content labels/classification

View File

@@ -272,6 +272,15 @@ permissions:
write: [out]
network: false
assisted_generation: false
policy:
subject_from_token: examples/policy/netkingdom-claims.yaml
policy_map: examples/policy/enterprise-policy-map.yaml
required_assurance:
mfa: true
emergency_justification: INC-123
decision_log: .markitect/policy-decisions.jsonl
flex_auth:
resource_manifest: examples/policy/flex-auth-resource-manifest.yaml
responsibilities:
human:
approves_outputs: true

View File

@@ -38,7 +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` | Markitect-side enterprise IAM access-control integration: NetKingdom/key-cape-compatible identity claims, flex-auth resource/policy contract, directory group resolution, decision-log sink, and external PDP request examples. |
| `MKTT-WP-0014` | complete | done | `MKTT-WP-0009` | Markitect-side enterprise IAM access-control integration is complete: NetKingdom/key-cape-compatible identity claims, flex-auth resource/policy contract, directory group resolution fixtures, decision-log sink, workflow declarations, CLI commands, and external PDP request 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. |
@@ -75,14 +75,13 @@ 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 Markitect-side enterprise IAM integration for the
access-control gateway. Central authorization administration should live in the
future `flex-auth` repo/service; Markitect should provide resource registration,
policy request, decision, diagnostics, and local development adapter contracts.
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.
`MKTT-WP-0014` completed Markitect-side enterprise IAM integration for the
access-control gateway. Central authorization administration remains
`flex-auth` scope; Markitect now provides resource registration, policy
request, decision, diagnostics, local development adapter contracts, workflow
declarations, and CLI inspection/mapping commands. Production deployment of
reactivatable agent context packages should still wait for a flex-auth-backed
enterprise policy service or equivalent.
## State Hub Mirror

View File

@@ -0,0 +1,58 @@
id: markitect-enterprise-policy-map
issuer: https://sso.example.test/realms/netkingdom
audiences:
- markitect-tool
defaults:
allowed_labels:
- public
trust_zones:
- public
groups:
/markitect/readers:
allowed_labels:
- public
- internal
trust_zones:
- public
- internal
actions:
- read
- query
- search
/markitect/stewards:
allowed_labels:
- public
- internal
- restricted
trust_zones:
- public
- internal
- restricted
actions:
- read
- query
- search
- package
- export
roles:
viewer:
actions:
- read
- query
- search
scopes:
markitect:read:
actions:
- read
- query
- search
trust_zones:
internal:
required_groups:
- /markitect/readers
restricted:
required_groups:
- /markitect/stewards
metadata:
owner: flex-auth
version: example

View File

@@ -0,0 +1,28 @@
relationship_request:
subject: oidc:https://sso.example.test/realms/netkingdom#user-123
relation: reader
object_id: document:internal-note
namespace: markitect/document
context:
action: query
trust_zone: internal
resource_path: examples/policy/private/internal-note.md
rule_request:
subject:
id: oidc:https://sso.example.test/realms/netkingdom#user-123
roles:
- viewer
groups:
- /markitect/readers
assurance:
mfa: true
action: query
object:
id: document:internal-note
type: document
labels:
- internal
trust_zone: internal
context:
policy_map_id: markitect-enterprise-policy-map
workflow_id: assisted-review-boundary

View File

@@ -0,0 +1,34 @@
id: markitect-example-knowledge-base
system: markitect-tool
actions:
- read
- query
- search
- package
- export
resources:
- id: knowledge-base:markitect-example
type: knowledge_base
labels:
- public
trust_zone: public
owner: team:platform-architecture
- id: document:public-note
type: document
parent: knowledge-base:markitect-example
path: examples/policy/public-note.md
labels:
- public
trust_zone: public
owner: team:platform-architecture
- id: document:internal-note
type: document
parent: knowledge-base:markitect-example
path: examples/policy/private/internal-note.md
labels:
- internal
trust_zone: internal
owner: team:platform-architecture
metadata:
source: markitect example policy fixtures
flex_auth_contract: resource-registration-v0

View File

@@ -0,0 +1,19 @@
iss: https://sso.example.test/realms/netkingdom
sub: user-123
aud:
- markitect-tool
exp: 4102444800
iat: 1767225600
preferred_username: ada
email: ada@example.test
name: Ada Lovelace
scope: openid profile markitect:read hub:read
azp: markitect-cli
realm_access:
roles:
- viewer
groups:
- /markitect/readers
amr:
- pwd
- otp

View File

@@ -0,0 +1,35 @@
# Policy-Aware Review Workflow
```yaml workflow
metadata:
id: policy-aware-review
intent:
summary: Declare enterprise identity and policy mapping for a review workflow.
inputs:
note:
file: ../policy/private/internal-note.md
selector: sections[heading=Decision]
steps:
shape:
kind: shape
data:
note: ${sources.note.items}
outputs:
review:
path: out/policy-aware-review.md
content: ${steps.shape.value.note}
permissions:
policy:
subject_from_token: examples/policy/netkingdom-claims.yaml
policy_map: examples/policy/enterprise-policy-map.yaml
required_assurance:
mfa: true
decision_log: .markitect/policy-decisions.jsonl
flex_auth:
resource_manifest: examples/policy/flex-auth-resource-manifest.yaml
responsibilities:
system:
enforces_policy: true
human:
reviews_denials: true
```

View File

@@ -52,7 +52,12 @@ from markitect_tool.generation import (
from markitect_tool.literate import tangle_markdown, weave_markdown, write_tangle_files
from markitect_tool.ops import IncludeError, compose_files, resolve_includes, transform_markdown
from markitect_tool.processor import ProcessorContext, run_fenced_processors
from markitect_tool.policy import LocalLabelPolicyGateway
from markitect_tool.policy import (
EnterprisePolicyError,
FlexAuthResourceManifest,
LocalLabelPolicyGateway,
load_enterprise_policy_subject,
)
from markitect_tool.query import (
InvalidQueryError,
extract_document,
@@ -791,6 +796,68 @@ def policy_check(
raise click.exceptions.Exit(0 if decision.get("allowed") else 1)
@policy.command("subject")
@click.argument("claims_file", type=click.Path(exists=True, dir_okay=False, path_type=Path))
@click.option(
"--policy-map",
"policy_map_file",
type=click.Path(exists=True, dir_okay=False, path_type=Path),
required=True,
help="Enterprise policy map file.",
)
@click.option("--group", "groups", multiple=True, help="Additional resolved group. May be repeated.")
@click.option(
"--environment",
type=click.Choice(["development", "test", "production"], case_sensitive=False),
help="Validation environment for issuer safety checks.",
)
@click.option(
"--format",
"output_format",
type=click.Choice(["json", "yaml", "text"], case_sensitive=False),
default="text",
show_default=True,
)
def policy_subject(
claims_file: Path,
policy_map_file: Path,
groups: tuple[str, ...],
environment: str | None,
output_format: str,
) -> None:
"""Map enterprise identity claims into a Markitect policy subject."""
try:
subject = load_enterprise_policy_subject(
claims_file,
policy_map_file,
extra_groups=list(groups),
environment=environment,
)
except EnterprisePolicyError as exc:
raise click.ClickException(str(exc)) from exc
_emit_subject_result({"subject": subject.to_dict()}, output_format)
@policy.command("resource-manifest")
@click.argument("manifest_file", type=click.Path(exists=True, dir_okay=False, path_type=Path))
@click.option(
"--format",
"output_format",
type=click.Choice(["json", "yaml", "text"], case_sensitive=False),
default="text",
show_default=True,
)
def policy_resource_manifest(manifest_file: Path, output_format: str) -> None:
"""Inspect a Markitect flex-auth resource registration manifest."""
try:
manifest = FlexAuthResourceManifest.from_file(manifest_file)
except EnterprisePolicyError as exc:
raise click.ClickException(str(exc)) from exc
_emit_resource_manifest_result({"manifest": manifest.to_dict()}, output_format)
@main.group("class")
def class_group() -> None:
"""Resolve deterministic content classes."""
@@ -1736,6 +1803,34 @@ def _emit_policy_result(data: dict, output_format: str) -> None:
click.echo(f"reason: {decision.get('reason')}")
def _emit_subject_result(data: dict, output_format: str) -> None:
if output_format == "json":
click.echo(json.dumps(data, indent=2, ensure_ascii=False))
elif output_format == "yaml":
click.echo(yaml.safe_dump(data, sort_keys=False))
else:
subject = data["subject"]
click.echo(f"subject: {subject.get('id')}")
click.echo(f"roles: {', '.join(subject.get('roles', [])) or '<none>'}")
click.echo(f"labels: {', '.join(subject.get('allowed_labels', [])) or '<none>'}")
click.echo(f"trust_zones: {', '.join(subject.get('trust_zones', [])) or '<none>'}")
click.echo(f"actions: {', '.join(subject.get('allowed_actions', [])) or '<none>'}")
def _emit_resource_manifest_result(data: dict, output_format: str) -> None:
if output_format == "json":
click.echo(json.dumps(data, indent=2, ensure_ascii=False))
elif output_format == "yaml":
click.echo(yaml.safe_dump(data, sort_keys=False))
else:
manifest = data["manifest"]
click.echo(f"manifest: {manifest.get('id')}")
click.echo(f"system: {manifest.get('system')}")
click.echo(f"resources: {len(manifest.get('resources', []))}")
actions = ", ".join(manifest.get("actions", [])) or "<none>"
click.echo(f"actions: {actions}")
def _emit_metrics(data: dict, output_format: str) -> None:
if output_format == "json":
click.echo(json.dumps(data, indent=2, ensure_ascii=False))

View File

@@ -200,6 +200,9 @@ def _local_label_policy_descriptor() -> ExtensionDescriptor:
capabilities=[
ProcessingCapability(id="policy", kind="authorize"),
ProcessingCapability(id="policy_filter", kind="filter"),
ProcessingCapability(id="identity_claims", kind="normalize"),
ProcessingCapability(id="resource_manifest", kind="register"),
ProcessingCapability(id="decision_log", kind="emit"),
ProcessingCapability(id="diagnostics", kind="emit"),
ProcessingCapability(id="provenance", kind="emit"),
],
@@ -211,6 +214,8 @@ def _local_label_policy_descriptor() -> ExtensionDescriptor:
cli={
"commands": [
"mkt policy check",
"mkt policy subject",
"mkt policy resource-manifest",
"mkt cache query --policy",
"mkt search --policy",
]

View File

@@ -14,6 +14,18 @@ from markitect_tool.policy.adapters import (
RulePolicyAdapter,
RulePolicyRequest,
)
from markitect_tool.policy.enterprise import (
EnterprisePolicyError,
EnterprisePolicyMap,
FlexAuthResource,
FlexAuthResourceManifest,
LocalDecisionLogStore,
LocalEnterprisePolicyMapper,
NetKingdomIdentityClaimsAdapter,
StaticDirectoryGroupResolver,
load_enterprise_identity_file,
load_enterprise_policy_subject,
)
from markitect_tool.policy.local import (
LocalLabelPolicy,
LocalLabelPolicyGateway,
@@ -36,16 +48,26 @@ __all__ = [
"DirectoryGroupResolutionRequest",
"DirectoryGroupResolver",
"EnterpriseIdentity",
"EnterprisePolicyError",
"EnterprisePolicyMap",
"EnterprisePolicyMapRequest",
"EnterprisePolicyMapper",
"FlexAuthResource",
"FlexAuthResourceManifest",
"IdentityClaimsAdapter",
"LocalDecisionLogStore",
"LocalEnterprisePolicyMapper",
"PolicyDecision",
"PolicyFilterResult",
"PolicyObject",
"PolicySubject",
"NetKingdomIdentityClaimsAdapter",
"RelationshipPolicyAdapter",
"RelationshipPolicyRequest",
"RulePolicyAdapter",
"RulePolicyRequest",
"StaticDirectoryGroupResolver",
"load_enterprise_identity_file",
"load_enterprise_policy_subject",
"policy_metadata_from_document",
]

View File

@@ -0,0 +1,579 @@
"""Enterprise policy integration helpers for Markitect-side adapters."""
from __future__ import annotations
import base64
import hashlib
import json
import time
from dataclasses import asdict, dataclass, field
from pathlib import Path
from typing import Any
import yaml
from markitect_tool.policy.adapters import (
DirectoryGroupResolution,
DirectoryGroupResolutionRequest,
EnterpriseIdentity,
EnterprisePolicyMapRequest,
)
from markitect_tool.policy.models import PolicyDecision, PolicySubject
NETKINGDOM_REQUIRED_CLAIMS = {
"iss",
"sub",
"aud",
"exp",
"iat",
"preferred_username",
}
class EnterprisePolicyError(ValueError):
"""Raised when enterprise identity or policy mapping is invalid."""
@dataclass(frozen=True)
class NetKingdomIdentityClaimsAdapter:
"""Validate NetKingdom/key-cape-compatible claims and normalize identity.
This adapter validates already trusted claims or explicitly allowed
unverified JWT fixtures. Live OIDC discovery, JWKS retrieval, and signature
verification belong in provider adapters, normally in flex-auth or
key-cape-facing integration code.
"""
issuer: str | None = None
audiences: list[str] = field(default_factory=list)
clock_skew_seconds: int = 60
reject_local_issuers_in_production: bool = True
def verify(
self,
token_or_assertion: str | dict[str, Any],
*,
context: dict[str, Any] | None = None,
) -> EnterpriseIdentity:
context = dict(context or {})
claims, provenance = self._claims(token_or_assertion, context)
self._validate_claims(claims, context)
roles = _roles_from_claims(claims)
scopes = _scopes_from_claims(claims)
groups = _string_list(claims.get("groups"))
issuer = str(claims["iss"])
subject = str(claims["sub"])
principal_type = _principal_type(claims, roles)
return EnterpriseIdentity(
issuer=issuer,
subject=subject,
principal_type=principal_type,
audience=_string_list(claims.get("aud")),
authorized_party=claims.get("azp") or claims.get("client_id"),
preferred_username=claims.get("preferred_username"),
roles=roles,
scopes=scopes,
groups=groups,
assurance=_assurance_from_claims(claims),
directory={
"groups_claim_present": "groups" in claims,
"group_overage": _has_group_overage(claims),
},
claims={key: claims[key] for key in sorted(claims) if key not in {"groups"}},
provenance=provenance,
)
def _claims(
self,
token_or_assertion: str | dict[str, Any],
context: dict[str, Any],
) -> tuple[dict[str, Any], dict[str, Any]]:
if isinstance(token_or_assertion, dict):
return dict(token_or_assertion), {"source": "claims", "verified_signature": context.get("verified_signature")}
if not context.get("allow_unverified_jwt_fixture"):
raise EnterprisePolicyError(
"JWT strings require a provider adapter with signature verification "
"or context.allow_unverified_jwt_fixture=true for test fixtures."
)
claims = _decode_unverified_jwt_payload(token_or_assertion)
return claims, {"source": "jwt-fixture", "verified_signature": False}
def _validate_claims(self, claims: dict[str, Any], context: dict[str, Any]) -> None:
missing = sorted(NETKINGDOM_REQUIRED_CLAIMS - set(claims))
if missing:
raise EnterprisePolicyError(f"Missing required NetKingdom claims: {missing}")
if not (_roles_from_claims(claims)):
raise EnterprisePolicyError("Missing required role claim: roles or realm_access.roles")
if not (_scopes_from_claims(claims)):
raise EnterprisePolicyError("Missing required scope claim: scope or scp")
issuer = str(claims["iss"])
if self.issuer and issuer != self.issuer:
raise EnterprisePolicyError(f"Unexpected issuer `{issuer}`; expected `{self.issuer}`")
if self.reject_local_issuers_in_production and context.get("environment") == "production":
if _is_local_development_issuer(issuer):
raise EnterprisePolicyError("Local development issuer is not accepted in production")
expected_audiences = set(self.audiences or _string_list(context.get("audiences")))
if expected_audiences:
actual_audiences = set(_string_list(claims.get("aud")))
if not actual_audiences.intersection(expected_audiences):
raise EnterprisePolicyError(
f"Token audience {sorted(actual_audiences)} does not include one of {sorted(expected_audiences)}"
)
now = int(context.get("now", time.time()))
exp = _int_claim(claims, "exp")
iat = _int_claim(claims, "iat")
if exp + self.clock_skew_seconds < now:
raise EnterprisePolicyError("Token is expired")
if iat - self.clock_skew_seconds > now:
raise EnterprisePolicyError("Token issued-at is in the future")
@dataclass(frozen=True)
class StaticDirectoryGroupResolver:
"""Deterministic group resolver for fixtures and local development."""
groups_by_subject: dict[str, list[str]] = field(default_factory=dict)
source: str = "static"
refreshed_at: str | None = None
def resolve(
self,
request: DirectoryGroupResolutionRequest,
) -> DirectoryGroupResolution:
groups = _unique(
list(request.groups)
+ _string_list(request.claims.get("groups"))
+ self.groups_by_subject.get(request.subject_id, [])
)
return DirectoryGroupResolution(
groups=groups,
source=self.source,
refreshed_at=self.refreshed_at,
overage=_has_group_overage(request.claims),
metadata={
"subject_id": request.subject_id,
"issuer": request.issuer,
"claim_group_count": len(_string_list(request.claims.get("groups"))),
},
)
@dataclass(frozen=True)
class EnterprisePolicyMap:
"""Versioned Markitect-side mapping from IAM claims to policy subjects."""
id: str = "enterprise-policy-map"
issuer: str | None = None
audiences: list[str] = field(default_factory=list)
defaults: dict[str, Any] = field(default_factory=dict)
groups: dict[str, dict[str, Any]] = field(default_factory=dict)
roles: dict[str, dict[str, Any]] = field(default_factory=dict)
scopes: dict[str, dict[str, Any]] = field(default_factory=dict)
trust_zones: dict[str, dict[str, Any]] = field(default_factory=dict)
metadata: dict[str, Any] = field(default_factory=dict)
@classmethod
def from_mapping(cls, raw: dict[str, Any]) -> "EnterprisePolicyMap":
data = raw.get("policy_map") if isinstance(raw.get("policy_map"), dict) else raw
return cls(
id=str(data.get("id", "enterprise-policy-map")),
issuer=data.get("issuer"),
audiences=_string_list(data.get("audiences") or data.get("audience")),
defaults=dict(data.get("defaults") or {}),
groups=_rule_mapping(data.get("groups")),
roles=_rule_mapping(data.get("roles")),
scopes=_rule_mapping(data.get("scopes")),
trust_zones=_rule_mapping(data.get("trust_zones")),
metadata=dict(data.get("metadata") or {}),
)
@classmethod
def from_file(cls, path: str | Path) -> "EnterprisePolicyMap":
data = yaml.safe_load(Path(path).read_text(encoding="utf-8")) or {}
if not isinstance(data, dict):
raise EnterprisePolicyError("Enterprise policy map must contain a mapping.")
return cls.from_mapping(data)
def to_dict(self) -> dict[str, Any]:
return _drop_empty(asdict(self))
@dataclass(frozen=True)
class LocalEnterprisePolicyMapper:
"""Map verified enterprise identity into Markitect's PolicySubject shape."""
policy_map: EnterprisePolicyMap
def map_subject(self, request: EnterprisePolicyMapRequest) -> PolicySubject:
identity = _identity_from_value(request.identity)
if self.policy_map.issuer and identity.issuer != self.policy_map.issuer:
raise EnterprisePolicyError(
f"Identity issuer `{identity.issuer}` does not match policy map issuer `{self.policy_map.issuer}`"
)
if self.policy_map.audiences:
if not set(identity.audience).intersection(self.policy_map.audiences):
raise EnterprisePolicyError(
f"Identity audience {identity.audience} does not match policy map audiences {self.policy_map.audiences}"
)
group_values = _unique(identity.groups + list(request.groups))
allowed_labels = _string_list(self.policy_map.defaults.get("allowed_labels"))
trust_zones = _string_list(self.policy_map.defaults.get("trust_zones"))
allowed_actions = _string_list(self.policy_map.defaults.get("actions") or self.policy_map.defaults.get("allowed_actions"))
attributes: dict[str, Any] = {
"policy_map_id": self.policy_map.id,
"matched_policy_rules": [],
"tenant": request.context.get("tenant"),
}
for rule_type, values, rules in (
("group", group_values, self.policy_map.groups),
("role", identity.roles, self.policy_map.roles),
("scope", identity.scopes, self.policy_map.scopes),
):
for value in values:
rule = rules.get(value)
if not rule:
continue
allowed_labels = _unique(allowed_labels + _string_list(rule.get("allowed_labels") or rule.get("labels")))
trust_zones = _unique(trust_zones + _string_list(rule.get("trust_zones") or rule.get("zones")))
allowed_actions = _unique(allowed_actions + _string_list(rule.get("actions") or rule.get("allowed_actions")))
attributes["matched_policy_rules"].append(f"{rule_type}:{value}")
attributes.update(dict(rule.get("attributes") or {}))
trust_zones = _unique(trust_zones + self._allowed_trust_zones(group_values, identity))
attributes["groups"] = group_values
attributes["scopes"] = identity.scopes
attributes["assurance"] = identity.assurance
return identity.to_policy_subject(
allowed_labels=allowed_labels,
trust_zones=trust_zones,
allowed_actions=allowed_actions,
attributes={key: value for key, value in attributes.items() if value not in (None, [], {})},
)
def _allowed_trust_zones(
self,
groups: list[str],
identity: EnterpriseIdentity,
) -> list[str]:
allowed: list[str] = []
group_set = set(groups)
role_set = set(identity.roles)
scope_set = set(identity.scopes)
for zone, rule in self.policy_map.trust_zones.items():
required_groups = set(_string_list(rule.get("required_groups")))
required_roles = set(_string_list(rule.get("required_roles")))
required_scopes = set(_string_list(rule.get("required_scopes")))
if required_groups and not required_groups.issubset(group_set):
continue
if required_roles and not required_roles.issubset(role_set):
continue
if required_scopes and not required_scopes.issubset(scope_set):
continue
if required_groups or required_roles or required_scopes:
allowed.append(zone)
return allowed
@dataclass(frozen=True)
class FlexAuthResource:
"""Resource record Markitect can register with flex-auth."""
id: str
type: str
path: str | None = None
parent: str | None = None
labels: list[str] = field(default_factory=list)
trust_zone: str | None = None
owner: str | None = None
attributes: dict[str, Any] = field(default_factory=dict)
@classmethod
def from_mapping(cls, raw: dict[str, Any]) -> "FlexAuthResource":
return cls(
id=str(raw["id"]),
type=str(raw.get("type", "document")),
path=raw.get("path"),
parent=raw.get("parent"),
labels=_string_list(raw.get("labels") or raw.get("label")),
trust_zone=raw.get("trust_zone"),
owner=raw.get("owner"),
attributes=dict(raw.get("attributes") or {}),
)
def to_dict(self) -> dict[str, Any]:
return _drop_empty(asdict(self))
@dataclass(frozen=True)
class FlexAuthResourceManifest:
"""Manifest of Markitect resources intended for flex-auth registration."""
id: str
system: str = "markitect-tool"
resources: list[FlexAuthResource] = field(default_factory=list)
actions: list[str] = field(default_factory=list)
metadata: dict[str, Any] = field(default_factory=dict)
@classmethod
def from_mapping(cls, raw: dict[str, Any]) -> "FlexAuthResourceManifest":
data = raw.get("manifest") if isinstance(raw.get("manifest"), dict) else raw
resources = [
FlexAuthResource.from_mapping(item)
for item in data.get("resources", [])
if isinstance(item, dict)
]
return cls(
id=str(data.get("id", "markitect-resources")),
system=str(data.get("system", "markitect-tool")),
resources=resources,
actions=_string_list(data.get("actions")),
metadata=dict(data.get("metadata") or {}),
)
@classmethod
def from_file(cls, path: str | Path) -> "FlexAuthResourceManifest":
data = yaml.safe_load(Path(path).read_text(encoding="utf-8")) or {}
if not isinstance(data, dict):
raise EnterprisePolicyError("Flex-auth resource manifest must contain a mapping.")
return cls.from_mapping(data)
def to_dict(self) -> dict[str, Any]:
data = asdict(self)
data["resources"] = [resource.to_dict() for resource in self.resources]
return _drop_empty(data)
@dataclass
class LocalDecisionLogStore:
"""Local JSONL decision sink for development and tests."""
path: Path | None = None
_entries: dict[str, dict[str, Any]] = field(default_factory=dict, init=False)
def record(
self,
decision: PolicyDecision,
*,
context: dict[str, Any] | None = None,
) -> str:
payload = {
"decision": decision.to_dict(),
"context": _redact_sensitive_context(context or {}),
"recorded_by": "markitect.local-decision-log",
}
audit_id = "audit:" + hashlib.sha256(
json.dumps(payload, sort_keys=True, ensure_ascii=False, default=str).encode("utf-8")
).hexdigest()
payload["audit_id"] = audit_id
self._entries[decision.decision_id] = payload
if self.path:
self.path.parent.mkdir(parents=True, exist_ok=True)
with self.path.open("a", encoding="utf-8") as handle:
handle.write(json.dumps(payload, sort_keys=True, ensure_ascii=False, default=str) + "\n")
return audit_id
def get(self, decision_id: str) -> dict[str, Any] | None:
if decision_id in self._entries:
return self._entries[decision_id]
if not self.path or not self.path.exists():
return None
with self.path.open(encoding="utf-8") as handle:
for line in handle:
entry = json.loads(line)
if entry.get("decision", {}).get("decision_id") == decision_id:
self._entries[decision_id] = entry
return entry
return None
def load_enterprise_identity_file(
path: str | Path,
*,
issuer: str | None = None,
audiences: list[str] | None = None,
environment: str | None = None,
) -> EnterpriseIdentity:
"""Load and validate deterministic NetKingdom/key-cape-style claims."""
claims = yaml.safe_load(Path(path).read_text(encoding="utf-8")) or {}
if not isinstance(claims, dict):
raise EnterprisePolicyError("Enterprise identity claims file must contain a mapping.")
adapter = NetKingdomIdentityClaimsAdapter(issuer=issuer, audiences=list(audiences or []))
return adapter.verify(claims, context={"environment": environment} if environment else {})
def load_enterprise_policy_subject(
claims_file: str | Path,
policy_map_file: str | Path,
*,
extra_groups: list[str] | None = None,
environment: str | None = None,
) -> PolicySubject:
"""Load claims and policy map files and return a gateway-ready subject."""
policy_map = EnterprisePolicyMap.from_file(policy_map_file)
identity = load_enterprise_identity_file(
claims_file,
issuer=policy_map.issuer,
audiences=policy_map.audiences,
environment=environment,
)
group_resolver = StaticDirectoryGroupResolver()
group_result = group_resolver.resolve(
DirectoryGroupResolutionRequest(
subject_id=identity.canonical_id,
issuer=identity.issuer,
groups=list(extra_groups or []),
claims=identity.claims | {"groups": identity.groups},
)
)
mapper = LocalEnterprisePolicyMapper(policy_map)
return mapper.map_subject(
EnterprisePolicyMapRequest(
identity=identity,
policy_map=policy_map.to_dict(),
groups=group_result.groups,
context={"environment": environment},
)
)
def _roles_from_claims(claims: dict[str, Any]) -> list[str]:
roles = _string_list(claims.get("roles"))
realm_access = claims.get("realm_access")
if isinstance(realm_access, dict):
roles += _string_list(realm_access.get("roles"))
resource_access = claims.get("resource_access")
if isinstance(resource_access, dict):
for client in resource_access.values():
if isinstance(client, dict):
roles += _string_list(client.get("roles"))
return _unique(roles)
def _scopes_from_claims(claims: dict[str, Any]) -> list[str]:
return _unique(_string_list(claims.get("scope")) + _string_list(claims.get("scp")))
def _assurance_from_claims(claims: dict[str, Any]) -> dict[str, Any]:
amr = _string_list(claims.get("amr"))
return {
"acr": claims.get("acr"),
"amr": amr,
"mfa": bool(claims.get("mfa") or {"otp", "mfa", "hwk"}.intersection(amr)),
}
def _principal_type(claims: dict[str, Any], roles: list[str]) -> str:
if claims.get("client_id") and "service" in roles:
return "service"
if claims.get("azp", "").startswith("svc-") or "service" in roles:
return "service"
return "human"
def _has_group_overage(claims: dict[str, Any]) -> bool:
return bool(claims.get("hasgroups") or claims.get("_claim_names", {}).get("groups"))
def _decode_unverified_jwt_payload(token: str) -> dict[str, Any]:
parts = token.split(".")
if len(parts) != 3:
raise EnterprisePolicyError("JWT fixture must have three segments.")
payload = parts[1] + "=" * (-len(parts[1]) % 4)
try:
decoded = base64.urlsafe_b64decode(payload.encode("ascii"))
claims = json.loads(decoded.decode("utf-8"))
except (ValueError, json.JSONDecodeError) as exc:
raise EnterprisePolicyError("JWT fixture payload is not valid JSON.") from exc
if not isinstance(claims, dict):
raise EnterprisePolicyError("JWT fixture payload must contain claims mapping.")
return claims
def _identity_from_value(value: EnterpriseIdentity | dict[str, Any]) -> EnterpriseIdentity:
if isinstance(value, EnterpriseIdentity):
return value
return EnterpriseIdentity(
issuer=str(value["issuer"]),
subject=str(value["subject"]),
identity_scheme=str(value.get("identity_scheme", "oidc")),
principal_type=str(value.get("principal_type", "human")),
audience=_string_list(value.get("audience")),
authorized_party=value.get("authorized_party"),
preferred_username=value.get("preferred_username"),
roles=_string_list(value.get("roles")),
scopes=_string_list(value.get("scopes")),
groups=_string_list(value.get("groups")),
assurance=dict(value.get("assurance") or {}),
directory=dict(value.get("directory") or {}),
claims=dict(value.get("claims") or {}),
provenance=dict(value.get("provenance") or {}),
)
def _int_claim(claims: dict[str, Any], key: str) -> int:
try:
return int(claims[key])
except (TypeError, ValueError) as exc:
raise EnterprisePolicyError(f"Claim `{key}` must be an integer timestamp.") from exc
def _rule_mapping(value: Any) -> dict[str, dict[str, Any]]:
if not isinstance(value, dict):
return {}
return {
str(key): dict(raw or {})
for key, raw in value.items()
if isinstance(raw, dict)
}
def _is_local_development_issuer(issuer: str) -> bool:
return any(marker in issuer for marker in ("localhost", "127.0.0.1", ".local", "dev.local"))
def _redact_sensitive_context(context: dict[str, Any]) -> dict[str, Any]:
redacted = dict(context)
for key in list(redacted):
if key.lower() in {"token", "access_token", "refresh_token", "assertion"}:
redacted[key] = "<redacted>"
return redacted
def _string_list(value: Any) -> list[str]:
if value is None:
return []
if isinstance(value, str):
return [item for item in value.split() if item]
if isinstance(value, list | tuple | set):
return [str(item) for item in value if item not in (None, "")]
return [str(value)]
def _unique(values: list[str]) -> list[str]:
seen: set[str] = set()
result: list[str] = []
for value in values:
normalized = str(value).strip()
key = normalized.lower()
if normalized and key not in seen:
result.append(normalized)
seen.add(key)
return result
def _drop_empty(data: dict[str, Any]) -> dict[str, Any]:
return {
key: value
for key, value in data.items()
if value not in (None, [], {}, "")
}

View File

@@ -94,7 +94,12 @@ def test_builtin_policy_descriptor_exposes_cli_and_adapter_boundary():
assert {capability.id for capability in descriptor.capabilities} >= {
"policy",
"policy_filter",
"identity_claims",
"resource_manifest",
"decision_log",
}
assert "mkt policy check" in descriptor.cli["commands"]
assert "mkt policy subject" in descriptor.cli["commands"]
assert "mkt policy resource-manifest" in descriptor.cli["commands"]
assert "IdentityClaimsAdapter" in descriptor.metadata["external_adapters"]
assert "RelationshipPolicyAdapter" in descriptor.metadata["external_adapters"]

View File

@@ -7,11 +7,19 @@ from markitect_tool.cli import main
from markitect_tool.policy import (
DirectoryGroupResolution,
DirectoryGroupResolutionRequest,
EnterprisePolicyError,
EnterprisePolicyMap,
EnterpriseIdentity,
EnterprisePolicyMapRequest,
FlexAuthResourceManifest,
LocalDecisionLogStore,
LocalEnterprisePolicyMapper,
LocalLabelPolicy,
LocalLabelPolicyGateway,
NetKingdomIdentityClaimsAdapter,
StaticDirectoryGroupResolver,
)
from markitect_tool.policy.models import PolicyDecision
POLICY_TEXT = """id: example-policy
@@ -247,6 +255,203 @@ def test_enterprise_policy_adapter_requests_serialize_cleanly():
assert map_request.to_dict()["identity"]["canonical_id"].endswith("#user-123")
def test_netkingdom_claims_adapter_validates_required_claims_and_audience():
adapter = NetKingdomIdentityClaimsAdapter(
issuer="https://sso.example.test/realms/netkingdom",
audiences=["markitect-tool"],
)
identity = adapter.verify(_claims())
assert identity.canonical_id == "oidc:https://sso.example.test/realms/netkingdom#user-123"
assert identity.roles == ["viewer"]
assert identity.scopes == ["openid", "profile", "markitect:read"]
assert identity.groups == ["/markitect/readers"]
assert identity.assurance["mfa"] is True
def test_netkingdom_claims_adapter_rejects_local_issuer_in_production():
adapter = NetKingdomIdentityClaimsAdapter(audiences=["markitect-tool"])
claims = _claims() | {"iss": "http://localhost:8080/realms/dev"}
try:
adapter.verify(claims, context={"environment": "production"})
except EnterprisePolicyError as exc:
assert "Local development issuer" in str(exc)
else:
raise AssertionError("expected local production issuer rejection")
def test_static_group_resolver_reports_group_overage_and_freshness():
request = DirectoryGroupResolutionRequest(
subject_id="oidc:https://sso.example.test/realms/netkingdom#user-123",
issuer="https://sso.example.test/realms/netkingdom",
claims={"hasgroups": True},
)
resolver = StaticDirectoryGroupResolver(
groups_by_subject={request.subject_id: ["/markitect/readers"]},
refreshed_at="2026-05-04T10:00:00Z",
)
result = resolver.resolve(request)
assert result.groups == ["/markitect/readers"]
assert result.overage is True
assert result.refreshed_at == "2026-05-04T10:00:00Z"
def test_local_enterprise_policy_mapper_maps_groups_roles_and_scopes():
identity = NetKingdomIdentityClaimsAdapter(
issuer="https://sso.example.test/realms/netkingdom",
audiences=["markitect-tool"],
).verify(_claims())
policy_map = EnterprisePolicyMap.from_mapping(_enterprise_policy_map())
subject = LocalEnterprisePolicyMapper(policy_map).map_subject(
EnterprisePolicyMapRequest(identity=identity)
)
assert subject.allowed_labels == ["public", "internal"]
assert subject.trust_zones == ["public", "internal"]
assert subject.allowed_actions == ["read", "query", "search"]
assert subject.attributes["matched_policy_rules"] == [
"group:/markitect/readers",
"role:viewer",
"scope:markitect:read",
]
def test_flex_auth_resource_manifest_serializes_resources():
manifest = FlexAuthResourceManifest.from_mapping(
{
"id": "markitect-example",
"system": "markitect-tool",
"actions": ["read", "query"],
"resources": [
{
"id": "document:public-note",
"type": "document",
"path": "examples/policy/public-note.md",
"labels": ["public"],
"trust_zone": "public",
}
],
}
)
data = manifest.to_dict()
assert data["id"] == "markitect-example"
assert data["resources"][0]["id"] == "document:public-note"
def test_local_decision_log_store_redacts_token_context(tmp_path: Path):
store = LocalDecisionLogStore(tmp_path / "decisions.jsonl")
decision = PolicyDecision(
subject="subject-1",
action="query",
object_id="document:internal",
effect="deny",
reason="missing label",
)
audit_id = store.record(decision, context={"token": "secret", "policy_version": "v1"})
entry = store.get(decision.decision_id)
assert audit_id.startswith("audit:")
assert entry is not None
assert entry["context"]["token"] == "<redacted>"
assert entry["context"]["policy_version"] == "v1"
def test_mkt_policy_subject_maps_claims_file_to_subject(tmp_path: Path):
claims_file = tmp_path / "claims.yaml"
map_file = tmp_path / "policy-map.yaml"
claims_file.write_text(yaml_dump(_claims()), encoding="utf-8")
map_file.write_text(yaml_dump(_enterprise_policy_map()), encoding="utf-8")
result = CliRunner().invoke(
main,
[
"policy",
"subject",
str(claims_file),
"--policy-map",
str(map_file),
"--format",
"json",
],
)
data = json.loads(result.output)
assert result.exit_code == 0
assert data["subject"]["allowed_labels"] == ["public", "internal"]
assert data["subject"]["allowed_actions"] == ["read", "query", "search"]
def test_mkt_policy_resource_manifest_inspects_manifest(tmp_path: Path):
manifest_file = tmp_path / "resources.yaml"
manifest_file.write_text(
yaml_dump(
{
"id": "manifest-1",
"system": "markitect-tool",
"actions": ["read"],
"resources": [{"id": "document:one", "type": "document"}],
}
),
encoding="utf-8",
)
result = CliRunner().invoke(
main,
["policy", "resource-manifest", str(manifest_file), "--format", "json"],
)
data = json.loads(result.output)
assert result.exit_code == 0
assert data["manifest"]["resources"][0]["id"] == "document:one"
def yaml_dump(value: dict) -> str:
import yaml
return yaml.safe_dump(value, sort_keys=False)
def _claims() -> dict:
return {
"iss": "https://sso.example.test/realms/netkingdom",
"sub": "user-123",
"aud": ["markitect-tool"],
"exp": 4102444800,
"iat": 1767225600,
"preferred_username": "ada",
"scope": "openid profile markitect:read",
"realm_access": {"roles": ["viewer"]},
"groups": ["/markitect/readers"],
"amr": ["pwd", "otp"],
}
def _enterprise_policy_map() -> dict:
return {
"id": "markitect-enterprise-policy-map",
"issuer": "https://sso.example.test/realms/netkingdom",
"audiences": ["markitect-tool"],
"defaults": {"allowed_labels": ["public"], "trust_zones": ["public"]},
"groups": {
"/markitect/readers": {
"allowed_labels": ["internal"],
"trust_zones": ["internal"],
"actions": ["read", "query", "search"],
}
},
"roles": {"viewer": {"actions": ["read", "query", "search"]}},
"scopes": {"markitect:read": {"actions": ["read", "query", "search"]}},
"trust_zones": {"internal": {"required_groups": ["/markitect/readers"]}},
}
def _policy_mapping() -> dict:
return {
"id": "example-policy",

View File

@@ -83,6 +83,39 @@ def test_load_workflow_file_preserves_standard_sections(tmp_path: Path):
assert plan.steps[0]["id"] == "render"
def test_load_workflow_file_preserves_policy_identity_permissions(tmp_path: Path):
workflow = tmp_path / "policy.workflow.md"
workflow.write_text(
"""# Policy Workflow
```yaml workflow
metadata:
id: policy-aware
inputs:
static:
value: ok
permissions:
policy:
subject_from_token: examples/policy/netkingdom-claims.yaml
policy_map: examples/policy/enterprise-policy-map.yaml
required_assurance:
mfa: true
emergency_justification: INC-123
decision_log: .markitect/policy-decisions.jsonl
flex_auth:
resource_manifest: examples/policy/flex-auth-resource-manifest.yaml
```
""",
encoding="utf-8",
)
plan = load_workflow_file(workflow)
assert plan.permissions["policy"]["subject_from_token"] == "examples/policy/netkingdom-claims.yaml"
assert plan.permissions["policy"]["required_assurance"]["mfa"] is True
assert plan.permissions["flex_auth"]["resource_manifest"].endswith("flex-auth-resource-manifest.yaml")
def test_workflow_runner_collects_sources_and_renders_output(tmp_path: Path):
workflow = _write_workflow_fixture(tmp_path)
plan = load_workflow_file(workflow)

View File

@@ -3,10 +3,10 @@ id: MKTT-WP-0014
type: workplan
title: "Enterprise IAM Access-Control Integration"
domain: markitect
status: todo
status: done
owner: markitect-tool
topic_slug: markitect
planning_priority: P2
planning_priority: complete
planning_order: 82
depends_on_workplans:
- MKTT-WP-0009
@@ -34,6 +34,24 @@ results. NetKingdom/key-cape-compatible SSO should supply identity claims.
External policy engines and enterprise directories should attach through
provider-neutral adapters.
## Implementation Summary
Implemented the Markitect-side enterprise integration layer without importing
central authorization administration into this repo:
- `NetKingdomIdentityClaimsAdapter` for deterministic IAM-profile claim
validation and `EnterpriseIdentity` normalization.
- `EnterprisePolicyMap` and `LocalEnterprisePolicyMapper` for mapping groups,
roles, and scopes into `PolicySubject` labels, trust zones, and actions.
- `StaticDirectoryGroupResolver` for local group freshness/overage fixtures.
- `FlexAuthResourceManifest` for Markitect resource registration manifests.
- `LocalDecisionLogStore` for JSONL development/test decision logs.
- `mkt policy subject` and `mkt policy resource-manifest`.
- Examples for claims, policy maps, flex-auth resource manifests, external PDP
request shapes, and policy-aware workflows.
- Documentation updates for access-control, enterprise IAM, and workflow
permission declarations.
## Background
`MKTT-WP-0009` implemented local labels, trust zones, path rules, query/search
@@ -87,7 +105,7 @@ directory groups -> canonical roles/scopes/trust labels -> PolicySubject
```task
id: MKTT-WP-0014-T001
status: todo
status: done
priority: high
state_hub_task_id: "1894c50f-95c3-4e1a-bd4f-388f7624ebd7"
```
@@ -110,20 +128,21 @@ Output: schema, examples, diagnostics, and tests.
```task
id: MKTT-WP-0014-T002
status: todo
status: done
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.
Implement an optional `IdentityClaimsAdapter` for
NetKingdom/key-cape-compatible claims.
It must validate:
- issuer
- audience
- expiry and issued-at
- signature through JWKS
- signature verification provenance for trusted claims or explicit local JWT
fixtures; live JWKS verification remains provider-adapter/flex-auth scope
- authorized party/client id where required
- MFA/assurance claims for privileged actions
@@ -133,7 +152,7 @@ Output: adapter, fixtures, negative tests, and clear diagnostics.
```task
id: MKTT-WP-0014-T003
status: todo
status: done
priority: high
state_hub_task_id: "6861d4bc-1bb8-440d-bb9e-33e20c7feb55"
```
@@ -150,7 +169,7 @@ administration remains flex-auth scope.
```task
id: MKTT-WP-0014-T004
status: todo
status: done
priority: medium
state_hub_task_id: "56d6bad6-d706-47b3-b321-1f0e870ecc0d"
```
@@ -165,7 +184,7 @@ Output: resolver contract, freshness metadata, overage handling, and tests.
```task
id: MKTT-WP-0014-T005
status: todo
status: done
priority: high
state_hub_task_id: "f212662c-4ffc-4cac-ace2-a43777f4960c"
```
@@ -184,7 +203,7 @@ Output: storage adapter, CLI inspection path, and tests.
```task
id: MKTT-WP-0014-T006
status: todo
status: done
priority: medium
state_hub_task_id: "573a198f-df0b-470a-b11c-9ac839c0845e"
```
@@ -202,7 +221,7 @@ external PDP administration belongs in flex-auth.
```task
id: MKTT-WP-0014-T007
status: todo
status: done
priority: high
state_hub_task_id: "c4650304-0e2b-49c5-8569-e69907c08ccc"
```
@@ -224,7 +243,7 @@ Output: workflow/context integration design, examples, and tests.
```task
id: MKTT-WP-0014-T008
status: todo
status: done
priority: medium
state_hub_task_id: "0486e0c2-2cb9-4902-9a09-9ec729e9e79f"
```