tapes_harnesses/lib.rs
1#![doc = include_str!("../README.md")]
2//!
3//! # Module map
4//!
5//! Exactly three places hold harness knowledge; this crate is one of them (the
6//! deriver and the envelope spec/fixtures are the other two). It owns these
7//! responsibilities:
8//!
9//! - [`harness`] — the registry: one declaration per harness, bundling its id,
10//! User-Agent rule, launch support, attribution strategy, transcript
11//! location, and plugin needs. The other modules take their harness ids from
12//! it and consumers derive their supported-agent lists from it, so adding a
13//! harness starts in exactly one place.
14//! - [`launch`] — per-harness env/config injection to run a harness under a
15//! capture proxy.
16//! - [`config`] — persistent harness-config patch grammars: how an installer
17//! patches a capture provider into a harness's *own* config file,
18//! idempotently and preserving the user's content. Where [`launch`] plans
19//! per-process config that dies with the process, this module owns the
20//! durable install a desktop app or long-lived integration needs.
21//! - [`plugin`] — the artifacts a harness with no base-URL knob needs installed
22//! *into* it before capture is possible at all, and the environment contract
23//! those artifacts read. Consumers are installers; the bytes live here so
24//! every client installs the same ones.
25//! - [`attribution`] — session-file reads, fork-parent recovery, peer-PID
26//! lookup, the peer-trust ancestry walk, and the codex session watcher,
27//! grouped per harness.
28//! - [`transcript`] — discovering and packaging harness transcripts for the
29//! `POST /v1/ingest/transcript` lane.
30//!
31//! # The three ways a harness gets captured
32//!
33//! This is the distinction to hold on to, because it decides which modules
34//! apply to a given harness. It correlates with
35//! [`harness::AttributionStrategy`] but is not the same axis: that enum says
36//! how a request acquires an identity, this says how the traffic is reached at
37//! all.
38//!
39//! | mechanism | when it applies | plan it with |
40//! | --- | --- | --- |
41//! | **Launch redirect** — point the harness's base-URL knob at a proxy | the harness has such a knob (`claude`, `codex`, `opencode`) | [`launch`] |
42//! | **Installed plugin** — code runs *inside* the harness and stamps its own envelope | the harness has no such knob (`pi`) | [`plugin`] |
43//! | **Lifecycle hooks** — a hook plugin reports allowlisted evidence at session boundaries | the harness is configured rather than launched (`codex-app`) | [`plugin::codex_app`] and [`config`] |
44//!
45//! A harness declares which of these it needs through
46//! [`harness::LaunchSupport`] and [`harness::PluginDelivery`]; nothing here
47//! infers it.
48//!
49//! # What is *not* here
50//!
51//! Everything above changes when a harness is added. The parts of capture that
52//! do not live in [`tapes_capture`], which this crate depends on: the
53//! `X-Tapes-*` envelope producer and its harness-id vocabulary, the
54//! capture-gateway environment contract and launch-nonce protocol, peer-PID
55//! lookup, and the peer-trust ancestry check. The edge runs one way by
56//! construction: a harness module may reach for a capture primitive, and
57//! nothing over there can reach back, because the moment a capture primitive
58//! knows a harness's name it stops being the thing every harness shares.
59//!
60//! The envelope is the sharpest case, because the arrow points the way that
61//! first looks backwards. Harness *ids* are envelope vocabulary — they are what
62//! goes on the wire — so [`harness`] takes its ids from `tapes_capture` rather
63//! than declaring them and having the envelope import them back. Reading them
64//! the other way is what used to make the two mutually dependent, and it is why
65//! the producer now asks for a `tapes_capture::HarnessSession` instead of
66//! naming any harness's session type.
67//!
68//! # Provenance
69//!
70//! [`attribution`] is extracted from a daemon client's proxy session layer —
71//! the code that validated peer-PID attribution and fork-parent discovery
72//! against real Claude and Codex traffic.
73//!
74//! [`launch`] is extracted from the same client's per-agent env/config
75//! injection, with the Go `tapes start` opencode/codex knowledge folded in —
76//! including opencode, which that client never supported. Its recipes are pure:
77//! they plan argv, environment, and config documents, and the consumer owns
78//! process spawning and cleanup.
79//!
80//! [`transcript`] is extracted from that client's transcript uploader — its
81//! discovery/packaging half, the push trigger, and the ingest payload shape —
82//! and adds a startup sweep of the transcript tree, which closes a gap every
83//! daemon client has: a session that began and ended while the daemon was down
84//! is never re-registered by live traffic, so its fork skeleton was previously
85//! lost. Delivery, auth, and retry stay in each client.
86//!
87//! # Names
88//!
89//! The repository is `tapes-crates`; this crate is one of its four members.
90//! There is no `tapes-harness` crate — the singular spelling is reserved as a
91//! stub redirect so the near-miss cannot be claimed by someone else. `tapes` is
92//! a different repository entirely: the server a capture client ships to.
93#![warn(missing_docs)]
94
95pub mod attribution;
96pub mod config;
97pub mod harness;
98pub mod launch;
99pub mod plugin;
100pub mod transcript;