Skip to content

ADR 0009: v1.0 Public API Scope and Stability Guarantee

Status

accepted

Context

NENE2 reached v0.7.0 with full CRUD example endpoints, MCP integration, JWT auth, and Packagist publication. The CHANGELOG.md carries the notice "breaking changes may occur between minor versions until v1.0.0."

Before cutting v1.0, the project needs to answer two questions:

  1. What is the "public API"? — which classes, interfaces, and constants does NENE2 commit to not breaking between major versions?
  2. What is src/Example/? — framework core or reference implementation? The ADR 0006 decision deferred this to post-v0.2.x.

Options evaluated for src/Example/

OptionProsCons
Separate into its own repositoryClean library boundaryTwo repos to maintain; MySQL integration tests depend on Note/Tag repositories; significantly higher solo-dev overhead
Keep in core, mark stableNo split costPollutes the version signal — changes to example code would require major-version bumps
Keep in core, mark as reference implementation (unstable)No split cost; tests stay intact; AI tools can read usage patterns from vendorRequires explicit documentation that src/Example/ is not covered by the stability promise

Scope of "public API"

The project uses semantic versioning. For v1.0+, a breaking change is any modification that:

  • Removes or renames a public class, interface, method, or constant that application code is expected to reference.
  • Changes a method signature in a way that breaks existing call sites.
  • Changes the behavior of a method in a way that violates its documented contract.

Decision

1. src/Example/ stays in the core repository

src/Example/Note/ and src/Example/Tag/ remain part of the hideyukimori/nene2 package through v1.0 and beyond. Separating them into a standalone repository is deferred indefinitely unless team size or adoption patterns make it necessary.

Rationale: The MySQL integration tests in tests/Database/MySql/ use the Note/Tag repositories as their fixture layer. Splitting the repository would either duplicate the test infrastructure or create a cross-repository test dependency — both are worse than the namespace cost.

2. src/Example/ is explicitly outside the stability guarantee

src/Example/ is a reference implementation — code that demonstrates how to use the framework, not code that applications are expected to depend on directly.

  • Changes to src/Example/ classes, method signatures, or DTOs do not trigger a major version bump, even after v1.0.
  • Application code should copy and adapt the example patterns, not import the example classes as library dependencies.
  • This policy is analogous to a framework's scaffold generator: the generated code belongs to the application, not to the framework.

3. The stable public API surface

The following namespaces and their public members are covered by the v1.0 stability guarantee (no breaking changes without a major version bump):

NamespaceKey public surfaces
Nene2\RoutingRouter (methods: get, post, put, patch, delete, handle, param), PARAMETERS_ATTRIBUTE, RouteNotFoundException, MethodNotAllowedException
Nene2\HttpRuntimeApplicationFactory (constructor params and create()), JsonResponseFactory, ResponseEmitter, HealthCheckInterface, HealthStatus, JsonRequestBodyParser, JsonBodyParseException, PaginationQuery, PaginationQueryParser, QueryStringParser, ConditionalGetHelper, ConditionalWriteHelper
Nene2\DependencyInjectionContainerBuilder (set, value, addProvider, build), ServiceProviderInterface
Nene2\ConfigAppConfig (incl. $problemDetailsBaseUrl, $allowDevSecret, $demo), DatabaseConfig, AppEnvironment, ConfigException
Nene2\DatabaseAll *Interface types (DatabaseConnectionFactoryInterface, DatabaseQueryExecutorInterface, DatabaseTransactionManagerInterface), DatabaseConnectionException, DatabaseConstraintException
Nene2\ErrorDomainExceptionHandlerInterface, ProblemDetailsResponseFactory
Nene2\AuthTokenVerifierInterface, TokenIssuerInterface, TokenVerificationException, BearerTokenMiddleware, LocalBearerTokenVerifier (constructor accepts an optional Nene2\Http\ClockInterface $clock, default UtcClock — non-breaking addition; verify() evaluates exp/nbf against the injected clock so tests can pin time deterministically), CompositeAuthMiddleware, TotpAuthenticator, RecoveryCodes, GuardedJwtSecretResolver (constructor, resolve(), fromConfig()), JwtSecretException — see ADR 0013
Nene2\MiddlewareAll shipped middleware classes, RateLimitStorageInterface, ThrottleMiddleware
Nene2\ValidationValidationException, ValidationError
Nene2\ViewNativePhpViewRenderer, HtmlResponseFactory, HtmlEscaper, TemplateNotFoundException
Nene2\McpLocalMcpServer, LocalMcpToolCatalog, LocalMcpHttpClientInterface, LocalMcpHttpResponse, LocalMcpException
Nene2\TestingDatabaseTestKit (constructor, public readonly properties connectionFactory / queryExecutor / transactionManager, factories sqlite / fromConfig) — see ADR 0012
Nene2\AuditAuditEvent, AuditQuery, AuditPayloadMode, AuditTableConfig (VOs/enum/config — stable), and the contracts AuditRecorderInterface, AuditRecorderFactoryInterface, AuditEventRepositoryInterface — see ADR 0014. Not stable: the concrete AuditRecorder, AuditRecorderFactory, PdoAuditEventRepository, and AuditServiceProvider (see decision 5)
Nene2\ExportCsvWriter (constructor $stream / $headers / $bom / $sanitizeFormulas, methods writeRow, writeAll) — safe-by-default CSV writer: formula-injection neutralisation, RFC 4180 quoting, optional BOM, generator streaming. See ADR 0015
Nene2\DemoThe contracts DisposableOrgProvisionerInterface, DisposableOrgReaperInterface, DemoSessionSeaterInterface, DemoDataSeederInterface, DemoTemplateKeyInterface, DemoCapacityGuardInterface, DemoErrorPageRendererInterface; the VOs ProvisionedDemoOrg, DemoOrgRecord, SweepReport, DemoConfig; the exceptions SlugConflictException, DemoCapacityExceededException, DemoThrottledException; and the concretes StartDisposableDemoHandler (incl. TEMPLATE_PARAMETER), DisposableDemoSweeper, CountingDemoCapacityGuard, DemoRouteRegistrar (constructor accepts any PSR-15 RequestHandlerInterface since ADR 0018), MinimalDemoErrorPageRenderer (class name and constructor only — its rendered markup and copy are presentation, not contract) — see ADR 0017 / ADR 0018

