The data copy needed a classification
Before consolidating P6X4's stores, I labelled every duplicate as authority, snapshot, projection, cache, or accidental second authority.
P6X4 had four copies of a document title, and every owner called its copy necessary.
The document database held the editable title. Rendering saved the title used in the latest artifact. Delivery stored it for message composition. Search kept a normalized form for matching. A fifth copy sometimes lived in a cache.
They looked like ordinary denormalization until I asked what should happen when the title changed between rendering and delivery. Each component answered from its own record. The same field represented current truth, historical evidence, derived query state, and disposable acceleration without saying which was which.
I delayed the migration and classified every copy first.
Duplication was not the defect
A system that forbids copies cannot produce immutable receipts, efficient search, offline views, or stable historical output. The problem was not that values repeated. It was that the repeated values made incompatible claims.
I used five categories:
- Authority: the state against which a new decision is validated.
- Snapshot: an immutable record of what a specific operation observed or used.
- Projection: a derived view optimized for a question and rebuildable from named sources.
- Cache: disposable acceleration with explicit freshness and invalidation policy.
- Accidental second authority: a copy that callers could update or trust as current without a reconciliation contract.
The fifth category was a diagnosis, not a legitimate target design.
Every table and message field received a category, source, freshness expectation, rebuild path, retention rule, and consumers allowed to act on it. Unknown was permitted during the inventory. “A copy” was not.
Authority depended on the decision
There was not always one globally authoritative row for every question.
The document module was authoritative for the current title of an editable document revision. A completed rendering manifest was authoritative for the title actually embedded in artifact artifact_84. Delivery's effect ledger was authoritative for which artifact and destination had been accepted under one intent.
If someone renamed the document later, the artifact manifest did not become stale. It remained a truthful snapshot of a historical operation. Updating it would destroy evidence.
I began writing authority statements as sentences:
The document revision table decides which title a new render request may use.
The artifact manifest decides which title and source digest an existing artifact contains.
The delivery ledger decides which immutable artifact was sent for a delivery intent.
The sentences included an action. That prevented the vague claim that one service “owned the title” in all time and contexts.
Snapshots required identity and purpose
Several tables were described as snapshots but behaved like editable replicas.
A useful snapshot named:
- The operation or decision it supported.
- The source identity and revision observed.
- When the observation was recorded.
- The schema or policy used to interpret it.
- Whether the value could be redacted later and how that affected evidence.
The render manifest included document revision, template revision, asset digests, locale, renderer version, and output digest. It did not contain a broad copy of every document field “just in case.”
Delivery referenced the immutable artifact rather than copying its title and body into a mutable delivery record. If an adapter required a title in its request, the exact outbound request digest and bounded metadata became attempt evidence.
Snapshots were deliberately redundant. Their redundancy froze meaning at a consequential boundary.
Projections admitted lag
Search had copied document title, status, tags, and access fields into an index. Callers sometimes treated the index as authoritative because querying it was convenient.
I wrote the projection contract:
- Source: committed document revisions and policy facts.
- Ordering: highest source revision per document.
- Freshness objective: observable through projection lag, not guaranteed instant.
- Rebuild: new index from a bounded source sequence, then atomic alias switch.
- Failure behavior: results may omit recent permitted documents; stale authorization must not expose forbidden ones.
- Allowed decisions: discovery only; consequential actions revalidate authority.
The security asymmetry mattered. A stale search projection could safely be conservative by omitting a document. It could not safely show a document after access was removed. I added an authoritative filter before returning sensitive results and prioritized policy-change events.
Projection lag became a state the interface could explain. Search did not get to call itself eventually consistent without naming what temporary disagreement was acceptable.
Caches needed an answer to “what if it lies?”
The title cache used a time-to-live and no invalidation on document change. Most pages looked correct; a few displayed an old title for minutes.
I classified consequences by reader:
- A navigation label could tolerate brief staleness if the destination identity remained stable.
- An editable form needed current revision and conflict detection.
- A render request needed an authoritative source revision.
- An audit view needed the historical snapshot, not the current cached value.
The cache API returned both value and freshness metadata. Callers declared the consistency mode they required. A strict call bypassed or revalidated the cache. A tolerant call could accept a bounded age.
I avoided one generic getDocument helper whose behavior changed through hidden options. Names such as getCurrentDocumentForUpdate, getDocumentSummary, and getArtifactSnapshot made the promise visible.
If the cache disappeared, correctness remained. If the authority disappeared, the system stopped accepting decisions. That became a practical classification test.
The delivery service had its own document table. It began as a projection populated from document events. Later, an administrative route allowed correction of a delivery title when synchronization lagged. That route turned a projection into a writable source.
The next incoming document event could overwrite the correction. Or the correction could remain and disagree with the source. Neither system knew which action represented intent.
I removed the correction endpoint before migration. Recovery could:
- Refresh the projection from a named document revision.
- Choose a different immutable artifact through a new delivery intent.
- Attach an operator note to evidence.
- Correct authoritative document metadata through the document module.
It could not mutate a convenient copy until the screen looked right.
This made some recovery slower. It made the state transition explainable and preserved the distinction between current truth and historical request.
A copy inventory exposed hidden contracts
I built a generated inventory from schema annotations and checked-in metadata. Each record included:
field: search_documents.title
class: projection
source: documents.revisions.title
source_order: document_revision
freshness: observable-lag
rebuild: search-index-v3
consumers:
- document-discovery
forbidden_uses:
- render-request
- delivery-requestThe generator could verify that declared tables and fields existed. It could not prove the human classification. Scenario tests enforced the consequential rules: a render request constructed from search results failed unless it loaded the authoritative revision; a projection rebuild converged after duplicate and out-of-order facts; deleting the cache did not lose accepted work.
I added inventory review to schema changes that introduced a duplicate of an authoritative field. The question was not “may we denormalize?” It was “what exact kind of copy are we creating?”
Migration order followed classification
Consolidating databases without the inventory would have made it tempting to merge similarly named tables.
I moved data by semantic category:
- Establish the target authority and a single writer.
- Import authoritative records with stable identities and revision checks.
- Preserve immutable snapshots and their source references.
- Rebuild projections from authority rather than treating old projections as truth.
- Drop caches and let the new system repopulate them.
- Resolve accidental authorities through explicit decisions before cutover.
This order prevented stale search or delivery copies from overwriting current documents. It also avoided spending effort faithfully migrating disposable cache contents.
For each authoritative import, I compared counts, identities, revision ranges, uniqueness constraints, and content digests. Differences went into a quarantine table with reason codes. A migration did not silently choose the latest timestamp.
Timestamps could not select truth
Several early scripts resolved conflicting copies using updated_at. The newest value won.
Clocks differed, replication delayed writes, and snapshots were legitimately recorded later than the authority they observed. A rendering completed at 14:10 using document revision seven from 14:05; document revision eight committed at 14:08. The render snapshot's later timestamp did not make its title the current document title.
I used domain revisions and authority rules instead. Current document state selected the highest committed revision under the document authority. A projection stored its applied source revision. A snapshot remained tied to the referenced revision regardless of wall-clock order.
Where no stable revision existed in legacy data, the migration recorded uncertainty. I could compare event order, identifiers, and content, but I did not manufacture confidence from timestamps.
Time located evidence. It did not decide meaning.
Deletion needed lineage
Privacy and retention complicated duplication. Deleting current document data did not automatically mean deleting every artifact, audit record, or delivery receipt. Retaining everything because it was “historical” was equally careless.
The copy inventory gained purpose and retention fields. A projection and cache followed source deletion promptly. A snapshot retained only fields necessary to establish the operation and used pseudonymous or redacted references where possible. Test fixtures exercised erasure across every classified copy.
The system produced a deletion report listing authority removed, projections rebuilt, caches invalidated, snapshots retained under a named rule, and failures requiring recovery. It did not claim data was gone because one primary row disappeared.
This connected Q2F8's earlier lesson to P6X4: data lifecycle belongs to architecture, not a banner or one table.
Restore tests treated categories differently
Backups initially captured every database on independent schedules. Restoring them together could combine an older authority with a newer projection.
The restore procedure prioritized authoritative state and immutable snapshots. Projections were rebuilt. Caches were empty. Outbox and effect receipts were restored consistently with the transactions that created them.
I ran a scenario in which the search index contained revision nine while the restored document authority ended at revision eight. Startup refused to serve the index as current, created a new projection generation, and rebuilt from the restored boundary. Another scenario ensured a delivery receipt was not lost while its corresponding intent remained, which could otherwise permit a duplicate effect.
“Restore the latest backup of each store” was not a coherent recovery point. Classification defined what could be regenerated and what evidence could not be discarded.
The interface used the categories
The classification was not confined to schema documentation.
P6X4 displayed “Current document” for authoritative state, “Rendered from revision 7” for a snapshot, and “Search updated through revision 6” when projection lag mattered. A recovery screen linked the accepted delivery request to its exact artifact instead of showing the current title as though it had been sent.
I avoided labels such as “sync issue” when the system knew the disagreement. “Search is still indexing the latest revision” suggested waiting or refreshing. “This delivery used an earlier artifact” required a new intent if the result was no longer appropriate.
Clear data semantics made clearer language possible.
I had approached P6X4's databases as a choice between centralized normalized truth and distributed duplication. Neither described the system I needed.
One authority could support many deliberate representations. Snapshots preserved historical decisions. Projections answered specialized queries. Caches reduced repeated work. Each copy was safe only when its relationship to authority, time, failure, and retention remained explicit.
The dangerous records were not necessarily the oldest or most duplicated. They were the copies whose category had drifted—especially projections with repair endpoints and snapshots casually refreshed to current values.
After the inventory, the title still appeared in several places. The system became simpler because each appearance made a narrower claim.
The migration could then move data without asking a timestamp to decide what was true.
Offline copies needed a synchronization category
X8B6 had taught me that a disconnected browser copy is not merely a cache when it can create intent. P6X4's early vocabulary still called all browser storage “cached data.”
I split client-held state into three forms:
- A disposable response cache that could be dropped at any time.
- A read snapshot labelled with its source revision and freshness.
- A durable client outbox containing operations not yet accepted by authority.
The outbox was neither an alternative document authority nor a projection of server state. It was evidence of local intent. When connectivity returned, the server validated each operation against the expected revision and returned an accepted result or explicit conflict. The browser then projected the authoritative response.
This prevented a tempting migration mistake: importing a local “current document” value as though it were truth. Only acknowledged operations and server revisions participated in authority transfer. Unacknowledged local intent remained pending until reconciled by its stable identity.
The interface could now say “Saved on this device; not yet accepted” instead of using one vague saved state. Classification improved language again.
Analytics copies carried purpose, not authority
P6X4's synthetic usage events repeated document type, route, and interaction labels. They were not needed to operate the document and should never decide its state.
Q2F8's rules applied: every event field had a named decision purpose, minimized shape, retention window, and deletion behavior. Document content and human-readable titles were excluded. Stable document identifiers were replaced with scoped pseudonymous identifiers when cohort analysis genuinely required continuity.
The analytics store was classified as an observational dataset, outside the operational copy hierarchy. It could suggest that a workflow caused confusion; it could not repair a workflow record or decide authorization. When the operational authority deleted or restricted a document, the analytics lifecycle followed its own declared purpose and linkability policy rather than retaining a convenient shadow catalogue.
This category kept “projection” from becoming a blanket label for every derived dataset. A search projection exists to serve a product query and rebuild from current authority. An analytical observation records a bounded historical occurrence under a different retention and privacy contract.
The inventory linked the two where lineage existed but did not pretend they were interchangeable.
Reconciliation generated evidence, not silent fixes
The first reconciliation script compared copies nightly and overwrote whichever side was designated secondary. It made dashboards green and erased the circumstances that caused drift.
I changed it to produce discrepancy records. Each record named both sources, their categories, revisions, content digests, first observation, and the rule that should resolve the difference. Automatic action was allowed only when the classification made the direction certain—for example, rebuilding a projection from a higher authoritative revision or dropping an expired cache.
Differences involving two apparent authorities stopped the process. A human review in the lab created a correction command against the chosen authority and documented why. Snapshots were never refreshed merely because they differed from current state.
The reconciler tracked whether the discrepancy returned after repair. A repeated drift suggested a broken publication or invalidation path, not a need for a more frequent overwrite job.
This made discrepancy volume temporarily worse because problems stayed visible. It reduced the dangerous kind of cleanliness in which stores agreed only because a script continually concealed weak ownership.
Contract evolution preserved source meaning
When an authoritative field changed shape, projections and snapshots did not all migrate the same way.
For current documents, a migration could normalize a locale from a free string to a supported identifier under an explicit rule. Search rebuilt from the normalized authority. A historical render manifest kept the original input and added a derived interpretation if one could be proven. It did not rewrite what the renderer had received.
Events carried contract versions and source revisions. Upcasters were allowed to supply defaults only when old semantics implied them. If an old fact lacked enough information, the consumer handled an explicit legacy variant or returned “unknown.”
The copy inventory listed the oldest supported source version for each projection and snapshot reader. Removing a version required either rebuilding affected projections, migrating retained snapshots with evidence, or accepting that a historical view would remain limited.
Schema cleanliness was not permission to improve history into something it had never recorded.
The inventory had an exit test
Documentation tends to decay when nothing depends on it. I connected classifications to observable failure tests.
For a cache, delete the table and confirm that correctness remains. For a projection, rebuild it from declared sources into an empty store and compare domain output. For a snapshot, change current authority and confirm the historical operation does not change. For authority, make a conflicting write elsewhere and ensure it cannot be accepted. For accidental authority, no passing test existed; the record needed redesign.
The tests were not exhaustive proofs. They made the category operational. A pull request that relabelled a delivery table from projection to snapshot had to change behavior around mutability and retention, not only edit YAML.
During the final migration, every retained duplicate passed its category's exit test. Several caches were removed because their performance benefit no longer justified invalidation complexity. One reporting projection was rebuilt on demand rather than migrated. The system carried fewer copies, but that was a consequence of better reasoning, not the original goal.