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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ on:
branches:
- main
- v2
- v2.1

permissions:
contents: read
Expand All @@ -22,6 +23,29 @@ jobs:
with:
fetch-depth: 0

- name: Enforce pull request branch policy
if: github.event_name == 'pull_request'
shell: bash
env:
BASE_REF: ${{ github.base_ref }}
HEAD_REF: ${{ github.head_ref }}
run: |
if [[ "$BASE_REF" == "v2.1" ]]; then
if [[ "$HEAD_REF" =~ ^(feat|fix|test|chore)/.+$ ]]; then
exit 0
fi
echo "::error::Pull requests into v2.1 must come from feat/*, fix/*, test/*, or chore/* branches."
exit 1
fi

if [[ "$BASE_REF" == "main" ]]; then
if [[ "$HEAD_REF" == "v2.1" || "$HEAD_REF" =~ ^hotfix/.+$ ]]; then
exit 0
fi
echo "::error::Only v2.1 release promotion or an explicit hotfix/* branch may target main."
exit 1
fi

- name: Set up Node.js
uses: actions/setup-node@v4
with:
Expand Down
28 changes: 28 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Repository workflow policy

## Protected branches

- `main` is production-only. `v2.1` is the active integration branch for the v2.1.0 development cycle.
- Never commit feature, fix, test, or chore work directly to `main` or `v2.1`.

## Development work

- Create short-lived `feat/*`, `fix/*`, `test/*`, or `chore/*` branches from `v2.1`.
- Sprint and routine development pull requests target `v2.1`, not `main`.
- After a successful merge, delete only the merged short-lived branch when cleanup is authorized.

## Merge authority

- Creating a pull request and merging it are separate operations.
- An agent must not merge its own pull request automatically. Passing CI does not authorize a merge.
- Merge only when the user explicitly requests that specific merge after review. Never enable auto-merge without an explicit request.

## Production promotion

- Normal development reaches `main` only through a dedicated release or hardening pull request from `v2.1`.
- Creating the v2.1.0 tag or release requires separate explicit authorization after final verification.

## Hotfixes

- Production hotfixes use a dedicated `hotfix/*` branch and pull request into `main`.
- Never push a hotfix directly to `main`.
1 change: 1 addition & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ The root [README](../README.md) introduces Secure Tools. This directory owns det
| Document | Responsibility |
| --- | --- |
| [Architecture](./architecture.md) | Static application structure, shared browser foundations, delivery, and future ecosystem direction |
| [Development workflow](./development-workflow.md) | Production, integration, Sprint, release, hotfix, merge-authority, and branch-cleanup rules |
| [Privacy model](./privacy-model.md) | Local-processing and network boundaries, storage, security controls, and bounded privacy claims |
| [Dependencies](./dependencies.md) | Production runtime inventory, versions, vendoring, licenses, and integrity ownership |
| [Local OCR foundation](./ocr-foundation.md) | Self-hosted Tesseract assets, languages, lifecycle, cancellation, caching, and privacy guarantees |
Expand Down
43 changes: 43 additions & 0 deletions docs/development-workflow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Development workflow

Secure Tools separates product versions from Sprint numbers. A Sprint is a bounded unit of work within a product development cycle; it does not create a version, tag, or release by itself. Sprint 16B, for example, belongs to the v2.1.0 development cycle.

## Branch roles

- `main` is the production branch. Routine development does not target it.
- `v2.1` is the integration branch for the v2.1.0 cycle.
- Short-lived `feat/*`, `fix/*`, `test/*`, and `chore/*` branches start from `v2.1` and return through pull requests into `v2.1`.
- Direct feature or fix commits to `main` or `v2.1` are prohibited.

```text
main (production)
release PR after hardening
v2.1 (integration)
Sprint PRs
feat/* fix/* test/* chore/*
```

## Sprint delivery

1. Update local `v2.1` from `origin/v2.1`.
2. Create a short-lived branch from that exact integration state.
3. Commit and validate only the Sprint’s intended changes.
4. Open a pull request into `v2.1` and wait for required CI.
5. Treat review and merge as a separate step. An agent does not merge its own pull request or enable auto-merge unless the user explicitly authorizes that specific action.
6. After a successful merge and verification, remove the merged short-lived branch when branch cleanup is authorized.

## Production release

After the v2.1.0 scope is integrated, complete release hardening and final verification on `v2.1`. Promote it through a dedicated `v2.1` → `main` pull request. Only after that pull request is explicitly reviewed and merged may a separately authorized task create the v2.1.0 tag and release.

## Hotfixes

Urgent production fixes use a dedicated `hotfix/*` branch and pull request into `main`. They are never pushed directly. After production verification, carry the correction back into the active integration line as needed through an appropriate pull request.

## Enforced pull request policy

CI permits routine `feat/*`, `fix/*`, `test/*`, and `chore/*` pull requests into `v2.1`. Pull requests into `main` pass the branch-policy gate only when the head is exactly `v2.1` or a dedicated `hotfix/*` branch. The repository protects both long-lived branches with required pull requests, the existing `Validate static tools` check, resolved review conversations, blocked force pushes, and blocked deletion. Because the repository currently has one maintainer, an approving-review count is not required; explicit merge authorization remains mandatory.
10 changes: 9 additions & 1 deletion tests/ci-foundation.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,19 @@ import fs from "node:fs";
const workflow = fs.readFileSync(".github/workflows/ci.yml", "utf8");
assert.match(workflow, /^name: CI$/m);
assert.match(workflow, /^\s{2}pull_request:$/m);
assert.match(workflow, /^\s{2}push:\s*$[\s\S]*?^\s{6}- main$[\s\S]*?^\s{6}- v2$/m);
assert.match(workflow, /^\s{2}push:\s*$[\s\S]*?^\s{6}- main$[\s\S]*?^\s{6}- v2$[\s\S]*?^\s{6}- v2\.1$/m);
assert.match(workflow, /uses: actions\/checkout@v4/);
assert.match(workflow, /uses: actions\/setup-node@v4/);
assert.match(workflow, /node-version: 24/);
assert.match(workflow, /permissions:\s*\n\s+contents: read/);
assert.match(workflow, /name: Enforce pull request branch policy/);
assert.match(workflow, /if: github\.event_name == 'pull_request'/);
assert.match(workflow, /BASE_REF: \$\{\{ github\.base_ref \}\}/);
assert.match(workflow, /HEAD_REF: \$\{\{ github\.head_ref \}\}/);
assert.match(workflow, /\^\(feat\|fix\|test\|chore\)\/\.\+\$/);
assert.match(workflow, /HEAD_REF" == "v2\.1"/);
assert.match(workflow, /\^hotfix\/\.\+\$/);
assert.match(workflow, /Only v2\.1 release promotion or an explicit hotfix/);
assert.match(workflow, /git diff --check/);
assert.match(workflow, /run: npm ci --ignore-scripts/);
assert.match(workflow, /run: npm run build/);
Expand Down
Loading