Skip to content

Changelog

This page is generated from the commit history by tools/changelog.php — do not edit it by hand, fix the commit message instead.

Every user-facing change across the four packages. The bold prefix is the commit’s scope: core, attributes, laravel and inference-phpstan are the packages, and the rest (website, repo, ci) ship no package. Entries begin after v0.1.2; older history is in the repository git log.

Each package repository also carries its own CHANGELOG.md with just its entries.

  • laravel: descend into the source roots the application declares (#442)
    • Docuccino\Core\Inference\TypeEngineBuilder::build() takes a new array $declaredPaths = [] parameter — the descend scope before the host narrowed it, which the engine uses as the yardstick for whether a declined hop is the host’s own narrowing (reportable) or the engine’s containment (not). It is optional and empty means “no yardstick, report nothing”, so a host that ignores it keeps working; but any third-party IMPLEMENTOR of the interface must add the parameter to match the signature. The one in-repo caller passes it by name, and a positional caller that previously passed $configFile sixth now hits a TypeError rather than misbehaving silently.
  • core: escape a name once, where the diagnostic that carries it is made (#438)
  • inference-phpstan: read the status a declared throw was built with (#440)
  • inference-phpstan: read a status the application declared wherever it lives (#437)
  • bump astro from 7.2.0 to 7.3.2 in /website (#435)
  • bump js-yaml from 4.3.1 to 4.3.2 in /website (#434)
  • bump sharp from 0.35.3 to 0.35.4 in /website (#433)
  • laravel: read a document keyword as the set it declares, not as any string (#425)
    • config.unknown-error-responses and config.unknown-tag-strategy are retired into a single config.unknown-value (warning), so a diagnostics.accept list naming either of the old codes will report them as accepted-but-never-emitted; replace both with config.unknown-value. An unrecognised value for documents.*.error_responses, tags.default_strategy, versioning, on_route_error, representation.filters, representation.nullable, representation.operation_id, representation.enums.naming or a document’s viewer.source is now refused and reported rather than passed through, so a document that was silently carrying a bogus keyword now builds from the documented default and says so.
  • laravel: name the setting that says which requests are authenticated, not the mechanism (#420)
    • documents.*.security.auto_detect_middleware is renamed to documents.*.security.auth_middleware. The old spelling is not read and is not reported — it lives in config/docuccino.php, whose build keys this release already stops reading and reports wholesale as config.not-migrated or config.stale-php-keys. Use the new name in docuccino.yaml; behaviour, default (auth*) and position under security are unchanged, and document.configHash moves once for any document that sets it.
  • laravel: read a build’s configuration from the tool’s own file (#417)
    • build configuration moves out of config/docuccino.php into a docuccino.yaml at the project root, and the keys left behind are no longer read. Everything a build reads moves — each document’s info, servers, routes, security, error_responses, tags, api_version, webhooks, content, examples, coverage, overlays, representation, versioning, integrations and export, plus top-level extensions, lint, diagnostics, engine, on_route_error, cache.enabled and cache.path. enabled, each document’s viewer bag and cache.store stay in config/docuccino.php, because the service provider reads them when the application boots and when the viewer serves a request. Run php artisan docuccino:install to write the new file. An application that has not migrated is reported rather than silently ignored: config.not-migrated where no docuccino.yaml exists, and config.stale-php-keys where one does and leftovers remain. There is no merge and no precedence between the two files — a build key in config/docuccino.php has no effect at all.
  • laravel: name a route filter by class, and drop the key that could never be cached (#415)
    • documents.*.routes.closure is removed. Move the predicate into a class implementing Docuccino\Core\Extensions\Contracts\RouteFilter and name that class under documents.*.routes.filter; it is resolved from the container, so whatever the closure closed over becomes a constructor dependency. Then delete the closure line — no configuration file has a form for a callable, so there is nothing the key can hold that a build will honour. DocumentConfig::$routeFilter is now ?RouteFilter rather than a mixed callable slot, so an extension that read it and called it as a callable calls includes() instead.
  • core: drop the representation keyword no reader has ever read (#413)
    • documents.*.representation.lists is no longer read. It never affected the emitted document — both of its values produced the identical parameter — so removing it changes no consumer-visible byte, but it does change x-docuccino.document.configHash for any document that set it, and an application that leaves the key in its own config file will simply have it ignored.
  • core: leave the viewer wiring out of the hash that keys a document (#410)
    • x-docuccino.document.configHash no longer folds in a document’s viewer wiring, so it stops changing when only the viewer’s route, middleware, gate, source, driver, CDN flag or driver configuration changes — and its value changes once, for every document that configures a viewer. Anything comparing the hash across this version boundary will see one difference; anything keying on it continues to work, and now shares a fragment cache entry across viewer-only edits.
  • laravel: write the configuration file from the one it replaces (#424)
  • core: read a project’s YAML configuration as a shape that refuses rather than casts (#411)
  • laravel: report a ->can() gate that publishes a 403 no request can provoke (#393)
  • core: read a parameter name as the leakage lint already reads a property name (#388)
  • laravel: rename a request field and a parameter across an API version (#385)
  • bump svgo (#432)
  • core: fingerprint a float the same on every host (#427)
  • laravel: read every branch a policy resolves through, not the four one major had (#422)
  • repo: regenerate a golden with the version it already records (#416)
  • core: refuse a configuration file that expands past what a configuration can hold (#428)
  • laravel: refuse a configuration value the build cannot read, instead of defaulting in silence (#429)
  • laravel: key a fragment on the code and configuration that shaped it (#426)
  • laravel: key a fragment on the mapper that shaped it, not the name it was resolved by (#423)
  • laravel: refuse a build whose configuration could not be read, rather than exiting clean (#419)
  • laravel: keep in the framework config only what boot and a request need (#418)
  • laravel: read every configured switch one way, and refuse a value that names none (#412)
  • core: mint a component id by the rule that decides whether two components are one (#409)
  • laravel: read the middleware a route inherits, which a console build never saw (#408)
  • core: key a signature and a minted name on the order their readers actually resolve in (#407)
  • laravel: read the authentication middleware by every name a route can give it (#402)
  • laravel: key a digest on the order its reader resolves in, not only on the pairs it holds (#401)
  • laravel: report a ->can() gate only where the policy body is one its reader owns (#397)
  • inference-phpstan: read a throw as the file the fold gave up in, not the file its class was declared in (#396)
  • laravel: publish the status an exception pins in its own constructor, not a 500 (#395)
  • inference-phpstan: report every status it could not read, and name the throw it came from (#392)
  • laravel: refuse a path-parameter rename, and name the operation that refused one (#387)
  • inference-phpstan: analyse an action once per build, not once per version document (#383)
  • laravel: check a form request body as the message arrived, not as the application rewrote it (#380)
  • laravel: publish every recorded example name, on an error status as on any other (#379)
  • laravel: remove the problem-details error preset (#358)
    • the problem-details error preset is removed. error_responses now accepts 'default' or 'none' only, and the bag form (['preset' => …, 'errors_shape' => …]) is gone with it. A document still configured either way is built as if it said 'default' and reports config.unknown-error-responses at warning, so nothing fails silently. Set it to 'default' or 'none' explicitly: REMOVING the key is not the same edit, because an absent error_responses resolves to 'none' and publishes no error responses at all.
  • laravel: say what the too-dynamic warning is, and what a filled example really carries (#374)
  • repo: edit the fixture tree as one writer replacing whole files, never truncating one others read (#372)
  • core: read a bound as the values it admits, not as the value one unit past it (#371)
  • inference-phpstan: depend on a declaration a fold read, and locate one without loading it (#370)
  • core: illustrate a schema from every keyword that names a value (#369)
  • inference-phpstan: depend on every file a traced body was written in, not only the class that reports it (#368)
  • repo: read a config key by presence, and widen three guards to the domain they claim (#363)
  • laravel: withhold the framework error body at every producer that fills one in (#362)
  • inference-phpstan: read a class by everything that builds it, and depend on every file that decided (#361)
  • laravel: illustrate an unread error member with a value its own schema accepts (#360)
  • core: publish one generated illustration where a second only filled a member in (#359)
  • laravel: publish the media type a handler proved when its body could not be read (#357)
  • inference-phpstan: read a status where the throw is not a literal construction (#355)
  • laravel: keep the error body a handler proved when its status could not be read (#354)
  • ci: install the phpstan the fixture app can actually resolve, and give clover room (#365)
  • laravel: scope a scaffolded change only where the application forked the shape (#346)
  • laravel: write a version change beside the module that owns the shape (#345)
  • laravel: scaffold version-change classes from the diff the product can already compute (#342)
  • laravel: read version changes from every directory a modular app keeps them in (#341)
  • laravel: put a response field a version removed back into the older document (#336)
  • laravel: declare when a field became required, and in which direction (#334)
  • laravel: scope a version change to chosen operations (#321)
  • laravel: derive a document per API version from declared changes (#320)
  • attributes: declare the API version-change vocabulary (#318)
  • ci: retry the type-coverage gate serially, where its cache cannot race itself (#353)
  • laravel: publish one error contract for an HTTP error whose status could not be read (#351)
  • inference-phpstan: read a construction’s status by one rule, in the scope it is written (#350)
  • laravel: document the one status a route-name conditional takes on that route (#349)
  • inference-phpstan: read the status from the factory a throw names (#348)
  • inference-phpstan: read the status an HttpException subclass sets on itself (#347)
  • core: mint a class schema identity in one place, facet and all (#333)
  • inference-phpstan: stabilise a walk scope through the interface that promises the call (#339)
  • laravel: rewrite an example with the schema a version renames (#328)
  • core: key a cached fragment on the document whose identities it carries (#326)
  • laravel: refuse to widen a version-change scope, and read a version set as versions (#323)
  • laravel: declare the API version header once and point every operation at it (#327)
  • core: give one schema direction one verdict, wherever it was computed (#312)
    • docuccino:diff --enforce now rejects changes it accepted before, all of them a widening on a RESPONSE: a type set that grew (schema.type-widened) or a type constraint that left (schema.type-removed); an allOf branch or the whole allOf removed; a not removed; a contains that was asserting something removed; a minContains lowered and a maxContains raised. An $id arriving on a schema is also breaking now, where before only a changed or removed one was. Requests are unaffected. Migrating a schema between nullable: true and type: [T, null] now reports nothing at all, where it previously reported a non-breaking schema.type-widened.
  • core: report a body declaration no operation could read (#309)
    • attribute.schema-class-unusable is a new warning, so a pipeline running --fail-on=warning can newly fail on an application that binds a request type carrying #[BodyParameter] to read routes only. Delete the declaration, describe the parameters with #[QueryParameter] on the action, or accept the code via diagnostics.accept.
  • core: decide every keyword the draft model knows, so no repointed discriminator passes –enforce as safe (#305)
    • docuccino:diff --enforce now rejects changes it previously accepted. A discriminator arriving, its tag property renamed, or a mapping entry removed or repointed is breaking on both sides; a mapping entry added, and the discriminator leaving, are breaking on a response, the same argument a value joining a response enum makes; a null withdrawn is breaking on both sides and one admitted is breaking on a response; and an $id, $anchor or $schema changed is breaking, because a pointer this diff does not resolve may name the old one and a changed dialect makes every comparison beside it a comparison of two languages. A pipeline gating on the diff may need a version bump it did not need before.
  • core: give every refinement keyword a direction, so no tightened bound passes –enforce as safe (#304)
    • docuccino:diff --enforce now rejects changes it previously accepted. A refinement tightened (schema.refinement-narrowed) is breaking on both sides; one relaxed (schema.refinement-widened) is breaking on a response, the same argument a value joining a response enum makes; and one whose direction cannot be computed (schema.refinement-changed) is breaking, because a false alarm costs the author one look and a false “safe” costs the consumer a broken client. A pipeline gating on the diff may need a version bump it did not need before.
  • core: let a request type carry the declarations that are true of it (#295)
    • a #[BodyParameter] on a request class used to document nothing and now patches that class’s component, so an application that had written one emits a different request body. A class-target attribute a request type is not read for now raises attribute.schema-class-unread rather than being ignored, which a build run with warnings as failures will notice. And RuleSetNormalizer::report() no longer defaults its source-class argument, so a caller that omits it is refused by PHP rather than silently weighing one declaration site of the two — a signature break for anything outside this repository that calls it.
  • core: give every subschema position a polarity, so no composition narrowing passes –enforce as safe (#294)
    • docuccino:diff --enforce now rejects changes it previously accepted — a narrowing under any composition or conditional keyword, and any change under not, if or a definition store. A pipeline that was green on such an edit will now fail, which is the gate telling the truth for the first time; the change is either honest under the document’s versioning policy or it is not.
  • core: answer the values OpenAPI 3.2 added to a member’s domain when downleveling (#293)
    • A downleveled 3.1 or 3.0 document no longer carries a parameter whose location OpenAPI 3.2 alone defines, nor a style value it alone defines, and each loss raises a downlevel.value-not-in-3.1 warning. The bytes those documents used to carry were invalid against the version they claimed, so a consumer’s validator already refused them; a build gating on warnings will now see one where it previously saw none.
  • core: key a security requirement the way every other diff identity is keyed (#311)
  • core: publish no path the scrubber could not account for (#310)
  • core: ask a recognised root how deep it is before attributing a run to it (#308)
  • core: reduce a bare path a brace or a backslash had refused, where a root already accounts for it (#307)
  • core: stop taking a compression wrapper as proof of a local file (#306)
  • core: read the two keywords that say what an example may not be (#292)
  • core: decide every registered stream wrapper, and guard the table against going short (#291)
  • laravel: let a response-header declaration say nothing about what it did not write (#287)
    • Exchange::$headers is array<string, list<string>>; Exchange::header() returns a list. Callers reading a single string must take the first element.
  • laravel: publish only the model keys a response actually carries (#285)
    • an Eloquent model schema stops publishing keys the server does not return — an append or a $with relation named in $hidden or absent from a $visible allow-list, a name written in both lists, and the framework’s own exists, timestamps, incrementing, preventsLazyLoading, wasRecentlyCreated and usesUniqueIds properties. A client generated from the new document loses fields it was never receiving, and a diff against a previously published document reports those keys as removals.
  • attributes: let a parameter declaration say nothing about required (#284)
    • $required on #[QueryParameter], #[HeaderParameter], #[CookieParameter] and #[ResponseHeader] is now ?bool defaulting to null. A declaration that relied on the old false default to mark a parameter optional must now say required: false.
  • core: tell an absent answer from an answer nobody can read (#281)
    • ContractParameter::schema() returns a ParameterSchema rather than ?array. Call ->read() for the node and ->kind for why there isn’t one.
  • attributes: let a body declaration say a field is optional (#280)
    • BodyParameter::$required is now ?bool defaulting to null. Code constructing the attribute positionally, or reading $required as a bool, must handle the third state.
  • core: drop ValidationField::type(), which answers a union with null (#279)
    • ValidationField::type() is removed from the rule-transformer surface a third-party RuleTransformer is handed. Use types(): list<string>, which answers every type word the field carries: one for a scalar type, several for a union, none where nothing has typed it yet. Null is never among the words — nullability is a flag the schema applies as it assembles, so a rule running after nullable still reads what the field is. count($types) === 1 ? $types[0] : null restores the old answer exactly, and restores the defect with it: branch on the words instead — $types === [] is “nothing has typed this”, and a field stating several is a case to handle, not one to fall through.
  • laravel: report an author-supplied name that matched nothing instead of dropping it silently (#283)
  • repo: restore the entries a squashed stack took out of v0.11.0’s changelog (#282)
  • laravel: credit a webhook delivery for what the check proved (#277)
  • laravel: let a nested body declaration reach the key it names (#276)
  • core: gate contract coverage on documented responses, not operations (#260)
    • docuccino:coverage --min is measured against documented responses rather than operations, so an existing floor will read lower. CoverageReport::total() and exercisedCount() are removed rather than silently re-meaning; missing() changes from “operations never exercised” to “operations with any unexercised response”. documentedStatuses() renders in family order, so {500, 5XX, 1XX} reads 500, 1XX, 5XX where it read 1XX, 500, 5XX. assertValidRequest() no longer credits a response.
  • laravel: record an example only where an assertion names it (#271)
    • ApiContract::record() no longer publishes an example for every checked response. An exchange is recorded only where the assertion names the scenario — assertValidExchange(recordAs: 'with-tags') — so a suite that records today records nothing tomorrow until its call sites name what is worth publishing. Committed recordings already on disk are still read and still publish; each build reports them once as examples.recording-unnamed, since no run will refresh them. An explicit recordAs: '' now raises rather than being ignored.
  • core: hold the payload a webhook dispatches to its contract (#262)
  • core: check the response headers the document publishes (#261)
  • laravel: report a nested data collection that will be wrapped (#265)
  • core: hold a security requirement to the schemes the document publishes (#272)
  • laravel: mark the response headers the framework always sends as required (#263)
  • core: read a parameter’s type in the grammar the validator reads (#266)
  • core: read an ambiguous empty body as the container the contract accepts (#267)
  • core: follow a $ref wherever the grammar permits one (#268)
  • laravel: read a #[BodyParameter] name as a field path, not a map key (#269)
  • laravel: read a bare array rule as either container, not as a list (#270)
  • core: resolve a Reference Object before the diff decides what changed (#273)
  • core: report an annotation-only schema change without gating on it (#256)
  • core: read a generated key as its kind, not its identity (#257)
  • core: describe a request body per operation (#253)
  • laravel: keep the declared container through the rule vocabulary (#252)
  • laravel: a date field’s format comes from its most specific source (#251)
  • core: read a lint safelist pointer in the spelling every message prints (#250)
  • core: let a class describe the schema it publishes (#247)
  • laravel: follow a remapped property to the key the request accepts (#246)
  • core: publish an input DTO’s own prose on the request side (#245)
  • laravel: wrap every arm of a composite at the response root (#243)
  • core: read a quoted JSON string example as the string it quotes (#239)
  • laravel: a union keeps every member a producer contributes one shape to (#238)
  • ci: make the coverage gate fast and separately re-runnable (#241)
  • laravel: say what became of a configured path instead of failing in silence (#228)
    • a document whose info.description.file could not be read published description: "" and now publishes no description member, with the configHash moving to match. An empty string claims this API’s description is the empty string; absent is the true answer, and it now arrives with an error or a warning naming the key. Separately, a document naming coverage.log by absolute path re-fingerprints once, because that path is no longer folded into configHash — it was machine-dependent there.
  • core: scrub a machine out of a message without rewriting what the author wrote (#218)
    • the drop half of downlevel.path-item-ref is now downlevel.path-item-unresolved. A pipeline at --fail-on=warning that accepted the old code to silence the inlining notice can now fail on the drop, which is the point — the two were never the same finding.
  • attributes: stop declaring a target nothing can honour (#216)
    • #[Summary] no longer declares TARGET_PROPERTY and #[Example] no longer declares TARGET_PARAMETER. Code reflecting either at those targets now throws where it did not, and a #[Summary] written on a property is a PHP error rather than a build diagnostic.
  • laravel: consult #[IgnoreResponse] before a producer converts a body (#206)
    • a document built from unchanged code can now lose responses it used to publish. A consumer cannot tell a repaired defect from a withdrawn response — a generated client loses a case from its error union, and docuccino:diff reports a removed response — so this is treated as breaking from the outside even though the old behaviour was the bug.
  • core: derive a schema keyword’s shape from its own contract (#203)
    • unevaluatedItems, unevaluatedProperties and additionalItems now publish as objects rather than empty arrays and take their place in the normative member order. A boolean at items, contains, not, if, then, else or propertyNames is published as written, which reverses what not: false means to a generated client.
  • core: downlevel a response whose key is spelled like a keyword (#202)
    • a 3.0 document’s responses.default and any component or header named after a schema keyword are now converted rather than passed through, so 2020-12 constructs at those positions become their 3.0 equivalents. A components.pathItems entry referenced from paths is inlined at each use site instead of leaving a $ref to a bucket 3.0 does not have.
  • core: stop a numeric or multi-shape component name colliding in silence (#199)
    • a shared error response offering two or more representations that each reference a distinct component is now published as AuthenticationChallenge_ProblemDetailsData rather than AuthenticationChallengeProblemDetailsData, renaming that type in generated clients. --yaml output quotes numeric mapping keys, so every responses key moves from 200: to '200':.
  • core: give every server variable the default the spec requires (#196)
    • postman.server-variable-no-default is retired in favour of server.variable-no-default, which every emitter raises; a diagnostics.accept entry or --fail-on filter naming the old code stops matching and will surface as config.accept-unused. A server variable declaring an enum and no default now publishes the enum’s first value as its default; one declaring neither is no longer emitted.
  • core: name a contested error response for the body it actually carries (#192)
    • an error response carrying a representation its claimed name does not describe is now published under a name derived from the components it references, instead of that claim plus a content hash. A document that published two hash-suffixed variants of one name now publishes the claimed name and a derived one, so a generated client’s type name changes for the contested body.
  • core: read #[Example] and #[Description] on the properties they declare (#204)
  • attributes: let a #[Response] name the error component it declares (#193)
  • core: say a recordings directory was refused instead of publishing nothing (#236)
  • core: reduce a path a colon interrupts, and redact a shallow machine prefix (#229)
  • core: name a class the same way wherever a build prints one (#227)
  • core: read a boolean subschema as the schema it is when building an example (#226)
  • core: drop every member OpenAPI 3.2 added when downleveling (#224)
  • core: read a boolean at a top-level Schema slot instead of dropping the member (#221)
  • laravel: describe the filter kinds the installed query builder ships (#211)
  • laravel: recognise every name mapper the installed spatie ships (#209)
  • core: close the loose ends a diagnostics sweep left (#208)
  • inference-phpstan: follow a fluent call to the status it sets (#207)
  • laravel: read an authored @example wherever it is written (#205)
  • core: pull JSON into a document through one reader (#200)
  • core: write empty maps as maps in YAML output (#197)
  • core: give a 3.0 operation the responses the spec requires of it (#195)
  • core: keep an authored empty-object example an object from docblock to bytes (#191)
  • core: audit examples against the document that ships, and never let a lint kill the export (#189)
  • core: retract the keywords a declared schema shape supersedes (#185)
    • a schema whose shape is declared by an attribute, docblock or overlay no longer publishes the inference keywords that declaration supersedes. Documents gain a closed shape where they previously advertised extra keys, and lose type/items beside a declared $ref.
  • laravel: patch a recovered request body instead of replacing it (#184)
    • an operation with both a recovered request body and a #[BodyParameter] now publishes the recovered properties with the declared one patched in, rather than only the declared one, and gains the 422 it was previously missing. Documents affected by the old behaviour will change shape — in the direction of describing what the endpoint actually accepts.
  • core: name a shared error component for what it is, not for what surrounded it (#183)
    • shared error components are named and grouped differently. Two operations whose error bodies differ only in wording now share one components.responses entry — most often gaining a named type where each previously kept an inline schema, since the wording had been keeping them below the sharing threshold — and a hoisted shape under a multi-representation response is named Error<status> rather than inheriting the response’s claimed name. Non-plurality arms gain summary/description beside their $ref in 3.1/3.2 output; a 3.0 export drops that wording with a downlevel.ref-siblings note. Examples now merge across wordings, so an operation may advertise an example another operation recorded. A generated client written against the previous names will need regenerating.
  • core: let a declared response retract the placeholders it supersedes (#181)
    • the inferred-response.unpinned-redirect diagnostic is now lint.unpinned-redirect. Anything filtering on the old code, or safelisting it under diagnostics.accept, needs the new name.
  • laravel: stop three help strings prescribing a remedy that changes nothing (#187)
  • laravel: remove an ignored parameter late enough that it stays removed (#186)
  • laravel: read a docblock example as the type its schema declares (#180)
  • core: honour a document’s configured format samples in the Postman collection (#179)
  • laravel: share the pagination envelope links and meta across page components (#177)
  • laravel: let a document override filter descriptions and format samples (#176)
  • core: synthesize a property example from the validation rules a request recovers (#171)
  • laravel: share one page component per item type and paginator kind (#169)
  • laravel: stop the diagnostics channel drowning in noise nobody can act on (#175)
  • laravel: stamp coverage-span tests from one base and let a reason replace the generic note (#174)
  • laravel: let a QB entry’s own comment and default outrank its filter class attribute (#173)
  • laravel: describe each Query Builder filter by the match it performs (#170)
  • laravel: declare the packages the integrations target, and name the real fields separator (#168)
  • core: publish the reason a deprecated operation carries (#167)
  • core: project tag groups and pass viewer configuration through (#158)
  • laravel: document sparse fieldsets as enums of the allow-list (#154)
  • laravel: describe and name the include and sort enum values (#153)
  • laravel: document include and sort as enums of the allow-list (#148)
  • laravel: serve each viewer the OpenAPI version it implements (#157)
  • laravel: report unreadable attributes, inherit class-level ones, read @deprecated (#161)
  • laravel: type string-backed enum route bindings from the enum (#160)
  • core: flag unions whose empty branch erases the typed contract (#162)
  • laravel: degrade include and sort typing on spatie/laravel-query-builder below v7 (#152)
  • core: emit an explicit empty schema for untyped parameters (#147)
  • laravel: type QB foreign-key filters off the related model’s key (#142)
  • laravel: type QB filters off the subject model’s primary key (#141)
  • laravel: let a shared filter class declare its schema via a class-level attribute (#143)
  • website: add robots.txt pointing crawlers at the sitemap (#139)
  • laravel: type QB filters off their declared column binding (#137)
  • attributes: add format to the parameter attributes (#135)
    • $format sits before $required in the constructors of QueryParameter, HeaderParameter, CookieParameter and BodyParameter, so positional arguments past $description shift by one; named arguments are unaffected.
  • core: classify response-side enum changes as breaking (#131)
    • docuccino:diff –enforce now fails changesets that add an enum value to, or drop an enum constraint from, a response schema or a referenced component schema; these previously passed as non-breaking.
  • core: track component schema direction to refine enum classification (#132)
  • inference-phpstan: replay a recorded file walk instead of re-running the resolver (#128)
  • inference-phpstan: harvest a file’s methods, closures and assignments in one pass (#127)
  • attributes: address the API consumer with #[Summary] and #[Description] (#118)
    • #[DescriptionFromFile('docs/x.md')] is removed. Use #[Description(file: 'docs/x.md')], which does the same thing and also takes inline prose as text:. A second attribute that does almost the same job is a worse API than one that does both — the same reasoning that gave #[Example] its file: argument rather than a sibling attribute.
  • laravel: make contract coverage a post-run command (#124)
  • laravel: walk a new install to its first document (#120)
  • laravel: point a diagnostic at the page that documents it (#119)
  • laravel: read the provenance trail back with docuccino:explain (#116)
  • laravel: accept the diagnostic codes you have already read (#114)
  • laravel: record your test suite’s responses as documented examples (#109)
  • core: show several named examples, and read one from a file (#108)
  • laravel: assert your test suite against the generated contract (#107)
  • laravel: rebuild and refresh the viewer as your code changes (#106)
  • laravel: resolve the viewer through its contract and ship a Redoc driver (#105)
  • core: lint the document for missing prose, unusable ids and undeclared tags (#104)
  • laravel: let –fail-on gate on the info and hint rungs (#103)
  • laravel: hint how a mock server should fake a property (#102)
  • laravel: document the webhooks an API delivers (#101)
  • laravel: document binary, file, streamed and SSE responses (#99)
  • laravel: let projects extend the engine’s PHPStan configuration (#90)
  • core: promote the schema conversion surface used by integrations to a public contract (#85)
  • core: emit a Postman collection (#81)
  • laravel: write every configured export target in one run (#80)
  • laravel: widen a rule’s values for every entry that names none (#125)
  • core: report a credential a recording cannot redact (#126)
  • core: place a call’s arguments where its readers index them (#123)
  • core: agree the example report’s verb with how many examples lied (#121)
  • laravel: say why a driver’s own response never live-reloads (#115)
  • repo: stage a cache-invalidation test’s disagreement in its own directory (#110)
  • laravel: document a rendered view as text/html instead of reflecting it (#98)
  • inference-phpstan: gate the throw-registry rescue on the resolved callee (#89)
  • laravel: reject unknown option values and diagnose misconfigured keys (#88)
  • laravel: publish the real generator version (#87)
  • laravel: print diagnostic help text in command output (#86)
  • core: surface engine boot failures and stop caching their degraded fragments (#84)
  • core: keep the export destination out of the document config hash (#79)
  • core: let a finding the whole document reports travel on the route that found it (#70)
  • laravel: document a page-size key only where the key’s value IS the size (#75)
  • inference-phpstan: serve a local’s value only to the method that wrote it, and only until something rewrites it (#74)
  • inference-phpstan: report the file a node was written in, which is not always the file being analysed (#73)
  • core: read every form that writes a local, not only the plain assignment (#72)
  • laravel: report the handler deferrals a warm build had been coming back without (#71)
  • laravel: let a diagnostic name the closure it means without naming the machine (#69)
  • core: stop a collision blaming an author for a name nothing claimed, and name the remedy (#66)
  • laravel: let the tier that cannot read an error body stand aside for one that can (#65)
  • inference-phpstan: read a response named in a local as the response it was named from (#64)
  • laravel: document the page-size key a list endpoint really reads (#63)
  • laravel: name only the paging keys an endpoint really reads (#58)
    • a Spatie Query Builder list endpoint no longer documents a per_page query parameter, and documents its page key under the name the paginating call site gave it. An application that really reads a page-size key, or one whose helper renames the page key out of sight, declares it with #[QueryParameter].
  • core: share one error response between arms that only illustrate it differently (#51)
    • an application whose operations state one error response with differing examples now publishes one components.responses entry with an examples map, where it previously published either an inline response per operation or one hash-discriminated component per illustration. The $refs those operations emit, and the type names a client generated from them carries, change accordingly.
  • core: diff what a client must satisfy, not only what it asks for (#55)
    • Changeset::$unreferencedSchemas is now $unreferencedComponents (unreferencedComponents in the JSON payload), since a security scheme nothing requires is stood down into the same list. The ChangeTarget enum gains a securityScheme case.
  • core: count every parameter an old artifact declares, and print none of its text as written (#46)
    • a diff whose old side declares parameters on a path item, or reuses one x-docuccino.id across two nodes, now reports removals, additions and edits it previously passed over in silence, so docuccino:diff --enforce can fail a comparison it used to allow. The finding is real either way — the gate was reading a document it could not see all of.
  • core: read a diff’s $ref’d parameters as the parameters they name (#43)
    • a diff of a document whose operations share parameters through components.parameters now reports parameter removals, additions and edits it previously passed over in silence, so docuccino:diff --enforce can fail a comparison it used to allow. The finding is real either way — the gate was reading a document it could not see all of.
  • core: let the producer of an error response name the component it publishes under (#40)
    • shared error components are published under the name of the error rather than its status. Laravel’s own errors become BadRequest, Unauthorized, Forbidden, NotFound, UnprocessableEntity and TooManyRequests in components.schemas and components.responses, in place of Error400, Error401, Error403, Error404, Error422 and Error429; their x-docuccino component ids change with them. Regenerating a client renames its error types to match — the statuses and bodies are unchanged, so only a hand-written catch on the old type name needs updating. A status nothing claims a name for is still Error<status>. To choose your own names, register an ExceptionToResponse or claim over a built-in from an OperationExtension; see the docs at /extending/extension-authoring/#naming-the-component-an-error-publishes-under.
  • core: pair an exported artifact’s parameters and schemas by the id it carries (#38)
    • emitted documents change for existing projects. Response component names are content-derived, so dropping an unbindable member’s placeholder from an example re-mints the hash suffix of any component whose example carried one, and those names flow into generated client type names. A request property recovered as array<string, V> whose class overrides rules() now emits {"type":"object","additionalProperties":{}} where it emitted {"type":"array"}. Regenerate and re-commit the artifact you diff against.
  • laravel: tell a consumer how to ask for the next page, not just which one they are on (#52)
  • laravel: let the render method that built an error body name it (#54)
  • laravel: let an exception name the error component it publishes under (#45)
  • core: tell the truth about two changes the diff went quiet on (#61)
  • laravel: let a thrown message name a file without naming the machine (#60)
  • inference-phpstan: read a match arm’s several conditions as alternatives, not requirements (#57)
  • core: make every byte a diff prints, and every node it pairs, survive the artifact (#56)
  • repo: finish escaping at the render boundary, and guard the fixture app against drift (#53)
  • core: stop a schema no operation reaches failing the diff gate as breaking (#50)
  • repo: stop the cold type-coverage run deadlocking in a forked child (#49)
  • laravel: escape a diagnostic where it is printed, not where it is written (#48)
  • inference-phpstan: stop promising a body member the response sometimes omits (#44)
  • core: name the thing a diagnostic’s reader must go and change (#37)
    • identity.duplicate-operation is renamed to route.duplicate-operation. Anything matching that code by name must be updated. The other two renames were added after v0.3.0 and have never shipped.
  • core: mint every component name from what it is, and stop warning about correct code (#35)
    • two contested definitions in components.responses or components.securitySchemes now take content-derived names instead of a first-come _2 suffix, and the security requirements naming them are repointed. A host-bound operation’s servers URL now carries the port and base path of the document server it inherits from.
  • core: publish a component under the name its schema earns, not the slot it landed in (#30)
    • a class-derived request body now publishes under a Request-suffixed component name. The facet applies whether or not the name is contested — that is what makes it local, since adding a read endpoint can then only ever add a name rather than reassign one. A Spatie ArticleData used only as a request body publishes as ArticleDataRequest where it published as ArticleData, so a generated client renames that type once. Pin the old name with #[SchemaName] if you need it unchanged.
  • inference-phpstan: delete the worker pool and the engine result cache (#21)
    • docuccino/inference-phpstan no longer ships the bin/worker.php binary or depends on symfony/process, and the @internal Orchestration and Cache namespaces are gone along with PhpStanEngineFactory::createOrchestrated() and ::createCaching(). The docuccino/laravel engine.mode values “orchestrated” and “caching” are removed; setting either now degrades to in-process with an engine.mode-unknown warning in place of engine.mode-not-wired.
  • laravel: warn when a published value came from the build machine (#34)
  • core: key a fragment on where a fact was written, and give two identities two components (#33)
  • laravel: type a bound parameter from the column it names, and stop publishing the catch-all (#32)
  • laravel: document a route bound to a host as an operation of its own (#29)
  • inference-phpstan: serve a memoised response shape only to a caller that could have earned it (#27)
  • core: share an error body by its shape, not by its wording (#26)
  • laravel: document rate-limit headers by meaning, not by value (#24)
  • laravel: emit real types where the schemas documented nothing (#23)
  • laravel: make the fragment cache safe to turn on, and warm builds cheap (#20)
  • core: carry node identities into exported OpenAPI so the diff stays semantic (#18)
    • docuccino:export writes an x-docuccino-id member on every node of an OpenAPI artifact. Re-exporting an existing artifact shows that as a one-time diff; pass --drop-ids for the previous bytes. Emitting through the library is unaffected — OpenApi32Emitter::emit() still drops every Docuccino member by default.
  • laravel: recover the Query Builder allow-lists a method or a constructor builds (#19)
  • laravel: illustrate an error member with the value its schema states (#16)
  • laravel: document an error response under the status its body states (#14)
  • laravel: document spatie’s inherited POST 201 default (#10)
    • a POST action returning a spatie Data class without its own calculateResponseStatus() override is now documented 201 instead of 200. This matches what the application actually returns, but it changes emitted output — and so a docuccino:diff result — with no action from the user. An app that genuinely answers 200 on a POST is one that overrides the method, and the override is still read first.