|
| 1 | +--- |
| 2 | +title: pole-instrument Technical Design |
| 3 | +tags: [architecture, python, instrumentation, launcher] |
| 4 | +links: [pole-python-monorepo, todo] |
| 5 | +updated: 2026-08-18 |
| 6 | +sources: 4 |
| 7 | +--- |
| 8 | + |
| 9 | +# pole-instrument Technical Design |
| 10 | + |
| 11 | +Status: Proposed. This document fixes the product boundary and runtime contracts that an implementation must satisfy. Shipping remains blocked on the open framework and failure-default decisions below. |
| 12 | + |
| 13 | +## Scope and packaging boundary |
| 14 | + |
| 15 | +`pole-instrument` is a future executable supplied by a separate `pole-instrument` distribution. Its implementation modules live under `pole_instrument` and depend on, but are not imported by, `pole-client-python`. The existing `pole_client` import, exports, version, and explicit-use behavior do not change. Installing or importing `pole_client` never activates instrumentation. |
| 16 | + |
| 17 | +## Activation contract |
| 18 | + |
| 19 | +The proposed command is `pole-instrument [launcher options] -- application [arguments...]`. The separator is mandatory. Argument forwarding preserves every application argument at the Python string boundary and does not log it. Environment variables are inherited except for explicit launcher configuration. Startup validates configuration, loads one selected adapter, installs it, and starts the application. Shutdown unpatches and closes launcher resources. Signals and the application's exit status are preserved. Exit codes are `2` for launcher syntax or validation and `70` for configured fail-closed activation failure. |
| 20 | + |
| 21 | +### Adapter interface |
| 22 | + |
| 23 | +An adapter exposes `install(runtime) -> handle`; the handle exposes idempotent `unpatch()`. The Adapter boundary applies outbound target metadata and TrafficContext baggage only at documented request hooks. No concrete framework adapter is approved or advertised. |
| 24 | + |
| 25 | +## Existing SDK integration |
| 26 | + |
| 27 | +The launcher creates at most one `SidecarSession` per live process and never duplicates UDS logic. Adapters request current endpoints, construct `TargetService` metadata, and use `TrafficContext` extraction, scope, and injection. A disconnect invalidates availability immediately; no stale endpoint may be cached or reused. |
| 28 | + |
| 29 | +## Failure policy |
| 30 | + |
| 31 | +The implementation exposes `fail-open` and `fail-closed`; the default remains an Open question. |
| 32 | + |
| 33 | +| Failure | Fail-open | Fail-closed | |
| 34 | +| --- | --- | --- | |
| 35 | +| Launcher setup or validation | exit `2` | exit `2` | |
| 36 | +| Sidecar unavailable | warn and run uninstrumented | error and exit `70` | |
| 37 | +| Invalid listener snapshot | discard it, warn, run without route | error and exit `70` | |
| 38 | +| Adapter import or patch | roll back partial patch and continue | roll back and exit `70` | |
| 39 | +| Runtime callback | contain, warn, call original path | error and terminate only at a safe boundary | |
| 40 | +| Sidecar disconnect | invalidate routes and continue | invalidate routes and request controlled termination | |
| 41 | + |
| 42 | +## Patch lifecycle and Import order |
| 43 | + |
| 44 | +Patching occurs after validation and before application import. Activation is idempotent for the same adapter/configuration; conflicts fail. Every patch registers its inverse, and partial patch failure rolls back in reverse order. Shutdown and test isolation call `unpatch()`; repeated cleanup is harmless. Library imports have no patch side effect. |
| 45 | + |
| 46 | +## Process model |
| 47 | + |
| 48 | +An `exec` requires activation in the replacement program. A subprocess is instrumented only when it opts into the launcher. A pre-fork parent may validate and import adapter code but owns no active thread or gRPC channel. Each post-fork child installs hooks and creates a fresh SidecarSession. Fork after activation is unsupported: the child disables inherited handles and emits a stable diagnostic until explicit reactivation. |
| 49 | + |
| 50 | +## Configuration |
| 51 | + |
| 52 | +Precedence is `CLI > environment > default`. |
| 53 | + |
| 54 | +| Meaning | CLI | Environment | Default / validation | |
| 55 | +| --- | --- | --- | --- | |
| 56 | +| Adapter | `--adapter` | `POLE_INSTRUMENT_ADAPTER` | required registered name | |
| 57 | +| Failure mode | `--failure-mode` | `POLE_INSTRUMENT_FAILURE_MODE` | unresolved; two fixed values | |
| 58 | +| Socket | `--sidecar-socket` | `POLE_SIDECAR_SOCKET` | `resolve_sidecar_socket()` semantics | |
| 59 | +| Level | `--log-level` | `POLE_INSTRUMENT_LOG_LEVEL` | `warning`; fixed levels | |
| 60 | + |
| 61 | +Empty explicit values fail validation. Secrets, credentials, metadata, application arguments, and baggage values are not logged. |
| 62 | + |
| 63 | +## Diagnostics |
| 64 | + |
| 65 | +Diagnostics are bounded structured records on stderr with timestamp, severity, stable diagnostic ID, component, and redacted message. Categories cover activation, unsupported adapters, Sidecar availability, snapshots, patch failure, callbacks, fork misuse, and shutdown. Records redact secrets and carrier/baggage data and are rate-limited by ID. |
| 66 | + |
| 67 | +## Compatibility and artifacts |
| 68 | + |
| 69 | +The baseline is Python >=3.9. Optional dependencies are isolated by adapter extras or distributions and are not eagerly imported. A future wheel/sdist contains `pole_instrument` and its entry point. Existing `pole-client-python` artifacts still exclude `sitecustomize.py`, `usercustomize.py`, `.pth`, the launcher, adapter dependencies, and implicit patches. |
| 70 | + |
| 71 | +## Verification matrix |
| 72 | + |
| 73 | +| Contract | Required automated evidence before release | |
| 74 | +| --- | --- | |
| 75 | +| Launcher | argv, environment, streams, signals, and exit codes | |
| 76 | +| Failure policy | every failure in both modes | |
| 77 | +| Sidecar disconnect | real UDS test proving no stale endpoint reuse | |
| 78 | +| Idempotency | duplicate activation, rollback, repeated unpatch | |
| 79 | +| Propagation | public Adapter boundary target and baggage test | |
| 80 | +| Fork | pre-fork/post-fork, spawn, subprocess, exec fresh-process tests | |
| 81 | +| Diagnostics | IDs, severity, rate limiting, redaction | |
| 82 | +| Package isolation | wheel/sdist and clean-environment tests | |
| 83 | +| Compatibility | every advertised Python/framework pair | |
| 84 | + |
| 85 | +## Non-goals |
| 86 | + |
| 87 | +- Selecting framework-specific support. |
| 88 | +- automatic sitecustomize activation or `.pth` activation. |
| 89 | +- changes to pole_client public behavior. |
| 90 | +- Instrumenting subprocesses without explicit opt-in. |
| 91 | + |
| 92 | +## Open questions |
| 93 | + |
| 94 | +- Which frameworks and versions are approved first? |
| 95 | +- Is the default fail-open or fail-closed? |
| 96 | +- Which server integrations offer a reliable post-fork callback? |
| 97 | +- Is a public activation API needed besides the executable? |
| 98 | + |
| 99 | +No production implementation ships until these are accepted and executable compatibility evidence exists. |
| 100 | + |
| 101 | +## Related pages |
| 102 | + |
| 103 | +- [[pole-python-monorepo]] |
| 104 | +- [[todo]] |
0 commit comments