Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
a3df703
Origin-id codec and origin-decoder plugin
robnewton Jul 24, 2026
3d20bb6
Convey the client trace id for origin decoding; add a console origin …
robnewton Jul 24, 2026
53a5a86
console: cap the user-action node width so it cannot overlap the root
robnewton Jul 24, 2026
8b800ab
console: profile-aware, interactive user-action origin node
robnewton Jul 24, 2026
159b3ef
console: distill the user-action node to glance content
robnewton Jul 25, 2026
a96dee4
console: group chains started by one click into a shared user-action …
robnewton Jul 25, 2026
f1a77e4
docs: rewrite origin decoding prose in plain, final-state terms
robnewton Jul 25, 2026
294709a
observability: chain-root flag, origin_action, and best-effort chain …
robnewton Jul 25, 2026
1b5e047
console/db: chain rollup columns on invocations + seed
robnewton Jul 25, 2026
f4b568d
console: chain-first index, chain permalinks, and health-led dashboard
robnewton Jul 25, 2026
697a6e2
docs: chain observability guide + changeset
robnewton Jul 25, 2026
97b9ab6
console: generalize the origin node beyond a human click
robnewton Jul 25, 2026
7e2f8e6
docs: console feature screenshots for the PR
robnewton Jul 25, 2026
272f365
console: add expectedFlowResolver config + ExpectedFlowGraph contract
robnewton Jul 25, 2026
97fb425
console: per-invocation expected-flow overlay + expectedEvents drawer…
robnewton Jul 25, 2026
b16dd0d
console: dev-harness expectedFlowResolver + seed expectedEvents
robnewton Jul 25, 2026
e212f82
docs: expected-flow overlay guide + changeset
robnewton Jul 25, 2026
18d64e4
docs: console screenshots for the expected-flow overlay
robnewton Jul 25, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .changeset/chain-rollup.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
'hopdrive-eventkit': minor
---

Make the chain (all invocations sharing a correlation id) first-class in observability.

Sources now expose `meta.correlationMinted` so the observability writer can flag the chain root truthfully (the invocation that minted the correlation id, never inferred from timestamps, independent of any decoded origin). The writer stamps `is_chain_root`, promotes `context_data.origin.action` to an indexed `origin_action`, and on every member write rolls a summary up onto the chain's root row: `chain_last_activity_at` (greatest-wins) and `chain_status` (flips to `errored` when any member errored, never back). The rollup is best-effort and never blocks event handling.

New `invocations` columns (`origin_action`, `is_chain_root`, `chain_status`, `chain_last_activity_at`) with indexes are in `console/db/schema.sql`; existing databases apply `console/db/schema-delta-chain-rollup.sql` before deploying the writer.

The console gains a chain-first Invocations index, a `flow?correlationId=` permalink, origin-action search and filtering, and a health-led dashboard. See `docs/chain-observability.md`.
9 changes: 9 additions & 0 deletions .changeset/console-expected-flow-overlay.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
'hopdrive-eventkit': minor
---

Console: add a per-invocation expected-flow overlay. The host wrapper injects an `expectedFlowResolver` (like `userResolver`); it returns an `ExpectedFlowGraph`, which is `toFlowGraph()`'s output plus optional metadata, so a registry can emit it as plain JSON. With the "Expected flow" toggle on (or the `X` hotkey), each invocation's observed records are compared against its expected graph: expected-but-missing steps show as dashed ghost nodes, observed-but-unexpected steps get a small off-contract badge, matched steps are unchanged. It is display only and never a pass/fail gate; an invocation with no resolved flow renders exactly as today.

Also adds an "Expected events" section to the user-action drawer: when a decoded origin carries `expectedEvents`, each is checked against the events observed in the loaded chain.

See `docs/console-expected-flow-overlay.md`.
9 changes: 9 additions & 0 deletions .changeset/origin-trace-decoding.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
'hopdrive-eventkit': minor
---

Add the `originDecoder` plugin. Give it a `decode` function that turns an id the frontend sends (as the `x-b3-traceid` header, arriving at `meta.sourceTraceId`) into a small object, and it lands at `context_data.origin` on the first invocation of a chain, for display in observability and the console.

Behavior change: the Hasura source no longer uses an inbound `trace_context.trace_id` as the chain's correlation id by default; set `correlationFromTraceId: true` on the `hasuraEvent` source to keep that behavior.

See `docs/origin-trace-decoding.md`.
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,7 @@ Generic and config-driven, registered via `kit.use(plugin, config?)`. I/O plugin
| `graphqlSink` | `/plugins/observability/graphql-sink` | the built-in observability `sink` (bulk-upsert to Hasura) |
| `batch` | `/plugins/batch` | `{ store, logFlush? }`. Durability. `requires:['source:hasura']` |
| `loopGuard` | `/plugins/loop-guard` | `{ field?, serviceId?, codec? }`. Inbound provenance into `envelope.meta` |
| `originDecoder` | `/plugins/origin-decoder` | `{ decode }`. Runs your `decode` over the id the frontend sent (`meta.sourceTraceId`) and puts the result on `context_data.origin` ([docs](docs/origin-trace-decoding.md)) |
| `grafana` | `/plugins/transports/grafana` | `{ logger }` (bridge to sdk-server-logger) or `{ grafana: { endpoint, auth } }` (direct Loki) |
| `sentry` | `/plugins/transports/sentry` | `{ dsn?, send? }`. Forwards `onError` |

