BRB register
BRB-NNN findings are raised and prioritised in the maintainer's vault
(07-backlog/Backlog de Melhorias (Brabo)), which holds all 33 of them with
priority, effort, origin, proposal and a "done when". That register is the
source for what a finding is and for whether it is closed.
This file is the part the repository can verify. Created by FASE 29, session 1 (fase-29-instalacao-de-uma-linha.md).
Coverage criterion
Every id from BRB-001 to BRB-033 has exactly one row, in numeric order.
Until 2026-09-27 the page listed only the findings the code could prove or
disprove — 11 of 33 — and the rest were invisible here, including six the
vault had since measured as fixed (AT-186). A row per id costs nothing and
makes the absence of evidence visible instead of silent. Each row carries:
- vault state — copied from the vault register as of 2026-09-27. It is the maintainer's call and is never decided here.
- measured here — what the repository shows, measured against
devat34c1c83bdcon 2026-09-27 unless the evidence dates it otherwise. - evidence — the
file:line, PR or run behind that measurement.
A finding whose subject lives outside this repository (the vault's own notes, a
product decision, a paid credential) still gets a row, marked
not verifiable here, with the reason. An id with no information at all
would carry a TODO(humano) — none does today. A new BRB-NNN in the vault
gets its row here in the same change that first mentions it.
Why this file exists
BRB-018 is what the second rule below looks like in practice. When this file
was born nothing about it had changed in the code — no reprojection was written
— but the repository gained the ability to say why it was still open and
what it cost while it was, so the row existed before the fix did. The command
now exists (RN-569); the row stays because the cluster proof does not.
Two ids — BRB-005 and BRB-031 — were referenced across five documents here,
in prose, with no state anyone could check. The other ids were invisible to
this repository entirely: a full-disk search returned only those two.
The cost was measured, not imagined. Planning FASE 29 listed six BRB items as
prerequisites or risks; checking each against dev found that two describe
defects already fixed here, and that a third had been misread from its id
alone — see BRB-004 below, which is not about the tag anyone assumes.
Two rules follow:
- State is backed by
file:line, measured, never remembered. - The vault owns the finding; the repository owns the evidence. Where the two disagree about whether something still happens, the repository wins — it is the thing being described. Whether an item is formally closed stays the maintainer's call, in the vault.
The register
| id | title (from the vault) | prio | vault state | measured here | evidence |
|---|---|---|---|---|---|
| BRB-001 | Cap self-downgrade through the REMOVAL of a project membership | P1 | closed 2026-09-12 (PR #531) | defect no longer reproduces | RemoveProjectMemberUseCase.execute took (projectId, userId) — no actor — and the controller called it with the two route params, so a maintainer whose authority came from the project row (viewer in the workspace) could remove their own row and drop to viewer irreversibly; the case was pinned in a test whose own name said it was open. ADR 0127 had declared the gap in writing, cost estimate included. Closed by ADR 0156 / RN-556: the actor now reaches the use case, remocaoEhAutoRebaixamento delegates to the existing cap-2 rule with the workspace role as the requested one, and the lacuna test was INVERTED rather than deleted. The workspace-owner cap deliberately gets no counterpart — removal can only raise an owner |
| BRB-002 | Cap self-movement on the WORKSPACE member upsert | P2 | closed 2026-09-12 (PR #533) | defect no longer reproduces | AddWorkspaceMemberUseCase.execute took (workspaceId, userId, role) and called workspaces.addMember — no actor — while the controller (@Post(':workspaceId/members'), @RequireRole('owner')) passed the raw body with no @CurrentUser(). An owner could write themselves viewer and lose the whole workspace: nothing above catches the fall, and WorkspacesController has no member @Delete (measured), so undoing is that same route, demanding the owner just abandoned. ADR 0127 named it in writing ("same class of defect, one scope up") and ADR 0156 left it as a separate decision. Closed by ADR 0157 / RN-557: the actor reaches the use case and changing your own role is 403 in both directions. The cap does not count owners (the clause already guarantees a workspace never reaches zero, since removing the last owner would take that owner), and cap 1 gets no counterpart here — @RequireRole('owner') already forbids the hierarchy inversion it exists to stop, and adding it would make owner an absorbing state with no HTTP exit. The same ADR closes self-promotion on both association routes, revising ADR 0127's Consequences: it is the only half of the movement that escalates privilege |
| BRB-003 | Fix the three OpenAPI descriptions that promise "the higher of the two" (RN-471) | P2 | closed 2026-09-27 (AT-136) | defect no longer reproduces | grep -rn "higher of the two" apps/api/src is empty. apps/api/src/interfaces/http/iam/dto/add-member.dto.ts:29 and projects.controller.ts:190,239 say the project role OVERRIDES the workspace one in both directions — the text arrived with ADR 0127 / RN-472 (PR #445). First measured 2026-09-11 (AT-017) |
| BRB-004 | Pin third-party images (neo4j, pgvector, ollama) by digest | P2 | open | defect no longer reproduces | Was: every third-party image pinned by tag, not digest. Tag pinning had closed a different gap (PRs #401/#419) and this row read as if that were the fix — the reason the id/description distinction below earns its keep. Closed by ADR 0159: all 37 third-party references go by index digest with the tag in a trailing comment, and scripts/ci/imagens-pinadas.ts fails the lint job on a mutable reference, on a digest with no tag comment, and on the same tag carrying two different digests. The count is 37 and not the finding's own list, because three whole places were outside it: the Dockerfile FROM lines (the base of the four images we publish and sign), and the services: images of ci.yml/golden-set-rag.yml — the CI runner the sibling rule already protects, reached by the other door. The five images the product publishes are deliberately not covered (four when the row was written; the fifth, the broker, came with ADR 0162): no third party can move them, brabo-api:prod is a local tag whose digest does not exist before the build, and where they cross a registry ADR 0119 already resolves them — the overlay holds a marker on purpose, so a literal there would fail the correct state. Declared and not fixed, because it is the price of the pin: a digest gets no security update until someone bumps it by hand, and Dependabot's docker ecosystem stays off — a separate decision. Also uncovered, and stated: otel-collector-values.yaml, where image: opens a map whose tag the Helm chart chooses |
| BRB-005 | Sign the GHCR images and the runner binaries | P2 | open | implemented, not yet exercised | Was: no cosign/sigstore/attestation anywhere, release.yml without id-token: write, no checksums.txt. Now: release.yml signs the five images by digest (four when written; ADR 0162 added the broker) and verifies them in the same run; build-runner-binaries.yml gained a checksums job publishing one signed manifest for the five targets (ADR 0149, RN-524); verification procedure in the runbook. Not exercised: signing only runs on a real final tag, so nothing here proves it end to end yet. Still uncovered, and a separate item: OS code-signing of the binaries (notarization/Authenticode). Session 3 made the download proxy GET /runner-releases/binary verify — but only the sha256 against the release's checksums.txt, never the manifest's signature (RN-525): that is integrity, not provenance, and whoever can rewrite the Release rewrites both files. Both ways for the api to check the signature were measured and refused: cosign in the image is 155 MB (nearly doubling an Alpine+Node runtime image), and @sigstore/verify+@sigstore/tuf is cheap (2.5 MB) but makes a @Public() route depend on a second third-party host (tuf-repo-cdn.sigstore.dev) to verify something no Release carries yet — unprovable, and ADRs 0041/0042 forbid declaring an unproven capability. The signature consumer is install.sh (session 4/7), which has a real pinned cosign. So the provenance half of the proxy stays open, and refusal is already named there: a Release without checksums.txt is refused, not served with a warning |
| BRB-006 | Automate updating the pinned SHAs (Dependabot/Renovate) | P3 | open | defect no longer reproduces | .github/dependabot.yml:35 declares the github-actions ecosystem (and npm, :55/:61), all with target-branch: dev; .github/workflows/dependabot-para-dev.yml routes security updates to dev, and scripts/ci/dependabot-justifica-pin.ts (AT-094) writes the docs-not-needed: line on a pin-only bump. Not measured here: the "done when" asks for a real bump PR passing actions-pinadas.ts with no manual step — no PR was listed for this row |
| BRB-007 | Run the Automation-QA golden set in CI | P2 | delivered 2026-09-27 (PR #687, #689), not marked closed | defect no longer reproduces | .github/workflows/golden-set-qa.yml:39 runs it weekly (cron: '17 1 * * 6') plus workflow_dispatch, never on pull_request (ADR 0168). The instrument measures again under RN-502: the seed brings the container up through container_start (AT-076), and the proof runs scored 3/6 against a floor of 1/6. The floor in apps/engine/test/fixtures/golden_set_qa/floor.json is still the 2026-08-30 one — only a human writes it |
| BRB-008 | Change (or justify) the golden-set seed's default model | P2 | open | still reproduces | apps/api/scripts/seed-golden-set-qa.ts:119 still defaults to qwen2.5-coder:latest, whose floor in apps/engine/test/fixtures/golden_set_qa/floor.json is 1/6 (against 4/6 for gpt-oss:20b). No decision recorded |
| BRB-009 | Schedule the three property proofs (test-restore, rollout-test, hpa-test) | P2 | open | open — scheduled now; the mutation of the restore is caught, the last good run is a workflow artifact and not yet a metric | Was: the three targets (Makefile:41-48) ran only when someone typed make, and no workflow brought up a cluster (grep -rn "k3d|kind" .github/workflows/ was empty). Now .github/workflows/propriedades.yml runs weekly and on workflow_dispatch: it installs k3d/helm/kubectl by pinned checksum, runs the same deploy/k8s/bootstrap.sh a human runs, then make smoke-k8s, make hpa-test, make rollout-test and make test-restore, each timed into the run summary; a failing target opens an issue titled after it, and a repeat failure comments on the open one instead of opening another. It is not a gate and not a required check. Its first run already paid for itself: the bootstrap died creating the CNPG cluster, because the digest-only imageName ADR 0159 wrote is refused by the operator's webhook — kubeconform runs no webhook, so the manifests job had stayed green. Nine more followed, all fixed in the same change (listed in the runbook), one of them outside the local cluster: brabo-restore died on COMMENT ON EXTENSION vector on any Postgres where the app role is not a superuser. The first fully green run is 34784563928 (2026-09-13, 12 min 56 s), green again on a second attempt. The rollout proof reported an orphan session in one of four runs that reached it, and that is declared, not fixed. AT-126 closed the first of the two remaining clauses: make test-restore-mutacao breaks the restore on purpose (a table in the source that the dump does not have) and the scheduled run fails, and opens its own issue, if the restore does not reject it naming that table; a run in which the restore was deliberately blinded failed on that step. Still open, and not this repository's call to close from CI: the last successful run is now dated by the workflow (the restore-ultima-execucao-boa artifact, written only when the restore and the deliberate break both passed) but is not a metric next to the backup alerts nor a backupRuns row — that needs code in apps/api. Measured durations and cadence: runbook, Scheduled property proofs |
| BRB-010 | Exercise the CREDENTIALS_MASTER_KEY rotation with a script and a test | P2 | closed 2026-09-27 (AT-136) | defect no longer reproduces | Closed by PR #551, ADR 0158 / RN-562: apps/api/src/scripts/rewrap-deks.ts exports the core (reenvelopar) and apps/api/test/scripts/rewrap-deks.spec.ts is the named verification, against a real Postgres and both envelope tables; the runbook runs it (docs/runbook.md:2770). The command name differs from the finding's proposal (credenciais:reenvelopar); the "done when" — a named verification — is met. Since AT-146 the rotation also has a cluster rehearsal: it had never run in a real environment (maintainer, 2026-09-27), and make test-rotacao-chave-mestra-k8s runs the runbook's three steps weekly in propriedades.yml — the ExternalSecret, an api restart and the image's rewrap-deks.js included (first green run 36502355798) |
| BRB-011 | Cover the single point of failure of alerting (Grafana without Alertmanager) | P2 | open | still reproduces | deploy/k8s/observability/alerts/brabo-alerts.yaml:8 still says there is no Alertmanager and no receiver; nothing in deploy/k8s warns when Grafana itself stops evaluating (measured by AT-121, 2026-09-19) |
| BRB-012 | Prove the LLM providers never exercised against a real credential | P3 | open | not verifiable here | The run needs paid keys this repository does not hold. What the repository records: the chat table in docs/explanation/aceite-providers.md:108-115 (state as of 2026-08-07) has OpenRouter run for real and Together, DeepInfra, NVIDIA NIM, Bitdeer and Vultr skipped for a missing *_TEST_KEY; the embeddings table above it (:83-93) proves only Ollama |
| BRB-013 | Clean up the leftovers of the rc step (enum and protected-branch list) | P3 | open | partly reproduces — the repository half is a decision | apps/api/src/domain/actions/protected-branches.ts:12 keeps rc and apps/api/src/db/schema/git.ts:24 keeps create_rc_branch — on purpose: the list decides what the merge cap refuses, and repositories bootstrapped by older versions still have the branch (CLAUDE.md, Conventions). The other half — two vault notes still listing rc as protected — lives in the vault |
| BRB-014 | Migrate the rest of api-types.ts to generated types | P3 | open | still reproduces | apps/web/src/lib/api-types.ts is 1,852 lines with 185 exports (the finding said ~1,600), next to the generated api-types.generated.ts |
| BRB-015 | Keep decomposing SessionPage.tsx | P3 | open | defect no longer reproduces | apps/web/src/routes/SessionPage.tsx is 834 lines (was 2,637 on dev at 018b8cd24c; the finding said 2,479, under pages/), after the ten mechanical PRs of ADR 0176 (AT-138). The "done when" is under 1,000, and it is now locked: apps/web/src/routes/SessionPage.teto.test.ts fails the file at 1,000 lines or more, proven by mutation. Raising the ceiling takes a new ADR |
| BRB-016 | Record the key id in the wrapped_dek to simplify rotation | P3 | closed 2026-09-27 (AT-136) | defect no longer reproduces | Closed by PR #551, ADR 0158 / RN-563: key_id (the master key's fingerprint) in apps/api/src/db/schema/llm.ts:275 and git.ts:76, so "how many credentials are still on the previous key?" is one query (IS DISTINCT FROM, counting unlabelled rows as pending). The label never decides whether an envelope opens — GCM does |
| BRB-017 | Meet the GPL obligation of the tools baked into the published engine image | P1 | open | defect no longer reproduces | Was: nothing under docker/engine/ mentioned licences, an obligation that became real when ADR 0119 started publishing to GHCR on every final tag. The offer existed and was good — THIRD_PARTY_NOTICES.md at the root, naming each component, its exact version and its licence — it just did not travel. It does now: docker/engine/Dockerfile.prod:190-192 copies it to /usr/share/doc/brabo/, before the USER and as root, so it ships inside every published engine image (PR #532). Re-measured 2026-10-01 (AT-120): by the maintainer's decision the same COPY, in the same form, is in the four other production Dockerfiles (api, web, broker, backup), and the runner Release carries the file as an asset inside the signed checksums.txt — no artifact is declared exempt. Locked by scripts/ci/oferta-de-fonte-na-imagem.spec.ts (each Dockerfile's final stage, the Release asset, mutation cases), and the ci.yml image job reads the file back out of each of the five images it builds. Found on the way: the engine copy was unreadable by the image's non-root user (BuildKit applied --chmod=0644 to the directory it created), so the documented docker run … cat check failed with "Permission denied"; the directory is now created 0755 first. Two things stay open, and neither is a defect: the sidecar (path 3) is still an unmade architecture choice, and whether the obligation is met is a legal judgement this repository cannot assert — it can only say the image carries the offer. Found on the way: the document asserted the opposite of what the repository does ("no image is published today", superseded by ADR 0119), and its TODO(humano) to decide "before the first push" expired unread |
| BRB-018 | Write and test the reconstruction of the Neo4j projection from the event log | P2 | open | implemented and proven on the local cluster (scheduled); no large-log time measurement | Was: no reprojection command anywhere (grep reprojet/reproject/graph:repro across apps/ and scripts/ returned nothing), while ADR 0152 decision 4 had already refused to back up neo4j_data on the grounds that reprojection is the answer. Now: apps/api/src/scripts/reprojetar-grafo.ts (pnpm --filter api grafo:reprojetar, node scripts/reprojetar-grafo.js in the image) walks session_events and the closed sessions by cursor, in batches, and hands each source to GraphEventTranslator — the same translation the forward GraphProjector now calls, extracted from it rather than written twice (RN-569). Idempotent (MERGE, never deletes), whole log or --project, does not touch the outbox the live projector keeps its progress in, and fails named when Neo4j is down or the project does not exist. The procedure is in the runbook (Losing the graph). The proof is make test-reprojecao (apps/api/test/scripts/reprojetar-grafo.spec.ts): forward-project a scenario, wipe its subgraph, reproject, compare node and edge counts and the key list, reproject again — green against the development compose's Neo4j, and it skips where there is no Neo4j, which includes the api CI job. The cluster proof (AT-127): make test-reprojecao-k8s (deploy/k8s/test-reprojecao.sh) is the fourth target of propriedades.yml — on a project it creates (a closed session, two events) it runs node scripts/reprojetar-grafo.js --project inside the api pod, wipes that subgraph in the cluster's Neo4j, reprojects and requires the same counts (5 nodes, 4 edges), then reprojects again; green in run 35471428634 in 16 s, after two runs that failed in the script itself (35469877869, 35470685598). Since AT-191 the scenario has four events — a Psychologist hypothesis and an Anamnesis profile besides the message and the handoff — so it exercises every path of the translator, and the counts are 9 nodes and 6 edges (run 36498776825, 18 s). It does not depend on test-restore or on the other targets' state. Still not closed: there is no time measurement on a large event log (the only real run is the dev compose, 98 events, 2.5 s), and PromptTemplate/PromptVersion are not in the event log — they come back through scripts/dev/seed-prompts.ts |
| BRB-019 | Stop running api and web as root in development | P3 | closed 2026-08-28 (PR #406) | defect no longer reproduces | docker/api/Dockerfile:52 and docker/web/Dockerfile:23 run as USER ${DEV_UID}:${DEV_GID} (PR #406, closed before the finding was raised) |
| BRB-020 | Give every alert in brabo-alerts.yaml a severity | P3 | open | defect no longer reproduces | All five alerts carry severity: (deploy/k8s/observability/alerts/brabo-alerts.yaml:29,59,87,123,160). Not measured here: the "done when" also asks the operational dashboard to filter by it |
| BRB-021 | Fill the runbook gaps: rollout and cost incident | P3 | open | defect no longer reproduces | docs/runbook.md:1671 ## Engine rollout {#rollout-do-engine} and :2899 ## Cost incident {#incidente-de-custo}, both reached from the triage table (:21, :30) |
| BRB-022 | Fix the prose counts the repository already knew were wrong | P2 | closed 2026-09-27 (AT-136) | defect no longer reproduces | Closed by PR #530: CONTRIBUTING.md:11 (ADR count) is checked by pnpm docs:check, and the glossary's artifact-schema count is derived from Engine.Harness.ArtifactSchemas (scripts/docs/generate.mjs:623-624), exercised by mutation in both directions |
| BRB-023 | Update the vault: ADR 0127, RN-471/472 and the diverging ADR count | P2 | open | not verifiable here | The finding is about the vault's own notes. The repository can only give the numbers the vault should copy — docs/adr/ and the RN files are the source |
| BRB-024 | Remove references to folders and paths that no longer exist | P3 | open | repository half no longer reproduces; vault half not verifiable here | README.md:296 and docs/getting-started.md:274 say apps/api/src/db/schema/<aggregate>.ts, and both phrases are anchored to the code by verificarFrasesAncoradasNoCodigo; design_handoff_brabo/ appears only in historical prose. The notes the finding names (UX Designer, Local Development, Commands) live in the vault |
| BRB-025 | Include apps/api/scripts/ and e2e/ in the generated environment inventory | P3 | open | defect no longer reproduces | scripts/docs/fontes-de-env.mjs:49-52 scans both, and a new process.env.X there fails docs:check (AT-124, PR #638, measured by mutation). The half that stayed open — after docs:generate the ⚠️ did not fail, and HUGGINGFACE_API_TOKEN/HUGGINGFACE_HUB_URL were committed that way — closed in AT-211 (2026-09-27): docs:check fails on any ⚠️ in the environment inventory, both source kinds (scripts/docs/inventario-de-env.mjs, proven by mutation in inventario-de-env.spec.ts), and the two variables are described in configuration.md |
| BRB-026 | Reorganise docs/adr/index.md — everything since 0044 sits under "Phase 12" | P3 | open | still reproduces | docs/adr/index.md:143 ## Phase 12 — Post-dogfooding operability still holds every later ADR, and the index has no generated block |
| BRB-027 | Record the methodology behind the "Scale" table | P3 | open | still reproduces | There is no scripts/docs/escala.mjs and docs/architecture.md has no generated scale block; the table itself lives in the vault |
| BRB-028 | Give the appsec moment (threat_model) a trigger | P2 | closed 2026-09-27 (AT-136) | defect no longer reproduces | Was: SecOpsAgentServer.run_design/2 had no production caller, and docs/fluxo.yml named assess_implementability as the natural trigger while declaring it out of scope. Merged into dev by PR #514 (RN-539 — renumbered from 522, already taken two days earlier): dev_lead_tools.ex:333-338 fires it from assess_implementability, exactly where the gap said it belonged, through Dispatcher.run_appsec_design/2 (dispatcher.ex:112); docs/fluxo.yml:188-196 now reads status: active with no gap declared. The trigger is guarded by idempotence and that is not zeal: the :sem_plano outcome asks the model to call assess_implementability again, and each re-call would otherwise spend another appsec LLM round and three more handoffs on the same story. One limit stays declared in the code: artifact.threat_model is emitted in the STORY session while the guard reads the DEV LEAD session — the same session in the common path, and a repeated LLM round, never wrong data, when they differ |
| BRB-029 | Decide the fate of the Psychologist and Anamnesis (paused since 2026-08-10) | P2 | open | not verifiable here — product decision | The pause is declared as an open product decision in CLAUDE.md and became reversible with RN-540; no ADR records keeping it off for a set period |
| BRB-030 | Reconcile the duplicated tab navigation (rail × sidebar) | P3 | open | not verifiable here — product decision | CLAUDE.md lists the duplication (LinhaDeAba, RN-196) as an accepted gap and a separate product decision, not taken in ADR 0126 |
| BRB-031 | Remove the manual chmod +x from browser-driven runner setup | P3 | closed 2026-10-01 (AT-014) | defect no longer reproduces | Was: the File System Access API does not preserve the execute bit (docs/adr/0118-configuracao-automatica-do-runner-pelo-navegador.md:134-140), so the browser flow of ADR 0118 ended in a manual chmod +x. Half of it closed first by another path: install.sh places the binary with install -m 0755 (RN-531) and pairs the machine from the terminal (RN-547). The other half closed by the owner's decision (01/10) to retire the browser flow: ADR 0203 / RN-687 removed apps/web/src/lib/runner-bootstrap.ts and POST /projects/:projectId/runner-device-keys, and RunnerOnboardingPanel now sends people to the installer command. grep -rn "chmod +x" apps/web/src finds only the panel docblock telling why the step left. Project keys registered by the browser before that keep working and stay revocable. The e2e download coverage the finding also asked for is moot: there is no browser download left to cover |
| BRB-032 | The broker never came up in the local compose | P1 | closed 2026-09-27 on the measured half only (AT-136) | defect no longer reproduces on two of three halves; the third not exercised | Closed in the vault on the measured half only. Measured: the broker left profiles: — the only profiles in the dev compose are local-llm (docker/docker-compose.yml:86,126); the root cause — corepack as root at build with no registry at runtime — was fixed by preparing COREPACK_HOME at build and adding a healthcheck (PR #503, RN-512), without giving the broker network; and .env.example no longer contradicts itself (2026-09-11). Still open: "container_start on a container project reaches the daemon" was never exercised — it needs an image decided by the Architect (RN-105), which no measured instance had |
| BRB-033 | reset-total.sh tore down the engine and the seed died with it | P2 | closed 2026-09-27 (AT-136) | defect no longer reproduces | 718b859e2 — "o reset total sobrevive a si mesmo e para de mentir no fim", merged into dev via c230e91af (PR #507). The script now probes /health of the three services and any failure exits naming the step |
On 2026-09-27 the vault records 10 closed (BRB-032 only on its measured
half), 1 delivered and not yet marked closed (BRB-007) and 22 open.
An open vault state next to "defect no longer reproduces" is not a
contradiction: it is a closure the maintainer has not made yet.
What the words in "measured here" mean
They are deliberately different words. Fixed would be the maintainer's judgement, recorded in the vault. Defect no longer reproduces is all this repository can honestly assert: the behaviour the finding describes is not what the code does today, measured at the date of the row. Implemented, not yet exercised is weaker: the code exists and the tests that can run do pass, but the path only executes somewhere this repository cannot reach — a real release tag, in CI. Calling that "fixed" would be claiming a proof nobody has. Still reproduces and partly reproduces say the finding's behaviour is still there, in whole or in part. Not verifiable here says the finding is about something this repository does not hold. Closing the item — and deciding whether the fix covers everything the finding asked for — stays where the finding was raised.
BRB-004 is the reason the distinction earns its keep. Read from its id, it
looked like the ollama:latest pin that closed in #419. Read from the vault, it
asked for digests, which no image had. An id is not a description — and the
misreading is what kept it open while a sibling rule for uses: had had a
mechanism since #419.
Changing a row
The id, title and priority are copied from the vault verbatim, and so is the vault state; the "measured here" state and the evidence are measured here. A row changes when the vault changes its state or when a change in this repository changes what can be proved — never to invent a finding.