← Corpus / augment-it / reminder

Record Count Stays Stable Across Versions — Augmenting Adds Columns, Never Rows

1 dataset = 1 row count. Forever. From ingest through every promote, every augment, every triage. If the user has 96 records, every surface that says a number should say 96. Adding properties + values to records becomes new COLUMNS on those records, not new rows in the store. Discovering 231 rows in the row-store for what the user knows is a 96-record dataset is a symptom — of either ghost data from earlier record sets, version-multiplication of the same 96 records across v3/v4/v5, or a leak in the ingest/promote path. Either way: the user never wants to see a number other than their dataset's record count, and the architecture must reflect that.

Path
reminders/Record-Count-Stays-Stable-Across-Versions.md
Authors
Michael Staton
Augmented with
Claude Code on Claude Opus 4.7 (1M context)
Tags
Reminder · Invariant · Augment-It · Data-Integrity · Record-Set · Row-Count · Augment-Adds-Columns

Record Count Stays Stable Across Versions

The invariant

One dataset has one record count. Forever.

If the user starts with 96 records, every surface — Record Collector, Records Surface, Response Reviewer, the chat, the splash, every diagnostic — must show 96 (or some clearly-scoped subset of 96).

The user’s mental model is: I have 96 records, and I’m augmenting them.

The system’s mental model must match.

Augmenting adds COLUMNS, not rows

When the user fires a connector, fan-out, prompt, agent, or hand-curation against a record:

  • The record gets a new field populated, or an existing field updated.
  • The field becomes a new column on the record set’s schema if it didn’t exist there before.
  • The row count does not change. Not in the store, not on the surface, not in the database.

If a record gets 5 new fields filled in, that’s 5 new columns on the same 96 rows. Not 5 × 96 = 480 new rows.

What I just observed (the trigger for this reminder)

Audit of the row-store backup at .backups/2026-06-02_records-surface-acceptances/rows.json:

total rows in store: 231
user's expected:      96

That extra ~135 rows is a problem. Likely causes (per [[../issues/Augment-Transformations-Not-Reliably-Persisting]]):

  • Version multiplication. The user has at least v3, v4, v5 of their 96-record set. If each version stores its own copy of all 96 rows in the same row-store namespace, 3 × 96 = 288. Some archived → 231. The “store” pretends each version is its own set of rows rather than treating “row” as immortal-identity + version-of-fields.
  • Ghost rows from prior sessions that weren’t promoted, archived, or cleared.
  • Duplicate-by-identity that the promote merge missed.

Whatever the cause, the user does not care. From the user’s seat the dataset is 96 records. The row-store showing 231 is the system exposing its own implementation detail at the wrong level of abstraction.

What this means for the architecture

Either:

  1. The row-store stops multiplying rows across versions. A row is identified by its identity across versions; versions hold the field values, not row duplicates. Adding a column to v5 doesn’t clone the 96 rows from v4; it adds a column to the same 96 row identities. Row count stays at 96. Forever.

    OR

  2. Every surface filters to the active record set’s row count. Even if the store multiplies internally, no surface displays a number larger than the active record set’s row count. The store’s total is an internal accounting concern the UI never surfaces.

Lean: (1) is the right long-term shape (row-as-immortal-identity is the natural model for augmentation). (2) is the immediate-fix shape that closes the trust gap without rearchitecting persistence.

How to apply this rule going forward

When I’m tempted to write code that:

  • Counts “rows in the store” and surfaces that number → don’t. Count rows in the active record set instead.
  • Stores a fresh copy of all 96 records when the user promotes → don’t. Add a version layer on top of stable row identities.
  • Imports a CSV / XLSX as new rows when the user already has the same 96 records → don’t. Match by identity, treat as version update, not as ingest.
  • Reports diagnostics like “total rows in row-store: N” → don’t surface that to the user without explaining what subset they’re looking at.

When I see a number that doesn’t match the user’s expected record count, that’s a bug worth catching before they catch it.

  • [[../issues/Augment-Transformations-Not-Reliably-Persisting]] — the data-loss issue this reminder grew out of; promote / version model is the load-bearing piece
  • [[../specs/Flow-for-Bundles-Packs]] — the new Records Surface surface should never show a number other than the active record set’s row count
  • services/row-store/src/store.ts — current promote + storage implementation; the row-multiplication-across-versions behavior lives here if (1) above turns out to be the right fix

Status

Active invariant. Apply on every Augment-It surface, every diagnostic, every fix going forward. Discovering a count mismatch is a bug, not a curiosity.