The document, not the code
Lints run on the emitted bytes, so they see what your consumers see — including anything an overlay or an extension added after inference was done.
Everything else in a build reads your application. The lints read the finished document — after every extension has run and every overlay has been applied, on exactly the bytes about to be written. They are the last thing to look at your API before your consumers do.
They never change it. A lint can raise a diagnostic and nothing else, so a build with findings emits the same document as a build without them. What they change is what you know, and — if you gate on them — whether the build passes.
php artisan docuccino:export --fail-on=warningThree questions, and each pass answers a piece of one.
lint.data-leakage reads every property name, every query and path parameter name, and every
value published under example, const, enum or default, and warns when one looks like a
credential. It is on by default, and it is the pass most worth leaving on: a field that reaches the
document reaches your published contract, and from there a generated client and anyone reading the
viewer.
class UserResource extends JsonResource{ public function toArray(Request $request): array { return [ 'id' => $this->id, 'email' => $this->email, 'api_token' => $this->api_token, ]; }}"UserResource": { "type": "object", "properties": { "id": { "type": "integer" }, "email": { "type": "string" }, "api_token": { "type": "string" } }, "required": ["id", "email", "api_token"]}The document is a faithful description of what that endpoint returns, which is the problem. The build says so:
[warning] lint.data-leakage: Property "api_token" (/components/schemas/UserResource/properties/api_token) looks like a token and may leak sensitive data. - Take the field out of the shape that publishes it — #[Hidden] removes a property from response schemas, #[HiddenFromRequest] from a request body — or, if intentional, safelist it under lint.leakage.allow.The finding names the member and its pointer, never the matched text — echoing a secret would only
move it into your build log. The recognized name tokens and credential shapes are listed under
Data leakage, and lint.leakage.patterns adds your
own vocabulary to the name heuristics.
These four are about the document being usable downstream — by a code generator, a validator, a contract test — rather than about it being complete.
lint.operation-id-style catches an operationId a generated client can’t turn into a method
name. Nothing Docuccino mints can trip it, so a finding is always a string somebody typed and can
type differently. On by default for that reason. Configured under lint.operation_ids.lint.example-mismatch checks every published example against the schema beside it, and
lint.example-uncheckable reports an example left unchecked because the validator wouldn’t read
that schema — which is a Docuccino bug, not yours. lint.unresolved-reference shares the same
pass and reports a $ref to a component the document never defines, usually after an overlay
renamed one. All three live under lint.examples.lint.vacuous-union finds an anyOf with an unconstrained branch, where the typed branches
beside it add no constraint and the value validates as anything. It is the trace of an honest
widening, and it names the arm to pin. Configured under lint.vacuous_union.lint.unpinned-redirect finds a redirect the document never says exactly one thing about.
Configured under lint.unpinned_redirect.Both of these are off by default, and for the same reason: on an API that never documented anything they fire once per operation, which is a backlog rather than a diagnostic.
lint.missing-description warns on an operation publishing neither a summary nor a description.
Turn lint.descriptions on when you are closing that gap and want the list.lint.undocumented-tag warns on a tag your operations carry that tags.definitions never
declares. Turn lint.tags on once your declared tags are meant to be the complete set — it stays
silent until the document declares at least one either way.Every key, every default and every per-rule detail is in the configuration reference; the codes, their severities and their remedies are in the diagnostics reference.
They are not interchangeable, and picking the wrong one is how a real finding goes missing later.
| Move | Scope | The finding | Use it when |
|---|---|---|---|
lint.<rule>.enabled: false |
The whole pass, every document | Never raised | The rule doesn’t describe a problem you have. |
lint.<rule>.allow: [...] |
One target | Never raised for that target | This one hit is fine; the rest of the pass still earns its place. |
diagnostics.accept |
The code, everywhere | Still printed, marked accepted |
You want the gate green today without going blind. |
The middle row is almost always the right one. A safelist is a decision you made about a specific property, operation or pointer, written where the next reader can see what you decided and check whether it still holds. Turning the pass off is a decision about all the hits you haven’t had yet.
The third row is different in kind: diagnostics.accept doesn’t silence anything. The diagnostic
still prints, marked accepted and counted in a closing line, and only stops counting towards
--fail-on. That is what makes a stricter gate
adoptable — and an entry nothing reports any more says so with config.accept-unused, so the list
can’t quietly rot.
Each pass takes the unit that fits what it finds — a property or parameter name or a JSON pointer for
leakage, an operation signature (GET /api/ping) or an operationId for the operation-level passes, a
tag name for tags. The reference gives the unit per rule.
lint: leakage: allow: - '/components/schemas/Invoice/properties/status' descriptions: enabled: true allow: ['GET /api/ping']A finding is worth silencing when the pass is right about the shape and wrong about the consequence:
token that carries a public, opaque identifier — a URL slug, an
unsubscribe key with no privileges behind it.lint.descriptions is on
because everything else has something to say.operationId your organization mints in a shape a generator you don’t use would object to.And it is the wrong answer when the finding is about a hole in what Docuccino could recover — a
vacuous union, an operation with no success response, a property with no items. Those are telling
you what a consumer loses, and the fix is an annotation on the code rather than a line in your config.
docuccino:explain tells you which layer produced the
field the lint is complaining about, so you know where the annotation goes.
Lint findings are ordinary diagnostics, so --fail-on is the whole mechanism:
php artisan docuccino:export --fail-on=warningMost report at warning, and a couple at info — so a --fail-on=warning gate never sees those, and
tightening to info is what brings them in. The
diagnostics reference gives the severity of each. On an
existing codebase start at warning, accept the codes you can’t act on today, and tighten later — a
gate that fires on day one is a gate the team switches off.
The document, not the code
Lints run on the emitted bytes, so they see what your consumers see — including anything an overlay or an extension added after inference was done.
Never a changed byte
No lint can alter the document. Turning one on or off changes what you’re told and nothing about what you ship.