Expand Down Expand Up @@ -330,6 +331,10 @@ createEventKit(...)`), and pass `--export <name>` if it isn't the default or `ki
in [`docs/planning/console-expected-flows.md`](docs/planning/console-expected-flows.md).
- **API reference (generated).** Run `npm run docs` to build `docs/api/`, the exhaustive,
every-symbol reference generated from source, so it can't drift.
- **Origin decoding.** [`docs/origin-trace-decoding.md`](docs/origin-trace-decoding.md)
covers how a frontend sends an action id as `x-b3-traceid` (used only for display, never
the chain id) and the `originDecoder` plugin that turns it into `context_data.origin` for
the console to show.
- **Design record.** The architecture decisions, ADRs, kickoff, decision register, and raw
planning conversations live in [`docs/planning/`](docs/planning/) (see its `README.md`
for the read order).
Expand Down
33 changes: 33 additions & 0 deletions console/db/schema-delta-chain-rollup.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
-- ---------------------------------------------------------------------------
-- Delta: chain rollup columns on invocations
-- ---------------------------------------------------------------------------
-- Apply this to an EXISTING observability database that was created from an
-- older schema.sql (before the chain columns). A fresh schema.sql already
-- includes everything here, so you only need this when upgrading in place.
--
-- These columns make a chain (all invocations sharing a correlation_id)
-- first-class without a new table: the root row carries the rollup, and the
-- console's default "Chains" view queries root rows directly.
--
-- Deploy ordering: apply this BEFORE deploying the eventkit version whose
-- observability writer sets these columns. The writer lists them in its
-- invocation upsert, so an older schema without them would reject every
-- invocation insert. (The chain rollup UPDATEs are best-effort and degrade
-- silently, but the insert is not.)
--
-- HopDrive's production obs schema is managed in the separate hasura-migrations
-- repo; this file is the canonical delta for anyone running their own obs DB.
-- Idempotent — safe to re-run.
-- ---------------------------------------------------------------------------

ALTER TABLE invocations ADD COLUMN IF NOT EXISTS origin_action TEXT;
ALTER TABLE invocations ADD COLUMN IF NOT EXISTS is_chain_root BOOLEAN NOT NULL DEFAULT FALSE;
ALTER TABLE invocations ADD COLUMN IF NOT EXISTS chain_status TEXT;
ALTER TABLE invocations ADD COLUMN IF NOT EXISTS chain_last_activity_at TIMESTAMPTZ;

CREATE INDEX IF NOT EXISTS idx_invocations_chain_roots
ON invocations(chain_last_activity_at DESC) WHERE is_chain_root;
CREATE INDEX IF NOT EXISTS idx_invocations_errored_chain_roots
ON invocations(chain_last_activity_at DESC) WHERE is_chain_root AND chain_status = 'errored';
CREATE INDEX IF NOT EXISTS idx_invocations_origin_action
ON invocations(origin_action);
18 changes: 17 additions & 1 deletion console/db/schema.sql
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,15 @@ CREATE TABLE invocations (
error_stack TEXT,

-- Custom context passed in
context_data JSONB
context_data JSONB,

-- Chain rollup (the unit of debugging is the chain: all invocations sharing a
-- correlation_id). These live on invocations so a chain is queryable as plain root
-- rows without a separate table.
origin_action TEXT, -- decoded origin action slug (e.g. 'dealer.move.create'), promoted out of context_data.origin.action; NULL when no decoded origin
is_chain_root BOOLEAN NOT NULL DEFAULT FALSE, -- true on the invocation that MINTED the chain's correlation id (vs inheriting it from an upstream invocation)
chain_status TEXT, -- chain rollup, meaningful only on root rows: 'ok' | 'errored' (room left for a future sweeper's 'incomplete')
chain_last_activity_at TIMESTAMPTZ -- root rows only: started_at of the most recent member invocation in the chain
);

-- Event module execution - each event module checked during an invocation
Expand Down Expand Up @@ -218,6 +226,14 @@ CREATE INDEX idx_invocations_source_event_id ON invocations(source_event_id);
CREATE INDEX idx_invocations_correlation_id ON invocations(correlation_id);
CREATE INDEX idx_invocations_source_job_id ON invocations(source_job_id);

-- Chain-first index (the console's default "Chains" view queries root rows directly,
-- no group-by). Partial indexes keep them small — only the ~85% root rows, and a much
-- smaller errored subset — and cover the default order (last activity desc) and the
-- persistent errors-only toggle.
CREATE INDEX idx_invocations_chain_roots ON invocations(chain_last_activity_at DESC) WHERE is_chain_root;
CREATE INDEX idx_invocations_errored_chain_roots ON invocations(chain_last_activity_at DESC) WHERE is_chain_root AND chain_status = 'errored';
CREATE INDEX idx_invocations_origin_action ON invocations(origin_action); -- origin-action typeahead + per-action index filter

CREATE INDEX idx_event_executions_invocation_id ON event_executions(invocation_id);
CREATE INDEX idx_event_executions_event_name ON event_executions(event_name);
CREATE INDEX idx_event_executions_detected ON event_executions(detected);
Expand Down
Loading
Loading