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.
v0.16.0
Section titled “v0.16.0”Breaking changes
Section titled “Breaking changes”- laravel: descend into the source roots the application declares (#442)
Docuccino\Core\Inference\TypeEngineBuilder::build()takes a newarray $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$configFilesixth now hits a TypeError rather than misbehaving silently.
Bug fixes
Section titled “Bug fixes”- 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)
v0.15.0
Section titled “v0.15.0”Breaking changes
Section titled “Breaking changes”- laravel: read a document keyword as the set it declares, not as any string (#425)
config.unknown-error-responsesandconfig.unknown-tag-strategyare retired into a singleconfig.unknown-value(warning), so adiagnostics.acceptlist naming either of the old codes will report them as accepted-but-never-emitted; replace both withconfig.unknown-value. An unrecognised value fordocuments.*.error_responses,tags.default_strategy,versioning,on_route_error,representation.filters,representation.nullable,representation.operation_id,representation.enums.namingor a document’sviewer.sourceis 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_middlewareis renamed todocuments.*.security.auth_middleware. The old spelling is not read and is not reported — it lives inconfig/docuccino.php, whose build keys this release already stops reading and reports wholesale asconfig.not-migratedorconfig.stale-php-keys. Use the new name indocuccino.yaml; behaviour, default (auth*) and position undersecurityare unchanged, anddocument.configHashmoves 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.phpinto adocuccino.yamlat the project root, and the keys left behind are no longer read. Everything a build reads moves — each document’sinfo,servers,routes,security,error_responses,tags,api_version,webhooks,content,examples,coverage,overlays,representation,versioning,integrationsandexport, plus top-levelextensions,lint,diagnostics,engine,on_route_error,cache.enabledandcache.path.enabled, each document’sviewerbag andcache.storestay inconfig/docuccino.php, because the service provider reads them when the application boots and when the viewer serves a request. Runphp artisan docuccino:installto write the new file. An application that has not migrated is reported rather than silently ignored:config.not-migratedwhere nodocuccino.yamlexists, andconfig.stale-php-keyswhere one does and leftovers remain. There is no merge and no precedence between the two files — a build key inconfig/docuccino.phphas no effect at all.
- build configuration moves out of
- laravel: name a route filter by class, and drop the key that could never be cached (#415)
documents.*.routes.closureis removed. Move the predicate into a class implementingDocuccino\Core\Extensions\Contracts\RouteFilterand name that class underdocuments.*.routes.filter; it is resolved from the container, so whatever the closure closed over becomes a constructor dependency. Then delete theclosureline — no configuration file has a form for a callable, so there is nothing the key can hold that a build will honour.DocumentConfig::$routeFilteris now?RouteFilterrather than amixedcallable slot, so an extension that read it and called it as a callable callsincludes()instead.
- core: drop the representation keyword no reader has ever read (#413)
documents.*.representation.listsis 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 changex-docuccino.document.configHashfor 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.configHashno longer folds in a document’sviewerwiring, 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.
Features
Section titled “Features”- 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)
Bug fixes
Section titled “Bug fixes”- 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)
Performance
Section titled “Performance”- inference-phpstan: analyse an action once per build, not once per version document (#383)
v0.14.1
Section titled “v0.14.1”Bug fixes
Section titled “Bug fixes”- 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)
v0.14.0
Section titled “v0.14.0”Breaking changes
Section titled “Breaking changes”- laravel: remove the problem-details error preset (#358)
- the
problem-detailserror preset is removed.error_responsesnow 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 reportsconfig.unknown-error-responsesat warning, so nothing fails silently. Set it to'default'or'none'explicitly: REMOVING the key is not the same edit, because an absenterror_responsesresolves to'none'and publishes no error responses at all.
- the
Bug fixes
Section titled “Bug fixes”- 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)
v0.13.1
Section titled “v0.13.1”Features
Section titled “Features”- 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)
Bug fixes
Section titled “Bug fixes”- 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)
Performance
Section titled “Performance”- laravel: declare the API version header once and point every operation at it (#327)
v0.13.0
Section titled “v0.13.0”Breaking changes
Section titled “Breaking changes”- core: give one schema direction one verdict, wherever it was computed (#312)
docuccino:diff --enforcenow rejects changes it accepted before, all of them a widening on a RESPONSE: a type set that grew (schema.type-widened) or atypeconstraint that left (schema.type-removed); anallOfbranch or the wholeallOfremoved; anotremoved; acontainsthat was asserting something removed; aminContainslowered and amaxContainsraised. An$idarriving on a schema is also breaking now, where before only a changed or removed one was. Requests are unaffected. Migrating a schema betweennullable: trueandtype: [T, null]now reports nothing at all, where it previously reported a non-breakingschema.type-widened.
- core: report a body declaration no operation could read (#309)
attribute.schema-class-unusableis a new warning, so a pipeline running--fail-on=warningcan 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 viadiagnostics.accept.
- core: decide every keyword the draft model knows, so no repointed discriminator passes –enforce as safe (#305)
docuccino:diff --enforcenow 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 responseenummakes; a null withdrawn is breaking on both sides and one admitted is breaking on a response; and an$id,$anchoror$schemachanged 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 --enforcenow 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 responseenummakes; 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 raisesattribute.schema-class-unreadrather than being ignored, which a build run with warnings as failures will notice. AndRuleSetNormalizer::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.
- a
- core: give every subschema position a polarity, so no composition narrowing passes –enforce as safe (#294)
docuccino:diff --enforcenow rejects changes it previously accepted — a narrowing under any composition or conditional keyword, and any change undernot,ifor 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’sversioningpolicy 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
stylevalue it alone defines, and each loss raises adownlevel.value-not-in-3.1warning. 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.
- A downleveled 3.1 or 3.0 document no longer carries a parameter whose location OpenAPI 3.2 alone defines, nor a
Bug fixes
Section titled “Bug fixes”- 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)
v0.12.0
Section titled “v0.12.0”Breaking changes
Section titled “Breaking changes”- laravel: let a response-header declaration say nothing about what it did not write (#287)
Exchange::$headersisarray<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
$withrelation named in$hiddenor absent from a$visibleallow-list, a name written in both lists, and the framework’s ownexists,timestamps,incrementing,preventsLazyLoading,wasRecentlyCreatedandusesUniqueIdsproperties. 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.
- an Eloquent model schema stops publishing keys the server does not return — an append or a
- attributes: let a parameter declaration say nothing about required (#284)
$requiredon#[QueryParameter],#[HeaderParameter],#[CookieParameter]and#[ResponseHeader]is now?booldefaulting tonull. A declaration that relied on the oldfalsedefault to mark a parameter optional must now sayrequired: false.
- core: tell an absent answer from an answer nobody can read (#281)
ContractParameter::schema()returns aParameterSchemarather than?array. Call->read()for the node and->kindfor why there isn’t one.
- attributes: let a body declaration say a field is optional (#280)
BodyParameter::$requiredis now?booldefaulting tonull. Code constructing the attribute positionally, or reading$requiredas abool, 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-partyRuleTransformeris handed. Usetypes(): 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 afternullablestill reads what the field is.count($types) === 1 ? $types[0] : nullrestores 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.
Bug fixes
Section titled “Bug fixes”- 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)
v0.11.0
Section titled “v0.11.0”Breaking changes
Section titled “Breaking changes”- core: gate contract coverage on documented responses, not operations (#260)
docuccino:coverage --minis measured against documented responses rather than operations, so an existing floor will read lower.CoverageReport::total()andexercisedCount()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}reads500, 1XX, 5XXwhere it read1XX, 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 asexamples.recording-unnamed, since no run will refresh them. An explicitrecordAs: ''now raises rather than being ignored.
Features
Section titled “Features”- 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)
Bug fixes
Section titled “Bug fixes”- 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)
v0.10.5
Section titled “v0.10.5”Features
Section titled “Features”- core: report an annotation-only schema change without gating on it (#256)
Bug fixes
Section titled “Bug fixes”- core: read a generated key as its kind, not its identity (#257)
v0.10.4
Section titled “v0.10.4”Features
Section titled “Features”- core: describe a request body per operation (#253)
Bug fixes
Section titled “Bug fixes”- 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)
v0.10.3
Section titled “v0.10.3”Features
Section titled “Features”- core: let a class describe the schema it publishes (#247)
Bug fixes
Section titled “Bug fixes”- 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)
v0.10.2
Section titled “v0.10.2”Bug fixes
Section titled “Bug fixes”- laravel: wrap every arm of a composite at the response root (#243)
v0.10.1
Section titled “v0.10.1”Bug fixes
Section titled “Bug fixes”- 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)
Performance
Section titled “Performance”- ci: make the coverage gate fast and separately re-runnable (#241)
v0.10.0
Section titled “v0.10.0”Breaking changes
Section titled “Breaking changes”- laravel: say what became of a configured path instead of failing in silence (#228)
- a document whose
info.description.filecould not be read publisheddescription: ""and now publishes nodescriptionmember, with theconfigHashmoving 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 namingcoverage.logby absolute path re-fingerprints once, because that path is no longer folded intoconfigHash— it was machine-dependent there.
- a document whose
- core: scrub a machine out of a message without rewriting what the author wrote (#218)
- the drop half of
downlevel.path-item-refis nowdownlevel.path-item-unresolved. A pipeline at--fail-on=warningthat 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.
- the drop half of
- attributes: stop declaring a target nothing can honour (#216)
#[Summary]no longer declaresTARGET_PROPERTYand#[Example]no longer declaresTARGET_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:diffreports a removed response — so this is treated as breaking from the outside even though the old behaviour was the bug.
- 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
- core: derive a schema keyword’s shape from its own contract (#203)
unevaluatedItems,unevaluatedPropertiesandadditionalItemsnow publish as objects rather than empty arrays and take their place in the normative member order. A boolean atitems,contains,not,if,then,elseorpropertyNamesis published as written, which reverses whatnot: falsemeans to a generated client.
- core: downlevel a response whose key is spelled like a keyword (#202)
- a 3.0 document’s
responses.defaultand 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. Acomponents.pathItemsentry referenced frompathsis inlined at each use site instead of leaving a$refto a bucket 3.0 does not have.
- a 3.0 document’s
- 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_ProblemDetailsDatarather thanAuthenticationChallengeProblemDetailsData, renaming that type in generated clients.--yamloutput quotes numeric mapping keys, so everyresponseskey moves from200:to'200':.
- a shared error response offering two or more representations that each reference a distinct component is now published as
- core: give every server variable the default the spec requires (#196)
postman.server-variable-no-defaultis retired in favour ofserver.variable-no-default, which every emitter raises; adiagnostics.acceptentry or--fail-onfilter naming the old code stops matching and will surface asconfig.accept-unused. A server variable declaring anenumand nodefaultnow publishes the enum’s first value as itsdefault; 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.
Features
Section titled “Features”- core: read #[Example] and #[Description] on the properties they declare (#204)
- attributes: let a #[Response] name the error component it declares (#193)
Bug fixes
Section titled “Bug fixes”- 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)
v0.9.1
Section titled “v0.9.1”Bug fixes
Section titled “Bug fixes”- core: audit examples against the document that ships, and never let a lint kill the export (#189)
v0.9.0
Section titled “v0.9.0”Breaking changes
Section titled “Breaking changes”- 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/itemsbeside a declared$ref.
- 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
- 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 the422it was previously missing. Documents affected by the old behaviour will change shape — in the direction of describing what the endpoint actually accepts.
- an operation with both a recovered request body and a
- 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.responsesentry — 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 namedError<status>rather than inheriting the response’s claimed name. Non-plurality arms gainsummary/descriptionbeside their$refin 3.1/3.2 output; a 3.0 export drops that wording with adownlevel.ref-siblingsnote. 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.
- shared error components are named and grouped differently. Two operations whose error bodies differ only in wording now share one
- core: let a declared response retract the placeholders it supersedes (#181)
- the
inferred-response.unpinned-redirectdiagnostic is nowlint.unpinned-redirect. Anything filtering on the old code, or safelisting it underdiagnostics.accept, needs the new name.
- the
Bug fixes
Section titled “Bug fixes”- 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)
v0.8.5
Section titled “v0.8.5”Features
Section titled “Features”- 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)
Bug fixes
Section titled “Bug fixes”- 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)
v0.8.4
Section titled “v0.8.4”Features
Section titled “Features”- 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)
Bug fixes
Section titled “Bug fixes”- 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)
v0.8.3
Section titled “v0.8.3”Features
Section titled “Features”- 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)
Bug fixes
Section titled “Bug fixes”- laravel: let a shared filter class declare its schema via a class-level attribute (#143)
v0.8.2
Section titled “v0.8.2”Features
Section titled “Features”- website: add robots.txt pointing crawlers at the sitemap (#139)
v0.8.1
Section titled “v0.8.1”Bug fixes
Section titled “Bug fixes”- laravel: type QB filters off their declared column binding (#137)
v0.8.0
Section titled “v0.8.0”Breaking changes
Section titled “Breaking changes”- attributes: add format to the parameter attributes (#135)
$formatsits before$requiredin the constructors ofQueryParameter,HeaderParameter,CookieParameterandBodyParameter, so positional arguments past$descriptionshift by one; named arguments are unaffected.
v0.7.0
Section titled “v0.7.0”Breaking changes
Section titled “Breaking changes”- 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.
Features
Section titled “Features”- core: track component schema direction to refine enum classification (#132)
v0.6.1
Section titled “v0.6.1”Performance
Section titled “Performance”- 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)
v0.6.0
Section titled “v0.6.0”Breaking changes
Section titled “Breaking changes”- 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 astext:. A second attribute that does almost the same job is a worse API than one that does both — the same reasoning that gave#[Example]itsfile:argument rather than a sibling attribute.
Features
Section titled “Features”- 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)
Bug fixes
Section titled “Bug fixes”- 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)
v0.5.1
Section titled “v0.5.1”Features
Section titled “Features”- core: let a finding the whole document reports travel on the route that found it (#70)
Bug fixes
Section titled “Bug fixes”- 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)
v0.5.0
Section titled “v0.5.0”Breaking changes
Section titled “Breaking changes”- laravel: name only the paging keys an endpoint really reads (#58)
- a Spatie Query Builder list endpoint no longer documents a
per_pagequery 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].
- a Spatie Query Builder list endpoint no longer documents a
- 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.responsesentry with anexamplesmap, 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.
- an application whose operations state one error response with differing examples now publishes one
- core: diff what a client must satisfy, not only what it asks for (#55)
Changeset::$unreferencedSchemasis now$unreferencedComponents(unreferencedComponentsin the JSON payload), since a security scheme nothing requires is stood down into the same list. TheChangeTargetenum gains asecuritySchemecase.
- 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.idacross two nodes, now reports removals, additions and edits it previously passed over in silence, sodocuccino:diff --enforcecan 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.
- a diff whose old side declares parameters on a path item, or reuses one
- 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.parametersnow reports parameter removals, additions and edits it previously passed over in silence, sodocuccino:diff --enforcecan 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.
- a diff of a document whose operations share parameters through
- 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,UnprocessableEntityandTooManyRequestsincomponents.schemasandcomponents.responses, in place ofError400,Error401,Error403,Error404,Error422andError429; theirx-docuccinocomponent ids change with them. Regenerating a client renames its error types to match — the statuses and bodies are unchanged, so only a hand-writtencatchon the old type name needs updating. A status nothing claims a name for is stillError<status>. To choose your own names, register anExceptionToResponseor claim over a built-in from anOperationExtension; see the docs at /extending/extension-authoring/#naming-the-component-an-error-publishes-under.
- shared error components are published under the name of the error rather than its status. Laravel’s own errors become
- 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 overridesrules()now emits{"type":"object","additionalProperties":{}}where it emitted{"type":"array"}. Regenerate and re-commit the artifact you diff against.
- 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
Features
Section titled “Features”- 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)
Bug fixes
Section titled “Bug fixes”- 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)
v0.4.0
Section titled “v0.4.0”Breaking changes
Section titled “Breaking changes”- core: name the thing a diagnostic’s reader must go and change (#37)
identity.duplicate-operationis renamed toroute.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.responsesorcomponents.securitySchemesnow take content-derived names instead of a first-come_2suffix, and thesecurityrequirements naming them are repointed. A host-bound operation’sserversURL now carries the port and base path of the document server it inherits from.
- two contested definitions in
- 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 SpatieArticleDataused only as a request body publishes asArticleDataRequestwhere it published asArticleData, so a generated client renames that type once. Pin the old name with#[SchemaName]if you need it unchanged.
- a class-derived request body now publishes under a
- 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.
Bug fixes
Section titled “Bug fixes”- 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)
v0.3.0
Section titled “v0.3.0”Breaking changes
Section titled “Breaking changes”- core: carry node identities into exported OpenAPI so the diff stays semantic (#18)
docuccino:exportwrites anx-docuccino-idmember on every node of an OpenAPI artifact. Re-exporting an existing artifact shows that as a one-time diff; pass--drop-idsfor the previous bytes. Emitting through the library is unaffected —OpenApi32Emitter::emit()still drops every Docuccino member by default.
Features
Section titled “Features”- laravel: recover the Query Builder allow-lists a method or a constructor builds (#19)
Bug fixes
Section titled “Bug fixes”- laravel: illustrate an error member with the value its schema states (#16)
v0.2.1
Section titled “v0.2.1”Bug fixes
Section titled “Bug fixes”- laravel: document an error response under the status its body states (#14)
v0.2.0
Section titled “v0.2.0”Breaking changes
Section titled “Breaking changes”- laravel: document spatie’s inherited POST 201 default (#10)
- a POST action returning a spatie Data class without its own
calculateResponseStatus()override is now documented201instead of200. This matches what the application actually returns, but it changes emitted output — and so adocuccino:diffresult — 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.
- a POST action returning a spatie Data class without its own