Skip to content

KVS activity model — PRs, issues, reviews, comments & the activity feed

kvs specs/kvs/activity-model.kmd

Specification body

Spec — KVS activity model (resolves kvs#247c)

The KVS host is the successor forge to Koder Flow (kvs-RFC-002: "KVS the host too — the forge that supersedes Flow; Flow becomes the legacy host we migrate off"). Therefore the activity/PR/issue model KVS builds is its own native, first-class forge social model — the SSOT it owns after cutovernot a thin log that links back to Flow. This spec is normative for that model's entities, storage, tenancy, identity, ingest, and read surface.

Why native, not link-back (the kvs#247c decision). The "how much of a forge's social model does KVS mirror vs link to Flow" question is settled by ratified kvs-RFC-002, not an open product call: §8 lists "PRs (with conflict-as-data), issues" as native feature-by-feature work; §5 makes PR + comment first-class MCP resources; §9 decommissions Flow after per-repo cutover. A link-back / thin-event-log design would be a "v1 with limitations to redo later" (violates stack-principles §2): post-cutover there is no Flow to link to — the rich model must live in KVS. Flow links are migration-phase provenance only (R6), never the durable design.

R1 — Entities are first-class namespace-tree resources

The model adds these entity kinds, each a resource under a repo (and thus under the repo's namespace chain, namespace-tree.kmd R1–R2):

  • pull_request — a proposed change between two mergeable-refs (concurrent-merge.kmd).
  • issue — a tracked work item on a repo.
  • comment — a body attached to a pull_request or issue (threaded by reply_to).
  • review — a verdict (approve / request-changes / comment) on a pull_request, optionally anchored to a diff position.
  • activity_event — an append-only record of a state change (see R7); the projection that powers the "activity feed", derived from the entities above, never the primary store of their content.

Each entity has a stable KVS id (ULID) and belongs to exactly one repo. Deleting a repo cascades (kdb blobref cascade, kdb#733) to its entities.

R2 — Storage on kdb-next (relational), bodies on kdb-obj when large

Per kvs-RFC-002 §7 (reuse-first D10): the entities + their relations live in kdb-next as sibling tables to namespace/repo (stack-RFC-001 data plane). Large/opaque bodies (long issue descriptions, attachments) MAY spill to kdb-obj via a BlobRef column (kdb#542) with cascading delete (kdb#733); short text stays inline. No new datastore — the activity model rides the same plane as the namespace tree.

R3 — Tenancy resolved THROUGH the tree (no separate tenant key)

An entity's tenant boundary is inherited from its repo's root namespace (namespace-tree.kmd R7); there is no per-entity tenant column to drift. RLS is enforced at the data plane (multi-tenancy/contract.kmd, multi-tenant-by-default.kmd): a principal reads/writes a pull_request/issue/comment/review iff it can read/write the owning repo by tree-resolved permission. Cross-tenant access returns 404, never 403 (kvs-RFC-002 §5, parity with the MCP cross-tenant rule).

R4 — Identity is first-class for humans AND agents

Author/actor on every entity is a Koder ID principal — human or agent (scoped token / SVID, kvs-RFC-002 §5). Every mutating op carries provenance (KVS#202 trailers) so "which agent did what" is auditable (D8). An entity MUST NOT require a human author; an agent opening a PR or commenting is a normal path.

R5 — Pull requests are conflict-as-data, not pass/fail

A pull_request references two mergeable-refs and a server-side merge attempt (concurrent-merge.kmd, KVS#179): an unmergeable state is a conflict object attached to the PR (resolvable as data), not a hard failure. This is the KVS differentiator over Flow's PR model — the activity model stores the conflict-object reference, not a boolean "mergeable". PR merge goes through the same engine as concurrent push (single-statement CAS, exactly-one-winner).

R6 — Ingest during coexistence is idempotent & unidirectional (migration-phase only)

While Flow is still authoritative (kvs-RFC-002 §9 coexistence, pre-cutover):

  1. The Flow→KVS webhook receiver (kvs#247, done) maps Flow events (pull_request, issues, issue_comment, pull_request_review, push) onto R1 entities. Mapping is unidirectional Flow→KVS (kvs#247 architectural decision — bidirectional would risk corrupting Flow from an unproven KVS).
  2. Idempotent upsert keyed by (flow_repo_id, flow_entity_kind, flow_entity_id) plus the webhook Delivery id for at-least-once dedupe. Re-delivery MUST NOT duplicate; out-of-order events converge (last-writer by Flow updated_at).
  3. A migration-phase flow_ref provenance field records the origin Flow entity — for the importer and audit only. It is not a runtime link the read surface depends on (R-rationale above): after cutover the field is historical.
  4. At cutover (per repo/namespace, kvs-RFC-002 §9) KVS becomes authoritative for that repo's entities; ingest stops; new PRs/issues/comments originate in KVS. Flow is decommissioned after parity.

R7 — The activity feed is a projection, append-only

activity_event is an append-only log derived from entity state transitions (PR opened/merged/closed, issue opened/closed, review submitted, comment added, branch pushed). It is idempotent by (entity, transition, monotonic_seq) and is the read-optimized surface for "what happened on this namespace/repo" — for the web feed, audit, and the MCP read surface. It carries no content the entities don't already hold (it references them); rebuilding it from the entities MUST be possible. Append-only ⇒ scales (hyperscale-first D4); a large feed never blocks a write.

R8 — MCP + web read/write parity (co-equal surfaces)

Per kvs-RFC-002 §5 (stack-RFC-002-mcp-integration): every entity is an MCP resource and every operation (open/close/comment/review/merge a PR; open/close/ comment an issue) is a scope-gated MCP tool, with per-token rate limit and the R3 cross-tenant-404 rule — co-equal to the web surface, not a plugin. Token- efficient structured payloads (the IRIS "10× fewer tokens" principle). The web surface renders the same entities via KDS/Verge.

Non-goals

  • Not a 1:1 clone of Flow's social schema — KVS's model is differentiated (conflict-as-data PRs R5, first-class agent identity R4, tree-resolved tenancy R3).
  • Not a link-back/thin-log design (rejected above; Flow is being migrated off).
  • Not the importer mechanics themselves (kvs#247 receiver + the §9 bulk importer own that) — this spec governs the model the importer writes into.
  • Feature-by-feature sequencing of PR/issue/review UI + CI lives in subsequent implementation tickets (kvs-RFC-002 §8), not here.

Acceptance (for the implementing tickets that follow)

  • kdb-next schema for pull_request/issue/comment/review/activity_event as siblings of namespace/repo, with tree-resolved RLS (R2/R3) + a passing cross-tenant isolation test (mirror namespace pgstore RLS test #238).
  • Idempotent Flow-event ingest (R6) with a re-delivery + out-of-order test.
  • PR entity references a conflict object, not a boolean (R5), wired to the concurrent-merge engine.
  • MCP resources/tools for each entity (R8), scope-gated, cross-tenant-404, rate-limited; web read surface renders the activity feed (R7).
  • Provenance trailers on every mutation (R4).

References