Skip to main content

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/ into github.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_key UNIQUE; 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_snapshots row keyed on (tenant_id, model, snapshot_date) (migration 0011) and stores the FK on the call row. pricing_version="unavailable" skips the snapshot — the cost_status column 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

plm_shared-1.0.3.tar.gz (227.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

plm_shared-1.0.3-py3-none-any.whl (197.8 kB view details)

Uploaded Python 3

File details

Details for the file plm_shared-1.0.3.tar.gz.

File metadata

  • Download URL: plm_shared-1.0.3.tar.gz
  • Upload date:
  • Size: 227.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.13

File hashes

Hashes for plm_shared-1.0.3.tar.gz
Algorithm Hash digest
SHA256 27caaacf2ef92e68853f7abfc87da50114a7ff719de94f68a364260fce3238c2
MD5 bf132085dfae983ec755e1a9c4fb3490
BLAKE2b-256 6b05d08eb2c58d01ab484989fda3f2f77e2549071e0dc306d2d7baf8f72926aa

See more details on using hashes here.

File details

Details for the file plm_shared-1.0.3-py3-none-any.whl.

File metadata

  • Download URL: plm_shared-1.0.3-py3-none-any.whl
  • Upload date:
  • Size: 197.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.13

File hashes

Hashes for plm_shared-1.0.3-py3-none-any.whl
Algorithm Hash digest
SHA256 815cec2d58e115efcd3a52b759e869a5d898f4aa4b96ee2c3212da94bebc4e29
MD5 a4efc4c3ccb53d8ff0414c0442fd2fb2
BLAKE2b-256 af1370ebe77d386823662504d4a06f0bd1aad680a142b7a8db2edc970719c356

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page