TYPO3 Extension Conformance
Skill: typo3-extension-conformance
Review or audit a TYPO3 project, sitepackage or extension against its checkout and active installation, and report what is wrong in priority order: TCA, services, backend modules, content elements, site sets, TypoScript, Fluid, labels, icons, security boundaries and deprecated APIs.
Markdown source#
---
name: typo3-extension-conformance
description: 'Review or audit a TYPO3 project, sitepackage or extension against its checkout and active installation, and report what is wrong in priority order: TCA, services, backend modules, content elements, site sets, TypoScript, Fluid, labels, icons, security boundaries and deprecated APIs.'
compatibility: Needs the typo3-dev-companion MCP server, which owns every lookup this workflow routes to and publishes this skill together with the references/base.md it opens on. Install it from github.com/TYPO3/dev-companion and run typo3-dev-companion install in the project. A copy taken out of that repository's skills directory alone has neither the tools nor that base file.
---
# TYPO3 Extension Conformance
Produce an evidence-backed audit against the active installation and the
checkout, not a generic checklist. Keep this skill as routing and assessment
method; do not embed versioned TYPO3 facts.
## Establish scope and evidence
1. Work through [references/base.md](references/base.md) — it fixes the order
every task here starts in, and an assessment is where that order matters
most: a rule fetched after the reading confirms a verdict instead of testing
it.
2. Read [references/checklist.md](references/checklist.md) for the audit
surfaces, the finding gate, and the severity rubric.
3. Write the surface list down before opening a single file, from the
checklist's surfaces narrowed to the ones this kind of checkout can have. It
is the work list, and the coverage the report closes on is this same list
with every entry answered.
4. Where the request names what it is about — security only, configuration only,
one subsystem — read the surfaces it names and mark the rest **not
requested** on that same list. The request narrows the reading, never the
list: an entry nobody asked about costs one line, and dropping it instead is
what leaves the reader unable to ask for the rest. A request that names no
surface is not a focused one, and every entry is read.
A surface is in scope because the checklist names it, not because the file tree
shows it. Listing the files first inverts that: `find` cannot show a manual that
was never written, a test that does not exist, or a documentation tree that is
absent, so the surfaces it hides are exactly the ones whose absence is the
finding. Derive the list from the checklist and `typo3_extension_describe`, then
let reading answer it.
## Ask before judging, on every surface in scope
Scope says which surfaces are in play, and reading says what is there. Neither
says whether it is right. That comes from the owner of the convention, and it is
asked for **before** a view of the subsystem is formed rather than to confirm
one that already exists:
- `typo3_hint_lookup` with the subsystem's concrete paths and a short English
description. One query per surface in scope; a single broad query is not
subsystem evidence. A surface the checkout has no files for is asked by its
hint id instead, because that is the surface whose absence is the finding and
the one whose paths cannot be passed.
- The lookup that owns that surface's runtime facts, where one exists:
`typo3_backend_module_lookup` for registered modules and routes,
`typo3_icon_lookup` for icon identifiers, `typo3_label_lookup` for labels and
overrides, `typo3_fluid_namespace_list` for globally available Fluid prefixes,
`typo3_configuration_lookup` for effective runtime configuration.
- `typo3_documentation_lookup` with several short English queries and the target
version where an official API or configuration detail decides the finding, and
for every "does this still work here" a surface raises — the base says why the
changelog cannot answer that one.
The first two answer different questions and neither stands in for the other. A
runtime lookup reports what is registered, what a path resolves to, what a value
really is — the facts of this installation. The conventions lookup reports
whether those facts are right. A surface can be confirmed by its own runtime
lookup and still break every rule that governs it, so a surface is not assessed
until both have been asked.
Read the checkout for what none of those can know: the files themselves, the
registrations, the tests, the documentation, and the conventions the project has
settled into.
Then read every returned rule in both directions. It says what new code should
do, and it says what this checkout is already doing wrong. A file that has
settled into the opposite of a rule is a finding, not a local style to preserve
— the project's own habits are part of what is being assessed, so consistency
with them establishes nothing.
Do not report the absence of an optional subsystem as a defect. But a surface
that is present and was never asked about is **unassessed**, and unassessed is
not clean: say so in the result. A defect nobody looked for and a defect that is
not there read identically in a report that does not separate them. Distinguish
a verified violation from a recommendation and from missing evidence.
The deprecation sweep the base fixes is reported the same way, and a review that
found nothing says the sweep ran and came back empty, with the majors it
covered. A sweep that is only visible when it produces a finding cannot be told
apart from one that never ran, and the surface it covers is the one whose
silence reads as a clean bill for the next major.
## Report
Order findings by severity and include:
1. the concrete file or runtime registration;
2. the observed behavior or configuration;
3. the applicable MCP or official-documentation evidence;
4. the consequence;
5. a scoped remediation and relevant project check.
Beside them, report what was raised while reading and dropped, with what dropped
it. A candidate let go in silence and a surface nobody opened leave the same
trace in the report, and the checklist's *What a dropped candidate owes* is the
bar each one is held to — including the one that could be neither established
nor disproved, which is reported as open rather than dropped.
Stop after findings. This skill changes nothing, whatever the request asked for:
the fixes are the next workflow's, and the skill that owns each finding's area
is named below. Stopping at findings is not stopping at reading: the commands
`typo3_project_describe` marks as checks hand the code back as it was, and an
audit told not to change files runs them and reports what they printed.
Close on coverage rather than on a summary: the surface list written in step 3,
every entry marked assessed, unassessed or not requested, clean ones briefly. It
is that list and not a recollection at the end — a summary assembled from memory
reports what the session noticed it skipped, never what it never reached.
Without the list a thorough report and a narrow one look alike, and the cheapest
way to look thorough is to examine less.
Unassessed and not requested both mean nothing was established there, and they
are not the same thing: one is this review's gap, the other is what the request
left out. Say which of the two per entry, and let neither read as clean.
**The report is markdown the reader can copy, and the answer is where it goes.**
The findings and the coverage list together are what make it long, and length is
what makes the form matter: an audit is carried into an issue, into a ticket or
into a chat, and rendered output is what does not survive being moved. Write it
to a file only where the caller asks for one, at a path outside the checkout
being assessed, which this workflow changes nothing in.
This skill owns assessment and prioritization, and it owns saying who takes each
finding onward. Name the workflow the follow-up belongs to —
`typo3-extension-testing`, `typo3-extension-documentation`,
`typo3-backend-module-development`, `typo3-content-element-development` or
`typo3-extension-upgrade` — in the result itself, whether or not fixes were
requested: a reader deciding what to do next needs that as much as a session
that was told to do it. When fixes are requested, hand over to that skill for
the changes in its area and keep conformance responsible for re-checking the
resulting finding. What the sweep returned goes to `typo3-extension-upgrade`
whole: it owns crossing the package to another supported range, and a review
that hands over one deprecation at a time has decided the order that workflow
exists to establish.
markdown
---
name: typo3-extension-conformance
description: 'Review or audit a TYPO3 project, sitepackage or extension against its checkout and active installation, and report what is wrong in priority order: TCA, services, backend modules, content elements, site sets, TypoScript, Fluid, labels, icons, security boundaries and deprecated APIs.'
compatibility: Needs the typo3-dev-companion MCP server, which owns every lookup this workflow routes to and publishes this skill together with the references/base.md it opens on. Install it from github.com/TYPO3/dev-companion and run typo3-dev-companion install in the project. A copy taken out of that repository's skills directory alone has neither the tools nor that base file.
---
# TYPO3 Extension Conformance
Produce an evidence-backed audit against the active installation and the
checkout, not a generic checklist. Keep this skill as routing and assessment
method; do not embed versioned TYPO3 facts.
## Establish scope and evidence
1. Work through [references/base.md](references/base.md) — it fixes the order
every task here starts in, and an assessment is where that order matters
most: a rule fetched after the reading confirms a verdict instead of testing
it.
2. Read [references/checklist.md](references/checklist.md) for the audit
surfaces, the finding gate, and the severity rubric.
3. Write the surface list down before opening a single file, from the
checklist's surfaces narrowed to the ones this kind of checkout can have. It
is the work list, and the coverage the report closes on is this same list
with every entry answered.
4. Where the request names what it is about — security only, configuration only,
one subsystem — read the surfaces it names and mark the rest **not
requested** on that same list. The request narrows the reading, never the
list: an entry nobody asked about costs one line, and dropping it instead is
what leaves the reader unable to ask for the rest. A request that names no
surface is not a focused one, and every entry is read.
A surface is in scope because the checklist names it, not because the file tree
shows it. Listing the files first inverts that: `find` cannot show a manual that
was never written, a test that does not exist, or a documentation tree that is
absent, so the surfaces it hides are exactly the ones whose absence is the
finding. Derive the list from the checklist and `typo3_extension_describe`, then
let reading answer it.
## Ask before judging, on every surface in scope
Scope says which surfaces are in play, and reading says what is there. Neither
says whether it is right. That comes from the owner of the convention, and it is
asked for **before** a view of the subsystem is formed rather than to confirm
one that already exists:
- `typo3_hint_lookup` with the subsystem's concrete paths and a short English
description. One query per surface in scope; a single broad query is not
subsystem evidence. A surface the checkout has no files for is asked by its
hint id instead, because that is the surface whose absence is the finding and
the one whose paths cannot be passed.
- The lookup that owns that surface's runtime facts, where one exists:
`typo3_backend_module_lookup` for registered modules and routes,
`typo3_icon_lookup` for icon identifiers, `typo3_label_lookup` for labels and
overrides, `typo3_fluid_namespace_list` for globally available Fluid prefixes,
`typo3_configuration_lookup` for effective runtime configuration.
- `typo3_documentation_lookup` with several short English queries and the target
version where an official API or configuration detail decides the finding, and
for every "does this still work here" a surface raises — the base says why the
changelog cannot answer that one.
The first two answer different questions and neither stands in for the other. A
runtime lookup reports what is registered, what a path resolves to, what a value
really is — the facts of this installation. The conventions lookup reports
whether those facts are right. A surface can be confirmed by its own runtime
lookup and still break every rule that governs it, so a surface is not assessed
until both have been asked.
Read the checkout for what none of those can know: the files themselves, the
registrations, the tests, the documentation, and the conventions the project has
settled into.
Then read every returned rule in both directions. It says what new code should
do, and it says what this checkout is already doing wrong. A file that has
settled into the opposite of a rule is a finding, not a local style to preserve
— the project's own habits are part of what is being assessed, so consistency
with them establishes nothing.
Do not report the absence of an optional subsystem as a defect. But a surface
that is present and was never asked about is **unassessed**, and unassessed is
not clean: say so in the result. A defect nobody looked for and a defect that is
not there read identically in a report that does not separate them. Distinguish
a verified violation from a recommendation and from missing evidence.
The deprecation sweep the base fixes is reported the same way, and a review that
found nothing says the sweep ran and came back empty, with the majors it
covered. A sweep that is only visible when it produces a finding cannot be told
apart from one that never ran, and the surface it covers is the one whose
silence reads as a clean bill for the next major.
## Report
Order findings by severity and include:
1. the concrete file or runtime registration;
2. the observed behavior or configuration;
3. the applicable MCP or official-documentation evidence;
4. the consequence;
5. a scoped remediation and relevant project check.
Beside them, report what was raised while reading and dropped, with what dropped
it. A candidate let go in silence and a surface nobody opened leave the same
trace in the report, and the checklist's *What a dropped candidate owes* is the
bar each one is held to — including the one that could be neither established
nor disproved, which is reported as open rather than dropped.
Stop after findings. This skill changes nothing, whatever the request asked for:
the fixes are the next workflow's, and the skill that owns each finding's area
is named below. Stopping at findings is not stopping at reading: the commands
`typo3_project_describe` marks as checks hand the code back as it was, and an
audit told not to change files runs them and reports what they printed.
Close on coverage rather than on a summary: the surface list written in step 3,
every entry marked assessed, unassessed or not requested, clean ones briefly. It
is that list and not a recollection at the end — a summary assembled from memory
reports what the session noticed it skipped, never what it never reached.
Without the list a thorough report and a narrow one look alike, and the cheapest
way to look thorough is to examine less.
Unassessed and not requested both mean nothing was established there, and they
are not the same thing: one is this review's gap, the other is what the request
left out. Say which of the two per entry, and let neither read as clean.
**The report is markdown the reader can copy, and the answer is where it goes.**
The findings and the coverage list together are what make it long, and length is
what makes the form matter: an audit is carried into an issue, into a ticket or
into a chat, and rendered output is what does not survive being moved. Write it
to a file only where the caller asks for one, at a path outside the checkout
being assessed, which this workflow changes nothing in.
This skill owns assessment and prioritization, and it owns saying who takes each
finding onward. Name the workflow the follow-up belongs to —
`typo3-extension-testing`, `typo3-extension-documentation`,
`typo3-backend-module-development`, `typo3-content-element-development` or
`typo3-extension-upgrade` — in the result itself, whether or not fixes were
requested: a reader deciding what to do next needs that as much as a session
that was told to do it. When fixes are requested, hand over to that skill for
the changes in its area and keep conformance responsible for re-checking the
resulting finding. What the sweep returned goes to `typo3-extension-upgrade`
whole: it owns crossing the package to another supported range, and a review
that hands over one deprecation at a time has decided the order that workflow
exists to establish.
References#
Where every task starts#
# Where every task starts
## Nothing starts until the server answers
A skill is a file the installer left behind: it loads and reads the same whether
the tools behind it are connected or not, and neither side notices. So the first
call below is also the check.
- No `typo3_` tool in this session, or a first call that errors: stop, say this
workflow needs the server and it is not there, and name what came back.
- Do not fall back to general TYPO3 knowledge or start reading the checkout.
That answer carries this workflow's order and confidence and none of its
evidence, and nothing in it says which of the two it is.
- Continue only when asked to after saying so, and repeat it in the answer and
in every finding a lookup would have carried.
## The order
This is the order, and it is an order rather than a list because each step
decides what the next one is worth. A convention fetched after the code has been
read confirms a view instead of testing it. A command recommended before the
project's own are known is a guess that sounds like advice. Where a step below
carries a condition for skipping it, that condition is narrow on purpose: a
prescription that gets skipped teaches the next reader to skip the ones that
matter too.
1. **`typo3_project_describe`** — the installation, its TYPO3 and PHP version,
the extensions that are the project's own, its sites, and the commands this
repository actually declares. Every later answer is filtered by that version,
and a check the repository does not declare is a wrong answer however
sensible it sounds. It ends with the whole procedures this server carries, as
ids: that list is the only place they are named to a client that renders no
resource list, and each one is a `typo3_rule_lookup` with that `documentId`
rather than a search.
2. **`typo3_extension_describe`** for each extension in scope — what it
registers, and what it ships beside that: its manual, its README, its test
layers, its XLF files with the source language each one declares. What it
does *not* ship is answered too, and that is the half no file listing can
give you.
Where step 1 reported none — a core checkout is the case, since it names the
project's own extensions and not TYPO3's — that answer is this step, and
there is nothing to call. Say so. A step passed over in silence cannot be
told from one that was dropped.
3. **`typo3_task_guide`** with a short English task, the paths it touches, the
target version and the change type — the workflow this task belongs to and
the checks that come with it.
Run it in every session, this skill's own tasks included. The brief is built
from the paths as well as the task text, and no skill knows which paths the
caller is holding: a skill that covers the task is not that brief, and
skipping the step costs the hints and the core checks those paths match.
Where the guide's own answer is what named this skill, this is one call for
an answer already in the session. That is the price of a step there is
nothing to decide about.
4. **`typo3_hint_lookup`** for each subsystem in scope, with its concrete paths.
One query per subsystem; a single broad query is not subsystem evidence.
Where step 3 ran with those paths, its answer says whether this step is still
owed. A brief that carried everything the lookup matched says so — "these are
everything typo3_hint_lookup matches for these paths" — and there the call is
made and asking again returns the same hints. One that stopped short says
that instead and names the ids it left, and those are what is owed: fetch
them by id rather than repeating the query. Read the sentence rather than the
populated `hints` key, which is present either way and does not tell the two
apart.
5. **`typo3_changelog_lookup` with `type: deprecation`**, at each major the
package declares, bounded by `tag` and with the query omitted. Those three
are the changelog's own axes, and the extension's vocabulary is not among
them: an entry carries a query only when its title carries every word of it
at once, and the core titled those entries about its own code. Words taken
from what step 2 reported are therefore matched against titles they were
never written in, and the sweep comes back empty however right the query
looks.
Step 2 picks the tags instead. `ext:core`, `ext:frontend`, `ext:form` and the
rest name the system extension a change is **in** — one call for each one the
package requires, renders through or registers into, which is more than its
manifest lists — and `TCA`, `TypoScript`, `Fluid`, `YAML`, `Backend`,
`Frontend` name the surface, one for each kind of file it ships. An extension
key of your own is not among them and matches nothing. Every call also
returns every tag that version and type carry, so the second call onwards is
read off the first rather than guessed at.
Step 2 is what the answers are checked against, which is the other half the
words were doing. Verify each identifier that comes back in the checkout — a
deprecation nothing here calls is not a finding — and carry the
`FullyScanned` / `PartiallyScanned` tag into the answer, because it says
whether the Extension Scanner can find the remaining call sites or whether
that reading is yours. Bounded this way the sweep is still writable before a
file is opened, which is why it is a step of the order: one left to the
reading reaches only what a finding stumbled into, and the deprecation that
decides whether the package survives the next major is not usually the one a
finding walks past.
A changelog records change events, so a pattern nothing has touched for ten
majors has no entry at all. An empty sweep is therefore not an answer about
what still works. "Does this still work in version N" goes to
`typo3_documentation_lookup` at that version — here, and whenever the reading
raises it again.
That is a question for a documented surface — a ViewHelper, a TCA type, a
TypoScript setting. The manual matches page titles and section paths, never
the text of a page, so a PHP identifier has no page to be titled after. It
reaches whatever its own words happen to spell instead. An identifier goes to
`typo3_changelog_lookup` under its own name, which reaches the entries
writing it however the change was titled, and then to the class below. Where
the manual has no page for a surface either, that is a result and not an
answer. Undocumented is not unsupported.
A task that produces no change does not reach this step at all. The property
is what the task produces, and a triage, a reproduction and a review are
illustrations of it rather than the list it is read off. The sweep asks what
a package will have to stop calling, and a task that writes nothing is not
going to call anything. Followed literally in a triage of one issue it costs
one call per declared major per tag and none of them bears on the report.
The exemption ends where the workflow produces a change. A review asked to
make the change is that other workflow, and it starts this order again
holding the files it is about to write.
Skip the sweep only where the change touches no TYPO3 API — a code style
fixer, a CI file, an `.editorconfig`. A deprecation is a statement about API
the package calls, so a change that calls none has nothing for the sweep to
land on and it is empty before it is run. That condition is worth stating
because this is the most expensive step of the order: one call per declared
major per tag. Which side a change falls on is read off the files it touches
and never off the task it started as — one PHP file edited along the way puts
it back among the ordinary ones, and a skip there costs the deprecation no
finding would have walked into.
A report names the step it did not reach, under either exemption. A step
passed over in silence cannot be told from one that was dropped, which is
what step 2 already asks of itself.
**Then** read the checkout. Not before: listing the files first makes everything
after the listing look optional, and the conventions arrive as a footnote to a
verdict that has already formed.
## When the lookups run out
A behaviour question that survives the lookups above is read out of the
installed source rather than guessed at. What answers it is the class that
implements the behaviour and the one it inherits from. That reading is the step
after the lookups, and what it replaces is changing the code until it works.
What it settles is what this installation does and never what TYPO3 supports. So
a finding says the question could not be settled beyond the version installed,
and an answer built on the reading names the version it holds for.
## Two kinds of lookup, and neither stands in for the other
`typo3_backend_module_lookup`, `typo3_icon_lookup`, `typo3_label_lookup`,
`typo3_fluid_namespace_list` and `typo3_configuration_lookup` report what is
registered, what a path resolves to, what a value really is at runtime. They
establish the facts of this installation and they are never a verdict on it.
`typo3_hint_lookup` and `typo3_documentation_lookup` say whether those facts are
right. A subsystem confirmed by its own runtime lookup can still break every
rule that governs it, so it is not established until both were asked.
## A rule is read in both directions
It says what new code should do, and it says what this checkout is already doing
wrong. A file that has settled into the opposite of a rule is a finding, not a
local style to preserve: consistency with a project's own habit establishes
nothing about whether the habit is right.
## What the code is for is evidence, and the repository states it
A mechanism that costs something is not a defect for costing it. Before
reporting one, find what it is there for — the manual, the README, the
changelog, the setting it is driven by, the versions the package declares it
supports — and say so. Where a purpose is documented, what you have is a
trade-off to name with its cost and its alternative, not a defect; where you
cannot find one, the finding says that it could not be established rather than
that none exists. This is the other direction of the rule above, and skipping it
turns a review into a list of everything the author did on purpose.
## What a finding rests on is part of the finding
Three things carry one: a file that was read, at its path and its line; a
command that was run, with what it printed; a mechanism traced into an installed
package. Say which of the three it is. Leaving it unsaid gives a finding read
out of a CI file the weight of one with a verified line, and the reader has no
way to separate them again.
Where one of the project's own commands would settle it, run it.
`typo3_project_describe` marks each command it lists **check**, **change** or
**unknown**, read off the declared body: a check reports and hands the code back
as it was, so even a task told not to change files runs it, and the linter the
repository already declares is the cheapest evidence in it. A change is not run
under that instruction, and an unknown — a test suite, a shell pipeline, a
console command — is named in the answer as evidence that is available rather
than run unasked. What a check prints is not the finding: the configuration that
makes it fail is still what the finding is about, and the run is what takes that
finding from derived to established.
## What this server does not know
It does not read your working tree. Which files changed, which branch you are
on, and whether a path or an identifier still exists there are yours to
establish — then pass the concrete paths back, because that is what turns a
general convention into an answer about this code.
## Query it in English
The knowledge is written in English and matched lexically, so a query in another
language reaches the loanwords the two happen to share and nothing else.
Translate the subject before calling and the answer back afterwards, whatever
language you are speaking with the user.
markdown
# Where every task starts
## Nothing starts until the server answers
A skill is a file the installer left behind: it loads and reads the same whether
the tools behind it are connected or not, and neither side notices. So the first
call below is also the check.
- No `typo3_` tool in this session, or a first call that errors: stop, say this
workflow needs the server and it is not there, and name what came back.
- Do not fall back to general TYPO3 knowledge or start reading the checkout.
That answer carries this workflow's order and confidence and none of its
evidence, and nothing in it says which of the two it is.
- Continue only when asked to after saying so, and repeat it in the answer and
in every finding a lookup would have carried.
## The order
This is the order, and it is an order rather than a list because each step
decides what the next one is worth. A convention fetched after the code has been
read confirms a view instead of testing it. A command recommended before the
project's own are known is a guess that sounds like advice. Where a step below
carries a condition for skipping it, that condition is narrow on purpose: a
prescription that gets skipped teaches the next reader to skip the ones that
matter too.
1. **`typo3_project_describe`** — the installation, its TYPO3 and PHP version,
the extensions that are the project's own, its sites, and the commands this
repository actually declares. Every later answer is filtered by that version,
and a check the repository does not declare is a wrong answer however
sensible it sounds. It ends with the whole procedures this server carries, as
ids: that list is the only place they are named to a client that renders no
resource list, and each one is a `typo3_rule_lookup` with that `documentId`
rather than a search.
2. **`typo3_extension_describe`** for each extension in scope — what it
registers, and what it ships beside that: its manual, its README, its test
layers, its XLF files with the source language each one declares. What it
does *not* ship is answered too, and that is the half no file listing can
give you.
Where step 1 reported none — a core checkout is the case, since it names the
project's own extensions and not TYPO3's — that answer is this step, and
there is nothing to call. Say so. A step passed over in silence cannot be
told from one that was dropped.
3. **`typo3_task_guide`** with a short English task, the paths it touches, the
target version and the change type — the workflow this task belongs to and
the checks that come with it.
Run it in every session, this skill's own tasks included. The brief is built
from the paths as well as the task text, and no skill knows which paths the
caller is holding: a skill that covers the task is not that brief, and
skipping the step costs the hints and the core checks those paths match.
Where the guide's own answer is what named this skill, this is one call for
an answer already in the session. That is the price of a step there is
nothing to decide about.
4. **`typo3_hint_lookup`** for each subsystem in scope, with its concrete paths.
One query per subsystem; a single broad query is not subsystem evidence.
Where step 3 ran with those paths, its answer says whether this step is still
owed. A brief that carried everything the lookup matched says so — "these are
everything typo3_hint_lookup matches for these paths" — and there the call is
made and asking again returns the same hints. One that stopped short says
that instead and names the ids it left, and those are what is owed: fetch
them by id rather than repeating the query. Read the sentence rather than the
populated `hints` key, which is present either way and does not tell the two
apart.
5. **`typo3_changelog_lookup` with `type: deprecation`**, at each major the
package declares, bounded by `tag` and with the query omitted. Those three
are the changelog's own axes, and the extension's vocabulary is not among
them: an entry carries a query only when its title carries every word of it
at once, and the core titled those entries about its own code. Words taken
from what step 2 reported are therefore matched against titles they were
never written in, and the sweep comes back empty however right the query
looks.
Step 2 picks the tags instead. `ext:core`, `ext:frontend`, `ext:form` and the
rest name the system extension a change is **in** — one call for each one the
package requires, renders through or registers into, which is more than its
manifest lists — and `TCA`, `TypoScript`, `Fluid`, `YAML`, `Backend`,
`Frontend` name the surface, one for each kind of file it ships. An extension
key of your own is not among them and matches nothing. Every call also
returns every tag that version and type carry, so the second call onwards is
read off the first rather than guessed at.
Step 2 is what the answers are checked against, which is the other half the
words were doing. Verify each identifier that comes back in the checkout — a
deprecation nothing here calls is not a finding — and carry the
`FullyScanned` / `PartiallyScanned` tag into the answer, because it says
whether the Extension Scanner can find the remaining call sites or whether
that reading is yours. Bounded this way the sweep is still writable before a
file is opened, which is why it is a step of the order: one left to the
reading reaches only what a finding stumbled into, and the deprecation that
decides whether the package survives the next major is not usually the one a
finding walks past.
A changelog records change events, so a pattern nothing has touched for ten
majors has no entry at all. An empty sweep is therefore not an answer about
what still works. "Does this still work in version N" goes to
`typo3_documentation_lookup` at that version — here, and whenever the reading
raises it again.
That is a question for a documented surface — a ViewHelper, a TCA type, a
TypoScript setting. The manual matches page titles and section paths, never
the text of a page, so a PHP identifier has no page to be titled after. It
reaches whatever its own words happen to spell instead. An identifier goes to
`typo3_changelog_lookup` under its own name, which reaches the entries
writing it however the change was titled, and then to the class below. Where
the manual has no page for a surface either, that is a result and not an
answer. Undocumented is not unsupported.
A task that produces no change does not reach this step at all. The property
is what the task produces, and a triage, a reproduction and a review are
illustrations of it rather than the list it is read off. The sweep asks what
a package will have to stop calling, and a task that writes nothing is not
going to call anything. Followed literally in a triage of one issue it costs
one call per declared major per tag and none of them bears on the report.
The exemption ends where the workflow produces a change. A review asked to
make the change is that other workflow, and it starts this order again
holding the files it is about to write.
Skip the sweep only where the change touches no TYPO3 API — a code style
fixer, a CI file, an `.editorconfig`. A deprecation is a statement about API
the package calls, so a change that calls none has nothing for the sweep to
land on and it is empty before it is run. That condition is worth stating
because this is the most expensive step of the order: one call per declared
major per tag. Which side a change falls on is read off the files it touches
and never off the task it started as — one PHP file edited along the way puts
it back among the ordinary ones, and a skip there costs the deprecation no
finding would have walked into.
A report names the step it did not reach, under either exemption. A step
passed over in silence cannot be told from one that was dropped, which is
what step 2 already asks of itself.
**Then** read the checkout. Not before: listing the files first makes everything
after the listing look optional, and the conventions arrive as a footnote to a
verdict that has already formed.
## When the lookups run out
A behaviour question that survives the lookups above is read out of the
installed source rather than guessed at. What answers it is the class that
implements the behaviour and the one it inherits from. That reading is the step
after the lookups, and what it replaces is changing the code until it works.
What it settles is what this installation does and never what TYPO3 supports. So
a finding says the question could not be settled beyond the version installed,
and an answer built on the reading names the version it holds for.
## Two kinds of lookup, and neither stands in for the other
`typo3_backend_module_lookup`, `typo3_icon_lookup`, `typo3_label_lookup`,
`typo3_fluid_namespace_list` and `typo3_configuration_lookup` report what is
registered, what a path resolves to, what a value really is at runtime. They
establish the facts of this installation and they are never a verdict on it.
`typo3_hint_lookup` and `typo3_documentation_lookup` say whether those facts are
right. A subsystem confirmed by its own runtime lookup can still break every
rule that governs it, so it is not established until both were asked.
## A rule is read in both directions
It says what new code should do, and it says what this checkout is already doing
wrong. A file that has settled into the opposite of a rule is a finding, not a
local style to preserve: consistency with a project's own habit establishes
nothing about whether the habit is right.
## What the code is for is evidence, and the repository states it
A mechanism that costs something is not a defect for costing it. Before
reporting one, find what it is there for — the manual, the README, the
changelog, the setting it is driven by, the versions the package declares it
supports — and say so. Where a purpose is documented, what you have is a
trade-off to name with its cost and its alternative, not a defect; where you
cannot find one, the finding says that it could not be established rather than
that none exists. This is the other direction of the rule above, and skipping it
turns a review into a list of everything the author did on purpose.
## What a finding rests on is part of the finding
Three things carry one: a file that was read, at its path and its line; a
command that was run, with what it printed; a mechanism traced into an installed
package. Say which of the three it is. Leaving it unsaid gives a finding read
out of a CI file the weight of one with a verified line, and the reader has no
way to separate them again.
Where one of the project's own commands would settle it, run it.
`typo3_project_describe` marks each command it lists **check**, **change** or
**unknown**, read off the declared body: a check reports and hands the code back
as it was, so even a task told not to change files runs it, and the linter the
repository already declares is the cheapest evidence in it. A change is not run
under that instruction, and an unknown — a test suite, a shell pipeline, a
console command — is named in the answer as evidence that is available rather
than run unasked. What a check prints is not the finding: the configuration that
makes it fail is still what the finding is about, and the run is what takes that
finding from derived to established.
## What this server does not know
It does not read your working tree. Which files changed, which branch you are
on, and whether a path or an identifier still exists there are yours to
establish — then pass the concrete paths back, because that is what turns a
general convention into an answer about this code.
## Query it in English
The knowledge is written in English and matched lexically, so a query in another
language reaches the loanwords the two happen to share and nothing else.
Translate the subject before calling and the answer back afterwards, whatever
language you are speaking with the user.
Conformance audit checklist#
# Conformance audit checklist
Read the relevant sections for a scoped review; read all sections for a full
extension audit. Either way the surface list below is written whole, and a
surface the request left out is reported as not requested rather than dropped.
Absence of an optional subsystem is not a defect.
## Audit surfaces
- Package: identity, Composer constraints, autoloading, extension metadata, and
supported TYPO3/PHP range.
- Registration and runtime: services, events, middleware, plugins, content
elements, backend modules, routes, permissions, and effective configuration.
- Persistence: TCA, schema, relations, DataHandler use, repositories, fixtures,
and upgrade paths.
- Rendering: site sets, TypoScript, TSconfig, Fluid roots and namespaces,
templates, translations, and public assets.
- Security: authorization boundaries, state-changing requests, output-context
escaping, raw rendering, query construction, user-controlled attributes, URLs
and paths, and secret exposure. Every one of them is a value and a sink, and
the finding gate below is how one is established.
- Quality: the test suite and the supported TYPO3 versions it runs on, the check
layer, documentation — `typo3_hint_lookup` with `id=extension-documentation`,
which says what a manual consists of and that it ships with the package —
deprecations, and upgrade readiness.
## The check layer
The commands a repository declares are where this surface is read, never what it
is. Measure them against what a complete layer covers, each entry named by what
the check establishes rather than by the tool behind it:
- **Syntax** — every shipped PHP file parses on every PHP version the package
declares support for.
- **Static analysis** — types, unreachable code, and calls that cannot succeed,
over the paths the package owns and at a level the project can hold today.
- **Coding standards** — the TYPO3 coding guidelines as the project applies
them, with the editor-configuration and declared-PHP-range checks that run
beside them.
- **Manifests and dependencies** — the manifest validates, agrees with what the
package declares about itself elsewhere, and carries no open advisory.
- **Shipped configuration and data** — the XLIFF, YAML and TypoScript the
package ships are well-formed. Fluid templates have no established linter and
are proven by the tests that render them.
- **Shipped frontend assets** — the JavaScript, TypeScript and CSS sources the
repository maintains, never the bundle a build step produced from them.
Which of them apply is decided by what the package ships: a check whose subject
it does not ship is absent for a reason, while one whose subject it ships and no
command covers is a gap in the layer rather than an optional subsystem, and that
absence is the finding. Ask the same of where each one runs — syntax and
analysis depend on the PHP and TYPO3 combination and belong in a matrix, while a
standards, manifest or format check is version-independent and one run of it
proves as much as sixteen, so a matrix whose every cell runs only
version-independent steps establishes that the files parse and nothing more.
The checks that exist are run, and what they printed is the ceiling of what this
surface is worth rather than its verdict: a green net proves the entries it
covers and nothing about the ones it has none for, so say which of the entries
above it leaves untouched. Establishing a missing one is
`typo3-extension-testing`'s workflow, and it names the default tool per check; a
review names the gap, routes it there, and changes nothing.
## Content element architecture
Before accepting a content-element implementation, verify the editor workflow
and ownership model rather than only the CType registration:
- Repeatable content owned by one element should normally use a dedicated inline
child table. References to existing records require an explicit reuse
requirement and reviewed visibility, localization, lifecycle and duplicate
rendering behavior.
- Keep shared content-element setup in the generic override and move one
element's fields and CType registration into a named sibling.
- Keep one content element per TypoScript file under the project's established
content-element directory.
- Load element-only CSS and JavaScript through the template AssetCollector.
Page-level inclusion is for assets needed across the site.
- Require functional coverage for inline persistence and rendering; require
browser coverage for JavaScript interaction and accessibility.
For each surface, compare checkout declarations, runtime evidence when
available, applicable architecture guidance, and versioned official
documentation. When an installation parser misses a dynamic PHP registration,
report that limitation and inspect the checkout rather than treating it as
absence.
## Severity
- Critical: exploitable security issue, destructive data loss, or release-wide
outage with no practical containment.
- High: likely security boundary failure, data corruption, or a primary feature
unusable in a supported setup.
- Medium: concrete incompatibility, unsafe rendering pattern, broken secondary
behavior, missing regression coverage for risky code, or misleading operator
documentation.
- Low: limited maintainability or convention issue with a concrete future cost.
- Recommendation: beneficial improvement without a verified violation.
Severity follows demonstrated consequence, not the number of files involved.
State missing evidence instead of inflating severity.
## Finding gate
A finding needs a concrete location, observed evidence, applicable rule or
documentation, consequence, remediation, and relevant project check. Otherwise
record it as a question or unverified category, not a violation.
A finding about a user-controlled value is a claim about a **sink** rather than
about a call site, and escaping and injection are the same claim about different
sinks: the tag or attribute the value is printed into, the statement it is
executed in, the header, path or process it ends up in. It is not established
until that sink is named and the code at it is read. Everything before it is the
path, and an escaping opt-out, a quoting helper or a ViewHelper that hands its
rendered children to another component is on the path rather than at the end of
it — where the sink protects the value on its own, that opt-out is what keeps it
from being encoded or quoted twice. Ask `typo3_hint_lookup` for the sinks of the
surface in hand, follow the value into the installed package that emits or
executes it, and where the path can be rendered or run, let the repository's own
test settle it. Otherwise report the finding as unverified and say which class
went unread. A security verdict is the expensive kind to get wrong: it has to be
disproved before it can be dismissed, which costs the maintainer exactly the
reading the review skipped.
## What a dropped candidate owes
An audit drops more than it reports, and dropping is the step nothing records.
Each candidate raised while reading and then let go is named with what let it go
— the setting that turned out to be the core default, the guard that turned out
to be there, the rule that turned out not to govern this package, the class that
was actually read. One sentence each, beside the findings.
A subsystem the package does not ship never enters this list. It is answered on
the coverage list as not applicable and costs the line it costs there; what
belongs here is what was entertained as a defect and then was not one.
The two directions are not held to the same bar. Raising a candidate costs a
reading; dropping one costs the maintainer a finding, silently, and nothing
afterwards says it happened. So a candidate is dropped only where something
concretely disproves it, and one that can be neither established nor disproved
is reported as open, with the reading that would settle it named beside it —
which is the finding gate's question rather than violation, read from the other
side.
Two dismissals go wrong reliably:
- Dropped because a comment, a docblock or an annotation says the code behaves
that way. That is a sentence somebody wrote, not the behaviour — read the
implementation it describes, and where the two disagree the disagreement is
the finding.
- Dropped because it looks unlikely to happen. Unlikely is not disproved. What
disproves a path is what makes it impossible: a guard that cannot be passed or
a caller that cannot exist, at a line.
The gate above states this bar for a security verdict, which is where it is
steepest, and the bar is not that subject's: what makes a dismissal expensive is
that its cost falls on the maintainer rather than on the audit, and it does that
on every surface here.
markdown
# Conformance audit checklist
Read the relevant sections for a scoped review; read all sections for a full
extension audit. Either way the surface list below is written whole, and a
surface the request left out is reported as not requested rather than dropped.
Absence of an optional subsystem is not a defect.
## Audit surfaces
- Package: identity, Composer constraints, autoloading, extension metadata, and
supported TYPO3/PHP range.
- Registration and runtime: services, events, middleware, plugins, content
elements, backend modules, routes, permissions, and effective configuration.
- Persistence: TCA, schema, relations, DataHandler use, repositories, fixtures,
and upgrade paths.
- Rendering: site sets, TypoScript, TSconfig, Fluid roots and namespaces,
templates, translations, and public assets.
- Security: authorization boundaries, state-changing requests, output-context
escaping, raw rendering, query construction, user-controlled attributes, URLs
and paths, and secret exposure. Every one of them is a value and a sink, and
the finding gate below is how one is established.
- Quality: the test suite and the supported TYPO3 versions it runs on, the check
layer, documentation — `typo3_hint_lookup` with `id=extension-documentation`,
which says what a manual consists of and that it ships with the package —
deprecations, and upgrade readiness.
## The check layer
The commands a repository declares are where this surface is read, never what it
is. Measure them against what a complete layer covers, each entry named by what
the check establishes rather than by the tool behind it:
- **Syntax** — every shipped PHP file parses on every PHP version the package
declares support for.
- **Static analysis** — types, unreachable code, and calls that cannot succeed,
over the paths the package owns and at a level the project can hold today.
- **Coding standards** — the TYPO3 coding guidelines as the project applies
them, with the editor-configuration and declared-PHP-range checks that run
beside them.
- **Manifests and dependencies** — the manifest validates, agrees with what the
package declares about itself elsewhere, and carries no open advisory.
- **Shipped configuration and data** — the XLIFF, YAML and TypoScript the
package ships are well-formed. Fluid templates have no established linter and
are proven by the tests that render them.
- **Shipped frontend assets** — the JavaScript, TypeScript and CSS sources the
repository maintains, never the bundle a build step produced from them.
Which of them apply is decided by what the package ships: a check whose subject
it does not ship is absent for a reason, while one whose subject it ships and no
command covers is a gap in the layer rather than an optional subsystem, and that
absence is the finding. Ask the same of where each one runs — syntax and
analysis depend on the PHP and TYPO3 combination and belong in a matrix, while a
standards, manifest or format check is version-independent and one run of it
proves as much as sixteen, so a matrix whose every cell runs only
version-independent steps establishes that the files parse and nothing more.
The checks that exist are run, and what they printed is the ceiling of what this
surface is worth rather than its verdict: a green net proves the entries it
covers and nothing about the ones it has none for, so say which of the entries
above it leaves untouched. Establishing a missing one is
`typo3-extension-testing`'s workflow, and it names the default tool per check; a
review names the gap, routes it there, and changes nothing.
## Content element architecture
Before accepting a content-element implementation, verify the editor workflow
and ownership model rather than only the CType registration:
- Repeatable content owned by one element should normally use a dedicated inline
child table. References to existing records require an explicit reuse
requirement and reviewed visibility, localization, lifecycle and duplicate
rendering behavior.
- Keep shared content-element setup in the generic override and move one
element's fields and CType registration into a named sibling.
- Keep one content element per TypoScript file under the project's established
content-element directory.
- Load element-only CSS and JavaScript through the template AssetCollector.
Page-level inclusion is for assets needed across the site.
- Require functional coverage for inline persistence and rendering; require
browser coverage for JavaScript interaction and accessibility.
For each surface, compare checkout declarations, runtime evidence when
available, applicable architecture guidance, and versioned official
documentation. When an installation parser misses a dynamic PHP registration,
report that limitation and inspect the checkout rather than treating it as
absence.
## Severity
- Critical: exploitable security issue, destructive data loss, or release-wide
outage with no practical containment.
- High: likely security boundary failure, data corruption, or a primary feature
unusable in a supported setup.
- Medium: concrete incompatibility, unsafe rendering pattern, broken secondary
behavior, missing regression coverage for risky code, or misleading operator
documentation.
- Low: limited maintainability or convention issue with a concrete future cost.
- Recommendation: beneficial improvement without a verified violation.
Severity follows demonstrated consequence, not the number of files involved.
State missing evidence instead of inflating severity.
## Finding gate
A finding needs a concrete location, observed evidence, applicable rule or
documentation, consequence, remediation, and relevant project check. Otherwise
record it as a question or unverified category, not a violation.
A finding about a user-controlled value is a claim about a **sink** rather than
about a call site, and escaping and injection are the same claim about different
sinks: the tag or attribute the value is printed into, the statement it is
executed in, the header, path or process it ends up in. It is not established
until that sink is named and the code at it is read. Everything before it is the
path, and an escaping opt-out, a quoting helper or a ViewHelper that hands its
rendered children to another component is on the path rather than at the end of
it — where the sink protects the value on its own, that opt-out is what keeps it
from being encoded or quoted twice. Ask `typo3_hint_lookup` for the sinks of the
surface in hand, follow the value into the installed package that emits or
executes it, and where the path can be rendered or run, let the repository's own
test settle it. Otherwise report the finding as unverified and say which class
went unread. A security verdict is the expensive kind to get wrong: it has to be
disproved before it can be dismissed, which costs the maintainer exactly the
reading the review skipped.
## What a dropped candidate owes
An audit drops more than it reports, and dropping is the step nothing records.
Each candidate raised while reading and then let go is named with what let it go
— the setting that turned out to be the core default, the guard that turned out
to be there, the rule that turned out not to govern this package, the class that
was actually read. One sentence each, beside the findings.
A subsystem the package does not ship never enters this list. It is answered on
the coverage list as not applicable and costs the line it costs there; what
belongs here is what was entertained as a defect and then was not one.
The two directions are not held to the same bar. Raising a candidate costs a
reading; dropping one costs the maintainer a finding, silently, and nothing
afterwards says it happened. So a candidate is dropped only where something
concretely disproves it, and one that can be neither established nor disproved
is reported as open, with the reading that would settle it named beside it —
which is the finding gate's question rather than violation, read from the other
side.
Two dismissals go wrong reliably:
- Dropped because a comment, a docblock or an annotation says the code behaves
that way. That is a sentence somebody wrote, not the behaviour — read the
implementation it describes, and where the two disagree the disagreement is
the finding.
- Dropped because it looks unlikely to happen. Unlikely is not disproved. What
disproves a path is what makes it impossible: a guard that cannot be passed or
a caller that cannot exist, at a line.
The gate above states this bar for a security verdict, which is where it is
steepest, and the bar is not that subject's: what makes a dismissal expensive is
that its cost falls on the maintainer rather than on the audit, and it does that
on every surface here.