The Merge Gate Charter
Project19 min · Slice → Gate → Run → Wrap → Prove · Updated 2026-10-28
You end this lesson with: factory/merge-gate-charter.md — review gate, holdout set, protected paths and risk lanes merged into one charter for one repo, with a red control for each gate
Chapters
- Brief01
- Steps02
- Acceptance03
- Rubric04
- Key Takeaways05
- What You Have Now06
Project 01. The Merge Gate Charter
Illustrative composite. Parcelwise is an invented six-person shipping-quote service. In its first month with agents, four gates were written by four people on four afternoons: a review gate, a hidden holdout set, a protected-path list and a risk-lane file. Each file was sensible alone. On a Tuesday an agent opened a 12-line pull request that deleted one assertion from the tenant-isolation test. The review gate read "tests only, under 40 lines" and called it tier 0, no human. The risk-lane file listed that same folder as a high-lane trigger. Nobody had ever laid the two files side by side, so the gate that ran first won, the change merged unread, and the test stayed missing for nine days. Modeled cost: one customer's rate card showed up in another customer's quote.
You already hold the four parts from Lessons 04 to 07. This project joins them. You will write the four module files for the Parcelwise repo, place twelve real-shaped pull requests through each module on its own, watch the files disagree, and settle every disagreement in one charter. The charter also needs a red control for each gate: a deliberately bad change that must fail, run before you trust the gate. The deliverable is factory/merge-gate-charter.md.
Brief
You are the platform lead's second pair of hands. Everything you need is in this section. Nothing external is required.
The repo and the people
The service is parcelwise-api: a quote engine that takes a parcel and a destination zone and returns a price. The agent identity that opens pull requests is parcelwise-agent[bot]. The factory/ directory you write is the team's private governance checkout, parcelwise-factory. The agent identity has no grant on it, and the service repo only runs the gate scripts that read from it.
parcelwise-api/ AGENTS.md rules the agent reads each run REVIEW.md instructions for the AI reviewer package.json dependencies (package-lock.json beside it) .claude/settings.json agent permissions .claude/hooks/ pre-tool hooks .github/workflows/ci.yml the required check .github/CODEOWNERS ownership routing scripts/gates/ check-protected.sh, lane.sh, tier-rules.txt src/pricing/ weight, zone and surcharge rules (the money path) src/carriers/ carrier adapters src/api/ HTTP handlers src/auth/ API keys and tenant checks migrations/ schema changes tests/ unit and contract tests tests/invariants/ tenant-isolation and no-negative-price tests docs/ public documentation| Person | Role | Owns |
|---|---|---|
| Mara Quill | Platform lead | platform-owners: .github/, .claude/, AGENTS.md, REVIEW.md, scripts/gates/ |
| Tomas Reyes | Pricing owner | pricing-owners: src/pricing/ |
| Ines Kowal | QA | qa-owners: writes and computes the holdout scenarios |
| Jae Park | API owner | api-owners: src/api/, src/carriers/, src/auth/ |
Illustrative composite. Agents open about 14 pull requests a week. The owners can give about 5 hours (300 minutes) a week to reading. One careful read of a change that needs a single owner takes 20 minutes. When two humans must read, each takes 30 minutes. A sampled audit read takes 15 minutes. All figures are modeled, not measured.
The issue the builder sees (pricing)
"Quote total is base plus fuel. Chargeable weight is the larger of actual kilograms and volumetric kilograms, where volumetric kilograms are length times width times height in centimetres divided by 5000. Round chargeable weight up to the next 0.5 kg. Base is chargeable kilograms times the zone rate per kilogram: Z1 is 3.20, Z2 is 4.10. The minimum base is 6.00. Fuel is eight percent of base, rounded half up to the cent. All amounts are in euros."
Module settings, as each author wrote them
Review gate (written by Jae). Fresh-context reviewer, advisory, never approves. It reads REVIEW.md and scripts/gates/tier-rules.txt from the base branch. Round cap 3. Rounds 2 and 3 review only new lines. Findings are Important (blocks), Nit or Pre-existing (never block). Scoreboard thresholds: resolution rate below 0.30 for two weeks, noise ratio above 0.40 for two weeks, cap hits above 0.10 of PRs. Tiers are set by the classifier from the diff:
| Tier | Diff touches | Specialist reviewers | Merge needs |
|---|---|---|---|
| 0 | only docs/** or tests/**, under 40 changed lines | 2 | CI green and the merge rule. No human |
| 1 | other app code (src/api/**, src/carriers/**, package.json), up to 400 changed lines | 4 | CI green, the merge rule, one named code owner who is not the person who prompted the agent |
| 2 | src/pricing/**, src/auth/**, migrations/**, CI workflow files, the reviewer config files | 6 | CI green, the merge rule, two humans (one a code owner), approvals pinned to the latest commit |
Holdout set (written by Ines). Three scenarios with parcels A, B and C below, stored outside the service repo. Validator sees the issue text and the running build, never the plan. Verdict is ids and clauses only. Two validator runs per PR, then a human.
| Parcel | Actual kg | Size cm | Zone | Red control it must fail |
|---|---|---|---|---|
| A | 3.2 | 50 × 40 × 30 | Z1 | R1: ignores volumetric weight |
| B | 1.2 | 20 × 20 × 20 | Z2 | R2: rounds to the nearest 0.5 kg instead of up |
| C | 0.4 | 10 × 10 × 10 | Z1 | R3: applies the minimum to the total after fuel instead of to the base |
Protected paths (written by Mara). Agent PRs touching these are refused; the owner makes the change: AGENTS.md, .claude/**, .github/workflows/**, .github/CODEOWNERS, scripts/gates/**. Server rule: ruleset on main, code-owner review required, no bypass actors. Client deny rules mirror the list.
Risk lanes (written by Tomas). Size is added plus deleted lines, lockfiles excluded; refusal cap 800. High lane, any one trigger: H1 protected path; H2 touches src/auth/**, migrations/** or tests/invariants/**; H3 size 401 to 800; H4 reviewer and validator disagree; H5 retry cap exhausted; H6 no reviewer verdict. Low lane, every test: L1 size 150 or fewer and 8 files or fewer; L2 no floor path; L3 required checks green including the holdout; L4 reviewer finished with no open or dismissed findings; L5 dependency manifests unchanged. Floors: src/pricing/** and package.json are medium at least. Medium is everything else. Audit sample: low lane ten percent, medium lane twenty-five percent, new paths in shadow mode for 30 PRs. Tripwire: rolling four weeks per lane, trip on a revert rate above the human baseline, two or more reverts in the last 40 lane PRs, or any incident traced to a low-lane merge.
Rules the platform lead has already decided
- Where two modules disagree about how much human reading a change needs, the stricter one wins, and the module that lost is edited so no file contradicts the charter.
- Gate order is fixed: protected paths, then required checks including the holdout, then the review gate's merge rule, then approvers.
- The author never sets a tier or a lane, and no bot approval fills a required human slot.
- The charter, the four module files and
scripts/gates/are themselves protected. - Crosswalk: tier 0 equals low lane equals final level 0; tier 1 equals medium equals final level 1; tier 2 equals high equals final level 2. A change's final level is the higher of its tier and its lane.
Final level 0 needs CI, the holdout and the merge rule only, plus the weekly audit draw. Level 1 adds one named code owner and a merge queue with a canary. Level 2 adds two humans and a named reader of the full diff.
The pull request log
All 12 were opened this month. "Checks" means the required CI checks. Lines exclude lockfiles.
| PR | Author | Files | Lines | Checks | Holdout | Reviewer |
|---|---|---|---|---|---|---|
| PR-01 | parcelwise-agent[bot] | docs/faq.md | 8 | green | pass | clean |
| PR-02 | parcelwise-agent[bot] | src/carriers/dhl-adapter.ts, tests/carriers/dhl-adapter.test.ts | 62 | green | pass | clean |
| PR-03 | parcelwise-agent[bot] | src/pricing/zones.ts | 5 | green | pass | clean |
| PR-04 | parcelwise-agent[bot] | package.json (lockfile adds 2,900 more) | 12 | green | pass | clean |
| PR-05 | parcelwise-agent[bot] | tests/invariants/tenant-isolation.test.ts | 12 | green | pass | clean |
| PR-06 | parcelwise-agent[bot] | .github/workflows/ci.yml | 3 | green | pass | clean |
| PR-07 | parcelwise-agent[bot] | REVIEW.md | 6 | green | pass | clean |
| PR-08 | parcelwise-agent[bot] | four files in src/api/ | 310 | green | pass | clean |
| PR-09 | parcelwise-agent[bot] | nine files in src/api/, mechanical rename | 520 | green | pass | clean |
| PR-10 | parcelwise-agent[bot] | src/api/quote-handler.ts, src/pricing/surcharge.ts | 40 | green | C fails | clean |
| PR-11 | Mara Quill | .github/workflows/ci.yml | 14 | green | pass | clean |
| PR-12 | parcelwise-agent[bot] | src/auth/api-keys.ts | 28 | green | pass | clean |
Shape of each file
The audit script reads these headings and columns, so use them exactly.
| File | Required headings | Required table |
|---|---|---|
factory/review-gate.md | Reviewer config, Round cap, Finding states, Approvers per tier, Reviewer scoreboard | Tier / Set by / Diff touches / Specialist reviewers / Merge needs |
factory/holdout-set.md | Placement, Where the scenarios live, How a figure gets in, H1, H2, H3, Controls procedure | none; each scenario carries a fixture, expected figures, Red control Rn and Author-side result on Rn |
factory/protected-paths.md | Server-side rule, Client-side mirror, Red control | Class / Patterns / May change / Enforced by |
factory/risk-lanes.md | High lane, Low lane, Floors, Human action per lane, Audit sample, Revert-rate tripwire | Lane / Human action before merge / After merge |
factory/merge-gate-charter.md | Scope and owner, Gate order, Gates and red controls, Precedence rules, Reconciled conflicts, PR placement, Reading budget, Red control log | see Step 6 |
Steps
Steps 1 to 4 are plain writing. Step 5 is worked for three rows and open for nine. Steps 6 to 9 are the real project.
- Write
factory/protected-paths.md. Turn Mara's settings into a class table, the ruleset, aCODEOWNERSblock and a red control. Write it as given. Do not fix anything yet. Producesfactory/protected-paths.md. - Write
factory/review-gate.md. Reviewer config from the base branch, round cap, finding states, the tier table above and the scoreboard. Same rule: write what Jae decided. Producesfactory/review-gate.md. - Write
factory/holdout-set.md. Compute the three expected quotes by hand with a calculator, from the issue text and the parcel only. Show the arithmetic the way Lesson 05 does. Then work out what each red control returns and name one property it still satisfies. Never run the service to get a figure. Producesfactory/holdout-set.md. Compare your figures with the check below only after you have written them.
- Write
factory/risk-lanes.md. Counting rules, H1 to H6, L1 to L5, floors, the human-action table, audit sample and tripwire, from Tomas's settings. Do not edit them to match the other files. Producesfactory/risk-lanes.md. - Place the 12 PRs through each module alone. For each PR write the module's own answer: refused by protected paths or not, review tier, risk lane. Keep this as scratch notes; it is what step 6 compares. Three rows are done for you.
| PR | Protected paths says | Review gate says | Risk lanes says |
|---|---|---|---|
| PR-01 | not touched | tier 0 (docs, 8 lines) | low (L1 to L5 all pass) |
| PR-02 | not touched | tier 1 (app code, 62 lines) | low (62 lines, 2 files, no floor) |
| PR-03 | not touched | tier 2 (src/pricing/**) | medium (floor on src/pricing/**, no high trigger) |
Your task is PR-04 to PR-12. Before you open the next check, list every PR where the three columns would send the change to different amounts of human reading, and every PR that one module cannot place at all.
- Write the charter skeleton and the gate table. Create
factory/merge-gate-charter.mdwith the headings in the shape table. Under Gates and red controls, add one row per gate with exactly these columns: Gate / Source file / Stops / Owner / Red control / Expected result. The four gates are named Protected paths, Holdout set, Review gate and Risk lanes. Owners are people from the table, never teams, never bots. Fill the Protected paths row from this worked example and write the other three yourself. Under Precedence rules write the five rules in your own words, with the crosswalk. Producesfactory/merge-gate-charter.md.
| Gate | Source file | Stops | Owner | Red control | Expected result |
|---|---|---|---|---|---|
| Protected paths | factory/protected-paths.md | An agent PR rewriting the files that instruct, constrain or judge it | Mara Quill | As the agent identity, append a blank line to AGENTS.md on branch agent/probe and open a PR | Check protected-paths fails and the merge button is blocked |
- Reconcile. Under Reconciled conflicts add one row for every seam you found, with columns Conflict / Module A says / Module B says / Charter decision. Then edit the losing module file so it says what the charter says. Under PR placement add all 12 PRs with columns PR / Author / Paths / Lines / Protected path / Review tier / Risk lane / Final approval / Deciding rule. The deciding rule names a file and a rule id, such as
risk-lanes H2. A protected-path hit by the agent identity is "refused", not a level. A red required check is "no lane". Under Reading budget show the weekly arithmetic for the modeled mix of 6 level-0, 5 level-1 and 3 level-2 pull requests, using the audit draw for level 0 only. Produces edits to the four module files and the filled charter. Check your hardest rows against the key only after you have written them.
- Run each red control and fill the log. Under Red control log add one row per gate with columns Gate / Control / Expected / Observed / Ran. Write the Expected cell before you run anything. The Ran cell says
ranif a script or a real branch produced the Observed cell andpaperif you walked it by hand. Run at least the protected-paths and holdout controls for real: the first with a local branch and thecheck-protected.shscript from Lesson 06, the second with a tiny quote function and your three mutants. Produces the Red control log infactory/merge-gate-charter.md. A filled example:
| Gate | Control | Expected | Observed | Ran |
|---|---|---|---|---|
| Holdout set | Run H3 on R3 | Fails: expected 6.48, R3 returns 6.00 | H3 failed on R3 with 6.00; passed on the good build | ran |
- Run the consistency checks and fix what they print. From the directory that holds
factory/:
# governing files named by the review gate must be protectedgrep -q "REVIEW.md" factory/protected-paths.md || echo "NOT PROTECTED: REVIEW.md"grep -q 'scripts/gates/\*\*' factory/protected-paths.md || echo "NOT PROTECTED: tier-rules.txt"# every gate row needs a red control and an expected resultawk -F'|' '/^\| *(Protected paths|Holdout set|Review gate|Risk lanes) *\|/ && ($6 ~ /^ *$/ || $7 ~ /^ *$/) {print "NO RED CONTROL: " $2}' factory/merge-gate-charter.md# twelve PRs placed (expect 12)grep -c '^| PR-[0-9][0-9] ' factory/merge-gate-charter.mdA clean run prints only 12. Produces no new file; fix the ones above.
Acceptance
- Five files exist under
factory/: the four modules andmerge-gate-charter.md. - Each module has the headings and table columns listed in the shape table.
-
factory/holdout-set.mdcarries 41.47, 8.86 and 6.48 with their arithmetic, and a red control R1, R2 and R3 each with an author-side result. -
factory/protected-paths.mdlistsREVIEW.md, andfactory/risk-lanes.mdkeepstests/invariants/**as a high trigger. -
tests/invariants/**is excluded from tier 0 infactory/review-gate.md. - The charter gate table has a row for each of four gates, each with a red control and an expected result, and each owner is a named person.
- The charter lists at least three reconciled conflicts, and the losing module file was edited to match.
- All 12 PRs are placed, each with a deciding rule that names a file and a rule id.
- The reading budget shows its arithmetic and states what breaks it.
- The red control log has an Expected cell written before the Observed cell, and two controls marked
ran. - The step 9 commands print nothing except
12. - A reviewer can check that no module lets the agent identity or a bot approval fill a human slot.
Rubric
| Criterion | Weak | Strong |
|---|---|---|
| Reconciliation | The charter pastes the four files together, or picks a winner without naming the conflict | Finds the seams by placing every PR through every module, applies stricter-wins, and edits the losing file so nothing contradicts the charter |
| Red controls | Absurd failures that any check catches, never run, or the Observed cell is filled before the Expected one | Plausible wrong builds, Expected written first, at least two run for real, and paper is honestly labelled |
| Placement | PRs placed by what the author says or by a single module; no rule named | Every row names a file and rule id, final level is the higher of tier and lane, refusals and red checks are handled apart |
| Enforceability | Rules read as intentions a person must remember | Every rule is a path pattern, a threshold, a script or a ruleset setting, owned by a named person |
| Budget honesty | No arithmetic, or a total that quietly exceeds the hours | Arithmetic shown, total against the available hours, and the one change that would break it |
Key Takeaways
- Place every pull request through every gate separately; the disagreements show you where the charter is needed.
- Resolve conflicts by the stricter human requirement, then edit the loser so no file contradicts the charter.
- Write the expected result of a red control before you run it.
- Compute holdout figures by hand from the fixture, and name the property each mutant still satisfies.
- Count the reading minutes your charter demands, and write down what would break the budget.
What You Have Now
factory/merge-gate-charter.md - review gate, holdout set, protected paths and risk lanes merged into one charter for one repo, with a red control for each gate. It sits in factory/ beside the four module files it reconciles and the earlier files from the Slice module, and from now on every gate change is an edit to the charter and the module together, never to one alone. Re-run the red control log whenever you change a ruleset, a threshold or a protected pattern. This is the Gate module's finished artifact: a change reaches the main branch through one ordered set of rules, one named human per level and one proof that each gate can fail.
factory/ line-map.md release-slice.md autonomy-map.md review-gate.md holdout-set.md protected-paths.md risk-lanes.md merge-gate-charter.mdNO-QUIZ
Quiz coming to this lesson
This lesson doesn't have a graded quiz yet — completion is still tracked, just not verified against questions.
Reading is free — your progress isn't being saved
Nothing you did on this page is recorded. Paid students get saved progress and verified completions, recorded quiz answers, the spaced-repetition review queue, labs, and a certificate for each course they finish.
30-day money-back guarantee. One-time payment — lifetime access and updates.