diff --git a/docs/discussions/2429-templated-documentation/20261009-templated-documentation-design/README.md b/docs/discussions/2429-templated-documentation/20261009-templated-documentation-design/README.md index 7c38208f4..f5a19cd63 100644 --- a/docs/discussions/2429-templated-documentation/20261009-templated-documentation-design/README.md +++ b/docs/discussions/2429-templated-documentation/20261009-templated-documentation-design/README.md @@ -106,6 +106,20 @@ verification for living documents; rendering for output-only documents, with fro data source. Drafted with an AI assistant using the Copilot SDK in VS Code (Claude Opus 5.5); reviewed and adopted by Jose Celano. +##### Q1 - Cameron Garnham (`da2ce7`), 2026-10-15, PR #2430 + +I agree that the line falls per fact, not per file; it is the line the proposal already draws for +records, whose "prose stays authored" ([Document Classes](initial-proposal.md#document-classes)). +Where I differ is the restated part of a living document: a subissue `Status` column, a findings +table, a count, an inventory. I would render it as a generated region rather than verify it with a +second tool, because a check that only verifies still leaves the copy to be edited by hand; a +rendering removes the copy. The original facts around it stay hand-edited, under typed frontmatter and +the validator as today. The root ADRs remain the first documents to convert, each conversion an +editorial pass, and equivalence with the previous bytes is never a gate condition +([Conversion Is an Editorial Pass](initial-proposal.md#conversion-is-an-editorial-pass)). + +AI assistance not disclosed: the author's private tooling may include machine-learning tools. + ### Q2 - Should a rendering be a second file or a generated region? The proposal leaves open "a rendering as a second file or as generated regions". With a second file, @@ -123,6 +137,20 @@ everything outside the rendered block, so they look like the better starting poi author's view on when a full second file is necessary. Drafted with an AI assistant using the Copilot SDK in VS Code (Claude Opus 5.5); reviewed and adopted by Jose Celano. +##### Q2 - Cameron Garnham (`da2ce7`), 2026-10-15, PR #2430 + +I concede the default: a generated region. The `.md` stays the file people edit, a prose fix needs no +regeneration, and only the marked block is locked. A whole-file rendering earns its template only +where a document is restated facts apart from a fixed preamble, such as the ADR index +([`docs/adrs/index.md`](../../../adrs/index.md)) or a catalog of skills or of specifications. So a +second file is necessary per document, not per class, and the choice stays where the proposal lists +it, among the [engineering choices](initial-proposal.md#engineering-choices-left-to-implementation). +Both forms sit behind one dataset interface: the renderer writes the same records into a whole file or +into a marked block, and the gate compares either with a fresh render, so moving a document from one +form to the other is engineering, not design. + +AI assistance not disclosed: the author's private tooling may include machine-learning tools. + ### Q3 - Should living documents become per-document render templates? The proposal converts records such as ADRs and specifications so that their frontmatter, tables, and @@ -146,6 +174,20 @@ dynamically; for living documents that is acceptable, and restated facts in them instead. Drafted with an AI assistant using the Copilot SDK in VS Code (Claude Opus 5.5); reviewed and adopted by Jose Celano. +##### Q3 - Cameron Garnham (`da2ce7`), 2026-10-15, PR #2430 + +I withdraw the root-ADR subissue's per-document templates ("Each of the 29 becomes a template", +[Subissues](initial-proposal.md#subissues)): a living document keeps no per-document template. On the +open question, the data generator reads frontmatter from the file people edit, which for a living +document is the `.md` itself, and never from a template. That frontmatter is the authority and is +never rendered. A managed output, whether a whole-file rendering or a generated region, enters the +census only by its declared template, never by its rendered bytes. This authority rule keeps the data +flow acyclic: every fact moves from a hand-edited authority through a dataset into a rendering, and no +rendering is read back as input, so no document's data can derive from its own output and the loop the +topic asks about cannot form. + +AI assistance not disclosed: the author's private tooling may include machine-learning tools. + ### Q4 - Is a committed JSON dataset layer needed from the start? The proposal commits JSON datasets and makes the renderer read only them. They are aggregates built @@ -156,7 +198,19 @@ datasets when a concrete need appears. Added by Jose Celano. Session record: S2, #### Positions -No entries yet. +##### Q4 - Cameron Garnham (`da2ce7`), 2026-10-15, PR #2430 + +I hold to committed datasets, with a narrower claim. A committed dataset is what makes the gate +deterministic: the renderer reads committed bytes, never a fresh scan, so every rendering is +reproducible from the tree alone, and a pull request shows the data change apart from the prose +change. One [dataset crate](initial-proposal.md#one-dataset-crate-as-the-only-shared-interface) stays +the only interface the data generator, the renderer and the checkers share, so no checker parses the +tree on its own terms. What I narrow is the reach: datasets arrive per aggregate, the ADR census +first, not as a layer under every document from the start. The merge-conflict point a generated file +adds for parallel pull requests is a cost I accept per aggregate, where the reviewable data diff pays +for it. + +AI assistance not disclosed: the author's private tooling may include machine-learning tools. ### Q5 - Where and when does the gate run? @@ -174,6 +228,20 @@ small deployable increments, so agents do not do much without committing, and co guardrail checks well. This would not hold if agents ran for hours without committing. Drafted with an AI assistant using the Copilot SDK in VS Code (Claude Opus 5.5); reviewed and adopted by Jose Celano. +##### Q5 - Cameron Garnham (`da2ce7`), 2026-10-15, PR #2430 + +I agree: pre-commit and pre-push, on the staged snapshot, as the frontmatter validator already runs +with `--staged` in [`pre-commit.sh`](../../../../contrib/dev-tools/git/hooks/pre-commit.sh). On the +open question, the hooks run `check` only, never `write`. A hook that runs `write` changes bytes the +author neither staged nor read, and it hides a stale rendering instead of reporting it; a failing +`check` names the command to run and leaves the edit, and its review, with the author. The roster fits +commit time: the data check, the document check, Markdown, spelling and the frontmatter check on the +staged files. Markdown and spelling took about 6 s each on the merged tree in the review session (S3 +in the [session record](maintainer-review-session.md)), and a check that renders in memory and +compares committed bytes adds little to that. + +AI assistance not disclosed: the author's private tooling may include machine-learning tools. + ### Q6 - How do forge facts enter, and who refreshes them? Under the proposal, GitHub facts enter only as committed captures stamped "as of" an instant, and the @@ -185,7 +253,19 @@ links, committed captures and check results") contain. Added by Jose Celano. Ses #### Positions -No entries yet. +##### Q6 - Cameron Garnham (`da2ce7`), 2026-10-15, PR #2430 + +A capture is refreshed by whoever edits the row it feeds, in the same change, so the refresh is +reviewed with the edit that needed it. Every capture carries its "as of" stamp, and the rendering +prints that stamp beside the value, so a reader sees the age of a forge fact instead of trusting it. +The gate fails when a capture is older than a declared window. The window is declared with the +aggregate that carries the capture, when that aggregate is introduced: it is a value set there, not a +design point for this discussion to decide. The "check results" among the data generator's inputs +([Four Stages](initial-proposal.md#four-stages-each-committed-and-gated)) are the outputs of the +repository's own checkers, such as the frontmatter validator and the link check, committed as stamped +data, never live tool runs. + +AI assistance not disclosed: the author's private tooling may include machine-learning tools. ### Q7 - How does template syntax relate to semantic links? @@ -206,6 +286,19 @@ placeholders are resolved in the final file and semantic links are not, but in a side by side. The two mechanisms need a clear boundary. Drafted with an AI assistant using the Copilot SDK in VS Code (Claude Opus 5.5); reviewed and adopted by Jose Celano. +##### Q7 - Cameron Garnham (`da2ce7`), 2026-10-15, PR #2430 + +I agree, and I would draw the boundary this way: a placeholder produces presentation text only and +never mints a semantic link, and no semantic-link syntax, whether frontmatter `semantic-links`, a +`` marker, a `review-finding:` reference or the prose-path syntax EPIC #2264 +adopts, appears inside a placeholder. A semantic link is written by hand and stays verbatim in the +rendering, where validators and agents read it; a placeholder is resolved and disappears. The boundary +is enforced by policy and by a lint over the templates, not left to the engine: a maintained template +engine substitutes whatever a template asks for and has no notion of a presentation-only template, so +it does not hold the boundary by itself. + +AI assistance not disclosed: the author's private tooling may include machine-learning tools. + ### Q8 - When should this be implemented? The proposal's author asked on PR #2430 whether this is work for now or for after version 4. The @@ -223,6 +316,18 @@ are too many things in progress, and a change of this size is better made in iso tasks in parallel. Drafted with an AI assistant using the Copilot SDK in VS Code (Claude Opus 5.5); reviewed and adopted by Jose Celano. +##### Q8 - Cameron Garnham (`da2ce7`), 2026-10-15, PR #2430 + +I agree: not before the tracker v4.0.0 release and the work in progress it carries. The conversions +change how every contributor and agent edits the documents they touch, and making that change while +many issues are open would cost more than it saves. The two phases can still be split if an earlier +review of the engine is wanted: the tooling converts no document, so it could land under +[`contrib/dev-tools`](../../../../contrib/dev-tools) first, behind its own review, at the cost of the +workspace registration the topic notes. Every conversion waits for the release either way, so nothing +a contributor edits changes before then. + +AI assistance not disclosed: the author's private tooling may include machine-learning tools. + ### Q9 - How should ideas mature into planned work? This proposal arrived as an EPIC with a binding design. A staged path would let ideas mature from high @@ -240,6 +345,18 @@ discussion, feature, EPIC, subissues. This workflow should be documented, in a s Drafted with an AI assistant using the Copilot SDK in VS Code (Claude Opus 5.5); reviewed and adopted by Jose Celano. +##### Q9 - Cameron Garnham (`da2ce7`), 2026-10-15, PR #2430 + +I agree with the path from discussion to feature, then EPIC, then subissues, and with documenting it +in a separate change. This round is its first instance: the proposal arrived as an EPIC with a binding +[design](initial-proposal.md#design), and it now sits in this folder as source material, kept as +written, while the questions it raised are argued topic by topic. That is the order the path asks for: +a binding design comes after the topics settle and the decision owner records the Outcome, and an EPIC +specification for issue #2429, if one is still wanted, is written from that Outcome rather than from +the proposal. Issue #2429 stays the issue this discussion informs. + +AI assistance not disclosed: the author's private tooling may include machine-learning tools. + ## Outcome Pending: no decision recorded. diff --git a/docs/discussions/2429-templated-documentation/20261009-templated-documentation-design/initial-proposal.md b/docs/discussions/2429-templated-documentation/20261009-templated-documentation-design/initial-proposal.md new file mode 100644 index 000000000..e4d146073 --- /dev/null +++ b/docs/discussions/2429-templated-documentation/20261009-templated-documentation-design/initial-proposal.md @@ -0,0 +1,234 @@ +--- +schema-version: 1 +doc-type: epic +status: planned +epic: null +github-issue: 2429 +spec-path: docs/issues/open/2429-templated-documentation/EPIC.md +epic-owner: null +last-updated-utc: "2026-10-03 14:53" +semantic-links: + skill-links: + - create-issue + - create-markdown-template + related-artifacts: + - .github/skills/dev/planning/create-issue/SKILL.md + - .github/skills/dev/planning/create-markdown-template/SKILL.md + - docs/templates + - docs/index.md + - docs/adrs/index.md + - docs/skills/semantic-skill-link-convention.md + - docs/schemas/frontmatter-v1.schema.json + - contrib/dev-tools/checks/frontmatter-validator + - contrib/dev-tools/git/hooks/pre-commit.sh + - "issue #2003" + - "issue #2264" + - "issue #2278" +--- + + + +# EPIC #2429 - Templated Documentation + +> **Source material, not policy.** Proposed by `da2ce7` on 2026-10-03 as the EPIC specification for +> issue #2429 (PR #2430), and withdrawn into this discussion in the same pull request. The text is +> kept as written, frontmatter included; only this note was added. Nothing had to change to pass the +> linters: the text links no repository file, its heading levels already pass the Markdown linter, and +> the frontmatter validator checks `spec-path` and `status` against a file's location only under +> `docs/issues/`, so the frontmatter, which still describes the withdrawn specification, passes here +> as written. The discussion and its positions are in [`README.md`](README.md). + +## Goal + +Make every fact a Markdown document restates from elsewhere a rendering of committed data, so the tree alone reproduces each document and a gate fails when a restated fact goes stale. A template holds a document's prose and shape, committed datasets hold every copied fact, and the committed rendering stays plain Markdown. + +The tree already shows the failure this removes. `docs/adrs/index.md` lists 28 of the 29 root ADRs: `docs/adrs/20260612000000_adopt_sccache_for_ci_bare_builds.md` has no row, and the table steps from `20260603000000` at `docs/adrs/index.md:26` to `20260617093046` at `docs/adrs/index.md:27`. Nothing flagged it: the index is a hand-made copy of a fact the tree holds. + +## Why This Is Needed + +Copies drift: a restated list, count or quoted line is right when written and wrong once its source changes, and no check compares the two. More evidence at `develop` `eb96d2957`: + +- The template table at `docs/index.md:158` lists 12 of the 14 templates (not `MANUAL-VERIFICATION-EVIDENCE.md` or `PR-REVIEW-RETROSPECTIVE.md`), though `.github/skills/dev/planning/create-markdown-template/SKILL.md:58` has each entered there, and in `docs/templates/README.md`, by hand. +- `docs/skills/semantic-skill-link-convention.md:153` lists the EPIC fields without `schema-version` or `epic` and admits the status `open`, against the validator's EPIC profile at `contrib/dev-tools/checks/frontmatter-validator/src/profile.rs:197`. +- EPIC subissue tables copy each subissue's GitHub state into a hand-kept `Status` column. + +The public friction register on EPIC #2003 tracks these drift classes. + +One artifact already works this way: `docs/schemas/frontmatter-v1.schema.json` is generated from the Rust model (`contrib/dev-tools/checks/frontmatter-validator/src/profile.rs:49`), and a `check` mode fails when the committed bytes differ from a fresh generation. + +At `develop` `eb96d2957` the tree tracks 970 Markdown files, among them 272 primary specifications (`ISSUE.md` or `EPIC.md` under `docs/issues/`), 143 PR-review audit files, 46 skills, 33 ADRs (29 at the root) and 14 templates: each a count that documents quote and the next change makes wrong. + +## Scope + +### In Scope + +- Everything stated in Design. +- Progressive conversion of the document classes, most important documents first. + +### Out of Scope + +- The shared runner, cache, CI integration and execution architecture (EPIC #2003). +- Frontmatter profiles, the semantic-link model and path-reference syntax (EPIC #2264). +- Rewriting closed or historical records. +- Reading the forge at render time; forge facts enter as committed, stamped captures. + +## Design + +This section is binding; subissues change it only through a recorded update to this EPIC. + +### Four Stages, Each Committed and Gated + +1. **Inputs.** Authored data (lists, catalogs, owner and placement tables) lives in hand-edited TOML whose schema the data generator validates first, so a broken edit fails there, not at a reader. +2. **Data generator.** A Rust program reads the inputs and the tree (frontmatter, paths, links, committed captures and check results) and emits JSON datasets; it alone derives facts (counts, classifications, name-to-path resolution). The tree census is a dataset like any other: generated, committed and proved current by the gate. +3. **Document generator.** It renders each template over the committed JSON only (never the TOML, the forge or the network), with render-time functions such as `require`, and compares with the committed rendering. A stale, missing or unpaired rendering fails; `write` refreshes it. +4. **Lint.** The tracker's own `linter` profile and the frontmatter validator run on the committed renderings; templates are linted for their prose under a profile that knows the delimiters. + +Generator, renderer and Markdown formatters compose to a fixed point: on a clean tree, running all four stages changes nothing. + +### The Editing Boundary + +People edit the TOML inputs, the templates' prose, the data generator's code (where a derived fact is defined) and the render-time functions. Datasets and renderings are generated; the gate rejects a hand edit to either. A render template, re-rendered on every gate run, keeps a name distinct from the authoring templates that `.github/skills/dev/planning/create-markdown-template/SKILL.md:20` calls starting points, "not generators". + +### One Envelope on Every Structured File + +Every TOML input and JSON dataset declares itself before any payload is read: + +- `namespace` then `version` are its first two top-level keys in source order; the file name alone selects the carrier. +- The namespace names the schema, never the instance: dot-separated, at least two lowercase alphanumeric atoms, hyphens only inside an atom, at most 255 bytes, shaped `com.torrust.tracker..`. +- The version is `[major, minor, patch]` in shortest-spelled non-negative integers; a reader requires an equal major, accepts a minor at or above its floor, and treats patch as documentary. +- A reader accepts a file only after validating carrier, namespace, version relation and payload; an envelope failure is a refused precondition, not a repository finding. +- Generated datasets carry it too: the data generator writes it and the gate checks it. + +The envelope owns namespace and version, each program owns its payload schema, no layer infers a missing fact from another, and a schema change is a version change. + +### One Dataset Crate as the Only Shared Interface + +One Rust crate owns every JSON dataset: a record type per namespace (the type is the schema, projected to JSON Schema as the frontmatter validator already does), the envelope type with its read-time version check, and the serde round trip. Serialization is deterministic (fixed key order and number spelling, one trailing newline), so a dataset diff is a data diff. The data generator writes through it, the document generator and every checker read through it, and no stage parses JSON alone. + +The TOML input schema stays out: it is the data generator's private contract, and sharing it would make the document generator depend on inputs it never reads. Layering: TOML input schema (private) → dataset crate (the only shared interface) → document generator and checkers. + +### Premises and Labels + +A sentence whose truth depends on the data states its premise with `require`, so a dataset that no longer supports the prose fails the render; a quoted line states the bytes it quotes. Every rendered record mints exactly one label, a stable identifier derived from the record (an ADR identifier, an acceptance-criterion ID, a `review-finding:pr--` reference), never typed by hand. + +### Conversion Is an Editorial Pass + +Converting a document to a template is a review of that document: each one is read, corrected and improved as it is converted, so a rendering that differs from the former text because the document got better is the intent, not a defect. Equivalence with the previous bytes is never a gate condition; the fixed-point gate applies to a converted document from its conversion onward. + +## Document Classes + +Conversion follows importance, not class. + +- **Class 1, inventories.** `docs/index.md`, `docs/adrs/index.md`, `docs/issues/README.md`, `docs/packages.md`, `docs/templates/README.md`: renderings of the tree census. +- **Class 2, records.** ADRs, specifications, audit records, retrospectives, security and evidence records: frontmatter, tables, thread states and related lists render; prose stays authored. Forge facts come from captures stamped with their time, so a status is "as of" a named instant. +- **Class 3, prose guides.** Skills, agent profiles, `AGENTS.md` and guides: only what they cite renders (tables of paths, counts, quoted lines). + +## Engineering Choices Left to Implementation + +The subissue that meets each of these records its choice and reason: where inputs, datasets and templates live; how many templates, per package or per concept; a rendering as a second file or as generated regions; the dependency graph within and between packages; whether envelope, schema and namespace handling get their own crate; where the generators live and how the gate invokes them; the delimiters; the conversion order within a subissue; the migrated layout. + +## Relationship to Existing EPICs + +- **EPIC #2003** owns the execution model: the `check` modes are checks in its taxonomy and the `write` modes actions, placed by its dev-tool precedent until it selects the shared architecture. +- **EPIC #2264** owns what records render against; the validator has two strict profiles, issue and EPIC (`contrib/dev-tools/checks/frontmatter-validator/src/profile.rs:16`). Its rows "Extend strict profiles and author guidance", "Normalize the semantic-link model" and "Decide path-reference scope and syntax" supply the other record types, the citation targets and the status of prose paths. +- **EPIC #2278** owns the audit-record contract: its rows "Port the audit validator to Rust with parity fixtures" and "Extend the audit validator to the adopted invariants" fix the thread states and invariants the audit template states as premises. + +## Subissues + +Status values: `TODO`, `IN_PROGRESS`, `BLOCKED`, `DONE`. + +| Order | Issue | Local Spec | Status | Notes | +| ----- | ----- | ---------- | ------ | ----- | +| 1 | #[To be assigned] - Add the dataset crate and the envelope | Not drafted | TODO | No dependencies. | +| 2 | #[To be assigned] - Add the data generator, its TOML input schema and the tree census | Not drafted | TODO | Depends on the dataset crate. | +| 3 | #[To be assigned] - Add the document generator and the staleness gate | Not drafted | TODO | Depends on the dataset crate. | +| 4 | #[To be assigned] - Convert the root ADRs | Not drafted | TODO | Each of the 29 becomes a template over the ADR census, reviewed and improved in its conversion; the ADR index renders from that census. Depends on both generators. | +| 5 | #[To be assigned] - Convert the specifications and audit records | Not drafted | TODO | Depends on the root ADRs and EPIC #2264's strict profiles; audit records also on EPIC #2278's validator rows. | +| 6 | #[To be assigned] - Convert the remaining inventories and the prose guides | Not drafted | TODO | Depends on the root ADRs; guides also on EPIC #2264's path-reference decision. | + +## Delivery Strategy + +The interface lands first; conversion then goes by importance. The root ADRs lead, and their census also renders `docs/adrs/index.md`, retiring its omission by construction; specifications and audit records follow EPIC #2264's profiles and EPIC #2278's validator rows; the remaining inventories and the prose guides come last. A conversion PR is reviewed as an improvement of its documents, not as a mechanical move. Each subissue merges through its own PR. + +For each subissue implementation in this EPIC, the default completion policy is: + +1. Run automatic checks (`linter all`, relevant tests, pre-push checks when applicable). +2. Run manual verification scenarios and record evidence. +3. Re-review acceptance criteria after implementation and update verification evidence. +4. Complete an evidence-based implementation review. Create or update an + issue-local retrospective for reusable lessons, material design changes, or + meaningful deviations from the plan; otherwise record why one was unnecessary + in the issue progress log. + +### Phase 1: Interface and Generators + +- Outcome and exit: a clean tree is a fixed point, and a mutated dataset or rendering fails the gate. + +### Phase 2: Root ADRs + +- Outcome and exit: every root ADR renders from its template, and `docs/adrs/index.md` from their census; adding an ADR without refreshing fails the gate. + +### Phase 3: Specifications and Audit Records + +- Outcome and exit: converted once their profiles and validator rows exist, or deferred with reasons. + +### Phase 4: Remaining Inventories and Prose Guides + +- Outcome and exit: converted, or deferred with reasons. + +## Progress Tracking + +### Workflow Checkpoints + +- [x] Epic spec drafted in `docs/issues/drafts/` +- [ ] Epic spec reviewed and approved by user/maintainer +- [x] GitHub epic issue created and issue number added to this spec +- [ ] Subissues created and linked in this spec +- [ ] Subissue statuses kept up to date in the `Subissues` table +- [ ] For each implemented subissue: automatic checks completed and recorded +- [ ] For each implemented subissue: manual verification completed and recorded +- [ ] For each implemented subissue: acceptance criteria reviewed post-implementation +- [ ] For each implemented subissue: implementation completion review recorded +- [ ] Epic acceptance criteria reviewed and checked off +- [ ] Epic issue closed and spec moved from `docs/issues/open/` to `docs/issues/closed/` + +### Progress Log + +- 2026-10-03 13:16 UTC - Specification drafter - Drafted the EPIC: binding design, engineering choices left to subissues, six proposed subissues. +- 2026-10-03 13:56 UTC - Specification drafter - Revised after review: root ADRs convert first, and conversion is an editorial pass. +- 2026-10-03 14:02 UTC - Specification drafter - Restored the template's completion policy and workflow checkpoints verbatim, and the subissue status drift class. +- 2026-10-03 14:20 UTC - Specification drafter - Issue #2429 created; spec moved from drafts to open. + +## Acceptance Criteria + +- [ ] AC1: Only the dataset crate reads or writes datasets, byte-deterministically. +- [ ] AC2: Every input and dataset carries the envelope, checked before its payload is read. +- [ ] AC3: The TOML input schema is private to the data generator. +- [ ] AC4: The gate fails on a stale dataset or a stale, missing or unpaired rendering; `write` refreshes both. +- [ ] AC5: An unsupported `require` premise fails the render. +- [ ] AC6: All four stages on a clean tree change nothing. +- [ ] AC7: The root ADRs are converted, and `docs/adrs/index.md` renders from their census and lists every root ADR. +- [ ] AC8: Each engineering choice is recorded with its reason where it was made. +- [ ] AC9: Template guidance distinguishes render templates from authoring templates. +- [ ] AC10: Every completed subissue carries verification evidence and its reviews. + +### Acceptance Verification + +| AC ID | Status (`TODO`/`DONE`) | Evidence | +| ----- | ---------------------- | -------- | +| AC1-AC6 | TODO | Interface subissues, mutation evidence | +| AC7 | TODO | Root-ADR subissue | +| AC8-AC10 | TODO | Subissue specifications and records | + +## Risks and Trade-offs + +- A data change diffs twice. Mitigation: deterministic output keeps both diffs minimal. +- A template that computes a fact recreates drift. Mitigation: only the data generator derives facts. +- Hand edits to renderings. Mitigation: the gate fails and names the template and dataset to edit. +- An editorial pass changes a decision's meaning. Mitigation: each ADR's conversion is reviewed in its own PR. + +## References + +- Related issues: #2003, #2264, #2278 +- Related PRs: none +- Related ADRs: `docs/adrs/20260519000000_define_global_cli_output_contract.md` (the generators' output contract) diff --git a/docs/pr-reviews/pr-2430-review/PR-REVIEW.md b/docs/pr-reviews/pr-2430-review/PR-REVIEW.md new file mode 100644 index 000000000..925ca76a3 --- /dev/null +++ b/docs/pr-reviews/pr-2430-review/PR-REVIEW.md @@ -0,0 +1,231 @@ +--- +semantic-links: + skill-links: + - process-pr-review + related-artifacts: + - .github/skills/dev/pr-reviews/process-pr-review/SKILL.md + - docs/discussions/2429-templated-documentation/20261009-templated-documentation-design/initial-proposal.md +--- + + + +# PR #2430 Review Audit + +Source: pull-request reviews and inline review threads for . + +## Ownership + +The PR author owns this tracked audit record. Reviewers, including repository review agents, +deliver findings through GitHub and have no repository-artifact obligation. + +- Post-merge workflow approval: N/A + +## Status Values + +- Relationship: `ORIGINAL`, `RE_RAISE_OF:` +- Disposition: `FIXED`, `NO_ACTION`, `SUPERSEDED`, `FOLLOW_UP` +- Thread state: `OPEN`, `RESOLVED`, `NON_RESOLVABLE`, `SUPERSEDED`. `OPEN` applies prospectively + to audits created or updated for approved follow-up work; historical audits remain valid without + bulk migration. +- Severity: `Blocker`, `Major`, `Minor`, `Nit`, `Suggestion`; append `(inferred)` when derived + from free prose. +- Author class: `Copilot`, `Human`, `Unknown` +- Category: `link-integrity`, `formatting`, `metadata`, `testing`, `correctness`, + `documentation`, `maintainability`, `security`, `other` +- An outdated thread whose concern was fixed is `FIXED`/`RESOLVED`, even when GitHub marks the + original thread outdated after the push. For in-PR feedback, use `NO_ACTION`/`SUPERSEDED` only + for a duplicate, superseded, or no-change concern. A post-merge `NO_ACTION` requires maintainer + approval to decline the follow-up work. + +## Findings + +Copilot review 5401297360 (round 1, state `COMMENTED`, submitted 2026-10-03 14:49 UTC on head `docs(issues): [#2429] add the templated documentation EPIC specification`, "Lite" effort) left three inline comments. Its body opens with a `[!WARNING]` alert: "Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a `copilot-code-review.yml` file specifying one with the `runs-on` attribute. See the [docs](https://gh.io/AA11tgch) for more details." The rest of the body is an overview, so it creates no additional finding. + +The comments carry no `[Severity]` bracket. The review's badge markup rates all three `Medium severity`; as in the PR #2421 audit, they are recorded as `Minor (inferred)`. F3 repeats F2's claim for a different table and asks for a change to other rows, so it is an original finding, not a re-raise. Thread states are recorded as captured after the replies: all three threads are resolved, recorded as `RESOLVED` for the fixed F1 and `SUPERSEDED` for the no-change F2 and F3, as the Status Values prescribe. + +Review 5469057611 by the decision owner (`josecelano`, round 2, state `CHANGES_REQUESTED`, submitted 2026-10-09 10:51 UTC on head `docs(pr-reviews): [#2429] add the PR #2430 review audit record`) has no inline comments. Its body opens with a verdict, that an EPIC with a binding design is premature, and answers the PR's timing question (after the v4.0.0 release and the current work in progress); neither requests a change, so neither makes a row. Its three numbered requests carry four independently actionable assertions, F4 to F7, whose severity and category are inferred from the prose; F4 is a `Blocker` because the review requests changes to keep the EPIC specification from merging. Having no thread, all four are `NON_RESOLVABLE`. The decision owner's conversation comment 6145187551 of 2026-10-13 repeats these requests as five steps and adds one, retitling the PR to match the rework; a conversation comment is not a review, so that step is recorded in the Processing Log, not as a row. + +Review 5506433536 by the review bot (round 3, state `COMMENTED`, submitted 2026-10-13 16:00 UTC on head `docs(pr-reviews): [#2429] add the PR #2430 review audit record`) has no inline comments and opens no thread. Its body is an overview: a verdict that summarizes its two items, "0 open findings", and three items resolved since its last review, which are the F1 to F3 threads and make no row. Its two "previously missed" items sit in collapsed sections whose full content is retrievable and actionable, so they are F8 and F9. The badge markup rates both `Medium severity`, recorded as `Minor (inferred)` as for F1 to F3. F8 asks for the change F4 records, against the same current tree, so it is `RE_RAISE_OF:F4` with F4's disposition; F9 asks for this record to normalize review 5469057611 and the rework, and is fixed by the commit that carries this record. Having no thread, both are `NON_RESOLVABLE`. + +Review 5535382217 by the decision owner (`josecelano`, round 4, state `APPROVED`, submitted 2026-10-15 15:10 UTC on head `docs(pr-reviews): [#2429] record the PR #2430 response to the reviews`) opens one inline thread. Its body is the approval verdict and requests no change, so it makes no row. The inline comment opens with its own `[Minor][F10]` prefix: F10 keeps the reviewer's ID, which collides with no earlier finding ID, and its severity is `Minor` as written. It asks for a sentence in the PR description, an edit outside the repository tree, so the PR conversation response that states the edit is its resolution reference. Its thread is `RESOLVED`, recorded as the round-1 threads were, once the reply citing that response is posted and the thread resolved. + +| Finding ID | Review finding reference | Author class | Severity | Category | Relationship | Disposition | Thread state | +| ---------- | ------------------------ | ------------ | -------- | -------- | ------------ | ----------- | ------------ | +| F1 | `review-finding:pr-2430-f1` | Copilot | Minor (inferred) | documentation | ORIGINAL | FIXED | RESOLVED | +| F2 | `review-finding:pr-2430-f2` | Copilot | Minor (inferred) | formatting | ORIGINAL | NO_ACTION | SUPERSEDED | +| F3 | `review-finding:pr-2430-f3` | Copilot | Minor (inferred) | formatting | ORIGINAL | NO_ACTION | SUPERSEDED | +| F4 | `review-finding:pr-2430-f4` | Human | Blocker (inferred) | documentation | ORIGINAL | FIXED | NON_RESOLVABLE | +| F5 | `review-finding:pr-2430-f5` | Human | Major (inferred) | documentation | ORIGINAL | FIXED | NON_RESOLVABLE | +| F6 | `review-finding:pr-2430-f6` | Human | Minor (inferred) | metadata | ORIGINAL | FIXED | NON_RESOLVABLE | +| F7 | `review-finding:pr-2430-f7` | Human | Suggestion (inferred) | documentation | ORIGINAL | FOLLOW_UP | NON_RESOLVABLE | +| F8 | `review-finding:pr-2430-f8` | Copilot | Minor (inferred) | documentation | RE_RAISE_OF:F4 | FIXED | NON_RESOLVABLE | +| F9 | `review-finding:pr-2430-f9` | Copilot | Minor (inferred) | documentation | ORIGINAL | FIXED | NON_RESOLVABLE | +| F10 | `review-finding:pr-2430-f10` | Human | Minor | documentation | ORIGINAL | FIXED | RESOLVED | + +## Finding Details + +### F1 - The counts sentence has no commit anchor + +- PR number: 2430 +- Source review ID: 5401297360 +- Reviewer finding ID: N/A +- Source URL: +- Concern: The sentence at `docs/issues/open/2429-templated-documentation/EPIC.md:51` quotes concrete counts (970 Markdown files, 272 specifications, and others) without anchoring them to a commit, so the EPIC is itself prone to the drift it describes; the reviewer suggested the "as of" framing the document already uses. +- Solution: Anchored the sentence to `develop` `eb96d2957`, the framing the same section already uses ("More evidence at `develop` `eb96d2957`:"). No number changed. `last-updated-utc` was bumped; no Progress Log line was added, because the EPIC template asks for one line per meaningful update and this wording anchor changes no scope, design or status. +- Current-tree verification: `sed -n '51p' docs/issues/open/2429-templated-documentation/EPIC.md` prints the sentence opening "At `develop` `eb96d2957` the tree tracks 970 Markdown files"; `git diff` of the fix commit changes only that sentence and `last-updated-utc`. +- Resolution reference: `docs(discussions): [#2429] keep the EPIC text as the proposal's source material` +- Follow-up PR URL: N/A +- Reply URL: + +### F2 - Subissues table rows reported as starting with a double pipe + +- PR number: 2430 +- Source review ID: 5401297360 +- Reviewer finding ID: N/A +- Source URL: +- Concern: The reviewer reported that the Subissues table rows (anchored at `docs/issues/open/2429-templated-documentation/EPIC.md:139`) start with `||`, which Markdown reads as an extra empty leading column, and asked for a single leading `|`. +- Solution: no change. No row in the file starts with `||`; every table row starts with a single `|`, so the requested change has nothing to apply to. +- Current-tree verification: at the branch head, `grep -c '^||' docs/issues/open/2429-templated-documentation/EPIC.md` prints `0` and `grep -c '^|' docs/issues/open/2429-templated-documentation/EPIC.md` prints `13` (the Subissues and Acceptance Verification tables, header and separator rows included); `sed -n '139p'` prints the row starting `| 6 |`. +- Resolution reference: +- Follow-up PR URL: N/A +- Reply URL: + +### F3 - Acceptance Verification table rows reported as starting with a double pipe + +- PR number: 2430 +- Source review ID: 5401297360 +- Reviewer finding ID: N/A +- Source URL: +- Concern: The reviewer reported the same `||` row prefix in the Acceptance Verification table (anchored at `docs/issues/open/2429-templated-documentation/EPIC.md:213`) and asked for a single leading `|` on every row. +- Solution: no change. No row in the file starts with `||`; the three Acceptance Verification rows, its header and its separator each start with a single `|`. +- Current-tree verification: the F2 commands, run at the same head, print `0` and `13`; `sed -n '213p' docs/issues/open/2429-templated-documentation/EPIC.md` prints the row starting `| AC8-AC10 |`. +- Resolution reference: +- Follow-up PR URL: N/A +- Reply URL: + +### F4 - Keep the EPIC text as the discussion's initial proposal and remove the EPIC specification + +- PR number: 2430 +- Source review ID: 5469057611 +- Reviewer finding ID: N/A +- Source URL: +- Concern: An EPIC with a binding design is premature; the reviewer asked for the EPIC text to be added verbatim as `initial-proposal.md` in the discussion folder that PR #2501 opened, and for `docs/issues/open/2429-templated-documentation/EPIC.md` to be removed (request 1). +- Solution: The branch was rebuilt from `develop` and no longer adds the EPIC specification. Its text is added as `docs/discussions/2429-templated-documentation/20261009-templated-documentation-design/initial-proposal.md`, frontmatter included, under a source-material note that says what was added and why nothing else had to change to pass the linters. +- Current-tree verification: `git ls-tree -r HEAD -- docs/issues/open/2429-templated-documentation` prints nothing; with the note's eight lines (the blockquote after the H1 and the blank line after it) removed, `initial-proposal.md` is byte-identical to the EPIC at the reviewed head. +- Resolution reference: `docs(discussions): [#2429] keep the EPIC text as the proposal's source material` +- Follow-up PR URL: N/A +- Reply URL: + +### F5 - Add the PR author's positions on Q1 to Q9 + +- PR number: 2430 +- Source review ID: 5469057611 +- Reviewer finding ID: N/A +- Source URL: +- Concern: The reviewer asked for the PR author's positions on topics Q1 to Q9 of the discussion, with new topics optional (request 1). +- Solution: One entry per topic, appended under each topic's `#### Positions` after the decision owner's entry, or in place of "No entries yet." under Q4 and Q6. No new topic: every position fits an existing one. No other line of the discussion README changes. +- Current-tree verification: `grep -c '^##### Q[1-9] - .*, 2026-10-15, PR #2430$'` on the discussion README prints `9`; with the nine entries removed and "No entries yet." restored under Q4 and Q6, the README is byte-identical to its `develop` version. +- Resolution reference: `docs(discussions): [#2429] add my round on templated documentation` +- Follow-up PR URL: N/A +- Reply URL: + +### F6 - Retitle issue #2429 and drop its EPIC label + +- PR number: 2430 +- Source review ID: 5469057611 +- Reviewer finding ID: N/A +- Source URL: +- Concern: The reviewer asked for issue #2429 to be retitled and its `EPIC` label dropped; it stays the issue the discussion informs (request 2). +- Solution: An issue edit outside the repository tree, made when this rework is pushed: the issue is retitled as the discussion's subject, its `EPIC` label is removed, and its body points to the discussion and its initial proposal. +- Current-tree verification: N/A in the tree; the issue title and labels are checked on GitHub after the edit. +- Resolution reference: +- Follow-up PR URL: N/A +- Reply URL: + +### F7 - Link the PR author's implementation once it is published + +- PR number: 2430 +- Source review ID: 5469057611 +- Reviewer finding ID: N/A +- Source URL: +- Concern: The reviewer asked for a link to the PR author's own implementation of the approach once it is published, as evidence for the discussion (request 3). +- Solution: The implementation is not yet published on a public `develop` branch, so nothing is linked; the link is added in a later round of the discussion once it is. +- Current-tree verification: no file on the branch links the implementation. +- Resolution reference: +- Follow-up PR URL: N/A +- Reply URL: + +### F8 - Remove the binding EPIC specification and keep its text as the initial proposal + +- PR number: 2430 +- Source review ID: 5506433536 +- Reviewer finding ID: N/A +- Source URL: +- Concern: A "previously missed" item anchored at `docs/issues/open/2429-templated-documentation/EPIC.md:69` reported that review 5469057611 and comment 6145187551 supersede the binding EPIC specification, and asked for the file to be removed and its text kept as `initial-proposal.md` in the discussion folder; it is the change F4 records. +- Solution: Re-raise of F4, resolved by F4's change; nothing further. +- Current-tree verification: F4's checks hold at the branch head: `git ls-tree -r HEAD -- docs/issues/open/2429-templated-documentation` prints nothing, and `initial-proposal.md` without its note is byte-identical to the EPIC at the reviewed head. +- Resolution reference: `docs(discussions): [#2429] keep the EPIC text as the proposal's source material` +- Follow-up PR URL: N/A +- Reply URL: + +### F9 - Record the decision owner's review and the rework in the audit + +- PR number: 2430 +- Source review ID: 5506433536 +- Reviewer finding ID: N/A +- Source URL: +- Concern: A "previously missed" item anchored at `docs/pr-reviews/pr-2430-review/PR-REVIEW.md:50` reported that the audit stopped at the round-1 review although review 5469057611 and comment 6145187551 carry actionable rework requests, and asked for them to be normalized with details, dispositions and append-only Processing Log entries. +- Solution: Review 5469057611 is normalized as F4 to F7, the PR retitle that comment 6145187551 adds is recorded in the Processing Log, and this review is normalized as F8 and F9, each row with its detail entry and the Processing Log appended, not rewritten. +- Current-tree verification: the audit validator reports 9 rows, each with its detail entry, and the Processing Log in chronological order. +- Resolution reference: `docs(pr-reviews): [#2429] record the rework of PR #2430 in its audit` +- Follow-up PR URL: N/A +- Reply URL: + +### F10 - The PR description does not say the history was rewritten + +- PR number: 2430 +- Source review ID: 5535382217 +- Reviewer finding ID: F10 +- Source URL: +- Concern: Anchored at the 2026-10-15 10:40 UTC Processing Log entry that records the rebuild, the reviewer noted that the branch was rebuilt and the three EPIC commits dropped, while `AGENTS.md` asks a rewrite to keep every step and to "Say in the PR body that the history was rewritten". Since the content is preserved (the EPIC text in `initial-proposal.md`, round 1 in this audit), the reviewer did not ask to restore the commits, only for a sentence in the PR description. Non-blocking. +- Solution: An edit to the PR body, outside the repository tree; the commits are not restored. A `## History` section before `## Validation` says the history was rewritten during review: the branch was rebuilt from `develop` instead of rebased, and the previous head's three commits are no longer on it. Each commit is named by its subject, with where its content is now: `docs(issues): [#2429] add the templated documentation EPIC specification` (the EPIC text, as `initial-proposal.md`), `docs(issues): [#2429] anchor the counts to the develop head` (the round-1 fix, verbatim at `initial-proposal.md:59`) and `docs(pr-reviews): [#2429] add the PR #2430 review audit record` (round 1, in this audit). The section adds that this audit cites the previous head by its last commit's subject. +- Current-tree verification: `git log --format=%s` over the previous head's three commits prints the three subjects the section names. `sed -n '59p'` on `initial-proposal.md` prints the sentence opening "At `develop` `eb96d2957` the tree tracks 970 Markdown files". F1 to F3 and their log entries are in this record. The Findings introduction names every head by commit subject, never by id. +- Resolution reference: +- Follow-up PR URL: N/A +- Reply URL: + +## Processing Log + +- 2026-10-03 14:52 UTC - Started audit for Copilot review 5401297360 from the captured REST reviews, REST review comments and GraphQL thread list: three inline threads, opened by review comments 4173600206 (F1), 4173600228 (F2) and 4173600238 (F3) and all `resolved=false`, no reviewer finding IDs, no human review, and a body with no independently actionable assertion. Normalized as F1-F3. +- 2026-10-03 14:53 UTC - Re-derived the three claims at the branch head: F1 holds; F2 and F3 do not (`grep -c '^||'` prints `0`). Committed the F1 fix as `docs(issues): [#2429] anchor the counts to the develop head`. +- 2026-10-03 14:54 UTC - Recorded the audit with the thread states as captured. Replies are prepared for all three threads (F1 `FIXED`; F2 and F3 in the prescribed `Superseded by :` form, each naming itself as an original no-change finding) and are posted after the fix and this record are pushed; their URLs, the F2 and F3 resolution references and the final thread states are recorded then. +- 2026-10-03 14:54 UTC - Ran `validate-audit-record.py --pr-number 2430 --base eb96d2957` against the captured round-1 review comments: 3 rows, 3 log entries, 5 failures, all of them the pending replies (no Reply URL yet on F1, F2 and F3; the F2 and F3 resolution references await their reply URLs). Review IDs, detail entries, the F1 commit subject and log order pass; the validator must exit `0` once the reply URLs are recorded. +- 2026-10-03 15:01 UTC - The replies were posted word for word at 15:00:13 (F1), 15:00:15 (F2) and 15:00:17 UTC (F3), each on its own source thread, after the fix commit was pushed. The thread opened by review comment 4173600206 was then resolved with reply , the one opened by 4173600228 with reply , and the one opened by 4173600238 with reply ; a fresh GraphQL capture reports all three `resolved=true`. Recorded the reply URLs, the F2 and F3 resolution references and the thread states. The earlier record commit, made before the replies existed, was replaced by this one, so the committed record carries the posted URLs. +- 2026-10-03 15:01 UTC - Ran `validate-audit-record.py --pr-number 2430 --base eb96d2957` against the fresh review-comment capture, replies included: `{"status": "ok", "rows": 3, "log_entries": 5, "failures": 0}`. +- 2026-10-15 10:40 UTC - Started round 2 from the captured REST reviews and review comments: review 5469057611 (`CHANGES_REQUESTED`, no inline comments, no reviewer finding IDs) normalized as F4-F7 from its three numbered requests; its opening verdict and its answer on timing request no change. The decision owner's conversation comment 6145187551 repeats the requests and adds a retitle of the PR, recorded in the entry below. +- 2026-10-15 10:40 UTC - Rebuilt the branch from `develop` instead of rebasing it: the EPIC specification and its fix commit are no longer carried. Committed F4 as `docs(discussions): [#2429] keep the EPIC text as the proposal's source material` and F5 as `docs(discussions): [#2429] add my round on templated documentation`. F1's fix commit, `docs(issues): [#2429] anchor the counts to the develop head`, is therefore not on the branch; the anchored sentence is carried verbatim at line 59 of `initial-proposal.md`, so F1's Resolution reference now names the commit that carries it. F1 to F3 keep citing `docs/issues/open/2429-templated-documentation/EPIC.md` lines 51, 139 and 213 as they stood when raised; the same lines are 59, 147 and 221 of `initial-proposal.md`. The round-1 head in the Findings introduction is named by its commit subject instead of its id, which is not on `develop`. +- 2026-10-15 10:41 UTC - The PR is retitled from `docs(issues): [#2429] add the templated documentation EPIC specification` to `docs(discussions): [#2429] add my round on templated documentation`, as comment 6145187551 asks, and issue #2429 is retitled without its `EPIC` label (F6), both when this rework is pushed; the link F7 asks for waits until the implementation is published. F6 and F7, and the Reply URLs of F4 to F7, wait for the PR conversation response that states this; its URL is recorded once it is posted. +- 2026-10-15 10:41 UTC - Ran `validate-audit-record.py --pr-number 2430 --base eb96d2957` against the captured review comments: `{"status": "failed", "rows": 7, "log_entries": 10, "failures": 2}`. Both failures are the pending F6 and F7 resolution references; review IDs, detail entries, the F1, F4 and F5 commit subjects and log order pass. The validator must exit `0` once the response URL is recorded. +- 2026-10-15 10:49 UTC - Started round 3 from the captured REST reviews, review comments and thread list: review 5506433536 by the review bot (`COMMENTED`) has no review comment, and the thread list still holds only the three round-1 threads, all resolved. Its "0 open findings" and its three resolved items (the F1 to F3 threads) make no row; its two "previously missed" items, retrievable in full from the review body, are normalized as F8 (`RE_RAISE_OF:F4`, `FIXED` by F4's commit) and F9 (`ORIGINAL`, `FIXED` by the commit that carries this record). Neither has a thread to reply on, so each is answered by the PR conversation response the skill prescribes for review bodies; one response may cover reviews 5469057611 and 5506433536 only if it names both review IDs and F4 to F9 with their dispositions and resolution references, and its URL then goes in each of those rows. +- 2026-10-15 10:49 UTC - Ran `validate-audit-record.py --pr-number 2430 --base eb96d2957` against the captured review comments: `{"status": "failed", "rows": 9, "log_entries": 12, "failures": 2}`. Both failures are still the pending F6 and F7 resolution references; F8 and F9, including F9's subject for this record's own commit, pass. +- 2026-10-15 11:34 UTC - Posted the PR conversation response (comment 6191069336) to reviews 5469057611 and 5506433536 after the rework was pushed, and recorded its URL as the Reply URL of F4 to F9 and as the resolution reference of F6 (the issue #2429 edit: retitled `Templated documentation`, the `EPIC` label removed, the body replaced) and F7 (the follow-up link, which waits for publication). +- 2026-10-15 15:16 UTC - Started round 4 from the captured REST reviews, review comments and thread list: review 5535382217 by the decision owner (`APPROVED`) opens one inline thread, from review comment 4284749699 (`resolved=false`), whose `[Minor][F10]` prefix gives the finding ID and the severity; the approval body requests no change. Normalized as F10, category `documentation`: the concern is prose missing from the PR description, not a structured field such as a title, label or link keyword, which `metadata` covers (F6). +- 2026-10-15 15:16 UTC - Fixed F10 outside the repository tree: the PR body gains a `## History` section, before `## Validation`, saying the history was rewritten, naming the previous head's three commits by subject and saying where each one's content is now. The PR conversation response to review 5535382217 states the edit and is F10's resolution reference; the reply on the thread cites that response, and the thread is resolved after the reply, so F10 is `RESOLVED`. +- 2026-10-15 15:16 UTC - Ran `validate-audit-record.py --pr-number 2430 --base 9c8bcdf8a` against the captured review comments: `{"status": "failed", "rows": 10, "log_entries": 16, "failures": 2}`. Both failures are F10's: its resolution reference and its Reply URL hold placeholders until the response and the reply are posted. Every other check passes, including F10's source review ID and its `Minor` severity against the comment's bracket. + +## Completion Rules + +- Re-derive the reply claim against the current tree before replying or resolving a thread. +- Reply on every resolvable thread before resolving it. +- For an outdated thread whose concern was fixed, record `Disposition=FIXED` and + `Thread state=RESOLVED`, even if GitHub marks the thread outdated after the push. For a + duplicate, superseded, or no-change in-PR thread, reply exactly + `Superseded by : .`, record `Disposition=NO_ACTION` and + `Thread state=SUPERSEDED`, then resolve it. A post-merge `NO_ACTION` requires maintainer + approval to decline the follow-up work. +- A consolidated PR conversation response may cover multiple review rounds only when it names + every review ID and every finding ID with its disposition and resolution reference. Record its + durable URL in each related row. +- Cite a fix by its unique Conventional Commit subject or durable reply URL, never by a branch SHA + that can change after a rebase. +- Refresh review threads using GraphQL and confirm that no unresolved actionable thread remains.