API stability 

Which classes the semantic-versioning promise covers, and what it promises. The authority is the marker in each class-level docblock, not this page and not the DI container visibility (ADR-127).

The three markers 

@api — call it 

Classes and interfaces you call: the feature services (CompletionServiceInterface and friends), LlmServiceManager, the option classes, the response and value objects they accept and return, and the typed exceptions they throw. Within a major version:

  • no class or method is removed,
  • no method signature changes incompatibly,
  • documented behaviour does not break.

Everything a marked method's signature mentions is itself @api — you never receive an object you are not allowed to rely on.

@api Extension point — implement it 

Interfaces and attributes third parties implement: ToolInterface, GuardrailInterface, ProviderInterface and the capability interfaces, TranslatorInterface, SearchBackendInterface, the preset/evaluation providers, the middleware contracts, and the #[AsLlmProvider] / #[AsTranslator] attributes. These carry a stricter promise, forced by the direction of implementation: no new abstract member within a major version — adding one would break every existing implementation, not just callers.

@internal — hands off 

Everything else: backend controllers, dashboard widgets, hooks, upgrade wizards, console commands, DI compiler passes, TCA form elements, Extbase repositories and the setup wizard. These may change or disappear in any release, including patch releases. PHPStan and modern IDEs warn when code outside this package touches them.

What is out of contract 

  • Subclassing internals: protected members of @api classes are not part of the promise. Extend via the extension points, not inheritance.
  • Constructor signatures of @api services: obtain them from the DI container, never via new. Constructing option/value objects directly is fine — their constructors are part of the signature promise.
  • Anything reached by reflection or by reading private state.

The snapshot below records the constructor a caller reaches with new, including the service ones that are out of contract here. That is deliberate: no mechanical rule separates a value object a consumer builds with new from a service it only ever injects, and a new required argument on the former breaks callers exactly as a deleted method would. Recording them costs a service-wiring change one snapshot regeneration; recording none of them let a widened constructor through the gate in silence.

The constructor is also the one member that is not recorded declared-only. Every other member has to be, because inherited TYPO3 core members differ between 13.4 and 14.x — but new Foo(...) binds to whatever constructor Foo inherits, so a declared-only rule left four Specialized services and two ProviderResponseException subclasses with no constructor line at all, and a required argument added to their shared base moved nothing. A constructor is therefore taken from the nearest declaring class whenever that class is inside Netresearch\NrLlm.

Two cases still carry no constructor(...) line, and both are intended:

  • The constructor is inherited from outside this repository — TYPO3 core's AbstractEntity, \RuntimeException. That signature is not ours and does differ across the version matrix, so recording it would make the snapshot depend on which matrix cell rendered it.
  • There is no public constructor. A value object with a private __construct and static factories is reached through the factories, which the snapshot records as methods.

Today that is 70 of the 97 @api classes with a constructor line.

Enforcement 

The rendered @api surface is frozen in Tests/Unit/Api/api-surface.txt: an unintended signature change fails CI before review, and the same test asserts the closure rule: every Netresearch\NrLlm type an @api method or property signature mentions is itself @api. An intended change updates the snapshot in the same PR — the diff is the review artifact.

Constructor parameter types are outside the closure rule. A DI-built service is handed its collaborators by the container, so its constructor names internals by design; the line is still frozen — widening it is a breaking diff — but the types on it are not a promise that you may call them.

The failure is classified, because "different" makes a new value object read like a deleted method. An additive diff (a new class, method, property, constant or enum case) is regenerated and noted under ### Added. A breaking one — anything removed or changed, a widened constructor included — is a decision, and the failure message says so and points at Deprecation and removal policy.

How something leaves the surface again is Deprecation and removal policy. Which TYPO3 and PHP versions the promise is made on is Support matrix.

Versioning during 0.x 

While the extension is pre-1.0, the promise applies one level down, as is conventional: minor releases (0.N → 0.N+1) may break, and do so only with a CHANGELOG entry under a BREAKING heading; patch releases do not break. From 1.0.0 on the promise applies as written above.