Repository navigation
Store entries with versions checked against their content type #2486
Copy link
Copy link
Closed
Labels
Milestone
Description
Part of #2416 (F-CNT). First slice: entries of every content type are stored with a full version history (CNT-02, ADR-0005). Each write is checked against the entry's content type definition and the field types it uses, and makes a new version that is never overwritten. An entry keeps two pointers, to its latest draft and to its published version. Entity types are content types too (#2465), so an entity such as a product is stored the same way, with its facts.
Scope
- Core (
FluentCMS.Core.Schema):IFieldTypegainsValidateValue(value, settings, fieldTypes), which checks one value of a field and returns errors withvalue_invalidinstead of throwing. Every built-in implements it, following whatDescribeValuealready tells agents: text and rich text withmaxLength, numbers withinteger,minandmax, booleans, dates and times (date-only too), enum values, media and reference ids as UUIDs, lists item by item through the registry, and any JSON.FieldTypeConformanceTestsgains values a type must accept and reject, checks that the type accepts its own documented example, and that it never throws whatever a value holds. - Core (
FluentCMS.Core.Content): the entry model. AnEntryhas its id, site, content type, latest-draft version and published version, plus the latest draft's fields. AnEntryVersionholds its number, the content type version it was checked against, its fields as one JSON document and when it was written. - Core: an entry document is a JSON object with one property per field. All problems come back at once, each naming its field (UX-03): a property that is not a field of the type, a required field that is missing, and a value its field type rejects. An explicit
nullreads as an absent value. On an entity type every value is a fact,{value, source, owner, validFrom, validUntil, lastVerified}, checked against the shape the schema document describes (SCH-03, SCH-06). - Core:
IEntryDirectory, returningResultfor expected failures. It lists the entries of a content type, reads an entry, reads any of its versions, lists its history, creates an entry, and updates an entry from the version the caller read. Writes are checked against the content type's current version. - Infrastructure:
EntriesandEntryVersionstables, scoped to the site. Fields arejsonbon PostgreSQL. A write inserts a version row and moves the latest-draft pointer from the version the caller named, so a concurrent update gets a conflict instead of overwriting. The published pointer is stored but moved only by publish, which is not part of this slice. A trigger on both providers refuses anyUPDATEof a version row. - Audit (AUD-01, AUD-N1):
entry.createdandentry.updated, with the entry as subject and, for an update, the base version's row as previous version, appended in the same unit of work. - Migrations for SQLite and PostgreSQL, each with a working
Down.
Out of scope
- Entry states and the change set transitions that move them, including publish moving the published pointer (CNT-01, next slice). Entry operations in change sets wait for the F-CHG lane's publish and revert slices (Add atomic change set publish #2482 and the one after it), because they change
ChangeSetDirectory. - Checking that a referenced entry, entity or media asset exists, the reference index and where used (CNT-03, CNT-05), and resolved views (CNT-04): later slices.
- Soft delete and restore (CNT-06, REL-03): later slice.
- Validity rules and flags on facts, such as unsourced and stale (ENT-02, ENT-03): F-ENT (F-ENT: Entity registry with sources, validity and blast radius #2417).
- The entry tools (F-MCP F-MCP: MCP server, agent credentials, scopes and caps #2423) and paging of entry lists: with the tools.
Acceptance
- A created entry belongs to the site, starts at version 1 with no published version, reads back with its fields, and
entry.creatednames it. - Every update stores the next version. Earlier versions stay readable, the history lists them all in order, and
entry.updatedcarries the base version's row as previous version. - An update from a version other than the latest gets
version_conflictand writes nothing. Of two concurrent updates from one version, one wins and the other gets a conflict. - An unknown property, a missing required field and an invalid value are reported together, each naming its field, and nothing is stored. An unknown content type, entry or version fails with its own code.
- An entity's facts are checked for their value and every metadata property.
- An
UPDATEof a version row fails on both providers. - A create in an abandoned unit of work leaves neither the entry nor its event, and a failed write leaves the caller's unit of work able to commit.
- Every built-in field type passes the extended conformance tests, and so do the sample plugin types.
- Everything above passes on SQLite and PostgreSQL, the new migration's
Downworks on both, andProjectReferenceRulesTestspass with no new package.
Activity