generated from coulomb/repo-seed
Finalize user-engine contracts and operability
This commit is contained in:
@@ -6,6 +6,7 @@ Headless multi-application, multi-tenant user management engine.
|
|||||||
make test
|
make test
|
||||||
```
|
```
|
||||||
|
|
||||||
See `docs/development.md`, `docs/configuration.md`, `docs/examples.md`, and
|
See `docs/development.md`, `docs/configuration.md`, `docs/contracts.md`,
|
||||||
`docs/scenarios.md` for implementation boundaries, local usage examples, and
|
`docs/examples.md`, `docs/scenarios.md`, `docs/operability.md`,
|
||||||
scenario coverage.
|
`docs/release.md`, `docs/ui-contracts.md`, and `docs/final-assessment.md`
|
||||||
|
for implementation boundaries, contracts, examples, and release readiness.
|
||||||
|
|||||||
49
docs/contracts.md
Normal file
49
docs/contracts.md
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
# Public Contracts
|
||||||
|
|
||||||
|
## Headless Service Surface
|
||||||
|
|
||||||
|
`UserEngineService` is the stable in-process API for the current MVP. Future
|
||||||
|
HTTP or RPC adapters should preserve these operation names:
|
||||||
|
|
||||||
|
- `health`, `readiness`, `operability_snapshot`, `outbox_diagnostics`
|
||||||
|
- `me`, `create_user`, `set_account_status`, `link_identity`
|
||||||
|
- `resolve_tenant_context`, `set_tenant_account_status`, `add_membership`,
|
||||||
|
`tenant_diagnostics`
|
||||||
|
- `register_application`, `publish_catalog`
|
||||||
|
- `set_profile_value`, `effective_profile`, `projection`
|
||||||
|
- `audit_records`, `outbox_events`
|
||||||
|
|
||||||
|
## Error Taxonomy
|
||||||
|
|
||||||
|
- `ValidationError`: caller supplied an invalid shape, state transition, or
|
||||||
|
catalog/profile value.
|
||||||
|
- `AuthorizationDenied`: the authorization port or tenant boundary denied the
|
||||||
|
operation.
|
||||||
|
- `NotFoundError`: a requested user, account, or active attribute is missing.
|
||||||
|
- `ConflictError`: uniqueness or ownership would be violated.
|
||||||
|
|
||||||
|
## Catalog Contract
|
||||||
|
|
||||||
|
Catalogs are active by namespace and owning application. Attribute keys must
|
||||||
|
use the namespace prefix. Active namespace ownership cannot move to another
|
||||||
|
application. Catalog updates cannot move versions backwards or downgrade
|
||||||
|
attribute sensitivity.
|
||||||
|
|
||||||
|
## Projection Contract
|
||||||
|
|
||||||
|
Application runtime, agent-context, and claims-enrichment projections require
|
||||||
|
an `application_id` and are filtered to that application's active catalogs.
|
||||||
|
Sensitive and secret values are redacted outside admin, audit, and
|
||||||
|
self-service projections.
|
||||||
|
|
||||||
|
## Audit And Event Contract
|
||||||
|
|
||||||
|
Every mutating service operation appends an audit record and outbox event with
|
||||||
|
the same correlation id and resolved tenant. Authorization denials are audited
|
||||||
|
without emitting outbox events.
|
||||||
|
|
||||||
|
## Migration Contract
|
||||||
|
|
||||||
|
The isolated store exposes `SCHEMA_VERSION = 0001_initial` and a `migrate`
|
||||||
|
hook. Database-backed stores must expose equivalent readiness semantics before
|
||||||
|
they are accepted by platform adapters.
|
||||||
37
docs/final-assessment.md
Normal file
37
docs/final-assessment.md
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
# Implementation Assessment
|
||||||
|
|
||||||
|
## Implemented
|
||||||
|
|
||||||
|
- Headless service API for users, accounts, identity links, applications,
|
||||||
|
catalogs, profiles, projections, audit records, and outbox events.
|
||||||
|
- Tenant context enforcement, tenant account state, memberships, tenant
|
||||||
|
profile precedence, tenant diagnostics, and cross-tenant denial.
|
||||||
|
- Multi-application catalog ownership, namespace collision protection,
|
||||||
|
semantic version checks, sensitivity downgrade prevention, app-filtered
|
||||||
|
projections, and claims-enrichment projection caching.
|
||||||
|
- Scenario fixtures and conformance-style tests for positive and negative
|
||||||
|
standalone, tenant, multi-app, redaction, audit, event, and cache paths.
|
||||||
|
|
||||||
|
## Boundary Verification
|
||||||
|
|
||||||
|
User-engine does not issue tokens, verify MFA, store credentials, act as the
|
||||||
|
policy decision point, own deployment, or provide a UI. It consumes verified
|
||||||
|
claims through an identity adapter, asks authorization through a port, emits
|
||||||
|
audit/outbox records, and exposes backend contracts for future UIs.
|
||||||
|
|
||||||
|
## Accepted Deviations
|
||||||
|
|
||||||
|
- The first persistence adapter is in-memory. It carries schema and migration
|
||||||
|
semantics but is not durable.
|
||||||
|
- The first API surface is in-process Python. HTTP/RPC transport adapters are
|
||||||
|
still future work.
|
||||||
|
- Metrics and cache diagnostics are local snapshots, not platform telemetry.
|
||||||
|
|
||||||
|
## Follow-Up Work
|
||||||
|
|
||||||
|
- Add a durable database adapter and migration tests.
|
||||||
|
- Add transport adapters with request/response contract tests.
|
||||||
|
- Add platform authorization, audit sink, secret provider, and outbox drain
|
||||||
|
adapters.
|
||||||
|
- Add release automation for SBOM, package build, static checks, and
|
||||||
|
deployment handoff.
|
||||||
39
docs/operability.md
Normal file
39
docs/operability.md
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
# Operability
|
||||||
|
|
||||||
|
## Diagnostics
|
||||||
|
|
||||||
|
Use `readiness()` for dependency checks and `operability_snapshot()` for
|
||||||
|
runtime counters and invariant checks. The snapshot currently reports store
|
||||||
|
readiness, audit correlation completeness, outbox diagnostic availability, and
|
||||||
|
counts for users, accounts, tenant accounts, memberships, applications,
|
||||||
|
catalogs, profile values, audit records, and pending outbox events.
|
||||||
|
|
||||||
|
## Structured Logs
|
||||||
|
|
||||||
|
Use `structured_log_context(correlation_id=..., tenant=..., actor=...)` as the
|
||||||
|
base log envelope. Adapters should add transport details around that envelope
|
||||||
|
without dropping correlation id or tenant.
|
||||||
|
|
||||||
|
## Outbox Drain
|
||||||
|
|
||||||
|
`outbox_diagnostics()` reports pending event count, event type counts, and the
|
||||||
|
oldest pending correlation id. A real outbox drain adapter should publish
|
||||||
|
events idempotently by `event_id`, retain `correlation_id`, and only mark
|
||||||
|
delivery after the sink acknowledges receipt.
|
||||||
|
|
||||||
|
## Cache Status
|
||||||
|
|
||||||
|
`ClaimsEnrichmentProjectionCache.status()` reports entry count and cached
|
||||||
|
tenant, application, and user keys. Token issuers must invalidate affected
|
||||||
|
users after profile, membership, or catalog changes before minting enriched
|
||||||
|
claims.
|
||||||
|
|
||||||
|
## Runbook Checks
|
||||||
|
|
||||||
|
1. Run `make test-conformance`.
|
||||||
|
2. Confirm `readiness().ready` is true.
|
||||||
|
3. Confirm `operability_snapshot().issues` is empty.
|
||||||
|
4. Confirm pending outbox events are either drained or expected for the local
|
||||||
|
environment.
|
||||||
|
5. Confirm production identity adapters reject local, expired, and
|
||||||
|
missing-tenant claims.
|
||||||
37
docs/release.md
Normal file
37
docs/release.md
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
# Release And Compatibility
|
||||||
|
|
||||||
|
## Version
|
||||||
|
|
||||||
|
The current implementation is `0.1.0`: a headless MVP with standard-library
|
||||||
|
runtime behavior, local adapters, and conformance-style tests. Until `1.0.0`,
|
||||||
|
schema and service contracts may evolve, but changes should include migration
|
||||||
|
notes and scenario test updates.
|
||||||
|
|
||||||
|
## Packaging
|
||||||
|
|
||||||
|
The package uses a `src/` layout with setuptools metadata in `pyproject.toml`.
|
||||||
|
Build artifacts should be created from clean commits after `make test`,
|
||||||
|
`make test-scenarios`, `make test-integration`, and `make test-conformance`
|
||||||
|
pass.
|
||||||
|
|
||||||
|
## Security And SBOM
|
||||||
|
|
||||||
|
The current runtime has no third-party dependencies. Release automation should
|
||||||
|
still generate an SBOM for the Python package and run static/security scans
|
||||||
|
before publishing or deploying a platform adapter.
|
||||||
|
|
||||||
|
## Migration Policy
|
||||||
|
|
||||||
|
Persistence adapters must expose a schema version, readiness check, and
|
||||||
|
forward migration hook. Catalog updates must not move versions backwards or
|
||||||
|
downgrade sensitivity.
|
||||||
|
|
||||||
|
## Compatibility Guarantees
|
||||||
|
|
||||||
|
- Identity, authorization, secret, deployment, and UI ownership remain outside
|
||||||
|
user-engine.
|
||||||
|
- Application runtime projections require explicit application ids.
|
||||||
|
- Tenant-scoped operations require explicit tenant context once exposed over a
|
||||||
|
transport adapter.
|
||||||
|
- Outbox and audit correlation ids are part of the public integration
|
||||||
|
contract.
|
||||||
32
docs/ui-contracts.md
Normal file
32
docs/ui-contracts.md
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
# UI Handoff Contracts
|
||||||
|
|
||||||
|
Future self-service and scope-admin UIs should consume user-engine through a
|
||||||
|
transport adapter that preserves the service shapes below.
|
||||||
|
|
||||||
|
## Self-Service Account UI
|
||||||
|
|
||||||
|
Required backend operations:
|
||||||
|
|
||||||
|
- `me` to resolve the current actor, user, account, and identity links.
|
||||||
|
- `effective_profile` with the actor tenant and optional application id.
|
||||||
|
- `projection` with `SELF_SERVICE` for editable user-visible fields.
|
||||||
|
- `set_profile_value` for fields whose catalog mutability includes `USER`.
|
||||||
|
- `audit_records` or a filtered audit transport for recent user-visible
|
||||||
|
account activity.
|
||||||
|
|
||||||
|
## Scope Admin UI
|
||||||
|
|
||||||
|
Required backend operations:
|
||||||
|
|
||||||
|
- `resolve_tenant_context` before all tenant-scoped screens.
|
||||||
|
- `set_tenant_account_status` for in-scope account state.
|
||||||
|
- `add_membership` for tenant/team membership changes.
|
||||||
|
- `projection` with `ADMIN` or a future admin transport projection.
|
||||||
|
- `tenant_diagnostics` for onboarding and support readiness checks.
|
||||||
|
|
||||||
|
## Fixtures
|
||||||
|
|
||||||
|
Use `user_engine.testing.scenarios` for human, tenant admin, platform
|
||||||
|
operator, delegated agent, invalid, expired, local issuer, and missing-tenant
|
||||||
|
fixtures. UIs should keep fixtures at the transport boundary and avoid
|
||||||
|
embedding identity-provider logic.
|
||||||
@@ -1,9 +1,16 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "user-engine"
|
name = "user-engine"
|
||||||
version = "0.0.0"
|
version = "0.1.0"
|
||||||
description = "Headless user-domain and profile engine."
|
description = "Headless user-domain and profile engine."
|
||||||
requires-python = ">=3.12"
|
requires-python = ">=3.12"
|
||||||
|
|
||||||
|
[build-system]
|
||||||
|
requires = ["setuptools>=69"]
|
||||||
|
build-backend = "setuptools.build_meta"
|
||||||
|
|
||||||
|
[tool.setuptools.packages.find]
|
||||||
|
where = ["src"]
|
||||||
|
|
||||||
[tool.user-engine]
|
[tool.user-engine]
|
||||||
package = "user_engine"
|
package = "user_engine"
|
||||||
workplan = "USER-WP-0001"
|
workplan = "USER-WP-0006"
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
"""Headless user-domain and profile engine."""
|
"""Headless user-domain and profile engine."""
|
||||||
|
|
||||||
from user_engine.projections import ClaimsEnrichmentProjectionCache
|
from user_engine.projections import CacheStatus, ClaimsEnrichmentProjectionCache
|
||||||
from user_engine.service import PLATFORM_TENANT, UserEngineService
|
from user_engine.service import PLATFORM_TENANT, UserEngineService
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
|
"CacheStatus",
|
||||||
"ClaimsEnrichmentProjectionCache",
|
"ClaimsEnrichmentProjectionCache",
|
||||||
"PLATFORM_TENANT",
|
"PLATFORM_TENANT",
|
||||||
"UserEngineService",
|
"UserEngineService",
|
||||||
"__version__",
|
"__version__",
|
||||||
]
|
]
|
||||||
|
|
||||||
__version__ = "0.0.0"
|
__version__ = "0.1.0"
|
||||||
|
|||||||
@@ -8,6 +8,14 @@ from user_engine.domain import Actor, ProjectionType
|
|||||||
from user_engine.service import Projection, UserEngineService
|
from user_engine.service import Projection, UserEngineService
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class CacheStatus:
|
||||||
|
entries: int
|
||||||
|
tenants: tuple[str, ...]
|
||||||
|
applications: tuple[str, ...]
|
||||||
|
users: tuple[str, ...]
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class ClaimsEnrichmentProjectionCache:
|
class ClaimsEnrichmentProjectionCache:
|
||||||
"""Cache claims-enrichment projections for external token adapters.
|
"""Cache claims-enrichment projections for external token adapters.
|
||||||
@@ -47,3 +55,11 @@ class ClaimsEnrichmentProjectionCache:
|
|||||||
for key in tuple(self._cache):
|
for key in tuple(self._cache):
|
||||||
if key[2] == user_id:
|
if key[2] == user_id:
|
||||||
del self._cache[key]
|
del self._cache[key]
|
||||||
|
|
||||||
|
def status(self) -> CacheStatus:
|
||||||
|
return CacheStatus(
|
||||||
|
entries=len(self._cache),
|
||||||
|
tenants=tuple(sorted({key[0] for key in self._cache})),
|
||||||
|
applications=tuple(sorted({key[1] for key in self._cache})),
|
||||||
|
users=tuple(sorted({key[2] for key in self._cache})),
|
||||||
|
)
|
||||||
|
|||||||
@@ -97,6 +97,21 @@ class TenantDiagnostics:
|
|||||||
memberships: tuple[Membership, ...]
|
memberships: tuple[Membership, ...]
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class OutboxDiagnostics:
|
||||||
|
pending_count: int
|
||||||
|
event_types: Mapping[str, int]
|
||||||
|
oldest_correlation_id: str | None
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class OperabilitySnapshot:
|
||||||
|
ready: bool
|
||||||
|
checks: Mapping[str, bool]
|
||||||
|
metrics: Mapping[str, int]
|
||||||
|
issues: tuple[str, ...]
|
||||||
|
|
||||||
|
|
||||||
class UserEngineService:
|
class UserEngineService:
|
||||||
"""Headless service API for isolated user and profile management."""
|
"""Headless service API for isolated user and profile management."""
|
||||||
|
|
||||||
@@ -667,6 +682,61 @@ class UserEngineService:
|
|||||||
def outbox_events(self) -> tuple[OutboxEvent, ...]:
|
def outbox_events(self) -> tuple[OutboxEvent, ...]:
|
||||||
return tuple(self.store.outbox_events)
|
return tuple(self.store.outbox_events)
|
||||||
|
|
||||||
|
def outbox_diagnostics(self) -> OutboxDiagnostics:
|
||||||
|
event_types: dict[str, int] = {}
|
||||||
|
for event in self.store.outbox_events:
|
||||||
|
event_types[event.event_type] = event_types.get(event.event_type, 0) + 1
|
||||||
|
oldest = self.store.outbox_events[0].correlation_id if self.store.outbox_events else None
|
||||||
|
return OutboxDiagnostics(
|
||||||
|
pending_count=len(self.store.outbox_events),
|
||||||
|
event_types=event_types,
|
||||||
|
oldest_correlation_id=oldest,
|
||||||
|
)
|
||||||
|
|
||||||
|
def operability_snapshot(self) -> OperabilitySnapshot:
|
||||||
|
audit_correlation_ok = all(
|
||||||
|
bool(record.correlation_id) for record in self.store.audit_records
|
||||||
|
)
|
||||||
|
checks = {
|
||||||
|
"ready": self.readiness().ready,
|
||||||
|
"audit_correlation": audit_correlation_ok,
|
||||||
|
"outbox_diagnostics": self.outbox_diagnostics().pending_count >= 0,
|
||||||
|
}
|
||||||
|
metrics = {
|
||||||
|
"users": len(self.store.users),
|
||||||
|
"accounts": len(self.store.accounts),
|
||||||
|
"tenant_accounts": len(self.store.tenant_accounts),
|
||||||
|
"memberships": len(self.store.memberships),
|
||||||
|
"applications": len(self.store.applications),
|
||||||
|
"catalogs": len(self.store.catalogs),
|
||||||
|
"profile_values": len(self.store.profile_values),
|
||||||
|
"audit_records": len(self.store.audit_records),
|
||||||
|
"pending_outbox_events": len(self.store.outbox_events),
|
||||||
|
}
|
||||||
|
issues = tuple(key for key, passed in checks.items() if not passed)
|
||||||
|
return OperabilitySnapshot(
|
||||||
|
ready=all(checks.values()),
|
||||||
|
checks=checks,
|
||||||
|
metrics=metrics,
|
||||||
|
issues=issues,
|
||||||
|
)
|
||||||
|
|
||||||
|
def structured_log_context(
|
||||||
|
self,
|
||||||
|
*,
|
||||||
|
correlation_id: str,
|
||||||
|
tenant: str,
|
||||||
|
actor: Actor | None = None,
|
||||||
|
) -> Mapping[str, str]:
|
||||||
|
context = {
|
||||||
|
"correlation_id": correlation_id,
|
||||||
|
"tenant": tenant,
|
||||||
|
}
|
||||||
|
if actor is not None:
|
||||||
|
context["actor_issuer"] = actor.issuer
|
||||||
|
context["actor_subject"] = actor.subject
|
||||||
|
return context
|
||||||
|
|
||||||
def _session(self, actor: Actor, user: User, account: Account) -> UserSession:
|
def _session(self, actor: Actor, user: User, account: Account) -> UserSession:
|
||||||
identities = tuple(
|
identities = tuple(
|
||||||
identity
|
identity
|
||||||
|
|||||||
88
tests/test_finalization_contracts.py
Normal file
88
tests/test_finalization_contracts.py
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
import unittest
|
||||||
|
|
||||||
|
from user_engine.adapters.local import (
|
||||||
|
InMemoryUserEngineStore,
|
||||||
|
LocalAuthorizationCheckPort,
|
||||||
|
)
|
||||||
|
from user_engine.projections import ClaimsEnrichmentProjectionCache
|
||||||
|
from user_engine.service import UserEngineService
|
||||||
|
from user_engine.testing.fixtures import (
|
||||||
|
FixtureIdentityClaimsAdapter,
|
||||||
|
human_actor_claims,
|
||||||
|
sample_application,
|
||||||
|
sample_application_binding,
|
||||||
|
sample_catalog,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class FinalizationContractTests(unittest.TestCase):
|
||||||
|
def test_operability_snapshot_and_outbox_diagnostics_are_consistent(self):
|
||||||
|
service = _service()
|
||||||
|
session = _bootstrap(service)
|
||||||
|
|
||||||
|
snapshot = service.operability_snapshot()
|
||||||
|
outbox = service.outbox_diagnostics()
|
||||||
|
log_context = service.structured_log_context(
|
||||||
|
correlation_id="corr-log",
|
||||||
|
tenant="tenant:coulomb",
|
||||||
|
actor=session.actor,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertTrue(snapshot.ready)
|
||||||
|
self.assertEqual(snapshot.issues, ())
|
||||||
|
self.assertEqual(
|
||||||
|
snapshot.metrics["pending_outbox_events"],
|
||||||
|
outbox.pending_count,
|
||||||
|
)
|
||||||
|
self.assertGreaterEqual(outbox.event_types["user.created"], 1)
|
||||||
|
self.assertEqual(log_context["correlation_id"], "corr-log")
|
||||||
|
self.assertEqual(log_context["actor_subject"], session.actor.subject)
|
||||||
|
|
||||||
|
def test_claims_enrichment_cache_reports_status(self):
|
||||||
|
service = _service()
|
||||||
|
session = _bootstrap(service)
|
||||||
|
cache = ClaimsEnrichmentProjectionCache()
|
||||||
|
|
||||||
|
cache.get(
|
||||||
|
service,
|
||||||
|
session.actor,
|
||||||
|
user_id=session.user.user_id,
|
||||||
|
tenant="tenant:coulomb",
|
||||||
|
application_id="app.demo",
|
||||||
|
correlation_id="corr-cache",
|
||||||
|
)
|
||||||
|
status = cache.status()
|
||||||
|
cache.invalidate_user(session.user.user_id)
|
||||||
|
|
||||||
|
self.assertEqual(status.entries, 1)
|
||||||
|
self.assertEqual(status.tenants, ("tenant:coulomb",))
|
||||||
|
self.assertEqual(status.applications, ("app.demo",))
|
||||||
|
self.assertEqual(cache.status().entries, 0)
|
||||||
|
|
||||||
|
|
||||||
|
def _service() -> UserEngineService:
|
||||||
|
return UserEngineService(
|
||||||
|
store=InMemoryUserEngineStore(),
|
||||||
|
identity_adapter=FixtureIdentityClaimsAdapter(),
|
||||||
|
authorization=LocalAuthorizationCheckPort(),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _bootstrap(service: UserEngineService):
|
||||||
|
session = service.me(human_actor_claims(), correlation_id="corr-me")
|
||||||
|
service.register_application(
|
||||||
|
session.actor,
|
||||||
|
sample_application(),
|
||||||
|
binding=sample_application_binding(),
|
||||||
|
correlation_id="corr-app",
|
||||||
|
)
|
||||||
|
service.publish_catalog(
|
||||||
|
session.actor,
|
||||||
|
sample_catalog(),
|
||||||
|
correlation_id="corr-catalog",
|
||||||
|
)
|
||||||
|
return session
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
@@ -4,7 +4,7 @@ type: workplan
|
|||||||
title: "User Engine Implementation Assessment And Polish"
|
title: "User Engine Implementation Assessment And Polish"
|
||||||
domain: netkingdom
|
domain: netkingdom
|
||||||
repo: user-engine
|
repo: user-engine
|
||||||
status: active
|
status: finished
|
||||||
owner: codex
|
owner: codex
|
||||||
topic_slug: netkingdom
|
topic_slug: netkingdom
|
||||||
planning_priority: medium
|
planning_priority: medium
|
||||||
@@ -28,7 +28,7 @@ operability, packaging, and UI handoff readiness.
|
|||||||
|
|
||||||
```task
|
```task
|
||||||
id: USER-WP-0006-T1
|
id: USER-WP-0006-T1
|
||||||
status: todo
|
status: done
|
||||||
priority: high
|
priority: high
|
||||||
state_hub_task_id: "e0b5621a-4935-45a6-bbd0-41476b3d3317"
|
state_hub_task_id: "e0b5621a-4935-45a6-bbd0-41476b3d3317"
|
||||||
```
|
```
|
||||||
@@ -38,7 +38,7 @@ interface guidance. Record gaps, accepted deviations, and follow-up work.
|
|||||||
|
|
||||||
```task
|
```task
|
||||||
id: USER-WP-0006-T2
|
id: USER-WP-0006-T2
|
||||||
status: todo
|
status: done
|
||||||
priority: high
|
priority: high
|
||||||
state_hub_task_id: "467eebf2-3a16-45b0-a5bd-28b5c1b634b1"
|
state_hub_task_id: "467eebf2-3a16-45b0-a5bd-28b5c1b634b1"
|
||||||
```
|
```
|
||||||
@@ -48,7 +48,7 @@ UI, or deployment responsibilities.
|
|||||||
|
|
||||||
```task
|
```task
|
||||||
id: USER-WP-0006-T3
|
id: USER-WP-0006-T3
|
||||||
status: todo
|
status: done
|
||||||
priority: medium
|
priority: medium
|
||||||
state_hub_task_id: "3b7efcaf-041d-4262-973d-6cfd2a011b47"
|
state_hub_task_id: "3b7efcaf-041d-4262-973d-6cfd2a011b47"
|
||||||
```
|
```
|
||||||
@@ -58,7 +58,7 @@ responses, audit event shapes, and migration contracts.
|
|||||||
|
|
||||||
```task
|
```task
|
||||||
id: USER-WP-0006-T4
|
id: USER-WP-0006-T4
|
||||||
status: todo
|
status: done
|
||||||
priority: medium
|
priority: medium
|
||||||
state_hub_task_id: "a0bc5469-6c6f-429a-a58e-2f6c6489f5c3"
|
state_hub_task_id: "a0bc5469-6c6f-429a-a58e-2f6c6489f5c3"
|
||||||
```
|
```
|
||||||
@@ -68,7 +68,7 @@ outbox drain diagnostics, cache status, and runbooks.
|
|||||||
|
|
||||||
```task
|
```task
|
||||||
id: USER-WP-0006-T5
|
id: USER-WP-0006-T5
|
||||||
status: todo
|
status: done
|
||||||
priority: medium
|
priority: medium
|
||||||
state_hub_task_id: "996ffd8d-5006-4393-8214-be8072ebae8e"
|
state_hub_task_id: "996ffd8d-5006-4393-8214-be8072ebae8e"
|
||||||
```
|
```
|
||||||
@@ -79,7 +79,7 @@ integration.
|
|||||||
|
|
||||||
```task
|
```task
|
||||||
id: USER-WP-0006-T6
|
id: USER-WP-0006-T6
|
||||||
status: todo
|
status: done
|
||||||
priority: medium
|
priority: medium
|
||||||
state_hub_task_id: "bc1ca685-be53-4f70-8717-7d7226c81944"
|
state_hub_task_id: "bc1ca685-be53-4f70-8717-7d7226c81944"
|
||||||
```
|
```
|
||||||
@@ -89,7 +89,7 @@ requirements, migration policy, and compatibility guarantees.
|
|||||||
|
|
||||||
```task
|
```task
|
||||||
id: USER-WP-0006-T7
|
id: USER-WP-0006-T7
|
||||||
status: todo
|
status: done
|
||||||
priority: low
|
priority: low
|
||||||
state_hub_task_id: "7c4fa8b5-f6c4-434a-85e6-0048449fbdc8"
|
state_hub_task_id: "7c4fa8b5-f6c4-434a-85e6-0048449fbdc8"
|
||||||
```
|
```
|
||||||
|
|||||||
Reference in New Issue
Block a user