TYPO3 Core Issue Triage
Skill: typo3-core-issue-triage
Say what is still true about an open issue on forge.typo3.org: find candidates in the backlog, read what the report claims, and establish against the core checkout whether it still happens, was fixed, or was never a defect. Writing or reviewing a patch is other work.
Markdown source#
---
name: typo3-core-issue-triage
description: 'Say what is still true about an open issue on forge.typo3.org: find candidates in the backlog, read what the report claims, and establish against the core checkout whether it still happens, was fixed, or was never a defect. Writing or reviewing a patch is other work.'
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 Core Issue Triage
Take one open issue and say what is still true about it. Keep this skill as
routing and working order; the tracker, the review server and the checkout's own
commands are lookups, and a copy of what they answer goes stale here with
nothing to report it.
Triage is not a smaller version of writing the patch. What it produces is a
statement somebody can act on — this still happens, this is gone, this was never
a defect, this cannot be settled without X — and the outcome that ends the work
early is the valuable one.
## Find the candidates
1. Work through [references/base.md](references/base.md), which fixes the order
every task here starts in. It establishes the checkout you are standing in,
which is what the verification below is against.
2. `typo3_forge_lookup` with `open` to get the backlog rather than one issue.
`oldest` and `stale` are two different questions and the second is usually
the one being asked: filed long ago is about the report, untouched for years
is about the attention it got, and an issue filed in 2009 with a comment from
last month is being worked by somebody. Narrow with `category` in the user's
own words, and with `tracker`, because an old Bug and an old Feature are two
different findings — one claims something is broken today, the other that
something was wanted once.
Read the count that comes back against the number of entries. A page is not
the set, and a triage that takes thirty of two thousand for the problem has
measured the limit rather than the backlog.
**Nothing in that list is a finding.** Age is what makes an issue a candidate
and says nothing about whether it is right; a report from 2011 can describe
behaviour the branch still has, and one from last year can be about code that no
longer exists. What separates them is the rest of this order.
**The list is the first deliverable, and choosing from it is not yours.**
Triaging a backlog and triaging an issue are two different jobs, and the step
below is the second one: it takes a number. Hand the backlog over first, one row
per candidate carrying what it has to be chosen on — the number, the area, the
subject, how long it has been untouched — and let whoever asked pick. A session
that picks for itself reports on four issues out of thirty-nine and has silently
answered a question it was never asked; where the request really was "just find
me something", say which rows you would take and why, and let that be the
choice.
**Where you do pick, pick on where the symptom is visible and on how much the
checkout already models it.** Age is not it, and neither is the subject matter.
Read in this order and stop at the first that decides:
- **What has already happened to it.** A change on the review server is the
cheapest description of what a fix looks like, and one that was abandoned is a
verdict somebody wrote down. A relation to an epic, or to a parent somebody
accepted, says the report is one strand of a larger piece whose decision is
not a session's to take.
- **The category, against the branch you are standing on.** One naming a
subsystem the branch no longer ships settles the issue before the report is
read. The tracker keeps a category long after the code goes, and most of an
old backlog still names subsystems that are there.
- **Where the symptom appears.** A rendered fragment, a stored row, a resolved
value — anything a process produces — is reachable in the layers that need
neither an installation nor a browser. One that appears only after an
interaction in the backend needs both standing up first, and that is most of
the session.
- **How far the mechanism reaches.** A report naming one class and the behaviour
in it is the settleable shape. One naming several and the order between them
has already said it is an interaction, and a reporter who worked that out is
usually right.
- **What the suite already models.** Look for a test over the class the report
is about, at the level the symptom appears at: a case added to a file that
exists is a reproduction with no fixture to build. The level is the whole of
it — a component tested on its own cannot see an order between components —
and the core models more constellations than a category suggests.
Say which of those decided, and say of the rows you passed over that you passed
over them. Skipping is not triaging, and the list is still what was asked for.
## Establish what the issue claims
3. `typo3_forge_lookup` with the number, and read what comes back as a report
rather than as a specification. Three parts of it are not in the description
a session otherwise starts from: the **status and target version as they
stand today**, the **relations**, which are one hop from the change that
introduced the behaviour, and the **notes**, where a maintainer said why. Who
it is assigned to is the fourth, and on an old issue it usually names who
last touched it rather than who is on it — an assignee is not evidence that
anybody is working on it, and an unassigned issue is not evidence that nobody
minds.
Separate the three claims the report mixes before verifying any of them: what
the reporter saw, what they believed caused it, and what they wanted instead.
The first is the only one a checkout can settle. A report is regularly right
about the symptom and wrong about the cause, and verifying the cause and
reporting the issue as invalid is the most common way this work goes wrong.
Where the issue quotes a rule — an API may not be used this way, this is not
supported — verify it in the checkout rather than carrying it at the strength
the reporter put on it. Enforced in code, warned about in a docblock and advised
in prose are three different claims, and the reporter's word for all three is
the same.
## Ask what happened since it was filed
4. `typo3_gerrit_lookup` with the issue number, **before opening the checkout**.
Its cheapest outcome is the one that ends the work: somebody has a patch up,
and the triage is that it is under review rather than unaddressed. An answer
of nothing is a result and a narrow one — the review server is read without a
credential, so nothing public names the issue, which is not that nobody fixed
it.
5. `typo3_changelog_lookup` with the words the report uses, for whether the area
was deprecated, removed or reworked since it was filed. A rework is what
turns a valid report into one about code that is gone, and it is also what
makes the reproduction below fail for a reason that has nothing to do with
the defect.
A changelog records change events, so an area nobody has touched has no entry at
all. An empty answer is not evidence that the behaviour is unchanged.
## Verify against the checkout you are standing in
Reproduce against what the branch does today, never against the version in the
report. Half of what an old issue describes is usually gone, and the half that
remains is the finding.
Establish the code path first: find the class the report is about and read
whether the behaviour it describes is still written there. That is what
separates "still happens" from "cannot happen any more, the method is gone", and
the second is a verdict that needs no reproduction at all.
**Before writing a test, look for the one the core already wrote and switched
off.** Where a defect was known and nobody fixed it, the suite regularly carries
it as a commented-out data-provider row with the reason beside it, over a
fixture that already models the constellation. `grep -rn "@todo" <sysext>/Tests`
narrowed to the subsystem the report is about is the whole of the search, and
the reason text is what says whether a hit is this report — "Fails, not expanded
to sub-pages" is one, and "wrong assertion" is a note to whoever wrote the test.
There are few of them and they are worth the one grep: removing a comment is a
reproduction with no fixture to build and no harness to prove.
`markTestSkipped` is a different thing and rarely this one. Most of them are
about the machine — no APCu, no Redis, no ImageMagick, a case-sensitive
filesystem — and a test skipped for the environment says nothing about the
report.
`typo3_test_run_guide` with the paths you have just read says which suites can
fail on them, and whether the behaviour can be pinned by a test at all. Where it
can, a failing test is the strongest thing a triage produces: it survives being
handed to somebody else, and it is the patch's first half already written. Where
no layer can hold it — backend markup, a build step, shipped JavaScript — say so
and reproduce by hand instead, writing down the steps and what you saw.
That test is a throwaway until a patch adopts it, and it has three rules of its
own. It goes where the suite already looks, mirroring the path of the class it
is about, because a file the runner does not collect proves nothing. **It has to
be seen failing before it is believed**: a reproduction that is green on its
first run is testing nothing until you have shown it red, and the first run
failing for a reason that is not the issue — a field the type does not show, a
fixture that was never loaded — is a result about your harness and not about the
report. And it comes out again when the triage ends, unless the work carries
straight on into the patch that keeps it.
**The core's suites are not the ones a manifest here declares, and they are not
run the way an extension's are.** They belong to the core's own runner, which no
`composer` script names, and `typo3_test_run_guide` is what gives the targeted
invocation for the paths in hand rather than a suite name to guess at.
`typo3_script_lookup` is the rest of what that runner offers — the options that
decide which PHP and which database a suite runs against, which is exactly what
an old report turns on when it says the behaviour depends on either.
Where the symptom is rendered output, the throwaway has to produce it before it
can assert anything, and the value is the unknown rather than the expectation.
`typo3_rule_lookup` with `documentId="core/testing/proving-a-rendering"` is that
harness: the snippet into TypoScript, the operator forms that silently do
something else instead, and how the rendered HTML is printed at all.
Two of those decide whether a reproduction means anything, and both were already
answered by the step the base opens with:
- **Where the checkout has a DDEV project, the suites and the console run inside
it.** The same command in your own shell runs on whatever PHP the machine
carries and against whatever database it has, which reproduces something else
and looks identical in the output. `typo3_project_describe` says whether this
checkout is one of those and what the form is; take it from there rather than
from what worked in another repository.
- **A green that ran over no files is not a green.** Where a suite reports
success, confirm it inspected something — the count of tests or files it names
— before reading it as the behaviour being gone. That is the failure mode a
triage is most exposed to, because "the suite passes" is the evidence it is
about to write a verdict on.
- **Once the change is committed, `git stash` measures nothing.** The same
failure in a second costume: the stash finds nothing to save on a clean tree,
the run that follows is the patched code, and the result reads as a
without-patch measurement. Compare against the parent instead — a worktree on
`HEAD~1`, or `git revert --no-commit` with a restore after — and confirm the
tree actually changed before believing the run. `git stash list` naming
nothing new is what should stop you.
An old report frequently names the versions it was seen on. Those are what the
reporter had, not what it still reproduces on, and the version the suites run
against here is a property of this checkout. Say which one the verification
used; a verdict that names no version and no branch cannot be repeated.
**A reproduction that fails to reproduce is a result and not a dead end.** Say
which of the three it is: the behaviour is gone, the steps were insufficient, or
the report never contained enough to try. They lead to opposite outcomes and
they look identical in a session that only writes down "could not reproduce".
## Where the finding is a vulnerability
**Ask it of every finding before the verdict is written, rather than when one
happens to look alarming.** A triage produces what a vulnerability report is
made of — a step-by-step reproduction against a branch people are running — and
it produces it for the tracker. Nothing else in this order asks the question, so
the finding would be disclosed by the step that was meant to report it.
The stopping point is the verified reproduction. It stands, and the public step
is not taken: nothing about the finding goes into the issue, onto the review
server or into a chat — not the reproduction, not the failing test, not the
verdict.
Where it goes instead is `typo3_rule_lookup` with
`documentId="any/security/reporting-a-vulnerability"`, which is the whole
procedure and also stands as
`typo3://guides/any/security/reporting-a-vulnerability`. The address is read
there and never from here: a contact route is the fact that moves, and this file
is a copy no release of this server corrects.
Hand over what that report needs — the branch, the code path, the reproduction
and the version it ran on — and say that the ordinary verdict is being withheld
and why. Filing it is the user's act: this workflow supplies what the report
rests on and takes no step of its own.
## Say what the triage found
[references/checklist.md](references/checklist.md) carries the verdicts, what
evidence each one owes, and the questions that decide between them. Read it
before writing the answer rather than after: the verdicts are not degrees of
confidence in one finding, and picking one first decides what still has to be
established.
Report what you did not establish beside what you did. A triage whose reading
stopped at the code path says so, because the next person's work is exactly the
part that was left.
**The verdict is markdown the reader can copy, and the answer is where it
goes.** It is written for the person who will act on it, 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 the reproduction ran in, whose tree this
workflow leaves as it found it.
## What a previous attempt cost
Where the issue was fixed once and the fix taken back out, the verdict is not
the answer somebody is waiting for. "Still happens" and "was tried and reverted"
are the same verdict and opposite propositions, and what separates them is why
it was reverted and whether that reason still holds.
The trigger is in the issue answer rather than in the reading. A relation marked
`precedes` or `duplicates` carries its subject, and `reviews` names every change
the journal mentions — so an issue whose history is a merged-then-reverted fix
says so before the checkout is opened.
6. Read the related issue the revert was filed under. The reason lives there and
nowhere else: the reverting commit says what was reverted, and the issue says
what it cost.
7. Read the attempt itself, which is the one thing no lookup here returns. The
issue answer carries the change numbers and the Gerrit search by issue number
their state, so what is left is the diff: fetch the patch set into the
checkout and read it. The ref it is under, and the remote it is on rather
than the one a core checkout fetches from, are two sections of one page, so
where the fetch is the task read it whole: `typo3_rule_lookup` with
`documentId="core/contribution/gerrit-workflow"`, which also stands as
`typo3://guides/core/contribution/gerrit-workflow`. Abandoned is a verdict
somebody wrote down, and the diff under it is the cheapest description of
what a fix looks like against a modern core. The patch set is read and goes
onto no branch: rebasing an abandoned attempt is writing the patch rather
than judging the issue.
`typo3_gerrit_lookup` with a change number earns a call of its own for a
`reviews` entry the search by issue number did not answer with. That search
finds a change whose commit message names the issue, so an entry missing from
it is the one whose branch, patch set and status nothing else has stated.
8. Find every production caller of the method the reverted patch touched, in the
checkout. A fix that scopes to one call site is a different proposition from
one that changes a path everything else goes through.
9. Establish whether the path named in the revert still routes through that
method. A subsystem rebuilt since is what turns the old objection into
history.
**A reverted core fix becomes re-attemptable when the shared consumer that made
it expensive has been rebuilt, or when the caller set has shrunk to the one site
the fix needs.** That is the form that transfers; neither half is readable off
the issue, and both are one grep and one file in the checkout.
What comes out of this is what a maintainer would need before the issue can
move, which is what the answer owes. It is not a design and not a patch: naming
the constraint the last attempt broke is the deliverable, and proposing the code
that respects it is the next workflow's.
## Where the triage ends and the patch begins
**When the verdict is that it still happens and you are asked to fix it, invoke
`typo3-core-patch-development` before making the change.** That is a step, not a
note about ownership: load the skill by name and work from it. What crosses over
is the issue number, the verdict, the code path that was established and the
failing test where there is one.
The reason it is written as a step is that it did not fire as anything else. A
session read this paragraph, held exactly that handoff, was asked for the patch,
and wrote it over forty more turns without ever opening the skill — deciding for
itself the changelog obligation, which suites to run and on which databases, the
commit trailers and the release branches. Nothing it decided was reported wrong;
all of it was reconstructed.
This skill owns saying what is still true about an issue: choosing it out of the
backlog, reading the report against the branch, reproducing it or failing to,
and the verdict that comes out. It stops at the tracker — nothing here comments,
assigns, closes or reopens anything, and the verdict is written for the person
who will. Judging a patch somebody pushed is `typo3-core-patch-review`, which
reads the diff rather than the report.
markdown
---
name: typo3-core-issue-triage
description: 'Say what is still true about an open issue on forge.typo3.org: find candidates in the backlog, read what the report claims, and establish against the core checkout whether it still happens, was fixed, or was never a defect. Writing or reviewing a patch is other work.'
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 Core Issue Triage
Take one open issue and say what is still true about it. Keep this skill as
routing and working order; the tracker, the review server and the checkout's own
commands are lookups, and a copy of what they answer goes stale here with
nothing to report it.
Triage is not a smaller version of writing the patch. What it produces is a
statement somebody can act on — this still happens, this is gone, this was never
a defect, this cannot be settled without X — and the outcome that ends the work
early is the valuable one.
## Find the candidates
1. Work through [references/base.md](references/base.md), which fixes the order
every task here starts in. It establishes the checkout you are standing in,
which is what the verification below is against.
2. `typo3_forge_lookup` with `open` to get the backlog rather than one issue.
`oldest` and `stale` are two different questions and the second is usually
the one being asked: filed long ago is about the report, untouched for years
is about the attention it got, and an issue filed in 2009 with a comment from
last month is being worked by somebody. Narrow with `category` in the user's
own words, and with `tracker`, because an old Bug and an old Feature are two
different findings — one claims something is broken today, the other that
something was wanted once.
Read the count that comes back against the number of entries. A page is not
the set, and a triage that takes thirty of two thousand for the problem has
measured the limit rather than the backlog.
**Nothing in that list is a finding.** Age is what makes an issue a candidate
and says nothing about whether it is right; a report from 2011 can describe
behaviour the branch still has, and one from last year can be about code that no
longer exists. What separates them is the rest of this order.
**The list is the first deliverable, and choosing from it is not yours.**
Triaging a backlog and triaging an issue are two different jobs, and the step
below is the second one: it takes a number. Hand the backlog over first, one row
per candidate carrying what it has to be chosen on — the number, the area, the
subject, how long it has been untouched — and let whoever asked pick. A session
that picks for itself reports on four issues out of thirty-nine and has silently
answered a question it was never asked; where the request really was "just find
me something", say which rows you would take and why, and let that be the
choice.
**Where you do pick, pick on where the symptom is visible and on how much the
checkout already models it.** Age is not it, and neither is the subject matter.
Read in this order and stop at the first that decides:
- **What has already happened to it.** A change on the review server is the
cheapest description of what a fix looks like, and one that was abandoned is a
verdict somebody wrote down. A relation to an epic, or to a parent somebody
accepted, says the report is one strand of a larger piece whose decision is
not a session's to take.
- **The category, against the branch you are standing on.** One naming a
subsystem the branch no longer ships settles the issue before the report is
read. The tracker keeps a category long after the code goes, and most of an
old backlog still names subsystems that are there.
- **Where the symptom appears.** A rendered fragment, a stored row, a resolved
value — anything a process produces — is reachable in the layers that need
neither an installation nor a browser. One that appears only after an
interaction in the backend needs both standing up first, and that is most of
the session.
- **How far the mechanism reaches.** A report naming one class and the behaviour
in it is the settleable shape. One naming several and the order between them
has already said it is an interaction, and a reporter who worked that out is
usually right.
- **What the suite already models.** Look for a test over the class the report
is about, at the level the symptom appears at: a case added to a file that
exists is a reproduction with no fixture to build. The level is the whole of
it — a component tested on its own cannot see an order between components —
and the core models more constellations than a category suggests.
Say which of those decided, and say of the rows you passed over that you passed
over them. Skipping is not triaging, and the list is still what was asked for.
## Establish what the issue claims
3. `typo3_forge_lookup` with the number, and read what comes back as a report
rather than as a specification. Three parts of it are not in the description
a session otherwise starts from: the **status and target version as they
stand today**, the **relations**, which are one hop from the change that
introduced the behaviour, and the **notes**, where a maintainer said why. Who
it is assigned to is the fourth, and on an old issue it usually names who
last touched it rather than who is on it — an assignee is not evidence that
anybody is working on it, and an unassigned issue is not evidence that nobody
minds.
Separate the three claims the report mixes before verifying any of them: what
the reporter saw, what they believed caused it, and what they wanted instead.
The first is the only one a checkout can settle. A report is regularly right
about the symptom and wrong about the cause, and verifying the cause and
reporting the issue as invalid is the most common way this work goes wrong.
Where the issue quotes a rule — an API may not be used this way, this is not
supported — verify it in the checkout rather than carrying it at the strength
the reporter put on it. Enforced in code, warned about in a docblock and advised
in prose are three different claims, and the reporter's word for all three is
the same.
## Ask what happened since it was filed
4. `typo3_gerrit_lookup` with the issue number, **before opening the checkout**.
Its cheapest outcome is the one that ends the work: somebody has a patch up,
and the triage is that it is under review rather than unaddressed. An answer
of nothing is a result and a narrow one — the review server is read without a
credential, so nothing public names the issue, which is not that nobody fixed
it.
5. `typo3_changelog_lookup` with the words the report uses, for whether the area
was deprecated, removed or reworked since it was filed. A rework is what
turns a valid report into one about code that is gone, and it is also what
makes the reproduction below fail for a reason that has nothing to do with
the defect.
A changelog records change events, so an area nobody has touched has no entry at
all. An empty answer is not evidence that the behaviour is unchanged.
## Verify against the checkout you are standing in
Reproduce against what the branch does today, never against the version in the
report. Half of what an old issue describes is usually gone, and the half that
remains is the finding.
Establish the code path first: find the class the report is about and read
whether the behaviour it describes is still written there. That is what
separates "still happens" from "cannot happen any more, the method is gone", and
the second is a verdict that needs no reproduction at all.
**Before writing a test, look for the one the core already wrote and switched
off.** Where a defect was known and nobody fixed it, the suite regularly carries
it as a commented-out data-provider row with the reason beside it, over a
fixture that already models the constellation. `grep -rn "@todo" <sysext>/Tests`
narrowed to the subsystem the report is about is the whole of the search, and
the reason text is what says whether a hit is this report — "Fails, not expanded
to sub-pages" is one, and "wrong assertion" is a note to whoever wrote the test.
There are few of them and they are worth the one grep: removing a comment is a
reproduction with no fixture to build and no harness to prove.
`markTestSkipped` is a different thing and rarely this one. Most of them are
about the machine — no APCu, no Redis, no ImageMagick, a case-sensitive
filesystem — and a test skipped for the environment says nothing about the
report.
`typo3_test_run_guide` with the paths you have just read says which suites can
fail on them, and whether the behaviour can be pinned by a test at all. Where it
can, a failing test is the strongest thing a triage produces: it survives being
handed to somebody else, and it is the patch's first half already written. Where
no layer can hold it — backend markup, a build step, shipped JavaScript — say so
and reproduce by hand instead, writing down the steps and what you saw.
That test is a throwaway until a patch adopts it, and it has three rules of its
own. It goes where the suite already looks, mirroring the path of the class it
is about, because a file the runner does not collect proves nothing. **It has to
be seen failing before it is believed**: a reproduction that is green on its
first run is testing nothing until you have shown it red, and the first run
failing for a reason that is not the issue — a field the type does not show, a
fixture that was never loaded — is a result about your harness and not about the
report. And it comes out again when the triage ends, unless the work carries
straight on into the patch that keeps it.
**The core's suites are not the ones a manifest here declares, and they are not
run the way an extension's are.** They belong to the core's own runner, which no
`composer` script names, and `typo3_test_run_guide` is what gives the targeted
invocation for the paths in hand rather than a suite name to guess at.
`typo3_script_lookup` is the rest of what that runner offers — the options that
decide which PHP and which database a suite runs against, which is exactly what
an old report turns on when it says the behaviour depends on either.
Where the symptom is rendered output, the throwaway has to produce it before it
can assert anything, and the value is the unknown rather than the expectation.
`typo3_rule_lookup` with `documentId="core/testing/proving-a-rendering"` is that
harness: the snippet into TypoScript, the operator forms that silently do
something else instead, and how the rendered HTML is printed at all.
Two of those decide whether a reproduction means anything, and both were already
answered by the step the base opens with:
- **Where the checkout has a DDEV project, the suites and the console run inside
it.** The same command in your own shell runs on whatever PHP the machine
carries and against whatever database it has, which reproduces something else
and looks identical in the output. `typo3_project_describe` says whether this
checkout is one of those and what the form is; take it from there rather than
from what worked in another repository.
- **A green that ran over no files is not a green.** Where a suite reports
success, confirm it inspected something — the count of tests or files it names
— before reading it as the behaviour being gone. That is the failure mode a
triage is most exposed to, because "the suite passes" is the evidence it is
about to write a verdict on.
- **Once the change is committed, `git stash` measures nothing.** The same
failure in a second costume: the stash finds nothing to save on a clean tree,
the run that follows is the patched code, and the result reads as a
without-patch measurement. Compare against the parent instead — a worktree on
`HEAD~1`, or `git revert --no-commit` with a restore after — and confirm the
tree actually changed before believing the run. `git stash list` naming
nothing new is what should stop you.
An old report frequently names the versions it was seen on. Those are what the
reporter had, not what it still reproduces on, and the version the suites run
against here is a property of this checkout. Say which one the verification
used; a verdict that names no version and no branch cannot be repeated.
**A reproduction that fails to reproduce is a result and not a dead end.** Say
which of the three it is: the behaviour is gone, the steps were insufficient, or
the report never contained enough to try. They lead to opposite outcomes and
they look identical in a session that only writes down "could not reproduce".
## Where the finding is a vulnerability
**Ask it of every finding before the verdict is written, rather than when one
happens to look alarming.** A triage produces what a vulnerability report is
made of — a step-by-step reproduction against a branch people are running — and
it produces it for the tracker. Nothing else in this order asks the question, so
the finding would be disclosed by the step that was meant to report it.
The stopping point is the verified reproduction. It stands, and the public step
is not taken: nothing about the finding goes into the issue, onto the review
server or into a chat — not the reproduction, not the failing test, not the
verdict.
Where it goes instead is `typo3_rule_lookup` with
`documentId="any/security/reporting-a-vulnerability"`, which is the whole
procedure and also stands as
`typo3://guides/any/security/reporting-a-vulnerability`. The address is read
there and never from here: a contact route is the fact that moves, and this file
is a copy no release of this server corrects.
Hand over what that report needs — the branch, the code path, the reproduction
and the version it ran on — and say that the ordinary verdict is being withheld
and why. Filing it is the user's act: this workflow supplies what the report
rests on and takes no step of its own.
## Say what the triage found
[references/checklist.md](references/checklist.md) carries the verdicts, what
evidence each one owes, and the questions that decide between them. Read it
before writing the answer rather than after: the verdicts are not degrees of
confidence in one finding, and picking one first decides what still has to be
established.
Report what you did not establish beside what you did. A triage whose reading
stopped at the code path says so, because the next person's work is exactly the
part that was left.
**The verdict is markdown the reader can copy, and the answer is where it
goes.** It is written for the person who will act on it, 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 the reproduction ran in, whose tree this
workflow leaves as it found it.
## What a previous attempt cost
Where the issue was fixed once and the fix taken back out, the verdict is not
the answer somebody is waiting for. "Still happens" and "was tried and reverted"
are the same verdict and opposite propositions, and what separates them is why
it was reverted and whether that reason still holds.
The trigger is in the issue answer rather than in the reading. A relation marked
`precedes` or `duplicates` carries its subject, and `reviews` names every change
the journal mentions — so an issue whose history is a merged-then-reverted fix
says so before the checkout is opened.
6. Read the related issue the revert was filed under. The reason lives there and
nowhere else: the reverting commit says what was reverted, and the issue says
what it cost.
7. Read the attempt itself, which is the one thing no lookup here returns. The
issue answer carries the change numbers and the Gerrit search by issue number
their state, so what is left is the diff: fetch the patch set into the
checkout and read it. The ref it is under, and the remote it is on rather
than the one a core checkout fetches from, are two sections of one page, so
where the fetch is the task read it whole: `typo3_rule_lookup` with
`documentId="core/contribution/gerrit-workflow"`, which also stands as
`typo3://guides/core/contribution/gerrit-workflow`. Abandoned is a verdict
somebody wrote down, and the diff under it is the cheapest description of
what a fix looks like against a modern core. The patch set is read and goes
onto no branch: rebasing an abandoned attempt is writing the patch rather
than judging the issue.
`typo3_gerrit_lookup` with a change number earns a call of its own for a
`reviews` entry the search by issue number did not answer with. That search
finds a change whose commit message names the issue, so an entry missing from
it is the one whose branch, patch set and status nothing else has stated.
8. Find every production caller of the method the reverted patch touched, in the
checkout. A fix that scopes to one call site is a different proposition from
one that changes a path everything else goes through.
9. Establish whether the path named in the revert still routes through that
method. A subsystem rebuilt since is what turns the old objection into
history.
**A reverted core fix becomes re-attemptable when the shared consumer that made
it expensive has been rebuilt, or when the caller set has shrunk to the one site
the fix needs.** That is the form that transfers; neither half is readable off
the issue, and both are one grep and one file in the checkout.
What comes out of this is what a maintainer would need before the issue can
move, which is what the answer owes. It is not a design and not a patch: naming
the constraint the last attempt broke is the deliverable, and proposing the code
that respects it is the next workflow's.
## Where the triage ends and the patch begins
**When the verdict is that it still happens and you are asked to fix it, invoke
`typo3-core-patch-development` before making the change.** That is a step, not a
note about ownership: load the skill by name and work from it. What crosses over
is the issue number, the verdict, the code path that was established and the
failing test where there is one.
The reason it is written as a step is that it did not fire as anything else. A
session read this paragraph, held exactly that handoff, was asked for the patch,
and wrote it over forty more turns without ever opening the skill — deciding for
itself the changelog obligation, which suites to run and on which databases, the
commit trailers and the release branches. Nothing it decided was reported wrong;
all of it was reconstructed.
This skill owns saying what is still true about an issue: choosing it out of the
backlog, reading the report against the branch, reproducing it or failing to,
and the verdict that comes out. It stops at the tracker — nothing here comments,
assigns, closes or reopens anything, and the verdict is written for the person
who will. Judging a patch somebody pushed is `typo3-core-patch-review`, which
reads the diff rather than the report.
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.
The verdicts, and what each one owes#
# The verdicts, and what each one owes
One issue gets one verdict. They are not degrees of confidence in a single
finding: each names a different thing that is true, and each is owed different
evidence. Pick the verdict first and the missing evidence names itself.
The seventh is asked before the other six, of every finding rather than of the
ones that look alarming, because it decides where the answer goes rather than
what it says: is what was established a security defect?
## Still happens
The behaviour the report describes is what the branch does today.
Owes: the code path, at its file and line, or the steps that reproduced it and
what was seen. A failing test where a layer can hold it. The branch the
reproduction ran on.
Not enough on its own: that the code looks like it would still do this. Reading
a method is evidence about the method and the report is usually about the
interaction of two.
## Gone
The behaviour cannot happen any more, and the reason is in the checkout.
Owes: what changed, named — the method that no longer exists, the branch that is
no longer taken, the entry that says it was reworked. A reproduction that came
out clean is not this verdict; it is the one below.
The trap: fixed by accident is as final as fixed on purpose, but only where the
mechanism is named. "It works for me now" is not a mechanism.
## Not reproducible as written
The steps in the report do not produce what it describes, and nothing says
whether that is the report or the branch.
Owes: what was tried, what happened instead, and which of the two it is — the
steps were incomplete, the environment differs, or the report never carried
enough to try. Say which parts of the report were verifiable and which were not
tested at all.
This is the verdict that most often gets written as "gone". They are opposite
outcomes: one closes the issue, the other asks the reporter a question.
## Superseded
Something else already covers this — a patch under review, a merged change, a
duplicate, a rework that made the request moot.
Owes: the change or issue number, its state, and whether it does the same work.
An alternative closes an issue only where what it drops is nothing the reporter
was reaching for. Name the arguments and the behaviour the original had and the
replacement does not.
## Not a defect
The branch behaves as the project intends. What the report wants is a change of
intent.
Owes: where the intent is stated — the documentation, the docblock, the test
that pins the behaviour, the changelog entry that introduced it. A verdict of
"works as designed" with no source is an opinion in a maintainer's voice.
This does not close the need. Say what it would take as a feature, and that the
argument for it is a different one: the argument that carries a bugfix is the
same inconsistency inside one version, and finding the place where the system
already does the right thing is what turns a wish into a defect.
## Cannot be settled here
The question is real and this checkout cannot answer it.
Owes: what specifically is missing — a running installation, a database that
behaves differently, a browser, the reporter's configuration, a version no
longer covered. And what would settle it, so the next person starts where this
stopped.
Legitimate and underused. It is the honest end of a triage whose reading ran out
before the evidence did.
## A security defect
What the report describes, or what the reading turned up beside it, is something
an attacker can use: access to a record the user may not read, a value that
reaches a sink unescaped, a check that can be walked around.
Owes: nothing to the tracker. This verdict is about where the answer goes, so
what it owes is the report the security team receives, and the skill's own step
says what that is and which lookup carries the address.
Whichever of the six is also true stays true and is written for that report
rather than for the issue. A defect that still happens and is exploitable is not
a "still happens" with a note attached: the note is the whole difference in who
may read the answer.
The trap: waiting to be sure. A finding that might be exploitable is one the
team rates, and the cost of asking them is an email, while the cost of deciding
it here and being wrong is a public exploit against installations with no fix
available.
# Before writing any of them
- Which of the report's three claims was verified: what was seen, what was
believed to cause it, or what was wanted. Only the first is what a checkout
settles, and verifying the cause and reporting the issue invalid is the
standard failure of this work.
- Which branch the verification ran on, said out loud. A verdict with no branch
is unrepeatable.
- Where the suites ran. Inside the checkout's DDEV project or in your own shell
are two different PHP versions and two different databases, and an old report
about behaviour that depends on either is settled by neither if this is left
unsaid.
- Whether a suite that reported success inspected anything. A green over no
files is what turns "not reproducible as written" into "gone" without anybody
noticing.
- Whether the review server was asked. The cheapest outcome sits there and it
costs one call.
- What was not established. The part that was skipped is the next person's whole
task, and a verdict that reads as complete hides it.
- Whether anything here is a recommendation to close, reassign or reopen. That
is the maintainer's act; the triage supplies what it rests on and stops.
markdown
# The verdicts, and what each one owes
One issue gets one verdict. They are not degrees of confidence in a single
finding: each names a different thing that is true, and each is owed different
evidence. Pick the verdict first and the missing evidence names itself.
The seventh is asked before the other six, of every finding rather than of the
ones that look alarming, because it decides where the answer goes rather than
what it says: is what was established a security defect?
## Still happens
The behaviour the report describes is what the branch does today.
Owes: the code path, at its file and line, or the steps that reproduced it and
what was seen. A failing test where a layer can hold it. The branch the
reproduction ran on.
Not enough on its own: that the code looks like it would still do this. Reading
a method is evidence about the method and the report is usually about the
interaction of two.
## Gone
The behaviour cannot happen any more, and the reason is in the checkout.
Owes: what changed, named — the method that no longer exists, the branch that is
no longer taken, the entry that says it was reworked. A reproduction that came
out clean is not this verdict; it is the one below.
The trap: fixed by accident is as final as fixed on purpose, but only where the
mechanism is named. "It works for me now" is not a mechanism.
## Not reproducible as written
The steps in the report do not produce what it describes, and nothing says
whether that is the report or the branch.
Owes: what was tried, what happened instead, and which of the two it is — the
steps were incomplete, the environment differs, or the report never carried
enough to try. Say which parts of the report were verifiable and which were not
tested at all.
This is the verdict that most often gets written as "gone". They are opposite
outcomes: one closes the issue, the other asks the reporter a question.
## Superseded
Something else already covers this — a patch under review, a merged change, a
duplicate, a rework that made the request moot.
Owes: the change or issue number, its state, and whether it does the same work.
An alternative closes an issue only where what it drops is nothing the reporter
was reaching for. Name the arguments and the behaviour the original had and the
replacement does not.
## Not a defect
The branch behaves as the project intends. What the report wants is a change of
intent.
Owes: where the intent is stated — the documentation, the docblock, the test
that pins the behaviour, the changelog entry that introduced it. A verdict of
"works as designed" with no source is an opinion in a maintainer's voice.
This does not close the need. Say what it would take as a feature, and that the
argument for it is a different one: the argument that carries a bugfix is the
same inconsistency inside one version, and finding the place where the system
already does the right thing is what turns a wish into a defect.
## Cannot be settled here
The question is real and this checkout cannot answer it.
Owes: what specifically is missing — a running installation, a database that
behaves differently, a browser, the reporter's configuration, a version no
longer covered. And what would settle it, so the next person starts where this
stopped.
Legitimate and underused. It is the honest end of a triage whose reading ran out
before the evidence did.
## A security defect
What the report describes, or what the reading turned up beside it, is something
an attacker can use: access to a record the user may not read, a value that
reaches a sink unescaped, a check that can be walked around.
Owes: nothing to the tracker. This verdict is about where the answer goes, so
what it owes is the report the security team receives, and the skill's own step
says what that is and which lookup carries the address.
Whichever of the six is also true stays true and is written for that report
rather than for the issue. A defect that still happens and is exploitable is not
a "still happens" with a note attached: the note is the whole difference in who
may read the answer.
The trap: waiting to be sure. A finding that might be exploitable is one the
team rates, and the cost of asking them is an email, while the cost of deciding
it here and being wrong is a public exploit against installations with no fix
available.
# Before writing any of them
- Which of the report's three claims was verified: what was seen, what was
believed to cause it, or what was wanted. Only the first is what a checkout
settles, and verifying the cause and reporting the issue invalid is the
standard failure of this work.
- Which branch the verification ran on, said out loud. A verdict with no branch
is unrepeatable.
- Where the suites ran. Inside the checkout's DDEV project or in your own shell
are two different PHP versions and two different databases, and an old report
about behaviour that depends on either is settled by neither if this is left
unsaid.
- Whether a suite that reported success inspected anything. A green over no
files is what turns "not reproducible as written" into "gone" without anybody
noticing.
- Whether the review server was asked. The cheapest outcome sits there and it
costs one call.
- What was not established. The part that was skipped is the next person's whole
task, and a verdict that reads as complete hides it.
- Whether anything here is a recommendation to close, reassign or reopen. That
is the maintainer's act; the triage supplies what it rests on and stops.