aigp-python 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
aigp/openlineage.py ADDED
@@ -0,0 +1,249 @@
1
+ """
2
+ AIGP OpenLineage Facet Builder
3
+ ==============================
4
+
5
+ Constructs OpenLineage-compatible facet dicts from AIGP governance events.
6
+ No OpenLineage library dependency -- produces plain dicts conforming to
7
+ the OpenLineage custom facet JSON Schema.
8
+
9
+ AIGP defines two custom facets for OpenLineage:
10
+
11
+ 1. **AIGPGovernanceRunFacet** (run facet) -- aggregate governance proof
12
+ attached to ``run.facets.aigp_governance``.
13
+ 2. **AIGPResourceInputFacet** (input dataset facet) -- per-resource
14
+ governance metadata attached to ``inputs[].inputFacets.aigp_resource``.
15
+
16
+ Usage::
17
+
18
+ from aigp.openlineage import (
19
+ build_governance_run_facet,
20
+ build_resource_input_facets,
21
+ build_openlineage_run_event,
22
+ )
23
+
24
+ # From an existing AIGP event dict:
25
+ run_facet = build_governance_run_facet(aigp_event)
26
+ input_facets = build_resource_input_facets(aigp_event)
27
+
28
+ # Or build a complete OpenLineage RunEvent:
29
+ ol_event = build_openlineage_run_event(
30
+ aigp_event,
31
+ job_namespace="finco.trading",
32
+ job_name="trading-bot-v2.invoke",
33
+ )
34
+
35
+ Architectural note -- Emission Granularity:
36
+ Implementations SHOULD emit at most one OpenLineage RunEvent per
37
+ governance session or task, using ``trace_id`` as the ``runId``.
38
+ Individual agent steps within a session SHOULD be tracked as OTel
39
+ spans, not as separate OpenLineage runs. OpenLineage was designed
40
+ for discrete Job Runs in a DAG; AI agents are conversational and
41
+ iterative -- emitting per-step runs overwhelms lineage backends.
42
+
43
+ Architectural note -- Active vs. Passive Lineage:
44
+ OpenLineage integration is PASSIVE (eventually consistent). It MUST
45
+ NOT be used for real-time enforcement decisions. Enforcement MUST use
46
+ the AIGP + OTel path. When pre-execution lineage context is needed
47
+ for governance, snapshot it, hash it as a ``"context"`` resource in
48
+ the Merkle tree -- making it an active governed artifact.
49
+ """
50
+
51
+ import uuid as _uuid
52
+ from datetime import datetime, timezone
53
+ from typing import Any
54
+
55
+
56
+ # ---------------------------------------------------------------------------
57
+ # Constants
58
+ # ---------------------------------------------------------------------------
59
+
60
+ PRODUCER = "https://github.com/open-aigp/aigp"
61
+
62
+ RUN_FACET_SCHEMA_URL = (
63
+ "https://github.com/open-aigp/aigp/blob/v0.9.0/"
64
+ "integrations/openlineage/facets/AIGPGovernanceRunFacet.json"
65
+ )
66
+
67
+ RESOURCE_FACET_SCHEMA_URL = (
68
+ "https://github.com/open-aigp/aigp/blob/v0.9.0/"
69
+ "integrations/openlineage/facets/AIGPResourceInputFacet.json"
70
+ )
71
+
72
+ OPENLINEAGE_SCHEMA_URL = (
73
+ "https://openlineage.io/spec/2-0-2/OpenLineage.json#/$defs/RunEvent"
74
+ )
75
+
76
+
77
+ # ---------------------------------------------------------------------------
78
+ # Facet Builders
79
+ # ---------------------------------------------------------------------------
80
+
81
+ def build_governance_run_facet(aigp_event: dict[str, Any]) -> dict[str, Any]:
82
+ """
83
+ Build an AIGPGovernanceRunFacet from an AIGP event dict.
84
+
85
+ The returned dict conforms to the ``AIGPGovernanceRunFacet`` JSON Schema
86
+ and is ready to be placed in ``run.facets.aigp_governance`` of an
87
+ OpenLineage RunEvent.
88
+
89
+ Args:
90
+ aigp_event: AIGP event dict (from ``create_aigp_event`` or any
91
+ ``AIGPInstrumentor`` method).
92
+
93
+ Returns:
94
+ Dict conforming to AIGPGovernanceRunFacet schema.
95
+ """
96
+ merkle_tree = aigp_event.get("governance_merkle_tree")
97
+ leaf_count = merkle_tree["leaf_count"] if merkle_tree else 1
98
+
99
+ facet: dict[str, Any] = {
100
+ "_producer": PRODUCER,
101
+ "_schemaURL": RUN_FACET_SCHEMA_URL,
102
+ "governanceHash": aigp_event.get("governance_hash", ""),
103
+ "hashType": aigp_event.get("hash_type", "sha256"),
104
+ "leafCount": leaf_count,
105
+ "agentId": aigp_event.get("agent_id", ""),
106
+ "traceId": aigp_event.get("trace_id", ""),
107
+ "specVersion": "0.9.0",
108
+ }
109
+
110
+ # Infer enforcement result from event type
111
+ event_type = aigp_event.get("event_type", "")
112
+ if "DENIED" in event_type or "VIOLATION" in event_type or "BLOCKED" in event_type:
113
+ facet["enforcementResult"] = "denied"
114
+ elif event_type:
115
+ facet["enforcementResult"] = "allowed"
116
+
117
+ # Optional: data classification
118
+ classification = aigp_event.get("data_classification", "")
119
+ if classification:
120
+ facet["dataClassification"] = classification
121
+
122
+ return facet
123
+
124
+
125
+ def build_resource_input_facets(
126
+ aigp_event: dict[str, Any],
127
+ ) -> list[dict[str, Any]]:
128
+ """
129
+ Build AIGPResourceInputFacet dicts from an AIGP event's governed resources.
130
+
131
+ If the event has a ``governance_merkle_tree``, produces one facet per leaf.
132
+ Otherwise, produces a single facet from the event's primary resource
133
+ (``policy_name`` or ``prompt_name``).
134
+
135
+ Args:
136
+ aigp_event: AIGP event dict.
137
+
138
+ Returns:
139
+ List of dicts, each conforming to AIGPResourceInputFacet schema.
140
+ Each dict is meant for ``inputs[].inputFacets.aigp_resource``.
141
+ """
142
+ merkle_tree = aigp_event.get("governance_merkle_tree")
143
+
144
+ if merkle_tree:
145
+ facets: list[dict[str, Any]] = []
146
+ for leaf in merkle_tree.get("leaves", []):
147
+ facets.append({
148
+ "_producer": PRODUCER,
149
+ "_schemaURL": RESOURCE_FACET_SCHEMA_URL,
150
+ "resourceType": leaf["resource_type"],
151
+ "resourceName": leaf["resource_name"],
152
+ "leafHash": leaf["hash"],
153
+ })
154
+ return facets
155
+
156
+ # Single resource: infer from event fields
157
+ facet: dict[str, Any] = {
158
+ "_producer": PRODUCER,
159
+ "_schemaURL": RESOURCE_FACET_SCHEMA_URL,
160
+ }
161
+
162
+ if aigp_event.get("policy_name"):
163
+ facet["resourceType"] = "policy"
164
+ facet["resourceName"] = aigp_event["policy_name"]
165
+ if aigp_event.get("policy_version"):
166
+ facet["resourceVersion"] = aigp_event["policy_version"]
167
+ elif aigp_event.get("prompt_name"):
168
+ facet["resourceType"] = "prompt"
169
+ facet["resourceName"] = aigp_event["prompt_name"]
170
+ if aigp_event.get("prompt_version"):
171
+ facet["resourceVersion"] = aigp_event["prompt_version"]
172
+ else:
173
+ return [] # No identifiable resource
174
+
175
+ if aigp_event.get("governance_hash"):
176
+ facet["leafHash"] = aigp_event["governance_hash"]
177
+
178
+ return [facet]
179
+
180
+
181
+ def build_openlineage_run_event(
182
+ aigp_event: dict[str, Any],
183
+ job_namespace: str,
184
+ job_name: str,
185
+ run_id: str = "",
186
+ event_type: str = "COMPLETE",
187
+ ) -> dict[str, Any]:
188
+ """
189
+ Build a complete OpenLineage RunEvent with AIGP governance facets.
190
+
191
+ This convenience function creates a full RunEvent dict that can be
192
+ sent to any OpenLineage-compatible lineage backend.
193
+
194
+ Governed resources become OpenLineage InputDatasets with the
195
+ ``aigp_resource`` input facet.
196
+
197
+ **Emission granularity:** Use ``trace_id`` as ``run_id`` to ensure
198
+ one RunEvent per governance session (not per agent step).
199
+
200
+ Args:
201
+ aigp_event: AIGP event dict.
202
+ job_namespace: OpenLineage job namespace (e.g., ``"finco.trading"``).
203
+ job_name: OpenLineage job name (e.g., ``"trading-bot-v2.invoke"``).
204
+ run_id: Optional run ID (UUID string). If not provided, uses the
205
+ AIGP event's ``trace_id``; falls back to a generated UUID.
206
+ event_type: OpenLineage event type. One of ``"START"``,
207
+ ``"RUNNING"``, ``"COMPLETE"``, ``"FAIL"``, ``"ABORT"``.
208
+
209
+ Returns:
210
+ Dict conforming to OpenLineage RunEvent schema with AIGP facets.
211
+ """
212
+ if not run_id:
213
+ # Prefer trace_id as run_id (one RunEvent per session).
214
+ run_id = aigp_event.get("trace_id") or str(_uuid.uuid4())
215
+
216
+ run_facet = build_governance_run_facet(aigp_event)
217
+ resource_facets = build_resource_input_facets(aigp_event)
218
+
219
+ # Each governed resource becomes an OpenLineage InputDataset.
220
+ inputs: list[dict[str, Any]] = []
221
+ for rf in resource_facets:
222
+ inputs.append({
223
+ "namespace": job_namespace,
224
+ "name": rf.get("resourceName", "unknown"),
225
+ "inputFacets": {
226
+ "aigp_resource": rf,
227
+ },
228
+ })
229
+
230
+ now = datetime.now(timezone.utc)
231
+
232
+ return {
233
+ "eventType": event_type,
234
+ "eventTime": now.strftime("%Y-%m-%dT%H:%M:%S.") + f"{now.microsecond // 1000:03d}Z",
235
+ "run": {
236
+ "runId": run_id,
237
+ "facets": {
238
+ "aigp_governance": run_facet,
239
+ },
240
+ },
241
+ "job": {
242
+ "namespace": job_namespace,
243
+ "name": job_name,
244
+ },
245
+ "inputs": inputs,
246
+ "outputs": [],
247
+ "producer": PRODUCER,
248
+ "schemaURL": OPENLINEAGE_SCHEMA_URL,
249
+ }
aigp/tracestate.py ADDED
@@ -0,0 +1,149 @@
1
+ """
2
+ AIGP W3C tracestate Vendor Key
3
+ ===============================
4
+
5
+ Manages the `aigp` vendor key in the W3C tracestate header for
6
+ lightweight governance signaling that survives through proxies
7
+ and load balancers.
8
+
9
+ Format:
10
+ tracestate: aigp=cls:{classification};pol:{policy_name};ver:{policy_version}
11
+
12
+ Unlike Baggage, tracestate is part of the W3C Trace Context standard
13
+ and is preserved by all compliant tracing infrastructure.
14
+ """
15
+
16
+ from aigp.attributes import AIGPAttributes
17
+
18
+
19
+ class AIGPTraceState:
20
+ """
21
+ Encodes and decodes AIGP governance context for the W3C tracestate header.
22
+ """
23
+
24
+ VENDOR_KEY = "aigp"
25
+
26
+ @staticmethod
27
+ def encode(
28
+ data_classification: str = "",
29
+ policy_name: str = "",
30
+ policy_version: int = 0,
31
+ ) -> str:
32
+ """
33
+ Encode AIGP governance context as a tracestate vendor value.
34
+
35
+ Args:
36
+ data_classification: AIGP classification level.
37
+ policy_name: AGRN policy name.
38
+ policy_version: Policy version number.
39
+
40
+ Returns:
41
+ Encoded vendor value string (e.g., "cls:con;pol:policy.trading-limits;ver:4").
42
+ """
43
+ parts = []
44
+
45
+ if data_classification:
46
+ abbrev = AIGPAttributes.CLASSIFICATION_ABBREV.get(
47
+ data_classification, data_classification[:3]
48
+ )
49
+ parts.append(f"cls:{abbrev}")
50
+
51
+ if policy_name:
52
+ parts.append(f"pol:{policy_name}")
53
+
54
+ if policy_version > 0:
55
+ parts.append(f"ver:{policy_version}")
56
+
57
+ return ";".join(parts)
58
+
59
+ @staticmethod
60
+ def decode(vendor_value: str) -> dict[str, str]:
61
+ """
62
+ Decode AIGP governance context from a tracestate vendor value.
63
+
64
+ Args:
65
+ vendor_value: The value portion of the aigp tracestate entry
66
+ (e.g., "cls:con;pol:policy.trading-limits;ver:4").
67
+
68
+ Returns:
69
+ Dict with decoded governance context.
70
+ """
71
+ result = {}
72
+ abbrev_reverse = {v: k for k, v in AIGPAttributes.CLASSIFICATION_ABBREV.items()}
73
+
74
+ for part in vendor_value.split(";"):
75
+ if ":" not in part:
76
+ continue
77
+ key, value = part.split(":", 1)
78
+
79
+ if key == "cls":
80
+ result["data_classification"] = abbrev_reverse.get(value, value)
81
+ elif key == "pol":
82
+ result["policy_name"] = value
83
+ elif key == "ver":
84
+ result["policy_version"] = value
85
+
86
+ return result
87
+
88
+ @staticmethod
89
+ def inject_into_tracestate(
90
+ existing_tracestate: str,
91
+ data_classification: str = "",
92
+ policy_name: str = "",
93
+ policy_version: int = 0,
94
+ ) -> str:
95
+ """
96
+ Inject AIGP vendor entry into an existing tracestate header value.
97
+
98
+ Per W3C spec, the most recently updated vendor entry moves to the front.
99
+
100
+ Args:
101
+ existing_tracestate: Current tracestate header value.
102
+ data_classification: AIGP classification level.
103
+ policy_name: AGRN policy name.
104
+ policy_version: Policy version number.
105
+
106
+ Returns:
107
+ Updated tracestate header value with aigp entry prepended.
108
+ """
109
+ aigp_value = AIGPTraceState.encode(
110
+ data_classification=data_classification,
111
+ policy_name=policy_name,
112
+ policy_version=policy_version,
113
+ )
114
+
115
+ if not aigp_value:
116
+ return existing_tracestate
117
+
118
+ aigp_entry = f"{AIGPTraceState.VENDOR_KEY}={aigp_value}"
119
+
120
+ # Remove existing aigp entry if present
121
+ if existing_tracestate:
122
+ entries = [
123
+ e.strip()
124
+ for e in existing_tracestate.split(",")
125
+ if not e.strip().startswith(f"{AIGPTraceState.VENDOR_KEY}=")
126
+ ]
127
+ if entries:
128
+ return f"{aigp_entry},{','.join(entries)}"
129
+
130
+ return aigp_entry
131
+
132
+ @staticmethod
133
+ def extract_from_tracestate(tracestate: str) -> dict[str, str]:
134
+ """
135
+ Extract AIGP governance context from a tracestate header value.
136
+
137
+ Args:
138
+ tracestate: The full tracestate header value.
139
+
140
+ Returns:
141
+ Dict with decoded governance context, or empty dict if no aigp entry.
142
+ """
143
+ for entry in tracestate.split(","):
144
+ entry = entry.strip()
145
+ if entry.startswith(f"{AIGPTraceState.VENDOR_KEY}="):
146
+ vendor_value = entry[len(f"{AIGPTraceState.VENDOR_KEY}="):]
147
+ return AIGPTraceState.decode(vendor_value)
148
+
149
+ return {}
@@ -0,0 +1,280 @@
1
+ Metadata-Version: 2.4
2
+ Name: aigp-python
3
+ Version: 1.0.0
4
+ Summary: AIGP — AI Governance Proof. Open standard for proving your AI agents used the approved policies, prompts, and tools.
5
+ Project-URL: Homepage, https://github.com/open-aigp/aigp
6
+ Project-URL: Documentation, https://github.com/open-aigp/aigp/tree/main/sdks/python
7
+ Project-URL: Repository, https://github.com/open-aigp/aigp
8
+ Project-URL: Issues, https://github.com/open-aigp/aigp/issues
9
+ Author: AIGP Community
10
+ License-Expression: Apache-2.0
11
+ Keywords: ai-agents,ai-governance,aigp,audit,governance,lineage,merkle-proof,openlineage,opentelemetry,tracing
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: Apache Software License
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
21
+ Classifier: Topic :: System :: Monitoring
22
+ Requires-Python: >=3.10
23
+ Requires-Dist: opentelemetry-api>=1.20.0
24
+ Requires-Dist: opentelemetry-sdk>=1.20.0
25
+ Provides-Extra: dev
26
+ Requires-Dist: pytest-cov>=4.0; extra == 'dev'
27
+ Requires-Dist: pytest>=7.0; extra == 'dev'
28
+ Provides-Extra: exporters
29
+ Requires-Dist: opentelemetry-exporter-otlp>=1.20.0; extra == 'exporters'
30
+ Description-Content-Type: text/markdown
31
+
32
+ # AIGP-OpenTelemetry Python SDK
33
+
34
+ [![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](../../LICENSE)
35
+ [![Python](https://img.shields.io/badge/Python-3.10+-blue.svg)](https://www.python.org/)
36
+
37
+ **Bridge between AIGP governance events and OpenTelemetry spans.**
38
+
39
+ AIGP is the governance-proof semantic payload. OpenTelemetry is the transport and correlation layer. This SDK handles dual-emit: every governance action produces both an AIGP event (compliance store) and an OTel span event (observability backend).
40
+
41
+ ---
42
+
43
+ ## Installation
44
+
45
+ ```bash
46
+ pip install opentelemetry-api opentelemetry-sdk
47
+
48
+ # Then add this package to your project
49
+ # (from the AIGP repo root)
50
+ pip install -e sdks/python/
51
+ ```
52
+
53
+ ## Quick Start
54
+
55
+ ```python
56
+ from opentelemetry import trace
57
+ from opentelemetry.sdk.trace import TracerProvider
58
+ from opentelemetry.sdk.resources import Resource
59
+
60
+ from aigp import AIGPInstrumentor
61
+
62
+ # 1. Initialize with AIGP Resource attributes
63
+ instrumentor = AIGPInstrumentor(
64
+ agent_id="agent.trading-bot-v2",
65
+ agent_name="Trading Bot",
66
+ org_id="org.finco",
67
+ event_callback=send_to_store, # your AI governance store
68
+ )
69
+
70
+ resource = Resource.create({
71
+ **instrumentor.get_resource_attributes(),
72
+ "service.name": "trading-bot-v2",
73
+ })
74
+ provider = TracerProvider(resource=resource)
75
+ trace.set_tracer_provider(provider)
76
+ tracer = trace.get_tracer("aigp.example")
77
+
78
+ # 2. Emit governance events within OTel spans
79
+ with tracer.start_as_current_span("invoke_agent") as span:
80
+ event = instrumentor.inject_success(
81
+ policy_name="policy.trading-limits",
82
+ policy_version=4,
83
+ content="Max position: $10M...",
84
+ data_classification="confidential",
85
+ )
86
+ # -> AIGP event sent to AI governance store (compliance)
87
+ # -> OTel span event with aigp.* attributes (observability)
88
+ ```
89
+
90
+ ## Features
91
+
92
+ ### Dual-Emit Architecture
93
+
94
+ Every call produces two outputs automatically:
95
+
96
+ ```
97
+ instrumentor.inject_success(...)
98
+ |
99
+ +---> AIGP Event (JSON) ---> event_callback (AI governance store)
100
+ |
101
+ +---> OTel Span Event -----> OTel-compatible observability backend
102
+ ```
103
+
104
+ ### Supported Event Types
105
+
106
+ | Method | AIGP Event Type | OTel Span Event |
107
+ |--------|----------------|-----------------|
108
+ | `inject_success()` | `INJECT_SUCCESS` | `aigp.inject.success` |
109
+ | `inject_denied()` | `INJECT_DENIED` | `aigp.inject.denied` |
110
+ | `prompt_used()` | `PROMPT_USED` | `aigp.prompt.used` |
111
+ | `prompt_denied()` | `PROMPT_DENIED` | `aigp.prompt.denied` |
112
+ | `policy_violation()` | `POLICY_VIOLATION` | `aigp.policy.violation` |
113
+ | `a2a_call()` | `A2A_CALL` | `aigp.a2a.call` |
114
+ | `governance_proof()` | `GOVERNANCE_PROOF` | `aigp.governance.proof` |
115
+ | `multi_policy_inject()` | `INJECT_SUCCESS` | `aigp.inject.success` (with array attributes) |
116
+ | `multi_resource_governance_proof()` | `GOVERNANCE_PROOF` | `aigp.governance.proof` (with Merkle tree) |
117
+
118
+ ### Multi-Policy / Multi-Prompt Support
119
+
120
+ When an agent is governed by multiple policies simultaneously:
121
+
122
+ ```python
123
+ event = instrumentor.multi_policy_inject(
124
+ policies=[
125
+ {"name": "policy.trading-limits", "version": 4},
126
+ {"name": "policy.risk-controls", "version": 2},
127
+ ],
128
+ content="Combined governed content...",
129
+ data_classification="confidential",
130
+ )
131
+ ```
132
+
133
+ This produces OTel array-valued attributes:
134
+ ```
135
+ aigp.policies.names = ["policy.trading-limits", "policy.risk-controls"]
136
+ aigp.policies.versions = [4, 2]
137
+ ```
138
+
139
+ ### Merkle Tree Governance Hash
140
+
141
+ When an agent is governed by multiple resources (policies, prompts, tools, contexts, lineage), compute a Merkle tree for per-resource verification:
142
+
143
+ ```python
144
+ from aigp.events import compute_merkle_governance_hash
145
+
146
+ resources = [
147
+ ("policy", "policy.refund-limits", "Refund max: $500..."),
148
+ ("prompt", "prompt.customer-support-v3", "You are a helpful..."),
149
+ ("tool", "tool.order-lookup", '{"name": "order-lookup", "scope": "read"}'),
150
+ ("context", "context.env-config", '{"env": "production", "region": "us-east-1"}'),
151
+ ("lineage", "lineage.upstream-orders", '{"datasets": ["orders", "customers"]}'),
152
+ ]
153
+
154
+ root_hash, merkle_tree = compute_merkle_governance_hash(resources)
155
+ # root_hash: "a3f2b8..." (Merkle root, used as governance_hash)
156
+ # merkle_tree: {"algorithm": "sha256", "leaf_count": 5, "leaves": [...]}
157
+ ```
158
+
159
+ Or use the instrumentor for triple-emit with Merkle:
160
+
161
+ ```python
162
+ event = instrumentor.multi_resource_governance_proof(
163
+ resources=[
164
+ ("policy", "policy.refund-limits", "Refund max: $500..."),
165
+ ("prompt", "prompt.customer-support-v3", "You are a helpful..."),
166
+ ("tool", "tool.order-lookup", '{"name": "order-lookup"}'),
167
+ ("context", "context.env-config", '{"env": "production"}'),
168
+ ("lineage", "lineage.upstream-orders", '{"datasets": ["orders"]}'),
169
+ ],
170
+ data_classification="confidential",
171
+ )
172
+ # governance_hash is the Merkle root
173
+ # hash_type is "merkle-sha256"
174
+ # governance_merkle_tree contains per-resource leaf hashes
175
+ # OTel span event carries aigp.governance.merkle.leaf_count
176
+ ```
177
+
178
+ Single-resource calls continue to produce flat SHA-256 hashes for full backward compatibility.
179
+
180
+ ### Baggage Propagation (Agent-to-Agent)
181
+
182
+ ```python
183
+ from aigp.baggage import AIGPBaggage
184
+
185
+ # Calling agent: inject governance context
186
+ ctx = AIGPBaggage.inject(
187
+ policy_name="policy.trading-limits",
188
+ data_classification="confidential",
189
+ org_id="org.finco",
190
+ )
191
+
192
+ # Receiving agent: extract governance context
193
+ extracted = AIGPBaggage.extract()
194
+ # {'aigp.policy.name': 'policy.trading-limits', ...}
195
+ ```
196
+
197
+ ### W3C tracestate Vendor Key
198
+
199
+ ```python
200
+ from aigp.tracestate import AIGPTraceState
201
+
202
+ # Encode AIGP into tracestate
203
+ tracestate = AIGPTraceState.inject_into_tracestate(
204
+ existing_tracestate="dd=s:1",
205
+ data_classification="confidential",
206
+ policy_name="policy.trading-limits",
207
+ policy_version=4,
208
+ )
209
+ # "aigp=cls:con;pol:policy.trading-limits;ver:4,dd=s:1"
210
+
211
+ # Decode on receiving side
212
+ context = AIGPTraceState.extract_from_tracestate(tracestate)
213
+ # {'data_classification': 'confidential', 'policy_name': 'policy.trading-limits', ...}
214
+ ```
215
+
216
+ ### OpenLineage Facet Builder
217
+
218
+ ```python
219
+ from aigp.openlineage import build_openlineage_run_event
220
+ from aigp.events import compute_merkle_governance_hash, create_aigp_event
221
+
222
+ # Context + lineage resources as governed inputs
223
+ resources = [
224
+ ("policy", "policy.fair-lending", policy_content),
225
+ ("prompt", "prompt.scoring-v3", prompt_content),
226
+ ("context", "context.env-config", env_config_json),
227
+ ("lineage", "lineage.upstream-orders", lineage_snapshot_json),
228
+ ]
229
+ root, tree = compute_merkle_governance_hash(resources)
230
+
231
+ aigp_event = create_aigp_event(
232
+ event_type="GOVERNANCE_PROOF",
233
+ event_category="governance-proof",
234
+ agent_id="agent.credit-scorer-v2",
235
+ trace_id="abc123...",
236
+ governance_hash=root,
237
+ hash_type="merkle-sha256",
238
+ governance_merkle_tree=tree,
239
+ )
240
+
241
+ # Build OpenLineage RunEvent (zero OL dependency)
242
+ ol_event = build_openlineage_run_event(
243
+ aigp_event,
244
+ job_namespace="finco.scoring",
245
+ job_name="credit-scorer-v2.invoke",
246
+ )
247
+ # Send to any OpenLineage-compatible lineage backend
248
+ ```
249
+
250
+ ## Modules
251
+
252
+ | Module | Purpose |
253
+ |--------|---------|
254
+ | `aigp.instrumentor` | Core triple-emit bridge (`AIGPInstrumentor`) |
255
+ | `aigp.attributes` | `aigp.*` semantic attribute constants |
256
+ | `aigp.events` | AIGP event creation, hash computation, and Merkle tree |
257
+ | `aigp.openlineage` | OpenLineage facet builder (zero OL dependency) |
258
+ | `aigp.baggage` | OTel Baggage propagation for A2A calls |
259
+ | `aigp.tracestate` | W3C tracestate vendor key encode/decode |
260
+
261
+ ## Running Tests
262
+
263
+ ```bash
264
+ cd sdks/python
265
+ pip install opentelemetry-api opentelemetry-sdk pytest
266
+ PYTHONPATH=. pytest tests/ -v
267
+ ```
268
+
269
+ ## Running the End-to-End Example
270
+
271
+ ```bash
272
+ cd sdks/python
273
+ PYTHONPATH=. python examples/end_to_end.py
274
+ ```
275
+
276
+ ## Related Documentation
277
+
278
+ - [AIGP Specification](../../spec/aigp-spec.md) (Sections 11.4-11.7)
279
+ - [AIGP OTel Semantic Conventions](../../integrations/opentelemetry/semantic-conventions.md)
280
+ - [OTel Collector Reference Config](../../integrations/opentelemetry/collector-config.yaml)
@@ -0,0 +1,12 @@
1
+ aigp/__init__.py,sha256=ie0jSZRTFaQwYn2BPse1OBTnZWp-bgXecEhyQQyfOOk,3824
2
+ aigp/attributes.py,sha256=RI04tuQJMLEQTfKj3ELqIJDDqClXEHcRbd6Fy_LceuA,5345
3
+ aigp/baggage.py,sha256=u3pUVWDNbNra1P_qUkFs-NhkcGs3w3cG6sIugP8DP_s,3820
4
+ aigp/cloudevents.py,sha256=ltokwfuB5Qm5Vc9y8K_s20wsCUN05aVAM4KcNexIVrY,7718
5
+ aigp/decorators.py,sha256=e6w9nNGF0F8Qqa3ip0ALUwyICvTnEvbStzwKlA2xTxc,26129
6
+ aigp/events.py,sha256=tuDXWL5j5qGMmDeDfJMFKPILkpMoYU3cncHnFpn0fnI,19392
7
+ aigp/instrumentor.py,sha256=zKJzZCBMAuxB7evD2AAvw1zSkl6qz3eOlKAGNvp-hxs,50179
8
+ aigp/openlineage.py,sha256=fEtXa75caZualGZcJvbdZlMRaiYtr2IPNplHQkFNPw4,8669
9
+ aigp/tracestate.py,sha256=cim3Bl2KFp0HQuUduXpH21qLDO86NS81og967HpTGQM,4581
10
+ aigp_python-1.0.0.dist-info/METADATA,sha256=FqaIkQ9Di_ZlkAYyYgCsjz5aeoCK_ZxVmLtcY0miqio,9520
11
+ aigp_python-1.0.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
12
+ aigp_python-1.0.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.28.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any