diff --git a/docs/README.md b/docs/README.md index 3e4cd0eb..b26f8ecf 100644 --- a/docs/README.md +++ b/docs/README.md @@ -7,6 +7,7 @@ picking the right document for what you're capturing. | Artifact | For | |---|---| +| **Design workflow** (`docs/design-workflow.md`) | How discussion becomes canonical design, how dependencies are recorded, and when implementation may begin. | | **Glossary** (`docs/glossary.md`) | Canonical term definitions — what words mean in this project. | | **PRD** (`docs/prds/`) | A feature: what to build, scope, success criteria. | | **Research note** (`docs/research/`) | A landscape/tradeoff investigation. | diff --git a/docs/decisions/0005-issues-own-tracker-metadata.md b/docs/decisions/0005-issues-own-tracker-metadata.md index 14220cab..5eb3b2d5 100644 --- a/docs/decisions/0005-issues-own-tracker-metadata.md +++ b/docs/decisions/0005-issues-own-tracker-metadata.md @@ -1,9 +1,13 @@ -# ADR 0005: Keep tracker metadata on issues +# ADR 0005: Keep tracker metadata on one tracker object - **Status:** Accepted - **Date:** 2026-07-18 - **Deciders:** didericis +> **Amended 2026-07-26.** A pull request may carry labels directly instead of +> linking a tracking issue. When a PR does link an issue, the issue remains the +> canonical owner of planning metadata and the reference is validated. + ## Context Gitea exposes labels on both issues and pull requests. Applying the same labels @@ -20,19 +24,29 @@ would make the issue history less truthful. ## Decision -Issues are the canonical tracker records and own labels. Every issue has at -least one label. An issue opened or left without labels receives -`Status/Needs Triage` automatically until it is classified. +Issues are the canonical tracker records and own labels when a separate work +item exists. Every issue has at least one label. An issue opened or left +without labels receives `Status/Needs Triage` automatically until it is +classified. -Pull requests carry no labels. Every new PR deliberately references at least -one existing issue in its title or description with one of these forms: +Every new pull request is tracked in exactly one of two mutually exclusive +ways: + +1. It deliberately references at least one existing issue in its title or + description. Tracker metadata stays on that issue and the PR remains + unlabelled. +2. It carries at least one label directly when a separate issue would add no + useful planning context. + +Issue references use one of these forms: - `Closes #123`, `Fixes #123`, or `Resolves #123` when merging completes it. - `Part of #123`, `Related to #123`, `Refs #123`, or `References #123` when it contributes without completing it. -Gitea Actions enforces both PR rules as a status check and repairs the empty -issue-label state. Branch protection makes the PR policy check required. +Gitea Actions enforces the exclusive either/or PR rule, validates any issue +references, and repairs the empty issue-label state. Branch protection makes +the PR policy check required. The policy applies from 2026-07-18 onward. Existing issues may be labelled as they are encountered, but closed PRs are grandfathered: no retrospective @@ -40,9 +54,13 @@ issues or PR labels are created solely to make history conform. ## Consequences -- Classification, priority, and workflow metadata have one source of truth. -- A PR's issue link is the navigation path to its planning metadata. +- Classification, priority, and workflow metadata have one source of truth for + each change: the linked issue when one exists, otherwise the PR. +- For issue-backed changes, the PR's issue link is the navigation path to its + planning metadata. - Multi-PR issues do not require copied or synchronized labels. +- Small standalone changes do not require a tracking issue created solely to + satisfy automation. - `Status/Needs Triage` is an intentional fallback, not a final classification. - Direct issue creation remains convenient; automation repairs a missing label @@ -53,5 +71,6 @@ issues or PR labels are created solely to make history conform. ## Links - Issue #405. -- `.gitea/workflows/tracker-policy.yml`. +- `.gitea/workflows/tracker-policy-pr.yml`. +- `.gitea/workflows/tracker-policy-issues.yml`. - `scripts/tracker_policy.py`. diff --git a/docs/design-workflow.md b/docs/design-workflow.md new file mode 100644 index 00000000..469fd8e4 --- /dev/null +++ b/docs/design-workflow.md @@ -0,0 +1,218 @@ +# Design workflow + +How bot-bottle turns discussion into canonical design and then into +implementation without leaving the repository's architecture scattered across +issue and review threads. + +The goal is not more documentation. The goal is one discoverable current answer +for every load-bearing design question. + +## Sources of truth + +Design artifacts have different jobs: + +| Artifact | Authority | +|---|---| +| Decision records | Stable system-wide boundaries, policies, and invariants | +| PRDs | The current design for a feature | +| Research notes | Evidence and tradeoff analysis; informative, not normative | +| Issues | Work tracking, open questions, and discussion | +| Pull-request comments | Review history; never the final home of a design decision | + +When a discussion changes the design, update the relevant PRD or decision +record before treating the discussion as resolved. A comment may explain why a +decision changed, but future implementers must not need to reconstruct the +decision from a thread. + +Avoid duplicating the same rule in several canonical documents. Prefer one +canonical statement and links from dependent documents. + +## Choosing the canonical artifact + +Use a PRD when the decision describes a feature: its behavior, scope, success +criteria, trust model, implementation slices, and tests. + +Use a decision record when the choice is broader than one feature or will +constrain several future features. Examples include state ownership, credential +boundaries, compatibility policy, and what the project does or does not claim +as a security guarantee. + +Use a research note when the conclusion depends on comparing external systems, +protocols, or approaches. Promote any resulting project decision into a PRD or +decision record. + +## From discussion to implementation + +### 1. Open the design discussion + +An issue may start with incomplete requirements. Record: + +- the problem and desired outcome; +- known security or compatibility constraints; +- the current owner of affected state and credentials; +- related PRDs, decisions, issues, and pull requests; +- open questions that would materially change the implementation. + +Do not disguise an unresolved trust-boundary or state-ownership decision as an +implementation detail. + +### 2. Draft or update the canonical design + +Before substantial implementation, write the feature PRD and update any +system-wide decision it changes. + +An active design should make these relationships visible near its top: + +```markdown +Status: Draft | Active | Superseded | Retargeted +Depends on: #... +Supersedes: ... +``` + +Record dependencies only on the dependent document. Do not maintain reverse +`Blocks` lists that can drift as dependent work changes. + +For security-sensitive work, state: + +- the exact guarantee and explicit non-guarantees; +- trusted and untrusted components; +- who creates each identity or attribution field; +- who owns durable state; +- failure and recovery behavior; +- how the design is tested at its boundaries. + +### 3. Resolve review into the repository + +When review settles a design-changing question: + +1. Update the canonical document in the same pull request. +2. Mark conflicting documents Superseded or Retargeted, or update them. +3. Add or adjust dependency links. +4. Leave a concise resolution comment linking to the canonical change. + +A useful resolution comment is: + +```text +Resolution: +Canonicalized in: +Supersedes: +Follow-up: +``` + +The resolution is incomplete until the repository reflects it. + +### 4. Check design readiness + +Implementation may begin when: + +- the PRD's material trust, ownership, and compatibility questions are settled; +- dependencies and blockers are explicit; +- the design agrees with current architecture and decision records; +- superseded documents are marked or updated; +- success criteria and boundary tests are concrete; +- remaining open questions can be answered during implementation without + changing the feature's guarantee or component ownership. + +Small exploratory spikes may happen earlier. A spike proves feasibility; it does +not establish a production contract or silently settle the design. + +### 5. Implement in ordered slices + +Prefer small, independently reviewable slices after the parent design is +accepted. Record the dependency chain explicitly. + +Parallel work is safe when slices do not compete for the same unsettled +interface or ownership boundary. If a foundational change will alter the +transport, schema, state owner, or trust domain used by another slice, land the +foundation first. + +An implementation pull request should identify: + +- the PRD or decision it implements; +- the implementation chunk; +- its base and blockers; +- any design deviation discovered during implementation. + +If implementation reveals a load-bearing design change, pause that slice and +update the canonical design. Do not let the code and review thread become an +undocumented replacement for the PRD. + +## Dependency and staleness management + +### Dependency direction + +Write dependencies in terms of contracts, not chronology: + +```text +credential provisioning contract + -> host-controller authentication + -> privileged host operations +``` + +If only part of a feature is blocked, say so. For example, a manifest parser may +proceed while that feature's durable audit-storage chunk waits for the canonical +audit schema. + +### Superseding documents + +Do not silently edit history to make an old design appear to have always said +the new thing. Preserve the rationale, but make current status unmistakable: + +```markdown +Status: Superseded +Superseded by: +Reason: +``` + +If part of a PRD remains valid, mark it Retargeted and identify which scope moved +elsewhere. + +Add a short supersession note near the top explaining what changed, why the old +design is no longer current, and where the current design lives. For a research +note whose original analysis remains useful, preserve that analysis and append +a dated addendum with the newer finding instead of rewriting the note as though +it had always reached the new conclusion. + +### Architecture sweeps + +After a foundational change, do a targeted architecture sweep before building +more features on it: + +1. Identify the concepts the change affects, such as `bot-bottle.db`, host + controller, orchestrator, audit ownership, or signing key. +2. Search active PRDs, decisions, and open issues for those concepts. +3. Update or supersede contradictory statements. +4. Refresh dependency links and the current architecture summary. +5. Confirm stacked implementation branches still have the correct base. + +This is a milestone activity, not a recurring documentation ceremony. + +## Pull-request checklist + +Use the relevant items in design and implementation pull requests: + +- [ ] The canonical PRD or decision is linked. +- [ ] Design-changing review decisions are reflected in-repo. +- [ ] Dependencies and blockers are explicit. +- [ ] State, credential, and trust ownership agree with current architecture. +- [ ] Superseded or retargeted documents are marked. +- [ ] Security guarantees and non-guarantees are precise. +- [ ] Open questions do not change the promised guarantee or ownership model. +- [ ] Implementation deviations updated the canonical design. + +## Lightweight maintenance + +Automation should enforce document shape, not pretend to understand +architecture. Useful checks include: + +- active PRDs contain status and dependency metadata; +- superseded PRDs link to their replacement; +- referenced documents and issues exist; +- implementation pull requests identify their PRD and chunk; +- document filenames and lifecycle states follow repository conventions. + +Human review remains responsible for detecting conflicting guarantees or +ownership claims. + +The durable rule is simple: **discussion discovers the decision; the repository +records it; implementation follows it.** diff --git a/docs/prds/0019-active-agents-in-dashboard.md b/docs/prds/0019-active-agents-in-dashboard.md index 1052c032..44f558ad 100644 --- a/docs/prds/0019-active-agents-in-dashboard.md +++ b/docs/prds/0019-active-agents-in-dashboard.md @@ -4,6 +4,11 @@ - **Author:** didericis - **Created:** 2026-05-26 +> **Superseded.** PRD 0049 removed the active-agents pane and agent-scoped +> operator edit verbs when the dashboard was narrowed back to a proposal-only +> supervise TUI. A future agent-management surface was deferred rather than +> carried forward from this design. The design below is retained as history. + ## Summary The dashboard today is proposal-centric: it lists every pending diff --git a/docs/prds/0020-start-and-attach-from-dashboard.md b/docs/prds/0020-start-and-attach-from-dashboard.md index 1088088a..a88d349e 100644 --- a/docs/prds/0020-start-and-attach-from-dashboard.md +++ b/docs/prds/0020-start-and-attach-from-dashboard.md @@ -4,6 +4,11 @@ - **Author:** didericis - **Created:** 2026-05-26 +> **Superseded.** PRD 0049 removed start, re-attach, and stop actions from the +> dashboard when it became the proposal-only supervise TUI. Bottle lifecycle +> remains in the dedicated CLI commands; no dashboard replacement from this +> design remains active. The design below is retained as history. + ## Summary Today the dashboard is read-only: it surfaces pending proposals diff --git a/docs/prds/0021-dashboard-tmux-split-pane.md b/docs/prds/0021-dashboard-tmux-split-pane.md index ca5fd0bd..abe8e7b1 100644 --- a/docs/prds/0021-dashboard-tmux-split-pane.md +++ b/docs/prds/0021-dashboard-tmux-split-pane.md @@ -4,6 +4,11 @@ - **Author:** didericis - **Created:** 2026-05-26 +> **Superseded.** PRD 0049 removed agent handoff and tmux pane management when +> the dashboard was reduced to the proposal-only supervise TUI. The split-pane +> interaction described below has no active replacement and is retained only +> as design history. + ## Summary When the dashboard runs inside tmux, lay it out as the **left diff --git a/scripts/tracker_policy.py b/scripts/tracker_policy.py index 4494875f..144aa27a 100644 --- a/scripts/tracker_policy.py +++ b/scripts/tracker_policy.py @@ -54,18 +54,20 @@ def check_pull_request(event: dict[str, Any], api: GiteaApi) -> list[str]: pull = event["pull_request"] errors: list[str] = [] labels = pull.get("labels") or [] - if labels: - errors.append( - "PRs must be unlabeled; put tracker metadata on the linked issue " - f"(found: {', '.join(label['name'] for label in labels)})." - ) - numbers = deliberate_issue_numbers(pull.get("title", ""), pull.get("body", "")) - if not numbers: + if labels and numbers: errors.append( - "PR must reference an issue with Closes/Fixes/Resolves #N, " - "Part of #N, Related to #N, Refs #N, or References #N." + "PR must use exactly one tracking mode: remove PR labels when " + "linking an issue, or remove the issue reference when labels " + "belong on the PR." ) + if not numbers: + if not labels: + errors.append( + "PR must either have a label or reference an issue with " + "Closes/Fixes/Resolves #N, Part of #N, Related to #N, " + "Refs #N, or References #N." + ) return errors real_issues = 0 diff --git a/tests/unit/test_tracker_policy.py b/tests/unit/test_tracker_policy.py index be79a174..3dba3c3a 100644 --- a/tests/unit/test_tracker_policy.py +++ b/tests/unit/test_tracker_policy.py @@ -27,7 +27,41 @@ class TestCheckPullRequest(unittest.TestCase): event = {"pull_request": {"title": "Change", "body": "Part of #12", "labels": []}} self.assertEqual(check_pull_request(event, api), []) - def test_rejects_labels_and_pr_reference(self): + def test_accepts_labelled_pr_without_issue(self): + api = Mock() + event = { + "pull_request": { + "title": "Change", + "body": "", + "labels": [{"name": "Kind/Documentation"}], + } + } + self.assertEqual(check_pull_request(event, api), []) + api.request.assert_not_called() + + def test_rejects_unlabelled_pr_without_issue(self): + api = Mock() + event = {"pull_request": {"title": "Change", "body": "", "labels": []}} + errors = check_pull_request(event, api) + self.assertEqual(len(errors), 1) + self.assertIn("either have a label or reference an issue", errors[0]) + api.request.assert_not_called() + + def test_rejects_labelled_pr_linked_to_real_issue(self): + api = Mock() + api.request.return_value = {"number": 12, "pull_request": None} + event = { + "pull_request": { + "title": "Change", + "body": "Closes #12", + "labels": [{"name": "Kind/Documentation"}], + } + } + errors = check_pull_request(event, api) + self.assertEqual(len(errors), 1) + self.assertIn("exactly one tracking mode", errors[0]) + + def test_still_validates_issue_reference_when_both_modes_are_used(self): api = Mock() api.request.return_value = {"number": 12, "pull_request": {}} event = { @@ -39,7 +73,7 @@ class TestCheckPullRequest(unittest.TestCase): } errors = check_pull_request(event, api) self.assertEqual(len(errors), 2) - self.assertIn("unlabeled", errors[0]) + self.assertIn("exactly one tracking mode", errors[0]) self.assertIn("not an issue", errors[1])