CI upkeep #488

Merged
didericis merged 6 commits from docs/design-workflow into main 2026-07-26 02:54:57 -04:00
8 changed files with 311 additions and 22 deletions
+1
View File
@@ -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. |
@@ -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`.
+218
View File
@@ -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: <what was decided>
Canonicalized in: <document/section/commit>
Supersedes: <older statement, if any>
Follow-up: <remaining implementation or question>
```
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: <document>
Reason: <one paragraph>
```
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.**
@@ -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
@@ -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
@@ -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
+11 -9
View File
@@ -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
+36 -2
View File
@@ -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])