ci(prd): require manual numbering before merge
prd-number-check / require-numbered-prds (pull_request) Successful in 10s
tracker-policy-pr / check-pr (pull_request) Successful in 14s
test / unit (pull_request) Successful in 54s
test / integration-docker (pull_request) Successful in 54s
test / coverage (pull_request) Successful in 18s
test / integration-docker (push) Successful in 16s
test / unit (push) Successful in 50s
lint / lint (push) Successful in 1m0s
test / coverage (push) Successful in 15s
Update Quality Badges / update-badges (push) Successful in 4m1s
prd-number-check / require-numbered-prds (pull_request) Successful in 10s
tracker-policy-pr / check-pr (pull_request) Successful in 14s
test / unit (pull_request) Successful in 54s
test / integration-docker (pull_request) Successful in 54s
test / coverage (pull_request) Successful in 18s
test / integration-docker (push) Successful in 16s
test / unit (push) Successful in 50s
lint / lint (push) Successful in 1m0s
test / coverage (push) Successful in 15s
Update Quality Badges / update-badges (push) Successful in 4m1s
This commit was merged in pull request #490.
This commit is contained in:
@@ -0,0 +1,26 @@
|
|||||||
|
name: prd-number-check
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
types: [opened, reopened, synchronize]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
require-numbered-prds:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Reject unnumbered PRDs
|
||||||
|
run: |
|
||||||
|
unnumbered=$(find docs/prds -maxdepth 1 -type f \
|
||||||
|
-name 'prd-new-*.md' -print | sort)
|
||||||
|
|
||||||
|
if [ -n "$unnumbered" ]; then
|
||||||
|
echo "::error::Assign every new PRD its final sequential number before merge."
|
||||||
|
echo "Unnumbered PRDs:"
|
||||||
|
echo "$unnumbered"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "All PRDs have final numbers."
|
||||||
@@ -1,122 +0,0 @@
|
|||||||
# Assign sequential numbers to prd-new-*.md files on merge to main.
|
|
||||||
#
|
|
||||||
# When a PR merges to main and includes prd-new-*.md files this workflow:
|
|
||||||
# 1. Finds the next available NNNN number by scanning existing PRDs.
|
|
||||||
# 2. Renames each prd-new-*.md to NNNN-<slug>.md.
|
|
||||||
# 3. Updates the title header (# PRD prd-new: → # PRD NNNN:).
|
|
||||||
# 4. Flips Status: Draft → Active when the push touched files outside
|
|
||||||
# docs/prds/ anywhere in its commit range (i.e. the implementation
|
|
||||||
# shipped together with the PRD).
|
|
||||||
# 5. Commits the renaming back to main.
|
|
||||||
#
|
|
||||||
# No-op if the working tree contains no prd-new-*.md files.
|
|
||||||
#
|
|
||||||
# NOTE: The workflow scans the working tree (not just HEAD~1..HEAD) because
|
|
||||||
# PRs land as multi-commit pushes and the prd-new file is often added in an
|
|
||||||
# earlier commit on the branch, not in the final squash/merge commit.
|
|
||||||
|
|
||||||
name: prd-number
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
paths:
|
|
||||||
- 'docs/prds/prd-new-*.md'
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
assign-numbers:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
permissions:
|
|
||||||
contents: write
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
|
|
||||||
# No actions/setup-python: the inline script is stdlib-only on the
|
|
||||||
# image's system Python 3.12 (older act_runner mishandles its PATH).
|
|
||||||
- name: Configure git
|
|
||||||
run: |
|
|
||||||
git config user.name "github-actions[bot]"
|
|
||||||
git config user.email "github-actions[bot]@users.noreply.github.com"
|
|
||||||
|
|
||||||
- name: Assign PRD numbers
|
|
||||||
run: |
|
|
||||||
python3 - <<'EOF'
|
|
||||||
import os
|
|
||||||
import re
|
|
||||||
import subprocess
|
|
||||||
import sys
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
prds_dir = Path("docs/prds")
|
|
||||||
|
|
||||||
# Scan the working tree — prd-new files may have landed in any
|
|
||||||
# commit of a multi-commit push, not just HEAD.
|
|
||||||
new_prds = sorted(prds_dir.glob("prd-new-*.md"))
|
|
||||||
|
|
||||||
if not new_prds:
|
|
||||||
print("No prd-new-*.md files found — nothing to do.")
|
|
||||||
sys.exit(0)
|
|
||||||
|
|
||||||
# Determine whether non-PRD files were also changed anywhere in
|
|
||||||
# the push range (BEFORE_SHA → HEAD). Falls back to HEAD~1 when
|
|
||||||
# the env var isn't set (e.g. local act runs).
|
|
||||||
before_sha = os.environ.get("GITHUB_EVENT_BEFORE", "HEAD~1")
|
|
||||||
all_changed = subprocess.run(
|
|
||||||
["git", "diff", "--name-only", before_sha, "HEAD"],
|
|
||||||
capture_output=True, text=True, check=True,
|
|
||||||
).stdout.splitlines()
|
|
||||||
non_prd_changed = any(
|
|
||||||
not f.startswith("docs/prds/") for f in all_changed
|
|
||||||
)
|
|
||||||
|
|
||||||
# Find next available number.
|
|
||||||
existing = sorted(
|
|
||||||
int(m.group(1))
|
|
||||||
for p in prds_dir.glob("*.md")
|
|
||||||
if (m := re.match(r"^(\d{4})-", p.name))
|
|
||||||
)
|
|
||||||
next_num = (max(existing) + 1) if existing else 1
|
|
||||||
|
|
||||||
for prd_path in sorted(new_prds):
|
|
||||||
slug = re.sub(r"^prd-new-", "", prd_path.stem)
|
|
||||||
new_name = f"{next_num:04d}-{slug}.md"
|
|
||||||
new_path = prds_dir / new_name
|
|
||||||
print(f" {prd_path.name} → {new_name}")
|
|
||||||
|
|
||||||
content = prd_path.read_text()
|
|
||||||
|
|
||||||
# Update title header.
|
|
||||||
content = re.sub(
|
|
||||||
r"^(#\s+PRD\s+)prd-new(:)",
|
|
||||||
rf"\g<1>{next_num:04d}\2",
|
|
||||||
content,
|
|
||||||
count=1,
|
|
||||||
flags=re.MULTILINE,
|
|
||||||
)
|
|
||||||
|
|
||||||
# Conditionally flip Status.
|
|
||||||
if non_prd_changed:
|
|
||||||
content = re.sub(
|
|
||||||
r"(\*\*Status:\*\*\s*)Draft",
|
|
||||||
r"\g<1>Active",
|
|
||||||
content,
|
|
||||||
count=1,
|
|
||||||
)
|
|
||||||
|
|
||||||
new_path.write_text(content)
|
|
||||||
subprocess.run(["git", "rm", str(prd_path)], check=True)
|
|
||||||
subprocess.run(["git", "add", str(new_path)], check=True)
|
|
||||||
next_num += 1
|
|
||||||
|
|
||||||
subprocess.run(
|
|
||||||
["git", "commit", "-m", "ci(prd): assign sequential numbers to new PRDs"],
|
|
||||||
check=True,
|
|
||||||
)
|
|
||||||
subprocess.run(["git", "push"], check=True)
|
|
||||||
EOF
|
|
||||||
@@ -44,10 +44,10 @@ backend remains available with `BOT_BOTTLE_BACKEND=docker` or
|
|||||||
|
|
||||||
- Three kinds of doc, each with its own conventions in-folder; see
|
- Three kinds of doc, each with its own conventions in-folder; see
|
||||||
`docs/README.md` for when to write which:
|
`docs/README.md` for when to write which:
|
||||||
- **PRDs** (`docs/prds/`) — one feature per file. While a PR is open
|
- **PRDs** (`docs/prds/`) — one feature per file. A draft may initially
|
||||||
the file is named `prd-new-<kebab>.md`; CI assigns a sequential
|
use `prd-new-<kebab>.md`, but its author must assign the next
|
||||||
number on merge to `main` and renames it. A `Status:` line tracks
|
sequential number before merge; CI rejects unnumbered PRDs. A
|
||||||
lifecycle: Draft → Active (shipped to `main`) →
|
`Status:` line tracks lifecycle: Draft → Active (shipped to `main`) →
|
||||||
Superseded/Retargeted. Format in `docs/prds/README.md`.
|
Superseded/Retargeted. Format in `docs/prds/README.md`.
|
||||||
- **Research notes** (`docs/research/`) — opinionated investigations;
|
- **Research notes** (`docs/research/`) — opinionated investigations;
|
||||||
unnumbered kebab-case, freeform and verdict-first. See
|
unnumbered kebab-case, freeform and verdict-first. See
|
||||||
|
|||||||
@@ -113,7 +113,7 @@ _MIGRATIONS = TableMigrations(
|
|||||||
# egress allowlist / routes / git config selected by source IP. The
|
# egress allowlist / routes / git config selected by source IP. The
|
||||||
# multi-tenant gateway resolves it per request via `attribute`.
|
# multi-tenant gateway resolves it per request via `attribute`.
|
||||||
"ALTER TABLE orchestrator_bottles ADD COLUMN policy TEXT NOT NULL DEFAULT ''",
|
"ALTER TABLE orchestrator_bottles ADD COLUMN policy TEXT NOT NULL DEFAULT ''",
|
||||||
# v4 — per-bottle encrypted egress secrets (PRD prd-new-secret-provider).
|
# v4 — per-bottle encrypted egress secrets (PRD 0080).
|
||||||
# One row per env-var: key (env-var name) is plaintext for auditing;
|
# One row per env-var: key (env-var name) is plaintext for auditing;
|
||||||
# value is the encrypted token string. The encryption key (ENV_VAR_SECRET)
|
# value is the encrypted token string. The encryption key (ENV_VAR_SECRET)
|
||||||
# lives only in the agent's environment — a row alone cannot recover the
|
# lives only in the agent's environment — a row alone cannot recover the
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
"""Symmetric encryption for per-bottle egress secrets (PRD prd-new-secret-provider).
|
"""Symmetric encryption for per-bottle egress secrets (PRD 0080).
|
||||||
|
|
||||||
Each agent receives a random ENV_VAR_SECRET at startup — passed as an env var,
|
Each agent receives a random ENV_VAR_SECRET at startup — passed as an env var,
|
||||||
never logged or persisted. The host uses this key to encrypt each egress auth
|
never logged or persisted. The host uses this key to encrypt each egress auth
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
# PRD prd-new: Encrypted at-rest egress secrets (SecretProvider, interim slice)
|
# PRD 0080: Encrypted at-rest egress secrets (SecretProvider, interim slice)
|
||||||
|
|
||||||
- **Status:** Draft
|
- **Status:** Draft
|
||||||
- **Author:** didericis
|
- **Author:** didericis
|
||||||
+8
-5
@@ -7,10 +7,13 @@ document vs. a research note or a decision record).
|
|||||||
|
|
||||||
## Naming and numbering
|
## Naming and numbering
|
||||||
|
|
||||||
New PRDs use a `prd-new-<kebab-title>.md` placeholder name while the PR
|
New PRDs may use a `prd-new-<kebab-title>.md` placeholder name while the
|
||||||
is open. On merge to `main` a CI workflow assigns the next sequential
|
design is being drafted. Before merge, assign the next sequential number
|
||||||
number (`0024-…`, `0025-…`), renames the file, and updates the title
|
after the highest-numbered PRD on `main`, rename the file to
|
||||||
header. Numbers are never reused; gaps are fine.
|
`NNNN-<kebab-title>.md`, and update the title header. CI blocks merging
|
||||||
|
while any `prd-new-*.md` placeholder remains. If concurrent PRs select the
|
||||||
|
same number, the later PR must take the next available number before it
|
||||||
|
merges. Numbers are never reused; gaps are fine.
|
||||||
|
|
||||||
Once numbered, the filename stays fixed for the life of the doc.
|
Once numbered, the filename stays fixed for the life of the doc.
|
||||||
|
|
||||||
@@ -26,7 +29,7 @@ The `Status:` line near the top tracks the PRD's lifecycle:
|
|||||||
## Format
|
## Format
|
||||||
|
|
||||||
```markdown
|
```markdown
|
||||||
# PRD prd-new: <short title> ← placeholder; CI fills in the number on merge
|
# PRD prd-new: <short title> ← replace with the final number before merge
|
||||||
|
|
||||||
- **Status:** Draft
|
- **Status:** Draft
|
||||||
- **Author:** <who>
|
- **Author:** <who>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
"""Unit tests for per-bottle egress secret encryption (PRD prd-new-secret-provider)."""
|
"""Unit tests for per-bottle egress secret encryption (PRD 0080)."""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user