4. Stable request attributes set by authentication middleware

Authentication middleware communicates identity to downstream handlers via PSR-7 request attributes. The following attribute names are part of the stable public API:

AttributeSet byValue
nene2.auth.credential_typeBearerTokenMiddleware, ApiKeyAuthenticationMiddleware'bearer' or 'api_key'
nene2.auth.claimsBearerTokenMiddleware (after successful token verification)array<string, mixed> — the JWT claims returned by TokenVerifierInterface::verify()

Handlers read these attributes via $request->getAttribute('nene2.auth.credential_type') and $request->getAttribute('nene2.auth.claims'). The attribute names will not change in a backward-incompatible way within the v1.x line.

5. Explicitly outside the stability guarantee

  • Nene2\Example\* — reference implementation (see decision 2)
  • Nene2\Audit\AuditRecorder / AuditRecorderFactory / PdoAuditEventRepository — default implementations; the *Interface types, VOs, and AuditTableConfig are the stable boundary (ADR 0014)
  • Nene2\Audit\AuditServiceProvider — reference wiring (copy and adapt, like src/Example/)
  • Nene2\Log\* — internal logging plumbing; PSR-3 (LoggerInterface) is the stable boundary
  • Nene2\Http\RuntimeServiceProvider — internal wiring; application code should not reference it directly
  • Nene2\Config\ConfigLoader — internal; applications receive AppConfig / DatabaseConfig DTOs
  • Concrete PDO adapters (PdoConnectionFactory, PdoDatabaseQueryExecutor, PdoDatabaseTransactionManager) — implementation detail; the *Interface types are the stable boundary
  • tools/ scripts — developer tooling, not part of the library API
  • docs/mcp/tools.json — catalog for the shipped example; applications define their own

5. MCP stability caveat

LocalMcpServer and LocalMcpToolCatalog track the MCP specification, which is externally controlled. If the MCP protocol introduces a breaking change, NENE2 will adapt and document it as an exception to the breaking-change rule, noting the upstream cause.

Consequences

Benefits

  • Application authors know exactly which surfaces they can depend on across major versions.
  • src/Example/ can evolve freely as a living tutorial without creating version pressure.
  • The MySQL integration test suite remains intact with no cross-repository dependency.

Costs / follow-up work

  • The composer.json description or README should note the src/Example/ status so adopters understand when they install the package.
  • Future additions to the stable surface require a conscious decision (and ideally an ADR or at minimum a CHANGELOG entry).
  • PHPDoc @internal annotations should be added to classes in the "outside the guarantee" category to help IDEs surface the boundary.
  • Issue: #334
  • Supersedes: ADR 0006 (deferred src/Example/ decision)
  • See also: CHANGELOG.md, docs/development/release-checklist.md

Released under the MIT License.