KVS activity model — PRs, issues, reviews, comments & the activity feed
Corpo da especificação
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 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); 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):
- 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 webhookDeliveryid 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 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_eventas siblings of namespace/repo, with tree-resolved RLS (R2/R3) + a passing cross-tenant isolation test (mirrornamespacepgstore 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).
Referências
rfcs/kvs-RFC-002-concurrent-ai-first-forge-on-kdb.kmdspecs/kvs/namespace-tree.kmdspecs/kvs/concurrent-merge.kmdspecs/multi-tenancy/contract.kmdpolicies/multi-tenant-by-default.kmdrfcs/stack-RFC-001-kdb-as-unified-data-plane.kmdrfcs/stack-RFC-002-mcp-integration.kmdpolicies/reuse-first.kmdpolicies/hyperscale-first.kmd