TracePulse PLM shared contracts: Kernel API surface, idempotency, capabilities, knowledge envelope, pricing, artifact store, trace context. Frozen in US-W1.0; consumed by US-W1.1+ and US-CR.0+.
Project description
plm-shared
Standalone repo since 2026-06-30 (FTR-924). Extracted from
plm-engine-core/shared/intogithub.com/plm-engine/plm-shared. See docs/POST-SPLIT.md for the split context, India onboarding pointer, and PRD §6.1 cross-reference.Integrating against plm-shared? Start with the consumer guide at docs/federation-contract-guide.md and the runnable e2e example at examples/consume_plm_shared.py.
Frozen contracts shared across TracePulse PLM product lines (Core, Skill Kernel, Skill Packages, Knowledge, Workbench, Studio, Connectors). Owned by US-W1.0; consumed by US-W1.1 → US-W1.13 and US-CR.0 → US-CR.13 without modification.
The Wave 1 origin story (7 PRs that built US-W1.0 pre-extraction) is preserved in this repo's commit history + the retrospective RELEASE-NOTES-v1.0.0.md. Post-extraction (FTR-924, 2026-06-30) the shipping model is release-please + PyPI: see CHANGELOG.md for every published version.
Surface
| Module | Purpose |
|---|---|
kernel_api |
Pydantic args + KernelEmitter Protocol (frozen) |
sqlalchemy_kernel_emitter |
Concrete SqlAlchemyKernelEmitter impl (PR-6) |
idempotency |
IdempotencyKey + compute_content_hash |
capabilities |
Capability enum + YAML loader |
knowledge_envelope |
KnowledgeEnvelopeV1 frozen schema |
trace_context |
TraceContext ContextVar + W0.6 bridge |
pricing |
compute_cost + CostStatus + PricingSnapshot dataclass |
artifact_store |
ArtifactStorageBackend Protocol + LocalFilesystemBackend + ObjectStoreBackend |
middleware |
PlmTenantMiddleware (ASGI) + parse_core_caller + cors_allowed_headers |
db |
SQLAlchemy 2.0 ORM models + tenant_scoped_session |
Kernel API (US-W1.0 / AC-2)
plm_shared.kernel_api.KernelEmitter is the frozen 4-method
Protocol that every product line uses to record events. The
authorised emitter boundary is Core: product lines submit decisions
to Core (e.g. Workbench routes HITL approvals), and Core is the
authorised emitter through plm-shared.
Protocol
from plm_shared.kernel_api import KernelEmitter
from plm_shared.kernel_api import (
EmitTelemetryArgs,
EmitGovernanceArgs,
EmitLlmCallArgs,
EmitConnectorCallArgs,
)
class KernelEmitter(Protocol):
def emit_telemetry(self, args: EmitTelemetryArgs) -> None: ...
def emit_governance(self, args: EmitGovernanceArgs) -> None: ...
def record_llm_call(self, args: EmitLlmCallArgs) -> None: ...
def record_connector_call(self, args: EmitConnectorCallArgs) -> None: ...
Every method returns None on success — including the silent replay
path (200 OK: same key + same content). A logical-key collision
(same identity, different content) raises
plm_shared.sqlalchemy_kernel_emitter.IdempotencyConflict.
Args
EmitTelemetryArgs(
event_type: str, # e.g. "knowledge_call"
span_id: str,
capability: str, # capability tag for the event
payload: Dict[str, Any] = {}, # event-specific shape
idempotency_key: IdempotencyKey,
)
EmitGovernanceArgs(
governance_event_type: str, # e.g. "hitl_resolved"
span_id: str,
payload: Dict[str, Any] = {},
idempotency_key: IdempotencyKey,
)
EmitLlmCallArgs(
span_id: str,
model: str,
prompt_tokens: int,
completion_tokens: int,
pricing_version: str, # "litellm-live" | "overrides-static" | "unavailable"
cost_eur: Optional[float] = None,
cost_status: str, # "pending" | "calculated" | "unavailable"
idempotency_key: IdempotencyKey,
)
EmitConnectorCallArgs(
span_id: str,
connector_id: str, # e.g. "3dx-rest"
capability: str, # e.g. "read.parts"
envelope: Dict[str, Any] = {},
pricing_version: str,
cost_eur: Optional[float] = None,
cost_status: str,
idempotency_key: IdempotencyKey,
)
Concrete emitter — SqlAlchemyKernelEmitter
from plm_shared.sqlalchemy_kernel_emitter import (
IdempotencyConflict,
SqlAlchemyKernelEmitter,
)
from plm_shared.idempotency import IdempotencyKey, compute_content_hash
from plm_shared.kernel_api import EmitLlmCallArgs
tenant_id = "00000000-0000-0000-0000-000000000000"
emitter = SqlAlchemyKernelEmitter(tenant_id=tenant_id)
payload = {"model": "gpt-4o", "prompt_tokens": 100}
key = IdempotencyKey(
tenant_id=tenant_id,
trace_id="trace-X",
span_id="span-1",
event_type="llm_call",
content_hash=compute_content_hash(payload),
)
try:
emitter.record_llm_call(EmitLlmCallArgs(
span_id="span-1",
model="gpt-4o",
prompt_tokens=100,
completion_tokens=50,
pricing_version="litellm-live",
cost_eur=0.0042,
cost_status="calculated",
idempotency_key=key,
))
# 200 — inserted, OR 200 — silent replay (same key + same content)
except IdempotencyConflict:
# 409 — same logical identity, different content
raise
The emitter:
- Opens a
tenant_scoped_session(tenant_id)so RLS engages on INSERT (migration 0009 — fail-closed for an unset GUC). - Stamps the
idempotency_keyUNIQUE; on collision, dispatches by constraint name (<table>_idempotency_key_key→ 200 silent replay,uq_<table>_logical→ 409 raise). - For LLM and connector calls, upserts a
pricing_snapshotsrow keyed on(tenant_id, model, snapshot_date)(migration 0011) and stores the FK on the call row.pricing_version="unavailable"skips the snapshot — thecost_statuscolumn carries the authoritative signal.
Backend wiring (deferred to US-CR.0)
PlmTenantMiddleware is published in plm_shared.middleware but is
NOT yet installed in 02_App/backend/main.py. US-CR.0 owns the
ordering — it adds IdentityMiddleware first, then plumbs
PlmTenantMiddleware behind it, then wires CORS so that
CORSMiddleware wraps both (failure responses still carry CORS
headers). Conv D leaves the middleware available-but-unwired so
US-CR.0 lands the full ordering in one place.
Migration chain
Frozen head: 0024. Full chain:
| Rev | Subject |
|---|---|
| 0001 | foundational tables tenants, runs + quality_level ENUM + actor_kind ENUM |
| 0002 | telemetry_events + composite index + UNIQUE idempotency_key |
| 0003 | llm_calls + cost_* columns + UNIQUE idempotency_key |
| 0004 | connector_calls + connector-specific fields |
| 0005 | artifacts + storage_uri + retention_until |
| 0006 | quality_level + cost_status transition triggers |
| 0007 | mcrc_v1_view (§1-§8 projection) |
| 0008 | Postgres roles plm_kernel_writer + plm_migrator |
| 0009 | Row-Level Security on the 5 tenant-scoped tables + plm_migrator BYPASSRLS |
| 0010 | logical UNIQUE constraints (backs the 200/409 IdempotencyKey contract) |
| 0011 | pricing_snapshots dedup table + nullable FKs |
| 0012 | plm_migrator table privileges — SELECT/INSERT/UPDATE/DELETE on the 7 tenant-scoped tables (BYPASSRLS alone does not grant table access) |
| 0013 | audit_log table for identity events (US-CR.0 PR-2) |
| 0014 | cost-canonicalisation columns on LLM + connector calls (US-W1.12 Conv H) |
| 0015 | artifact offload schema extension — prompt/completion refs (US-W1.5 Conv I) |
| 0016 | promote Wave 0 metadata to typed columns on telemetry_events (US-W1.13 Conv I) |
| 0017 | hitl_tickets HITL Control Plane persistence (US-CR.13) |
| 0018 | hitl_ticket_comments HITL comment persistence (US-CR.13) |
| 0019 | connector_access_index Knowledge curation index (FTR-603 Wave 5 Conv C) |
| 0020 | mcrc_v1_view widening — pricing + rate source projections (Wave 5 Conv D / FTR-605) |
| 0021 | widen runs.outcome enum to include awaiting_hitl (US-DC.2 Wave 6 Conv B) |
| 0022 | realised-baseline correction — DC-3 telemetry columns (US-DC.3 Wave 6 Conv D) |
| 0023 | telemetry workload indexes for DC-5 read-path (US-DC.5 Wave 6 Conv D) |
| 0024 | agent-invocations rename + realised-baseline correction (US-DC.4 Wave 6 Conv E) |
Run the chain with alembic upgrade head from this repo root (needs
PG_DSN exported).
Install (developer)
In a workspace context (uv workspace at c:\AIxPLM\plm-engine\),
uv sync resolves plm-shared editable as a workspace member. As
an external consumer:
pip install plm-shared # base install — from PyPI
pip install 'plm-shared[db]' # + SQLAlchemy / psycopg / alembic
pip install 'plm-shared[s3]' # + boto3 (ObjectStoreBackend)
pip install 'plm-shared[db,s3]' # combined
Pin per D3 = Option A (additive→minor, breaking→major): plm-shared = "^1.0"
in your sibling's pyproject.toml. See docs/POST-SPLIT.md.
Tests
cd plm-shared
python -m pytest tests/ -v
Audit + migration suites under tests/audit/ and tests/migrations/
are gated on PG_DSN. CI brings up the matching Postgres service in
.github/workflows/audit-pg.yml (postgres:15.7-alpine, tracepulse_ci
DB). Locally, bring Postgres up with plain docker run (the workspace
also provides dev/postgres.yml, but the compose file is only present
inside the plm-engine workspace clone, not this standalone repo):
# tracepulse_ci is the canonical DB name — matches CI (audit-pg.yml
# + example-smoke) and the local dev-compose file. Keeping one name
# means reproducing a CI failure locally needs no path translation.
docker run -d --name plm-shared-pg \
-e POSTGRES_USER=tracepulse \
-e POSTGRES_PASSWORD=tracepulse \
-e POSTGRES_DB=tracepulse_ci \
-p 5432:5432 \
postgres:15.7-alpine
export PG_DSN=postgresql+psycopg://tracepulse:tracepulse@localhost:5432/tracepulse_ci
alembic upgrade head
python -m pytest tests/audit/ tests/migrations/ -v
S3-protocol live tests under tests/test_object_store_backend.py gate
on S3_ENDPOINT_URL (skip-when-unset). Bring up minio (or any
S3-protocol endpoint) and export S3_ENDPOINT_URL, S3_ACCESS_KEY,
S3_SECRET_KEY, S3_BUCKET.
Architecture conformance gate (US-W1.0 / AC-4)
pyproject.toml declares one [tool.importlinter] Forbidden contract
banning sqlalchemy from every plm-shared module except db and
sqlalchemy_kernel_emitter. Run it from the repo root:
cd plm-shared
lint-imports --config pyproject.toml
The companion BL5 rule in
02_App/backend/scripts/architecture_guardrails.py (warn-only at
launch) regex-scans for INSERT INTO {telemetry_events|llm_calls|connector_calls|pricing_snapshots}
outside kernel_api / sqlalchemy_kernel_emitter / migrations.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters