KVS activity model — PRs, issues, reviews, comments & the activity feed
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 activityPRissue model KVS builds is its own native, first-class forge social model — the SSOT it owns after cutover — not 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" (violatesstack-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 apull_requestorissue(threaded byreply_to).review— a verdict (approve / request-changes / comment) on apull_request,optionally anchored to a diff position.
activity_event— an append-only record of a state change (see R7); theprojection 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 readswrites a pull_requestissuecommentreview 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):
- 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). - Idempotent upsert keyed by
(flow_repo_id, flow_entity_kind, flow_entity_id)plus the webhook
Deliveryid for at-least-once dedupe. Re-delivery MUST NOT duplicate; out-of-order events converge (last-writer by Flowupdated_at). - A migration-phase
flow_refprovenance 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.
- At cutover (per repo/namespace,
kvs-RFC-002 §9) KVS becomes authoritativefor that repo's entities; ingest stops; new PRsissuescomments 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 openedmergedclosed, 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 (openclosecommentreviewmerge a PR; openclose 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 PRissuereview 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_requestissuecommentreviewactivity_eventas siblings of namespacerepo, with tree-resolved RLS (R2R3) + a passing cross-tenant isolation test (mirror
namespacepgstore 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).