Skip to content

Schema changes as change set operations: content type writes wait for publish #2513

Closed

Description

@pournasserian

Part of #2415 (F-SCH). The SCH-05 slice, now that change sets publish (#2478) and revert (#2510): every content type write is a change set operation. A create or a revision stores its new version and adds the operation to a change set in one unit of work, so no schema change exists outside a change set, and what is live changes only when that set publishes. Earlier versions stay readable.

Decision: how content type writes wait for publish

  • A content type has two pointers. Version is the latest version written, which is the authoring view. PublishedVersion is what is live, and only publishing a change set moves it (Atomic publish: version pointers and provenance in one unit of work #2478).
  • No write changes what is live. The authoring reads keep reading the latest version, so a set can build on its own drafts and an agent writes entries against the schema it is changing. These are GetAsync, ListAsync, the schema document (SCH-06) and entry validation (F-CNT). The live read is new: GetPublishedAsync(slug), and the published version on ContentTypeSummary, for delivery (F-API F-API: Delivery API with provenance, Markdown and site index #2425) to read.
  • A revision is based on the latest version or on the published one, or, in a set that already holds the type, on the version that set proposes. Basing it on the published version drops a draft that is still unpublished, for example after its set was rejected, without having to revert it first. A field another type's draft references cannot be dropped this way either (SCH-04): the check covers the fields of every draft an open set proposes as well as the base's. A draft is a base only while a set that can still publish proposes it, so a rejected or reverted draft cannot be built on. While an open set proposes a draft, a revision keeps that draft's entity flag (SCH-03) as well as its referenced fields.
  • References between types (SCH-04) are checked against what each type is heading to: every draft an open set proposes, or its published version while there is none. A rejected draft neither removes a live reference nor offers a field to reference, and a field a pending draft removes is no target. Sets still publish in any order, so a check at publish follows in Check schema references when a change set publishes #2604.
  • A type that no set has published, or that a published delete took offline (a rejected create, a reverted create), has nothing live to update. Its next revision therefore records a create, which makes it live again when its set publishes, and an agent needs the create scope for it (MCP-05).
  • Considered and not chosen: switching the authoring reads to the published version. Entries that need a schema change could then not be written until that change publishes, which rules out a set that mixes both (CHG-09). Every content scenario would also have to publish its types first.

Scope

  • Core (FluentCMS.Core.ChangeSets): ChangeSetTarget, the set a write joins. It is either an existing set by id, or a session id with the rationale and sources for the set it opens, under the rules of AddToSessionAsync (CHG-02).
  • Core: ChangeSet.Record(operation) adds an operation, or extends the one the set already holds for the item. A set keeps one operation per item (CHG-03). A later write of the same item must be based on that operation's proposed version, and moves it to the new version: a create stays a create and an update keeps its base.
  • Core (FluentCMS.Core.Schema): IContentTypeDirectory.CreateAsync and ReviseAsync take a ChangeSetTarget. A create records a create proposing version 1, and a revision records an update from its base to the new version.
  • Infrastructure: one unit of work holds the version row, the operation and their events. content_type.created or content_type.revised now carries the set's id, followed by change_set.operation_added, or change_set.operation_updated when the write extends an operation. Expected failures on either side are found before the unit of work begins, as both directories already do: an invalid definition, a stale base, a set that is not open, an agent's items cap or ownership. An agent's daily cap is the exception: since Hold the agent's credential while counting its caps, and keep refusal events #2580 it is counted inside the unit of work, with the agent's credential held, so a refusal rolls the unit of work back.
  • Reads: ContentTypeSummary.PublishedVersion, and IContentTypeDirectory.GetPublishedAsync(slug), which fails with content_type_not_published for a type no set has published yet.
  • No migration: both pointers exist.

Out of scope

Acceptance

  • Creating a content type into an open set stores version 1 and a create operation in that set in one unit of work, and content_type.created carries the set's id. The type lists with no published version, and GetPublishedAsync fails with content_type_not_published until the set publishes.
  • Revising it into the same set extends the set's operation to the new version. The set keeps one operation for the type, and its diff runs from the base to the newest version.
  • Revising a published type into a new set stores the next version and an update from the published version. Once the set publishes, GetPublishedAsync returns the new version and the previous one stays readable through GetVersionAsync.
  • A revision based on the published version succeeds while a later, unpublished draft exists. A revision into a set that already holds the type is based on the version that set proposes, even after another set drafted the type. A revision based on any other version fails with version_conflict. It cannot drop a field another draft references.
  • After a rejected create, or a published create that was reverted, the type's next revision is a create, and publishing it makes the type live.
  • A write through a session target joins the session's open set or opens one (CHG-02).
  • A write into an unknown set, a set that is not open, or another agent's set, and a write past an agent's items cap, are refused. None of them stores a version, an operation or an event, and the caller's outer unit of work can still commit.
  • A write that would open a set past an agent's daily cap is refused and stores nothing but its security event. It is counted inside the unit of work (Hold the agent's credential while counting its caps, and keep refusal events #2580), so it rolls a caller's unit of work back.
  • A failure injected after the version row reached the database and before commit leaves neither the version nor the operation.
  • All of the above passes on SQLite and PostgreSQL, and ProjectReferenceRulesTests pass with no new package.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    type:featureNew capabilityv2FluentCMS v2 (v2 branch)

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions