docs(adr-0010): add amendments for OQ2, OQ3, OQ4 resolutions#331
Merged
ojongerius merged 7 commits intomainfrom May 6, 2026
Merged
docs(adr-0010): add amendments for OQ2, OQ3, OQ4 resolutions#331ojongerius merged 7 commits intomainfrom
ojongerius merged 7 commits intomainfrom
Conversation
Add three amendments to ADR-0010 resolving open design questions: OQ2 — Existing chain migration: Abandon v1 chains on upgrade. No in-place migration or import-chain script. Cost: auditors must preserve old SQLite DBs offline for long-term pre-Phase-2 audit. Rationale: pre-1.0 adoption stage, no production audit dependencies blocking a clean break, and migration logic compounds the Section 3 refactor burden. OQ3 — SDK cutover sequencing: Single-shot PR/release (all SDKs + OpenClaw + mcp-proxy at once). No phased rollout. Rationale: phased introduction creates a mixed-state window (v1 and v2 emitters writing separate chains), violating the daemon's single-unified-chain property. Single-shot on day one ensures all emitters speak the same schema. OQ4 — session_id rule: UUID generated at emitter startup, reused across all tool calls within process lifetime, persistent across daemon reconnects but retired on emitter exit. Uniform rule across all three SDKs. Cardinality: few long-lived sessions per deployment (~1–10 unique IDs/day). Indexing: add non-unique index on Issuer.SessionID for filtering by session. All three are design decisions only; no spec changes, no code changes. The amendments establish normative rules for the Section 3 emitter refactor and SDK major version bump.
Contributor
There was a problem hiding this comment.
Pull request overview
This PR amends ADR-0010 with resolutions for three previously-open design questions (OQ2/OQ3/OQ4) that gate the upcoming Section 3 “thin-emitter refactor”, clarifying migration policy, cutover sequencing, and session_id allocation.
Changes:
- Add OQ2 decision to abandon v1 in-process chains and start a fresh daemon-managed chain at
seq=1(no migration/import tooling). - Add OQ3 decision to do a single-shot cutover/release across all SDKs and integrations to avoid mixed-state multi-chain audit gaps.
- Add OQ4 decision defining
session_idgeneration and lifetime, plus notes on expected cardinality and planned indexing.
Comment on lines
+146
to
+165
| **Decision:** Each emitter process MUST generate a unique `session_id` (UUID v4 or v5) at startup and include it in every frame sent to the daemon. The `session_id` remains constant across daemon reconnects and process-local (lifetime of the emitter process). The daemon records `session_id` faithfully; verifiers MUST treat it as an advisory grouping hint, not a cryptographic boundary. | ||
|
|
||
| **Rationale:** | ||
| - **At startup (not per-run):** Agents invoke multiple tool calls within a single logical session. One `session_id` per agent-run would fragment a logical audit session into N receipts with N identifiers. Grouping by emitter-process lifetime naturally clusters tool calls. | ||
| - **Persistent across daemon reconnect:** The emitter holds the session_id in memory. If the daemon restarts or the network drops and reconnects, the emitter retransmits with the same `session_id`, keeping receipts logically grouped. No persist-to-disk is required (the session_id dies with the emitter process). | ||
| - **Uniform across SDKs:** All three SDK emitters (Go, TS, Py) and integration points (mcp-proxy, OpenClaw) initialize `session_id` at construction time; never generate a new one per emit(). | ||
|
|
||
| **Cardinality and indexing:** | ||
| - **Expected cardinality:** Few long-lived sessions per deployment. An agent session lasts minutes to hours; an emitter process lasts the lifetime of the agent (or MCP proxy). Database sees ~1–10 unique `session_id` values per day in typical usage. | ||
| - **Index:** Add a non-unique index on `Issuer.SessionID` in the receipts table to support queries like `SELECT * FROM receipts WHERE issuer_session_id = ?`. | ||
|
|
||
| **Normative spec line:** | ||
| > "Each emitter process MUST generate a unique `session_id` (UUID) at startup and include it in every frame sent to the daemon. The `session_id` remains constant across daemon reconnects, process-local (survives only the lifetime of the emitter process). The daemon makes no guarantee that `session_id` values are unique across deployments or across time, only that it records the value faithfully." | ||
|
|
||
| **SDK author guideline:** | ||
| > "Initialize `session_id` once per emitter/SDK instance at construction time using a UUID (v4 or v5). Do not generate a new session_id on each emit(). Reuse the same session_id across all tool calls and daemon reconnects within the process lifetime. No persistence to disk is required." | ||
|
|
||
| **Spec/code changes:** | ||
| - All three SDKs emit the same `session_id` for their process lifetime; no SDK-specific logic. | ||
| - Daemon: no new logic (session_id is already captured in `receipt.Issuer.SessionID`). Add the `session_id` index on the next SQLite schema version. |
Comment on lines
+153
to
+156
| **Cardinality and indexing:** | ||
| - **Expected cardinality:** Few long-lived sessions per deployment. An agent session lasts minutes to hours; an emitter process lasts the lifetime of the agent (or MCP proxy). Database sees ~1–10 unique `session_id` values per day in typical usage. | ||
| - **Index:** Add a non-unique index on `Issuer.SessionID` in the receipts table to support queries like `SELECT * FROM receipts WHERE issuer_session_id = ?`. | ||
|
|
- Line 53 (Schema split): Clarify that session_id is generated by emitter at startup and persists for emitter-process lifetime (not per-agent-run), with detailed allocation rule in OQ4 amendment. - Line 155 (OQ4 Cardinality and indexing): Clarify that schema extraction and indexing of session_id are deferred to Phase 2 Section 3, not blocking the design decision. For now, session_id is only in the receipt JSON. - Line 166 (OQ4 Spec/code changes): Update to note that CLI filtering is deferred pending the schema extraction. Resolves Copilot comments on inconsistent session_id scoping and unclear indexing strategy.
6 tasks
|
|
||
| **Consequences:** | ||
| - Auditors must preserve v1 SQLite databases offline if they require long-term audit of pre-Phase-2 events. This is a one-time notice; v2 chains persist continuously under daemon supervision. | ||
| - v1 receipt verification becomes impossible post-upgrade (no daemon will hold v1 public keys or databases). |
Comment on lines
+114
to
+115
| **Decision:** v1 users have per-emitter SQLite databases. Phase 2 (Section 3, thin-emitter refactor) will abandon existing v1 chains and start a fresh daemon-managed chain at `seq=1`. No in-place migration or `import-chain` script. | ||
|
|
Comment on lines
+144
to
+164
| ### 2026-05-06: OQ4 — session_id allocation rule — UUID at startup, persistent across reconnects | ||
|
|
||
| **Decision:** Each emitter process MUST generate a unique `session_id` (UUID v4 or v5) at startup and include it in every frame sent to the daemon. The `session_id` remains constant across daemon reconnects and process-local (lifetime of the emitter process). The daemon records `session_id` faithfully; verifiers MUST treat it as an advisory grouping hint, not a cryptographic boundary. | ||
|
|
||
| **Rationale:** | ||
| - **At startup (not per-run):** Agents invoke multiple tool calls within a single logical session. One `session_id` per agent-run would fragment a logical audit session into N receipts with N identifiers. Grouping by emitter-process lifetime naturally clusters tool calls. | ||
| - **Persistent across daemon reconnect:** The emitter holds the session_id in memory. If the daemon restarts or the network drops and reconnects, the emitter retransmits with the same `session_id`, keeping receipts logically grouped. No persist-to-disk is required (the session_id dies with the emitter process). | ||
| - **Uniform across SDKs:** All three SDK emitters (Go, TS, Py) and integration points (mcp-proxy, OpenClaw) initialize `session_id` at construction time; never generate a new one per emit(). | ||
|
|
||
| **Cardinality and indexing:** | ||
| - **Expected cardinality:** Few long-lived sessions per deployment. An agent session lasts minutes to hours; an emitter process lasts the lifetime of the agent (or MCP proxy). Database sees ~1–10 unique `session_id` values per day in typical usage. | ||
| - **Indexing strategy:** Phase 2 will extract `session_id` into a dedicated (or generated) column and add a non-unique index to support efficient queries like `SELECT * FROM receipts WHERE session_id = ?`. For now, session_id is only in the receipt JSON; extraction is deferred to the Section 3 schema evolution. | ||
|
|
||
| **Normative spec line:** | ||
| > "Each emitter process MUST generate a unique `session_id` (UUID) at startup and include it in every frame sent to the daemon. The `session_id` remains constant across daemon reconnects, process-local (survives only the lifetime of the emitter process). The daemon makes no guarantee that `session_id` values are unique across deployments or across time, only that it records the value faithfully." | ||
|
|
||
| **SDK author guideline:** | ||
| > "Initialize `session_id` once per emitter/SDK instance at construction time using a UUID (v4 or v5). Do not generate a new session_id on each emit(). Reuse the same session_id across all tool calls and daemon reconnects within the process lifetime. No persistence to disk is required." | ||
|
|
||
| **Spec/code changes:** | ||
| - All three SDKs emit the same `session_id` for their process lifetime; no SDK-specific logic. |
OQ2 (existing chain migration): - Clarify that v2 daemon/emitters use new default DB/key paths to prevent accidental resume of v1 chains. Operators managing v1/v2 coexistence must keep DBs in separate directories. - Fix consequences: v1 receipts remain cryptographically verifiable offline with preserved v1 DB + public key. v2 tooling will not verify/import v1 chains, not that verification is cryptographically impossible. OQ4 (session_id allocation): - Update normative spec and SDK guideline to accommodate channels with upstream session identifiers (e.g., claude_code_hook forwards Claude Code's session_id per ADR-0013). Flexible rule: forward host-provided session_id if available, otherwise generate UUID v4 at startup. - Specify UUID v4 explicitly (v5 is deterministic and requires namespace). Resolves Copilot comments on OQ2 DB path operational rule, OQ2 v1 verification wording, and OQ4 conflict with ADR-0013.
Comment on lines
+112
to
+114
| ### 2026-05-06: OQ2 — Existing chain migration policy — abandon old chains | ||
|
|
||
| **Decision:** v1 users have per-emitter SQLite databases. Phase 2 (Section 3, thin-emitter refactor) will abandon existing v1 chains and start a fresh daemon-managed chain at `seq=1`. No in-place migration or `import-chain` script. The daemon and emitters use new default DB/key paths (separate from v1) to ensure accidental resume does not happen; operators who want to preserve v1 chains for verification must keep v1 and v2 DBs in separate directories. |
Comment on lines
+158
to
+161
| > "Each emitter process MUST provide a stable `session_id` (UUID) for its logical session. If the host or parent process provides a session identifier (e.g., Claude Code's session ID, an agent-loop context ID), forward it unchanged. Otherwise, generate a new UUID v4 at emitter startup. The `session_id` remains constant across daemon reconnects, process-local (survives only the lifetime of the emitter process). The daemon makes no guarantee that `session_id` values are unique across deployments or across time, only that it records the value faithfully." | ||
|
|
||
| **SDK author guideline:** | ||
| > "Initialize `session_id` once per emitter/SDK instance at construction time. If the host provides a session identifier, use it; otherwise generate a new UUID v4. Do not generate a new session_id on each emit(). Reuse the same session_id across all tool calls and daemon reconnects within the process lifetime. No persistence to disk is required." |
Comment on lines
+118
to
+120
| **Consequences:** | ||
| - Auditors must preserve v1 SQLite databases and matching public keys offline if they require long-term audit of pre-Phase-2 events. V1 receipts remain cryptographically verifiable with those artifacts; v2 tooling and daemon will not verify or import v1 chains. | ||
| - v1 chains are not resumed on v2 daemon startup (separate DB/key paths prevent accidental coexistence). |
Comment on lines
+151
to
+164
| - **Uniform across SDKs:** All three SDK emitters (Go, TS, Py) and integration points (mcp-proxy, OpenClaw) initialize `session_id` at construction time; never generate a new one per emit(). | ||
|
|
||
| **Cardinality and indexing:** | ||
| - **Expected cardinality:** Few long-lived sessions per deployment. An agent session lasts minutes to hours; an emitter process lasts the lifetime of the agent (or MCP proxy). Database sees ~1–10 unique `session_id` values per day in typical usage. | ||
| - **Indexing strategy:** Phase 2 will extract `session_id` into a dedicated (or generated) column and add a non-unique index to support efficient queries like `SELECT * FROM receipts WHERE session_id = ?`. For now, session_id is only in the receipt JSON; extraction is deferred to the Section 3 schema evolution. | ||
|
|
||
| **Normative spec line:** | ||
| > "Each emitter process MUST provide a stable `session_id` (UUID) for its logical session. If the host or parent process provides a session identifier (e.g., Claude Code's session ID, an agent-loop context ID), forward it unchanged. Otherwise, generate a new UUID v4 at emitter startup. The `session_id` remains constant across daemon reconnects, process-local (survives only the lifetime of the emitter process). The daemon makes no guarantee that `session_id` values are unique across deployments or across time, only that it records the value faithfully." | ||
|
|
||
| **SDK author guideline:** | ||
| > "Initialize `session_id` once per emitter/SDK instance at construction time. If the host provides a session identifier, use it; otherwise generate a new UUID v4. Do not generate a new session_id on each emit(). Reuse the same session_id across all tool calls and daemon reconnects within the process lifetime. No persistence to disk is required." | ||
|
|
||
| **Spec/code changes:** | ||
| - All three SDKs emit the same `session_id` for their process lifetime; no SDK-specific logic. |
- Line 5 (Status): Update amendment date to include 2026-05-06 for OQ2–OQ4 - Line 146 (OQ4 Decision): Change UUID version to v4 only (was inconsistently v4 or v5), and update decision to reflect forward-or-generate rule (not just generate). Also change scope from process-local to instance-local for clarity. - PR description: Clarify that v1 receipts remain cryptographically verifiable offline with preserved artifacts; v2 tooling simply won't verify them (not that verification becomes impossible). Align with amendment wording. - Lines 149–154 (OQ4 Rationale/Cardinality): Change process-scope language to instance-scope; clarify that multiple SDK instances in same process should coordinate on a single session_id; update cardinality description to note typical one-instance-per-run usage. Resolves Copilot issues on UUID inconsistency, scope ambiguity, status line date, and v1 verification contradiction.
| - `v` (schema version) | ||
| - `ts_emit` (RFC 3339, advisory) | ||
| - `session_id` (UUID, scopes one agent run) | ||
| - `session_id` (UUID, generated by emitter at startup, persists for emitter-process lifetime; detailed allocation rule in the *Amendments* section, OQ4) |
Comment on lines
+144
to
+147
| ### 2026-05-06: OQ4 — session_id allocation rule — UUID at startup, persistent across reconnects | ||
|
|
||
| **Decision:** Each emitter process MUST provide a stable `session_id` (UUID v4) for its logical session. If the host provides a session identifier, forward it unchanged. Otherwise, generate a new UUID v4 at startup. The `session_id` remains constant across daemon reconnects and instance-local (lifetime of the emitter instance). The daemon records `session_id` faithfully; verifiers MUST treat it as an advisory grouping hint, not a cryptographic boundary. | ||
|
|
Comment on lines
+158
to
+166
| > "Each emitter process MUST provide a stable `session_id` (UUID) for its logical session. If the host or parent process provides a session identifier (e.g., Claude Code's session ID, an agent-loop context ID), forward it unchanged. Otherwise, generate a new UUID v4 at emitter startup. The `session_id` remains constant across daemon reconnects, process-local (survives only the lifetime of the emitter process). The daemon makes no guarantee that `session_id` values are unique across deployments or across time, only that it records the value faithfully." | ||
|
|
||
| **SDK author guideline:** | ||
| > "Initialize `session_id` once per emitter/SDK instance at construction time. If the host provides a session identifier, use it; otherwise generate a new UUID v4. Do not generate a new session_id on each emit(). Reuse the same session_id across all tool calls and daemon reconnects within the process lifetime. No persistence to disk is required." | ||
|
|
||
| **Spec/code changes:** | ||
| - All three SDKs emit the same `session_id` for their process lifetime; no SDK-specific logic. | ||
| - Daemon: no new logic (session_id is already captured in `receipt.Issuer.SessionID`). Schema extraction and indexing deferred to Section 3 (Phase 2). | ||
| - Agent-receipts verify CLI can filter by session_id (e.g., `--session <UUID>`) once the schema extraction lands. |
Resolve Copilot's type consistency issue: session_id was defined as UUID in Schema split and OQ4 Decision, but OQ4 also allows forwarding host-provided IDs that may not be UUIDs. Solution: Define session_id as an opaque string. When emitter-generated, recommend UUID v4 (future-proof, matches current systems like Claude Code). When host-provided, forward unchanged (forward-compatible for channels with custom session identifier formats). Changes: - Line 53 (Schema split): opaque string, UUID v4 recommended - Line 146 (OQ4 Decision): opaque string, forward or generate v4 - Lines 158, 161 (normative spec + SDK guideline): opaque string, UUID v4 recommended for generated IDs This maintains type safety for generated IDs while remaining flexible for host-provided identifiers.
|
|
||
| **Spec/code changes:** | ||
| - No migration logic in daemon startup. | ||
| - No schema migrations or data import tooling in `sdk/go/store`. |
|
|
||
| **Decision:** v1 users have per-emitter SQLite databases. Phase 2 (Section 3, thin-emitter refactor) will abandon existing v1 chains and start a fresh daemon-managed chain at `seq=1`. No in-place migration or `import-chain` script. The daemon and emitters use new default DB/key paths (separate from v1) to ensure accidental resume does not happen; operators who want to preserve v1 chains for verification must keep v1 and v2 DBs in separate directories. | ||
|
|
||
| **Rationale:** Agent-Receipts is pre-1.0 with early-stage adoption (solo dev and lab usage). No production audit dependencies exist that would prohibit a clean break, and the cost of migration logic (either in-process DB surgery during daemon startup or a separate import tool) compounds the already-substantial emitter refactor burden of Section 3. |
|
|
||
| ### 2026-05-06: OQ4 — session_id allocation rule — UUID at startup, persistent across reconnects | ||
|
|
||
| **Decision:** Each emitter process MUST provide a stable `session_id` (opaque string) for its logical session. If the host provides a session identifier, forward it unchanged. Otherwise, generate a new UUID v4 at startup (recommended form for generated IDs). The `session_id` remains constant across daemon reconnects and instance-local (lifetime of the emitter instance). The daemon records `session_id` faithfully; verifiers MUST treat it as an advisory grouping hint, not a cryptographic boundary. |
Line 125: Change "No schema migrations or data import tooling in sdk/go/store" to "No v1→v2 chain migration or data import tooling in sdk/go/store (schema migrations for other purposes are unaffected)". The original wording sounded like the store avoids all migrations, when the intent is specifically to avoid v1-to-v2 chain migration logic. Store schema migrations (like migrateToolName) continue unchanged.
Comment on lines
3
to
6
| ## Status | ||
|
|
||
| Accepted (2026-05-03), amended 2026-05-05 (see *Amendments*) | ||
| Accepted (2026-05-03), amended 2026-05-05 and 2026-05-06 (see *Amendments*) | ||
|
|
| **Rationale:** Agent-Receipts is pre-1.0 with early-stage adoption (solo dev and lab usage). No production audit dependencies exist that would prohibit a clean break, and the cost of migration logic (either in-process DB surgery during daemon startup or a separate import tool) compounds the already-substantial emitter refactor burden of Section 3. | ||
|
|
||
| **Consequences:** | ||
| - Auditors must preserve v1 SQLite databases and matching public keys offline if they require long-term audit of pre-Phase-2 events. V1 receipts remain cryptographically verifiable with those artifacts; v2 tooling and daemon will not verify or import v1 chains. |
| **Decision:** Each emitter process MUST provide a stable `session_id` (opaque string) for its logical session. If the host provides a session identifier, forward it unchanged. Otherwise, generate a new UUID v4 at startup (recommended form for generated IDs). The `session_id` remains constant across daemon reconnects and instance-local (lifetime of the emitter instance). The daemon records `session_id` faithfully; verifiers MUST treat it as an advisory grouping hint, not a cryptographic boundary. | ||
|
|
||
| **Rationale:** | ||
| - **At startup (not per-run):** Agents invoke multiple tool calls within a single logical session. One `session_id` per agent-run would fragment a logical audit session into N receipts with N identifiers. Grouping by emitter-instance lifetime naturally clusters tool calls. |
…s, wording) ADR-0010 fixes: - Line 116 (OQ2 rationale): Change "Agent-Receipts" to "agent-receipts" for consistency with rest of doc. - Line 146 (OQ4 Decision): Fix grammar "and instance-local" → "and is instance-local". - Line 119 (OQ2 Consequences): Clarify that v2 daemon does not automatically resume/import v1 chains (separate DB paths prevent coexistence), not that cryptographic verification is impossible. V1 receipts remain verifiable offline with preserved artifacts. - Line 149 (OQ4 Rationale): Rephrase "One session_id per agent-run..." to "Generating per tool call would fragment..." for clarity. Make the constraint about instance-lifetime obviously correct, not backwards. Status updates (ADR-0010 is now Accepted, Phase 1 shipped): - docs/adr/README.md line 26: Change ADR-0010 status from Proposed → Accepted - docs/threat-model.md line 3: Update "Both ADRs are Proposed" to "ADR-0010 Accepted, ADR-0015 Proposed" to reflect current status. - docs/threat-model.md line 130: Update roadmap table to "Accepted; Phase 1 shipped, Phase 2+ pending" for ADR-0010. Resolves all 5 Copilot review issues from latest pass.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Add three amendments to ADR-0010 resolving the open design questions that gate Section 3 (thin-emitter refactor). No code changes, design resolutions only.
OQ2 — Existing chain migration policy:
OQ3 — SDK cutover sequencing:
OQ4 — session_id allocation rule:
session_idfor efficient filtering (Phase 2).All three resolutions are prerequisites for Section 3 emitter refactor (issue #236). Resolving them here prevents mid-PR re-litigation of the design choices.
Related
agent-receipts verifyand 0644 public-key publishing #325, ci: add daemon module workflow #326, docs(adr-0010): amend IPC framing and default socket paths to match shipped defaults #327, daemon(socket): populate peer.exe_path on macOS via SYS_PROC_INFO syscall #328) being merged