Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .markdownlint.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"MD010": { "code_blocks": false },
"MD013": false,
"MD024": { "siblings_only": true },
"MD025": { "front_matter_title": "" },
"MD033": false,
"MD036": false,
"MD040": false,
Expand Down
15 changes: 14 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,8 @@ The full specification is in [`docs/specs/01_spec_cpp_memory_pool.md`](docs/spec
│ ├── adr/ # Architecture Decision Records
│ ├── patterns/ # design-patterns catalogue
│ ├── specs/ # functional/technical specifications
│ └── workflow/ # git & documentation conventions
│ ├── workflow/ # git & documentation conventions
│ └── bugs/ # in-repo bug ledger (ADR-0039)
└── (build/, CMakeLists.txt, etc.) # created in Milestone 1
```

Expand Down Expand Up @@ -250,6 +251,18 @@ At the **close of a work session that changed the project's state**, the agent:

The journal is documentation that ships with the work, like any other doc in this section — not a separate bookkeeping PR.

### 7.7 Bug ledger & triage protocol

Known defects and the triage of incoming reports live in [`docs/bugs/`](docs/bugs/) — one Markdown file per defect, named `BUG-NNNN-<slug>.md` under a discovery-date tree `docs/bugs/<YYYY>/<MM>/`, indexed by [`docs/bugs/README.md`](docs/bugs/README.md) ([ADR-0039](docs/adr/0039-bug-ledger-and-triage-protocol.md)). The ledger is the **source of truth** for defects (a GitHub issue, if any, is referenced, not authoritative); it holds the **open/in-flight/triaged** side, while the **closing** side — what shipped in which release — is the `CHANGELOG` `Fixed` line (§11). `NNNN` is a globally-monotonic id, never reused or renumbered (like an ADR number).

The agent's obligations:

1. **When asked to hunt for / find bugs**, create a ledger file *only* for a **verified, reproducible** defect — never a speculative one. Start from [`docs/bugs/template.md`](docs/bugs/template.md), fill the frontmatter (`status: confirmed`, `reporter: internal`, `severity`, `discovered`, `affected-versions`), capture the reproduction and root cause, and add the index row — in the same PR as the investigation.
2. **When a third party reports a bug**, **reproduce and root-cause it first.** Only on confirmation create a record (`reporter: third-party`, the reproduction as evidence). A report you **cannot** substantiate is **still recorded** — as `cannot-reproduce` (or `rejected` / `duplicate`) — documenting the investigation that reached that verdict, so the triage trail is preserved. Never transcribe an unconfirmed third-party claim into the ledger as if it were a real defect.
3. **When a fix lands**, flip the record to `status: fixed`, set `fixed-in`, link the fixing PR, and add the `CHANGELOG` `Fixed` line — in the same PR, per the hotfix/PATCH flow in [`docs/workflow/maintenance.md`](docs/workflow/maintenance.md).

This is a judgment task, not an automated trigger: deciding a defect is *real* belongs to the agent. Structural integrity (frontmatter keys, the `status`/`severity`/`reporter` vocabularies, filename↔`id` and path↔`discovered` agreement, monotonic ids, the index bijection, and that a `fixed` record names its `fixed-in`) is enforced by the consistency lint's `bugs` check (§6.4) — run `python tools/consistency_lint.py` before drafting the PR.

## 8. Design Patterns Policy

This is a **reference implementation**, and demonstrating fluency with classical design patterns is part of its value. Therefore:
Expand Down
15 changes: 15 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,21 @@ dated version block (`## [X.Y.Z] — YYYY-MM-DD`) when a release PR closes a mil

### Added

- **In-repo bug ledger (`docs/bugs/`) + agent triage protocol.** Known defects and
the triage of incoming reports now have a durable, reviewable home: one Markdown
record per defect, `BUG-NNNN-<slug>.md` under a discovery-date tree
`docs/bugs/<YYYY>/<MM>/`, with a stable monotonic id, structured frontmatter
(`status`/`severity`/`reporter`/…), an index + template, and a lifecycle
(`open → confirmed → fixed`, plus `wontfix`/`duplicate`/`cannot-reproduce`). The
ledger is the source of truth (a GitHub issue is referenced, not authoritative) and
cross-references the `CHANGELOG` `Fixed` line at close. The agent rule
([`AGENTS.md`](AGENTS.md) §7.7) requires a record only for **verified** defects, and
**verification before acceptance** of third-party reports (unsubstantiated reports
are still recorded as `cannot-reproduce`/`rejected`). A new `bugs` consistency-lint
check guards frontmatter, ids, the index bijection, and the `fixed`↔`fixed-in` link.
Governance in [`docs/workflow/maintenance.md`](docs/workflow/maintenance.md);
rationale in [ADR-0039](docs/adr/0039-bug-ledger-and-triage-protocol.md).
Documentation/process/tooling-only; no API change.
- README gains a **Technology stack** section (language standards, build / test /
docs / tooling, and packaging, with versions — `zero runtime dependencies`) and a
top-of-page **"Read this in: 简体中文 · 日本語"** pointer to the `docs/i18n/`
Expand Down
1 change: 1 addition & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ This directory holds the durable, versioned documentation for `pbr-cpp-memory-po
| `docs/patterns/` | Living catalogue of design patterns adopted, rejected, or under consideration. |
| `docs/workflow/` | Repository workflow conventions (git, documentation maintenance, release). |
| `docs/development/` | Procedural how-to guides for working on the code locally (toolchain, build, debug). |
| `docs/bugs/` | In-repo bug ledger — one record per known defect, with the triage trail (ADR-0039). |

## Reading order for newcomers

Expand Down
66 changes: 66 additions & 0 deletions docs/adr/0039-bug-ledger-and-triage-protocol.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# ADR-0039: In-repo bug ledger and agent triage protocol

- **Status:** Accepted
- **Date:** 2026-06-15
- **Deciders:** Project architect (maintainer), agent
- **Related:** [ADR-0036](0036-session-journal-extraction.md) (dated per-file documentation precedent), [ADR-0034](0034-post-release-maintenance-protocol.md) (maintenance governance), [ADR-0035](0035-agent-runnable-consistency-lint.md) (the lint this extends), [`docs/workflow/maintenance.md`](../workflow/maintenance.md), [`AGENTS.md`](../../AGENTS.md) §7.7, [`docs/bugs/`](../bugs/)

## Context

Entering the maintained-product phase (post-`v1.0.0`), the project tracks two halves of a defect's life but not the whole:

- A **fixed** defect already has a home — the `Fixed` category of the `CHANGELOG` ([ADR-0038](0038-changelog-version-split.md)) plus the hotfix/PATCH flow in [`docs/workflow/maintenance.md`](../workflow/maintenance.md). That records *what was fixed and in which release*.
- A **known-but-open** defect, and the **triage** of an incoming report (reproduction attempt, root-cause analysis, the verdict — including *rejected*), had **no durable record**. It lived only in chat or in a transient issue.

This is a gap for a reference repository whose whole premise is that every important artifact is versioned, reviewable, and readable offline (ADRs, the patterns catalogue, the session journal). A defect's investigation is exactly such an artifact: it has evidence, a root cause, an impact assessment, and a verdict that a future reader should be able to reconstruct without access to the original conversation.

A second, agent-specific force: the maintainer wants two repeatable behaviours from the agent — (1) when asked to *hunt* for bugs, the agent should land each **verified** finding as a durable record rather than a chat message; (2) when a **third party** reports a bug, the agent must **verify it (reproduce + root-cause) before** accepting it, never transcribe an unconfirmed claim into the record. These are judgment-bearing behaviours, so they belong in the agent contract, not in a deterministic hook.

## Decision

We add an **in-repo bug ledger** under [`docs/bugs/`](../bugs/) and codify the agent's bug-handling protocol.

**Storage & identity (the "ID + date-tree mix").** One Markdown file per defect, named `BUG-NNNN-<short-kebab-slug>.md`, stored under a discovery-date folder tree: `docs/bugs/<YYYY>/<MM>/BUG-NNNN-<slug>.md`.

- `NNNN` is a **zero-padded, globally monotonic** id (like ADR numbers — never reused, never renumbered), giving every defect a short stable handle (`BUG-0007`) for cross-references from commits, PRs, and the `CHANGELOG` `Fixed` line.
- The `<YYYY>/<MM>` folders (by **discovery** date) keep any one directory small — the same idiom already used by the session journal ([ADR-0036](0036-session-journal-extraction.md)), so there is no new convention to learn.
- [`docs/bugs/README.md`](../bugs/README.md) is the index (newest first) resolving every id to its path; [`docs/bugs/template.md`](../bugs/template.md) is the per-bug template.

**The ledger is the source of truth.** Bug records live in the repo and are reviewed like any other artifact. A GitHub issue, if one exists, is referenced from the record but is not authoritative — consistent with ADRs and the journal living in-repo, and with the project's offline-readable, self-contained premise.

**Structured frontmatter** carries the queryable state: `id`, `title`, `status`, `severity`, `reporter`, `discovered`, `affected-versions`, and (once closed) `fixed-in`. The lifecycle is `open → confirmed → fixed`, with the terminal states `wontfix`, `duplicate`, and `cannot-reproduce`.

**Agent triage protocol** (codified in [`AGENTS.md`](../../AGENTS.md) §7.7):

- *"find / hunt for bugs"* → the agent creates a ledger file only for a **verified, reproducible** defect — never a speculative one.
- *third-party report* → the agent **reproduces and root-causes first**; only on confirmation does it create a `confirmed` record (with `reporter: third-party` and the reproduction as evidence). A report it **cannot** substantiate is **still recorded** — as `cannot-reproduce` (or `rejected`/`duplicate`) with the investigation that reached that verdict — so the triage trail is preserved rather than lost.
- A fix then flows through the existing maintenance machinery: the fixing PR flips the record to `fixed`, fills `fixed-in`, and adds the `CHANGELOG` `Fixed` line — the ledger and the changelog cross-reference each other.

**Integrity is enforced by the consistency lint** ([ADR-0035](0035-agent-runnable-consistency-lint.md)): a new `bugs` check validates each record's frontmatter (required keys, allowed `status`/`severity`/`reporter` vocabularies), the filename↔`id` agreement, the path↔`discovered`-date agreement, globally-unique non-gapped ids, the index↔files bijection, and that a `fixed` record names its `fixed-in`. With zero records the check is a no-op, so the gate is green from the moment the scaffold lands.

## Alternatives Considered

- **GitHub Issues as the source of truth.** The platform-native tracker, free workflow and search. Rejected as the *authority* because it breaks the repo's self-contained, offline-readable premise (the investigation would not travel with a clone or a release tarball) and splits the record from the code and the ADRs it references. Issues remain welcome as a *front door* — their number is cross-referenced from the record.
- **Flat `docs/bugs/BUG-NNNN-slug.md` (no date folders), pure ADR shape.** Simplest, and the id already gives identity. Rejected on the maintainer's explicit concern: a single directory accreting every defect over a multi-year maintained life grows unwieldy (and is awkward to browse on Windows). The date-tree mix keeps each directory small at no cost to the stable id.
- **Pure date-tree `…/<YYYY>/<MM>/<YYYY-MM-DD>-slug.md` (journal shape, no id).** Matches the journal exactly, but the filename-as-identity is long and not resolvable from a short handle — cross-referencing a defect from a commit or `CHANGELOG` line would need the full dated slug. Rejected in favour of keeping the short monotonic `BUG-NNNN` id *and* the date folders.
- **Record only confirmed defects; drop unsubstantiated reports.** Fewer files. Rejected because the *triage* — the evidence that a reported bug could **not** be reproduced — is itself valuable institutional memory for a reference project, and prevents the same rejected report from being re-litigated.
- **A deterministic hook that auto-creates the file on a trigger phrase.** Rejected because deciding a defect is *real* (reproduce + root-cause, or refute a third-party claim) is a judgment task a hook cannot perform. A hook can enforce *structure* — that role is filled by the consistency-lint `bugs` check; the *judgment* lives in the agent contract.

## Consequences

- **No API / ABI / build impact** — documentation, process, and tooling only.
- A new agent obligation (`AGENTS.md` §7.7): verified defects and triaged third-party reports become ledger files in the same PR as the investigation, like any other doc-with-the-work rule.
- The maintained-product governance gains a defect-lifecycle section in [`docs/workflow/maintenance.md`](../workflow/maintenance.md) tying the ledger to the existing `Fixed`/hotfix/security flows; a `fixed` record and its `CHANGELOG` line are kept in lockstep by convention and, partly, by the lint.
- The consistency lint grows a sixth-plus `bugs` check and its remediation row; CI re-runs it via the existing `consistency` job. The path↔`discovered` agreement means the lint reads frontmatter, so a malformed date or a misfiled record fails fast and locally.
- Relative links inside a bug record must account for the `docs/bugs/<YYYY>/<MM>/` depth (`../../../` reaches `docs/`, `../../../../` the repo root) — the same rule the journal already follows; the `docs.yml` link check guards it.
- This is a **maintenance/process change, not a feature** ([`AGENTS.md`](../../AGENTS.md) §7.3, [ADR-0037](0037-new-feature-roadmap-placement.md)): no roadmap milestone, recorded in `CHANGELOG`, justified by this ADR.

## References

- [ADR-0036](0036-session-journal-extraction.md) — the dated per-session-file precedent the date-tree reuses.
- [ADR-0034](0034-post-release-maintenance-protocol.md) — the maintenance governance the lifecycle plugs into.
- [ADR-0035](0035-agent-runnable-consistency-lint.md) — the consistency lint extended with the `bugs` check.
- [ADR-0038](0038-changelog-version-split.md) — the `CHANGELOG` whose `Fixed` category records the closing side of a defect.
- [`docs/bugs/README.md`](../bugs/README.md) — the ledger index and how-to.
- [`docs/workflow/maintenance.md`](../workflow/maintenance.md) — the defect-lifecycle governance section.
- [`AGENTS.md`](../../AGENTS.md) §7.7 — the agent triage protocol.
1 change: 1 addition & 0 deletions docs/adr/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,5 +71,6 @@ Do **not** write one for purely local implementation details, formatting, or tri
| 0036 | [Extract the session journal from ROADMAP.md into dated per-session files](0036-session-journal-extraction.md) | Accepted |
| 0037 | [A new feature is planned on the roadmap — new milestone or appended item](0037-new-feature-roadmap-placement.md) | Accepted |
| 0038 | [Split the changelog into one immutable Markdown file per release](0038-changelog-version-split.md) | Accepted |
| 0039 | [In-repo bug ledger and agent triage protocol](0039-bug-ledger-and-triage-protocol.md) | Accepted |

When adding a new ADR, append a row to this table in the same PR.
83 changes: 83 additions & 0 deletions docs/bugs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
# Bug ledger

The durable, in-repo record of **known defects** and the **triage** of incoming
reports for `pbr-cpp-memory-pool`. One Markdown file per defect, reviewed like any
other artifact ([ADR-0039](../adr/0039-bug-ledger-and-triage-protocol.md)).

This ledger is the **source of truth** for defects. A GitHub issue, if one exists, is
*referenced* from a record but is not authoritative — the investigation travels with
the repo, offline, like the ADRs and the [session journal](../journal/).

The ledger holds the **open / in-flight / triaged** side of a defect's life; the
**closing** side — *what shipped in which release* — is recorded in the `CHANGELOG`
`Fixed` category ([ADR-0038](../adr/0038-changelog-version-split.md)) and governed by
[`docs/workflow/maintenance.md`](../workflow/maintenance.md). A `fixed` record and its
`CHANGELOG` line cross-reference each other.

## Format

File naming: `BUG-NNNN-<short-kebab-slug>.md`, stored under a **discovery-date** tree:

```
docs/bugs/<YYYY>/<MM>/BUG-NNNN-<slug>.md
```

- `NNNN` is a **zero-padded, globally monotonic** id — never reused, never renumbered
(like an ADR number). It is the short stable handle (`BUG-0007`) used to reference a
defect from a commit, a PR, or a `CHANGELOG` line.
- The `<YYYY>/<MM>` folders are the **discovery** year and month, keeping any one
directory small — the same idiom as the [session journal](../journal/).

Each record carries structured frontmatter:

| Key | Meaning | Vocabulary |
|-----|---------|------------|
| `id` | the `BUG-NNNN` handle — matches the filename | — |
| `title` | one-line description | — |
| `status` | lifecycle state | `open` · `confirmed` · `fixed` · `wontfix` · `duplicate` · `cannot-reproduce` |
| `severity` | impact level | `low` · `medium` · `high` · `critical` |
| `reporter` | who raised it | `internal` · `third-party` |
| `discovered` | discovery date — matches the `<YYYY>/<MM>` path | `YYYY-MM-DD` |
| `affected-versions` | version range affected | e.g. `">=1.0.0,<1.1.1"` |
| `fixed-in` | release that fixes it (required once `status: fixed`) | e.g. `v1.1.1` |

Start from [`template.md`](template.md).

## Lifecycle

```
open ─► confirmed ─► fixed
│ │
└─────────┴─► wontfix | duplicate | cannot-reproduce (terminal)
```

- **open** — recorded, not yet root-caused/confirmed.
- **confirmed** — reproduced and root-caused; awaiting a fix.
- **fixed** — a release fixes it; `fixed-in` is set and the `CHANGELOG` `Fixed` line links back here.
- **wontfix / duplicate / cannot-reproduce** — terminal; the record documents *why* (a `duplicate` links the canonical `BUG-NNNN`).

## How a record is created

The agent's triage protocol is codified in [`AGENTS.md`](../../AGENTS.md) §7.7. In short:

- **Hunting for bugs** → a record is created only for a **verified, reproducible** defect.
- **A third-party report** → the agent **reproduces and root-causes first**; only then a
`confirmed` record (`reporter: third-party`, repro as evidence). A report that does
**not** hold up is still recorded — as `cannot-reproduce` / `rejected` / `duplicate` —
with the investigation that reached that verdict.

The fix lands through the normal hotfix/PATCH flow ([`docs/workflow/maintenance.md`](../workflow/maintenance.md)),
which flips the record to `fixed`, sets `fixed-in`, and adds the `CHANGELOG` `Fixed` line —
all in the same PR. Integrity (frontmatter, ids, index bijection, date agreement) is
checked by `python tools/consistency_lint.py` (the `bugs` check).

## Index

Newest first, grouped by year and month. *(No defects recorded yet — the ledger is empty
as of `v1.1.0`.)*

<!-- When adding a record, append a row here:
| Id | Title | Status | Severity | Discovered |
|----|-------|--------|----------|------------|
| [BUG-0001](2026/06/2026...) | ... | open | medium | 2026-06-15 |
-->
Loading
Loading