Skip to content

feat(q4): session graph capture foundation — ingest path only#79

Merged
masonwyatt23 merged 1 commit into
mainfrom
feat/q4-session-graph-capture
May 23, 2026
Merged

feat(q4): session graph capture foundation — ingest path only#79
masonwyatt23 merged 1 commit into
mainfrom
feat/q4-session-graph-capture

Conversation

@masonwyatt23
Copy link
Copy Markdown
Member

Summary

Q4 Multiplayer DNA pillar: ship the CAPTURE side of the deferred session graph + replay UI. One structured event per SessionEnd describes 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

Field What it is Reversibility
identity_hash sha256(machine_id + quarterly_salt) One-way. Same shape as WAD-D.
github_hash sha256(github_login + quarterly_salt), optional One-way. Collapses one developer across machines.
session_id_hash `sha256(CLAUDE_SESSION_ID
branch_sha First 12 chars of git rev-parse HEAD, optional Not reversible to a repo on its own.
discovery_refs Opaque section IDs (e.g. discovery slugs) Local-only IDs. Never paths or content.
tool_count / tokens_saved Integers Aggregate shape metrics.
plugin_version semver string Cohort reporting only.

Never 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

POST /v1/session-events
Content-Type: application/json

{
  "identity_hash":   "aaaaaa…(64 hex)",
  "github_hash":     "bbbbbb…(64 hex)",
  "session_id_hash": "cccccc…(64 hex)",
  "ended_at":        "2026-05-22T12:34:56.000Z",
  "tool_count":      42,
  "tokens_saved":    12345,
  "branch_sha":      "abc123def456",
  "discovery_refs":  ["auth-jwt-flow", "rate-limit-buckets"],
  "plugin_version":  "1.31.0"
}

HTTP/1.1 202 Accepted
{"ok": true}

Idempotent: ON CONFLICT (identity_hash, session_id_hash) DO NOTHING.

What lands

Server

  • session_events table — sqlite migration via addSessionEventsTableIfMissing. Indexes: UNIQUE(identity_hash, session_id_hash), (identity_hash, ended_at), (received_at).
  • POST /v1/session-events — unauthenticated (matches /v1/events and /stats/daily-active; identity is already a one-way hash). Mounted before user-token middleware. Validated via zod. Returns 202.
  • 10 server tests — happy path, idempotency, optional-field omission, validation failures (bad identity / session / ended_at / negative tool_count / malformed JSON), discovery_refs JSON 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 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 — new step 8 in the chain, awaitWithBudget(500ms), respects the v1.29 2s safety net.
  • 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

  • Session graph UI / replay viewer (visualizing how knowledge flows across sessions)
  • Cross-session aggregator job (joining session_events by identity_hash + discovery_refs)
  • Per-session discovery-ref tracking (currently emits []; schema is forward-compat)
  • Query patterns / Grafana dashboards on top of session_events

Test plan

  • bun test server/tests/session-events.test.ts — 10/10 pass
  • bun test __tests__/session-events-emit.test.ts — 12/12 pass
  • bun test server/tests/daily-active.test.ts — 8/8 pass (no regression on adjacent WAD-D route)
  • bun build server/src/index.ts — clean bundle
  • ASHLR_TELEMETRY=off bun run scripts/session-event-emit.ts — exits 0 (consent gate)

🤖 Generated with Claude Code

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>
@masonwyatt23 masonwyatt23 merged commit b8ab597 into main May 23, 2026
9 of 14 checks passed
@vercel
Copy link
Copy Markdown

vercel Bot commented May 23, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
ashlr-plugin-site Building Building Preview, Comment May 23, 2026 2:32am

Request Review

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant