31 KiB
Scholarr data model
Status: DRAFT FOR OWNER REVIEW, 2026-07-23. This spec is review-ready and is not frozen. No implementation is authorized by this document.
This is Card 1 in TASKS.md. It defines the persistent domain model for
global author and publication identity, per-user follows and reading state, review records,
duplicate prevention, merge and undo, and migration from the legacy Scholarr database.
Status language
- DECIDED records a project decision that this spec must not reopen.
- PROPOSED is the concrete default in this draft. It becomes decided only when the owner approves and freezes the spec.
- OPEN Dn identifies an owner decision listed in Owner decisions.
Scope and boundaries
This spec owns:
- persistent identities for users, authors, publications, and external identifiers;
- global deduplication and per-user follows, visibility, read state, and retained favorite state;
- provenance paths from a provider record to an author-publication relation and a user's library;
- identity review records and confirmed-different decisions;
- auditable, reversible author and publication merges;
- schema migrations and the one-time legacy data import contract.
This spec does not own provider HTTP behavior, sync scheduling, PDF resolution, login/session security, configuration syntax, or onboarding screens. Later specs may add tables in those areas, but they may not weaken the invariants here without reopening Card 1.
Fixed product invariants
- DECIDED:
FollowedAuthoris the source-agnostic root author entity. - DECIDED: names are labels and search aids, never identity keys.
- DECIDED: an author can have multiple
AuthorSourceIdentityrecords. Scholar identities are inert import metadata and human-clickable links only. - DECIDED: publications are global and deduplicated across all users.
- DECIDED: follows and reading state are per user.
- DECIDED: exact external identifiers prevent duplicates. Similar names or titles alone do not prove identity.
- DECIDED: merges are audited and undoable. A wrong author merge is treated as the most damaging identity failure.
- DECIDED: unresolved shells remain explicit. The system never invents certainty when it has no name, works, or trusted identifier evidence.
- DECIDED: all persistent storage is SQLite. The write path must fit a serialized-writer design from the first migration.
- DECIDED: no row or workflow defined here permits application-initiated Google network contact.
Proposed storage conventions
These conventions apply to every table in this spec unless a table says otherwise.
- Internal joins use
INTEGER PRIMARY KEYrow IDs. API and audit references use a separate, immutable UUIDv7public_id TEXT NOT NULL UNIQUE. Internal row IDs never appear in public APIs, exports, URLs, or logs intended for users. - Timestamps are UTC Unix milliseconds in
INTEGERcolumns. A timestamp column ending in_atis nullable only when absence has domain meaning. - Booleans use
INTEGER NOT NULL CHECK (value IN (0, 1)). - Mutable rows carry
row_version INTEGER NOT NULL DEFAULT 1. Every successful update increments it. Undo conflict checks use this version. - Status and kind values use lower-case text with explicit
CHECKconstraints when the set is closed. Provider/source names stay registry-validated text so adding a sanctioned provider does not require rebuilding unrelated tables. - Flexible evidence and audit snapshots use versioned JSON objects stored as UTF-8 text and
guarded by
json_valid. Core identifiers, ownership, status, and timestamps never live only in JSON. - Foreign keys are enabled on every connection. Shared author and publication rows use
ON DELETE RESTRICT; user-owned rows useON DELETE CASCADEonly after the auth and privacy deletion policy permits hard deletion. Actor references useON DELETE SET NULL. - Every normalization algorithm stores a
normalization_version. A later algorithm version may add new normalized values, but may not silently reinterpret old uniqueness constraints.
OPEN D1: approve this internal integer plus public UUIDv7 scheme, or select a different public identifier strategy before freeze.
Relationship map
erDiagram
USERS ||--o{ USER_AUTHOR_FOLLOWS : follows
FOLLOWED_AUTHORS ||--o{ USER_AUTHOR_FOLLOWS : is_followed_by
FOLLOWED_AUTHORS ||--o{ AUTHOR_SOURCE_IDENTITIES : has
FOLLOWED_AUTHORS ||--o{ AUTHOR_NAMES : is_labelled_by
FOLLOWED_AUTHORS ||--o{ AUTHOR_PUBLICATIONS : authored
PUBLICATIONS ||--o{ AUTHOR_PUBLICATIONS : credits
AUTHOR_PUBLICATIONS ||--o{ AUTHOR_PUBLICATION_EVIDENCE : supported_by
AUTHOR_SOURCE_IDENTITIES ||--o{ AUTHOR_PUBLICATION_EVIDENCE : asserts
PUBLICATION_SOURCE_RECORDS ||--o{ AUTHOR_PUBLICATION_EVIDENCE : records
PUBLICATIONS ||--o{ PUBLICATION_SOURCE_RECORDS : has
PUBLICATIONS ||--o{ PUBLICATION_IDENTIFIERS : has
USERS ||--o{ USER_PUBLICATIONS : reads
PUBLICATIONS ||--o{ USER_PUBLICATIONS : appears_in
USER_PUBLICATIONS ||--o{ USER_PUBLICATION_ORIGINS : entered_through
USER_AUTHOR_FOLLOWS ||--o{ USER_PUBLICATION_ORIGINS : follow_path
AUTHOR_PUBLICATIONS ||--o{ USER_PUBLICATION_ORIGINS : authorship_path
REVIEW_ITEMS ||--o{ REVIEW_CANDIDATES : offers
REVIEW_ITEMS ||--o{ REVIEW_DECISIONS : resolved_by
OPERATIONS ||--o{ OPERATION_CHANGES : contains
REVIEW_DECISIONS }o--|| OPERATIONS : applies
The important separation is:
author_publicationsanswers which canonical author is connected to which global publication;user_publicationsholds one coherent reading state for one user and one publication;user_publication_originsrecords every followed-author path that put the publication in that user's library.
This prevents a publication credited to two followed authors from having contradictory read state for the same user.
User root and per-user state
users
Card 5 owns credentials and login identities. Card 1 defines only the domain row referenced by follows, reading state, review decisions, and operations.
| Column | Contract |
|---|---|
id, public_id |
Stable internal and public identifiers. |
status |
active, disabled, or pending_deletion. |
display_name |
User-facing label, not a login key. |
created_at, updated_at, row_version |
Shared storage conventions. |
Login email, username, password hash, OIDC subject, and trusted-header claims belong to the auth spec and are not columns on this domain row by implication.
user_author_follows
One row represents the complete lifecycle of one user's relationship to one canonical author.
| Column | Contract |
|---|---|
id, public_id |
Stable follow identity, retained across unfollow and refollow. |
user_id, author_id |
Required foreign keys. Unique together for all lifecycle states. |
status |
active, unfollowed, or merged. |
first_followed_at |
Never changes. |
active_since |
Changes on a later refollow. |
ended_at |
Set when unfollowed or superseded by a merge. |
superseded_by_follow_id |
Set only for merged; references the surviving follow. |
created_at, updated_at, row_version |
Shared storage conventions. |
An exact refollow reactivates this row. It never creates a second active follow. Unfollowing one user does not alter the global author, another user's follow, or shared publication metadata.
user_publications
This is the single per-user state row for a global publication.
| Column | Contract |
|---|---|
id, public_id |
Stable library item identity. |
user_id, publication_id |
Required and globally unique as a pair. |
first_seen_at |
Earliest time any follow path delivered this publication to the user. |
read_at |
Null means unread; non-null means read. |
favorited_at |
Retains legacy favorite state. No v1 UI is implied by this column. |
created_at, updated_at, row_version |
Shared storage conventions. |
There is no author ID in this table. Marking a publication read through one author marks the same publication read everywhere for that user, while another user's state remains unchanged.
user_publication_origins
This table preserves why a user can see a publication and makes unfollow, merge, and undo exact.
| Column | Contract |
|---|---|
user_publication_id |
Required parent library item. |
follow_id |
The user's follow that supplied the path. |
author_publication_id |
The canonical authorship path. |
discovery_kind |
baseline, incremental_sync, manual_import, or legacy_import. |
first_seen_at |
When this path first delivered the publication. |
created_at |
Immutable creation time. |
The triple (user_publication_id, follow_id, author_publication_id) is unique. A library item is
visible while at least one origin resolves through an active follow and active authorship link.
The row and reading state are retained when the final path becomes inactive, so refollow and undo
restore prior state without reconstructing history.
OPEN D2: approve the user-library semantics as one decision:
- unfollow hides publications that have no remaining active follow path but preserves their read and favorite state;
- a refollow restores that state;
favorited_atis migrated and retained even though the frozen v1 UI has no favorite control;- a legacy publication is read if any legacy link for that user says read;
NEWis derived only from anincremental_syncorigin and a configurable age window. Baseline, manual, and legacy imports never appear as new.
Global author identity
followed_authors
| Column | Contract |
|---|---|
id, public_id |
Stable canonical author identity. |
status |
active or merged. |
resolution_state |
resolved, needs_review, or shell. |
confidence_band |
high, medium, low, or shell; drives the frozen UI label. |
display_name |
Preferred label, nullable for a no-data shell. Never an identity key. |
sort_name |
Normalized display aid, never unique. |
primary_field, affiliation |
Nullable selected display metadata. |
active_from_year, active_to_year |
Nullable selected active-year range. |
avatar_ref |
Nullable reference to a locally managed image or generated avatar, never an untrusted remote URL. |
merged_into_author_id |
Required only for merged; points to an active author. |
created_at, updated_at, row_version |
Shared storage conventions. |
Constraints and application invariants:
merged_into_author_idis null exactly whenstatus = 'active'.- An author cannot merge into itself, and merge chains must be acyclic.
- Reads resolve a merged ID to its active target, but APIs preserve the old public ID as a stable alias so imported links and audit records do not break.
- A shell is a valid active author. It may contain only an inert Scholar import identity and no name or works.
author_source_identities
| Column | Contract |
|---|---|
id, public_id |
Stable source identity row. |
author_id |
Current canonical author, nullable only when the identity is detached after review. |
source |
Registered source, initially openalex, orcid, or scholar_import. |
external_id_raw |
Original display value. |
external_id_normalized |
Canonical value used for equality. |
normalization_version |
Parser version used for the normalized value. |
profile_url |
Human-clickable source URL. A Scholar URL is never dereferenced by the service. |
attachment_method |
direct, provider_crosswalk, calibration_auto, user_confirmed, or legacy_import. |
confidence_score |
Nullable numeric evidence for internal review, never shown as a raw UI percentage. |
evidence_version |
Hash or version of the evidence that supported the current attachment. |
normalized_metadata_json |
Versioned provider evidence for names, fields, affiliations, active years, and profile image candidates. |
first_observed_at, last_observed_at |
Provenance timestamps. |
status |
active or detached. |
created_at, updated_at, row_version |
Shared storage conventions. |
(source, external_id_normalized) is globally unique, including detached rows. Reattaching an
existing identity updates its author and audit history; it never creates a duplicate identity.
Exact source identity equality always resolves to the existing canonical author. A name match, even an exact one, never does. One author may hold more than one OpenAlex identity when the owner confirms that split profiles represent the same person.
The selected fields on followed_authors are projections from these source records. Projection
precedence is defined with provider contracts. Source evidence remains available when a selected
display value changes.
author_names
Provider labels, aliases, and transliterations are preserved without gaining identity power.
| Column | Contract |
|---|---|
author_id |
Required canonical author. |
name, normalized_name |
Display/search forms. Neither is unique. |
kind |
preferred, alias, or transliteration. |
locale |
Optional BCP 47 language tag. |
source_identity_id |
Optional provenance pointer. |
created_at |
Immutable creation time. |
At most one active preferred name exists per author. Changing the preferred name does not change identity or create a merge candidate by itself.
OPEN D3: approve the automatic cross-source attachment boundary:
- exact reuse of an already stored source identity is automatic;
- an explicit provider crosswalk, such as an ORCID asserted on the selected OpenAlex record, may attach both identities in the same operation;
- completed calibration rows classified
automay attach the OpenAlex identity to the imported Scholar shell; - calibration
review, name-only similarity, works-overlap below the frozen auto threshold, and conflicting strong identifiers always create or update a review item; unmatchedremains a shell.
The numerical auto threshold belongs to the onboarding or matching spec. Card 1 freezes only the trust boundary above.
Global publication identity and provenance
publications
| Column | Contract |
|---|---|
id, public_id |
Stable global publication identity. |
status |
active or merged. |
canonical_title, normalized_title |
Selected display title and normalized comparison text. |
canonical_title_hash |
Versioned blocking key for candidate lookup, not a uniqueness key. |
publication_date, publication_year |
Nullable normalized date fields. |
venue, publication_type |
Nullable selected display metadata. |
merged_into_publication_id |
Required only for merged. |
created_at, updated_at, row_version |
Shared storage conventions. |
The selected display fields are projections from source records. Later provider specs define source precedence and freshness. A projection update never discards the underlying source record.
publication_source_records
One row represents one provider's record of a work.
| Column | Contract |
|---|---|
id, public_id |
Stable provenance row. |
publication_id |
Current canonical publication. |
source, external_id_normalized |
Globally unique as a pair. |
record_version |
Provider version, update timestamp, or content hash when supplied. |
normalized_metadata_json |
Versioned normalized title, authors, date, venue, and type evidence. |
raw_payload_hash |
Optional integrity pointer. Raw provider payload retention is defined later. |
first_observed_at, last_observed_at |
Provenance timestamps. |
created_at, updated_at, row_version |
Shared storage conventions. |
legacy_import is a local provenance source, not a network provider. Its external ID includes a
non-secret dump fingerprint and legacy row ID so repeated dry runs remain idempotent.
publication_identifiers
| Column | Contract |
|---|---|
id, public_id |
Stable identifier row. |
publication_id |
Current canonical publication. |
kind |
Initially doi, arxiv, openalex, pmid, pmcid, or other. |
value_raw, value_normalized |
Display and equality forms. |
normalization_version |
Parser version. |
first_source_record_id |
Provenance for the first accepted assertion. |
confidence_score |
Bounded 0 through 1; internal evidence only. |
created_at, updated_at, row_version |
Shared storage conventions. |
(kind, value_normalized) is globally unique, not merely unique within a publication. Repeated
evidence attaches to the existing identifier. If an accepted strong identifier already belongs to
another publication, ingestion must resolve the collision through the publication merge policy in
the same transaction or stop for review. It may not insert a duplicate.
author_publications and author_publication_evidence
author_publications is the canonical many-to-many relationship. Its pair
(author_id, publication_id) is unique for all lifecycle states. It carries status (active or
retired), first_observed_at, last_observed_at, and the shared audit fields.
author_publication_evidence records why the relationship exists. It references one
author_publication, an optional author_source_identity, and one publication_source_record.
The tuple of those three references is unique. Removing or correcting one provider assertion does
not erase other evidence for the same authorship link.
Duplicate prevention and merge rules
Authors
- An incoming
(source, external_id_normalized)already in the database resolves to that identity's active author. - A different user following that identity creates only a new
user_author_followsrow. - Same-name records with different identifiers remain separate. The UI may raise a soft review notice, but name equality never blocks creation or causes a merge.
- Cross-source matches follow the D3 trust boundary. Ambiguous evidence creates one review item, not a speculative identity attachment.
- A merge target is selected deterministically: resolved beats shell, more accepted strong
identities beats fewer, older
created_atwins the next tie, and lowestpublic_idwins the final tie. Caller argument order cannot change the result.
Publications
- Exact provider record identity or exact normalized DOI, arXiv, PMID, or PMCID resolves to the existing publication.
- A provider's explicit work crosswalk may add another identifier to that publication.
canonical_title_hashnarrows candidate lookup only. Title, year, author string, venue, or a fuzzy score cannot be a global uniqueness constraint.- Conflicting strong identifiers block automatic merge. The system records an admin-visible data quality finding and leaves both publications intact.
- A publication merge uses the same deterministic target rule: more strong identifiers, then more source records, then older creation, then lowest public ID.
- A merge reassigns source records, identifiers, authorship links, library rows, and origins in
one transaction. Duplicate links are coalesced without losing the per-user read/favorite state
or provenance. The losing publication remains as
mergedwith a stable alias.
The legacy Scholar cluster ID may be retained as legacy_import evidence, but it is not a new
root identifier and never outranks a sanctioned provider identifier.
Identity review model
The v1 user-facing identity queue has exactly the four decided card types:
identify_shell;confirm_ambiguous_match;possible_duplicate;not_this_person_fallout.
review_items
| Column | Contract |
|---|---|
id, public_id |
Stable card identity. |
type |
One of the four types above. |
subject_author_id |
Required primary author. |
related_author_id |
Optional second author, stored in canonical public-ID order for pair cards. |
dedupe_key |
Deterministic key based on card type and subject or pair. |
evidence_version |
Changes only when materially new evidence arrives. |
status |
open, skipped, resolved, or superseded. |
raised_count |
Diagnostic count; repeated syncs do not create repeated cards. |
first_raised_at, last_raised_at, resolved_at |
Lifecycle timestamps. |
created_at, updated_at, row_version |
Shared storage conventions. |
There is at most one non-superseded row per dedupe_key. A repeated failure updates
last_raised_at and raised_count. A skipped or resolved card may reopen only when its
evidence_version changes.
review_candidates and review_decisions
review_candidates stores the stable candidate order, referenced existing author or source
identity, and a versioned evidence summary used by the UI. It never creates an identity attachment
before the user decides.
review_decisions is append-only. It records the item, action, actor, selected candidate or target,
evidence version, operation ID, creation time, and optional undone_at. A later decision never
overwrites an earlier one.
Negative identity evidence
Two small tombstone tables prevent review loops:
confirmed_different_author_pairsstores the canonical ordered author pair, confirmed evidence version, deciding user, decision ID, and time.rejected_author_identity_linksstores an author plus source identity candidate, confirmed evidence version, deciding user, decision ID, and time.
The detector suppresses evidence at or below the confirmed version. Materially new evidence may reopen the same review item rather than creating a second card.
Audit and undo
operations
Every consequential write groups into one operation. Kinds include author_merge,
publication_merge, review_decision, bulk_import, unfollow, refollow, and
admin_repair.
The row stores id, public_id, kind, actor user if retained, source context, status (applied or
undone), a safe summary JSON object, created_at, undone_at, and a link to the reversing
operation when applicable.
operation_changes
Each row stores an operation-local sequence number, entity type, entity public ID, change kind,
versioned before and after JSON, and the entity's row_version after the write. The pair
(operation_id, sequence) is unique. Snapshots contain only fields required to explain and
reverse the domain change. Credentials, tokens, raw provider payloads, and password data are
forbidden.
Transaction and undo contract
- The domain write, audit rows, and review decision commit in one SQLite transaction.
- An undo applies changes in reverse order in a new operation. It is all-or-nothing.
- Before undo, every touched row must still match the recorded after-version or an explicitly defined non-conflicting successor state. A conflict stops the undo without partial changes and reports the exact blocking entities.
- Undo restores moved source identities, follows, authorship links, library origins, review state, and negative-evidence tombstones. Rows coalesced during a merge are restored from their recorded lifecycle states rather than recreated with new IDs.
- Merged author and publication rows are retained. Undo never depends on recovering a deleted canonical row.
- Immediate UI undo and later Activity undo call the same domain operation.
OPEN D4: approve state-based undo with no arbitrary time limit while the conflict preconditions still hold. The alternative is a fixed undo window followed by admin-only repair.
Deletion and retention
- Unfollow is a reversible lifecycle change, not deletion.
- Removing one user cannot delete a shared author, publication, source record, identifier, or another user's library state.
- Merged rows, operation records, and negative identity evidence are not garbage collected while they can support alias resolution or undo.
- Provider cache retention is outside this spec. Provenance rows needed to explain canonical data remain.
- A future privacy deletion flow may remove or anonymize user-owned rows after its recovery window, but it must leave shared scientific metadata intact and null actor references where required.
OPEN D5: approve no automatic orphan deletion. The proposed policy retains unfollowed authors and publications until an explicit admin garbage-collection operation runs with a backup, dry-run preview, reference checks, and an audit record.
Migration contract
Rewrite schema migrations
- Every release carries numbered, deterministic migrations with explicit up and down behavior.
- Startup takes an application migration lock before serving traffic.
- A pre-migration SQLite backup is mandatory for a version change. Backup verification and restore UX are release-gate concerns, but the migration may not proceed after backup failure.
- Table rebuilds use create-copy-verify-swap inside the safest transaction SQLite permits.
- Each migration runs
foreign_key_checkplus model-specific invariant queries before commit. - CI tests every migration up and down from a seeded prior-version database.
- A failed migration leaves the prior database usable or restores the verified backup. It never continues with a partly upgraded schema.
One-time legacy import
The Postgres dump is an identity seed and partial publication floor, not ground truth. Import is an offline, restart-safe process with a mandatory dry-run report. It never contacts any provider.
The import proceeds in this order:
- Validate the dump fingerprint and schema revision. Record an
import_runwith a non-secret source fingerprint, importer version, status, counts, and errors. - Map legacy users to already-created rewrite users through an explicit local mapping. Credential and login migration waits for the auth spec. Real emails never appear in logs or fixtures.
- Collapse duplicate legacy Scholar IDs into one
scholar_importsource identity and one global author. Create separate per-user follow rows. Apply the D3 calibration policy to OpenAlex mappings; unresolved rows remain shells. - Import global publications and normalized identifiers. Preserve every legacy row as a
legacy_importsource record. Strong identifier collisions use the normal merge rules; title hash collisions are reported, not silently merged. - Import canonical author-publication links and provenance, then construct each user's publication origins through that user's follows.
- Collapse legacy per-profile reading state into one
user_publicationsrow per user and publication using D2. Mark every imported originlegacy_import, so the historical corpus does not appear as newly discovered. - Run invariant checks and emit a dry-run or applied report. Counts include source rows, distinct authors, follows, shells, review items, publications, identifier collisions, read-state collapses, and favorites preserved. Reports contain no names, emails, titles, or source IDs.
legacy_import_mappings records (source_fingerprint, legacy_table, legacy_row_id) to new public
ID and import run. The triple is unique, making reruns idempotent. Applied reruns verify and reuse
the mapping rather than duplicating domain rows.
The private real dump and calibration payloads remain outside git. Public tests use synthetic, anonymized fixtures that reproduce the same relationship shapes.
Integrity checks and observability
The application exposes or logs safe counts for these checks:
- no duplicate active source identity or publication identifier;
- no user-author or user-publication duplicate;
- no merged cycle and no merge target that is itself unresolved at query completion;
- every visible user publication has at least one active origin;
- every origin's follow and authorship link agree on the same canonical author;
- every active authorship link has at least one evidence row, except an explicit manual import;
- every resolved review decision references an applied or undone operation;
- every applied merge has a complete operation change set;
- no open review duplicate by
dedupe_key; - no foreign-key violations or malformed versioned JSON.
Logs and reports use public IDs, counts, operation kinds, and error codes. They do not emit raw provider payloads, full imported URLs, publication titles, author names, credentials, or personal email addresses by default.
Deterministic acceptance tests
Card 1 is ready to implement only after freeze, and implementation is accepted only when these tests exist:
- Two users follow the same OpenAlex ID: one author, one source identity, two follow rows.
- Two different OpenAlex IDs share an identical name: two authors, no automatic merge.
- One user follows two authors who share a publication: one user-publication state, two origins, one read toggle everywhere for that user.
- Another user sees the same global publication but retains independent read state.
- Unfollow removes the last visible origin, preserves state, and refollow restores it.
- Author merge and undo restore identities, follows, origins, review state, and aliases exactly.
- A post-merge conflicting edit blocks undo without partial reversal.
- Exact DOI and arXiv identifiers deduplicate; title hash similarity alone does not.
- Publication merge preserves all source records, identifiers, authorship evidence, origins, and per-user state; undo restores the prior graph.
- Repeated unresolved sync events update one review card. A new evidence version reopens it.
- Confirmed-different and rejected-identity tombstones suppress unchanged evidence.
- Legacy import is idempotent, any-read collapse follows D2, favorites follow D2, and no legacy origin is marked new.
- Each schema migration passes up, down, foreign-key, integrity, and interrupted-upgrade tests.
- Property tests prove normalization idempotence, merge outcome independence from argument order, and stable public-ID alias resolution.
Owner decisions
The draft recommends one answer for each unresolved choice:
| ID | Decision | Recommended answer |
|---|---|---|
| D1 | Public identity shape | Internal integer keys plus immutable UUIDv7 public IDs. |
| D2 | User-library semantics | Hide on last unfollow but retain state; any-read wins legacy collapse; preserve favorites; derive NEW only from recent incremental sync. |
| D3 | Cross-source auto-attachment | Auto only exact IDs, explicit provider crosswalks, and completed calibration auto rows; review everything weaker or conflicting. |
| D4 | Undo horizon | No time limit while recorded row-version preconditions still hold; otherwise stop with a conflict. |
| D5 | Orphan retention | Never delete automatically; require explicit backed-up, dry-run, audited admin garbage collection. |
Freezing this spec means the owner has answered D1 through D5, approved any resulting edits, and
explicitly changed the status at the top to FROZEN with the approval date. Until then, no schema
or implementation work begins.