Skip to content

Add ADR-0013 on extension versioning, a surface baseline, and conformance suites for every extension point - #2620

Merged
pournasserian merged 11 commits into
v2from
2612-extension-versioning
Oct 26, 2026
Merged

pournasserian merged 11 commits into
v2from
2612-extension-versioning

Conversation

@pournasserian

@pournasserian pournasserian commented Oct 19, 2026 •

Copy link
Copy Markdown
Member

Closes #2612

What

Two slices of F-EXT (#2429): the versioning policy for extension interfaces (EXT-N2), and a conformance suite for every extension point, with checks that keep both true (EXT-N1).

  • ADR-0013, status proposed:
    • The contract of an extension point: its interface, the behaviour its conformance suite checks, and the names it stores.
    • Which changes are additive and which are breaking.
    • Any change is allowed until the plugin SDK ships in M3, but each is recorded. From then on breaking changes wait for a major version, and a removal is marked [Obsolete] for at least a minor release first.
  • Surface baseline: tests/FluentCMS.Tests/Extensions/extension-points.txt lists every [ExtensionPoint] interface's members, with types, nullability, default arguments and which members have a default body.
    • ExtensionPointRulesTests compares the interfaces with it, each member within its own interface, and prints each added or removed member with the interface it belongs to. Events count as members.
    • FLUENTCMS_UPDATE_EXTENSION_BASELINE=1 rewrites it.
  • Conformance classes:
    • [ConformanceFor(typeof(...))] marks the class that checks an extension point. It is one file in tests/, compiled into both test projects.
    • The existing suites for field types, check types, tool and resource providers, and media storage now carry it.
    • New JobHandlerConformanceTests: a type name that is lowercase and stable and that no other handler takes; a RunAs that EngineJobs allows, so the enqueuer or the one engine Core lists the handler for; and a failure with a code and a message, not an exception, for a payload the handler can't read, unless the handler reads nothing from its payload. Every job handler derives from it: the check run, the retention purge, bulk entry updates and, in the integration tests, which compile the base class too, webhook delivery.
    • New SearchIndexConformanceTests, run on SQLite and PostgreSQL:
      • every word is matched as a prefix, and all of them are required;
      • indexing again replaces the words;
      • a write rolled back with its unit of work leaves nothing;
      • pages read in turn are one best-first read.
        The in-database indexes derive from it.
    • New ChangeSetEventsConformanceTests, for the publish listener extension point that came with webhook delivery (Deliver signed webhooks on publish and revert from the job queue, with retries and a delivery log #2623), run on SQLite and PostgreSQL. A listener takes a publish and a revert's publish without failing, and what it records rolls back with a publish that doesn't commit. The webhook listener derives from it, with a subscription seeded so it has something to queue.
  • The check: a test reads the test sources and fails for any extension point that no class marked [ConformanceFor] covers, naming it.
  • CONTRIBUTING.md: the conformance class, the baseline and the versioning rules. The job handler, publish listener and search index sections, and those interfaces' comments, name their suites. The ADR index lists ADR-0013.

Why

EXT-N1 asks that core features use the public extension interfaces, and the epic asks for a conformance suite per interface that every implementation passes. EXT-N2 asks for versioned interfaces with breaking changes only at majors. Until now nothing kept either true: an extension point could change, or land without a suite, unnoticed.

Choices worth a look:

  • A small baseline instead of PublicApiAnalyzers: it tracks only what plugins implement, adds no package, and keeps the rest of Core free to change while nothing is published.
  • The conformance check reads the sources, because the classes live in both test projects and neither references the other. The attribute is the marker, so a base class that covers two interfaces, as the tool provider one does, says so.
  • The ADR is proposed, since ADR-0001 has maintainers accept by setting the status.
  • The baseline covers the interfaces' own members. A change to a type that only appears in a signature, such as JobContext, is reviewed with that type, as the ADR says.
  • The baseline already caught one change: merging v2 with webhook delivery added IChangeSetEvents, the rule test named its member, and the baseline gained those two lines in this PR.
  • The job handler suite is compiled into both test projects, like the attribute, so a handler in Infrastructure, such as webhook delivery, derives from the same class as those in Core.
  • Not in this PR: risk scorers, notification channels and content storage get their suites when they land.

Tests

  • ExtensionPointRulesTests:
    • Every extension point has a conformance class. With the search index's marker removed, the test fails naming ISearchIndex.
    • Every extension point matches the baseline. A member that moves to another interface with a member of the same name is a change, and an event is a member.
  • CheckRunJobConformanceTests, BulkEntryUpdatesConformanceTests and WebhookDeliveryJobConformanceTests: each handler's type, its RunAs, and four payloads it can't read. RetentionPurgeJobConformanceTests: its type and RunAs.
  • InDatabaseSearchIndexConformanceTests: four checks, each on SQLite and PostgreSQL.
  • WebhookPublishListenerConformanceTests: two checks, each on SQLite and PostgreSQL.

The full suite ran locally against a postgres:17 container as well as SQLite, and markdownlint passes. No new package and no new project reference.

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 pournasserian added this to the M1 Governance core milestone Oct 19, 2026
@pournasserian pournasserian added v2 FluentCMS v2 (v2 branch) type:adr Architecture decision proposal area:ext Extension points (EXT) labels Oct 19, 2026
@pournasserian
pournasserian merged commit f079a76 into v2 Oct 26, 2026
2 checks passed
@pournasserian
pournasserian deleted the 2612-extension-versioning branch October 26, 2026 02:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:ext Extension points (EXT) type:adr Architecture decision proposal v2 FluentCMS v2 (v2 branch)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Extension interface versioning (ADR-0013), a surface baseline, and a conformance suite for every extension point

1 participant