Skip to content

Add the change set aggregate and its state machine - #2453

Merged
pournasserian merged 3 commits into
v2from
2452-change-set-aggregate
Oct 18, 2026
Merged

pournasserian merged 3 commits into
v2from
2452-change-set-aggregate

Conversation

@pournasserian

Copy link
Copy Markdown
Member

Closes #2452

What

The first slice of F-CHG (#2418): the change set model in Core, with no storage yet.

  • Operations (FluentCMS.Core.ChangeSets): ChangeOperation is a create, update or delete on an item of kind entry, entity, media, schema or rule (CHG-01). It names the item by kind and id and the version it was based on. A create has no base version; an update or delete names version 1 or later. Create reports every problem at once. Schema is the one kind that exists today: an operation on a content type uses its id and ContentType.Version.
  • Aggregate: ChangeSet has an id, site id (SCH-07), rationale, sources, initiator, operations, state and rejection reason. Open checks the site, the rationale and each source, and reports every problem at once, with the position of each blank source. Operations are added with AddOperation while the set is open, an item appears at most once, and entity and content operations can share a set (CHG-09).
  • State machine: ChangeSetTransitions is the explicit CHG-04 table. TransitionTo and Reject return Result. A move the table does not define is refused, and the error names the states the set can move to, or says the state is final. A set leaves open only with at least one operation (CHG-01).
  • Rejection: Reject(reason) needs a non-blank reason and stores it. TransitionTo(Rejected) is refused with rejection_reason_missing, so there is no way to reject without one.
From To
open checks running
checks running checks failed, awaiting review, approved
checks failed open, rejected
awaiting review approved, rejected
approved published
published reverted

Why

F-CHG is next after F-SCH in the build order (#2432), and this is the first slice the epic suggests. Storage waits for the next slice so it does not collide with the F-SCH storage slice, which owns migrations this round. That slice adds the application services, the stored set and its operations, migrations for both providers, and an audit event with the initiator for every transition (AUD-N1), because the audit log needs a unit of work.

The epic places CHG-08 in its last slice. The stored reason moves here because the table cannot define the rejected state without it; offering the reason as rule feedback stays with F-RUL.

Some choices worth a look:

  • Checks running → approved is the auto-publish disposition (RTE-01, MCP-06): nobody approves, but publish keeps one way in, from approved, so both routes reach the same atomic publish (CHG-05). The alternative is checks running → published.
  • Checks failed → rejected lets a reviewer reject from the desk's "checks failed" queue (DSK-02) instead of waiting for the initiator to revise.
  • No way back from awaiting review yet. A reviewer edit (CHG-07) will probably send the set back through checks (FRM-02). That slice adds the transition.
  • Site id on the set, as content types have (SCH-07), so multi-site (MST-01, M3) does not need a migration to add it.
  • One operation per item. One diff per item and one version to restore on revert (CHG-03, CHG-06). Session grouping (CHG-02) will have to replace an item's operation rather than add a second one.
  • Sources may be empty; a blank one is refused. Requiring a source is a check (CHK-01, "source present"), and a human fix may have none.
  • The base version is the version number (ContentType.Version), not a version row id. The audit log's PreviousVersionId is a Guid, so the storage slice maps one to the other.
  • The set is mutable and a refused call leaves it unchanged. ContentType is immutable because each version is its own row; a set is one row whose state moves.

Tests

Unit tests in tests/FluentCMS.Tests/ChangeSets:

  • Operations: valid operations of each kind; an operation built from a created and a revised content type; an empty item id; a create with a base version; an update or delete with no base version, 0 or a negative one; undefined kinds; every problem reported at once.
  • Sets: open with trimmed rationale and sources and the initiator kept as user or agent; no sources; an empty site, a blank rationale and blank sources reported at once; a null initiator; a mixed entity, entry and schema set submitted; the same item twice; no operations; operations refused outside open and accepted again after a reopen.
  • Transitions: the table is pinned; every state is reachable from open; rejected and reverted are final; published has one way in; every allowed pair succeeds; every other pair, a state to itself included, is refused and changes nothing; a rejection with a reason is still refused where the table has none; the refusal message names the next states; a rejection with no reason, a blank one or TransitionTo(Rejected) is refused; a rejection stores its trimmed reason.

No new package and no new project reference. No model change, so no migration.

Checklist

  • Linked to an issue a maintainer has confirmed
  • Targets v2
  • Tests or docs updated where behaviour changed
  • No tool-generated co-author trailers or footers

pournasserian added a commit that referenced this pull request Oct 18, 2026
Closes #2454

## What

`SignInTests.cs` imports `FluentCMS.Core.Audit` for `InitiatorKinds`
instead of `FluentCMS.Infrastructure.Audit`, which it used for nothing
else.

## Why

#2449 moved `InitiatorKinds` to `FluentCMS.Core.Audit`, and #2450 was
written against the old namespace. Each passed CI alone, but together
they break the build on `v2` and in every pull request into it, such as
#2453. Build with warnings as errors and all unit and integration tests
pass with the fix.

## Checklist

- [x] Linked to an issue a maintainer has confirmed
- [x] Targets `v2`
- [x] Tests or docs updated where behaviour changed
- [x] No tool-generated co-author trailers or footers
@pournasserian
pournasserian merged commit 5e51966 into v2 Oct 18, 2026
2 checks passed
@pournasserian
pournasserian deleted the 2452-change-set-aggregate branch October 18, 2026 16:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:change-sets Change sets (CHG) type:feature New capability v2 FluentCMS v2 (v2 branch)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Change set aggregate: operations, rationale, sources, initiator and the state machine

1 participant