feat(q4): session graph capture foundation — ingest path only#79
Merged
Conversation
Q4 Multiplayer DNA pillar: ship the CAPTURE side of the deferred session graph + replay UI. One structured event per SessionEnd describes a session's shape (tool counts, savings totals, branch SHA, discovery refs touched) — without the transcript or any raw identifier. Privacy is the whole point. What lands: Server - session_events table (sqlite migration; idempotent via UNIQUE(identity_hash, session_id_hash) + (identity_hash, ended_at) and (received_at) indexes for query + retention sweeps). - POST /v1/session-events — unauthenticated (matches /v1/events telemetry + /stats/daily-active WAD-D precedent; identity is already a one-way sha256). Validated via zod; returns 202; ON CONFLICT DO NOTHING for re-emit idempotency. Mounted before user-token middleware. - 10 server tests: happy path, idempotency, optional-field omission, validation failures (bad identity / session / ended_at / tool_count / malformed JSON), discovery_refs persistence, cross-session same- identity yields two rows. Plugin - scripts/session-event-emit.ts — last step in SessionEnd hook chain. Resolves identity_hash via _identity-hash.ts, hashes the local CLAUDE_SESSION_ID (or ppid fallback), reads tool_count + tokens_saved from session buckets in stats.json, best-effort `git rev-parse --short=12 HEAD`, conservative empty discovery_refs (forward-compat). Gated on isTelemetryEnabled(); fire-and-forget POST with internal AbortSignal.timeout(500ms). - hooks/session-end-consolidate.ts — awaitWithBudget(500ms) for the new emit, respecting the 2s SessionEnd safety net (v1.29). - 12 plugin tests: consent gate, payload shape, session_id_hash derivation, branch_sha undefined in non-git dirs / populated in git repos, ppid fallback, sha256 determinism, network-failure quiet path. Deferred (next PR): - Session graph UI / replay viewer - Cross-session aggregator job - Discovery-ref tracking per session (currently emits []) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
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
Q4 Multiplayer DNA pillar: ship the CAPTURE side of the deferred session graph + replay UI. One structured event per
SessionEnddescribes the session's shape (tool counts, savings totals, branch SHA, discovery refs touched) — without the transcript or any raw identifier.This is the ingest foundation. The replay UI / cross-session graph visualization is the follow-up PR.
Privacy model
identity_hashsha256(machine_id + quarterly_salt)github_hashsha256(github_login + quarterly_salt), optionalsession_id_hashbranch_shagit rev-parse HEAD, optionaldiscovery_refstool_count/tokens_savedplugin_versionNever logged: raw identity_hash, raw session_id_hash, raw discovery_refs. Error path emits only a 6-char prefix of identity_hash (PR #67 / PR #73 precedent).
Sample request/response
Idempotent:
ON CONFLICT (identity_hash, session_id_hash) DO NOTHING.What lands
Server
session_eventstable — sqlite migration viaaddSessionEventsTableIfMissing. Indexes:UNIQUE(identity_hash, session_id_hash),(identity_hash, ended_at),(received_at).POST /v1/session-events— unauthenticated (matches/v1/eventsand/stats/daily-active; identity is already a one-way hash). Mounted before user-token middleware. Validated via zod. Returns 202.ended_at/ negativetool_count/ malformed JSON),discovery_refsJSON persistence, cross-session same-identity yields two rows.Plugin
scripts/session-event-emit.ts— last step in SessionEnd hook chain. Resolves identity via_identity-hash.ts, hashes the localCLAUDE_SESSION_ID(or ppid fallback), readstool_count/tokens_savedfrom session buckets instats.json, best-effortgit rev-parse --short=12 HEAD, conservative emptydiscovery_refs(forward-compat). Gated onisTelemetryEnabled(); fire-and-forget POST with internalAbortSignal.timeout(500ms).hooks/session-end-consolidate.ts— new step 8 in the chain,awaitWithBudget(500ms), respects the v1.29 2s safety net.session_id_hashderivation,branch_shaundefined in non-git dirs / populated in git repos, ppid fallback, sha256 determinism, network-failure quiet path.Deferred
session_eventsbyidentity_hash + discovery_refs)[]; schema is forward-compat)session_eventsTest plan
bun test server/tests/session-events.test.ts— 10/10 passbun test __tests__/session-events-emit.test.ts— 12/12 passbun test server/tests/daily-active.test.ts— 8/8 pass (no regression on adjacent WAD-D route)bun build server/src/index.ts— clean bundleASHLR_TELEMETRY=off bun run scripts/session-event-emit.ts— exits 0 (consent gate)🤖 Generated with Claude Code