F09 · Foundation Session 9
Clamps hold the work
A fixture clamp holds a part so the operation cannot drift. A quality clamp holds a change so a broken rule cannot reach the floor. This element names the ten clamps every change must pass, wires them into the pipeline, and shows how to prove they still work.
8 min5 chapters≈ 1¾ hours3 hands-on exercises12-question assessment · 80% passes
By the end of this chapter you can
- Say what a quality clamp is and why the platform needs ten of them.
- Describe how this element is scored and what counts as complete.
- Know what you will have built by the end of Session 9.
Why clamps
By Session 9 your platform has accounts, a schema, a naming standard, a role matrix, access rules and sign-in that denies by default. Each of those is a rule. A rule written in a document holds only as long as everyone remembers it. From Session 12 onward, AI agents will write most of the code, quickly and in volume. They do not remember last week's decisions unless something checks them every time.
A quality clamp is that check. It is an automated test that runs on every pull request and blocks the merge when a rule is broken. Nine clamps are run by a machine; the tenth is a person who reads the change. Together they are the platform's control plan: the same idea as the gauges and error-proofing devices on a production line, applied to software.
What you will do
- Chapter 1 names the ten clamps: what each checks, the command that runs it, and the plant mistake it prevents.
- Chapter 2 wires them into GitHub Actions and makes each one a required check, so a red clamp blocks the merge.
- Chapter 3 audits them: is each clamp required, does it still catch a known-bad change, is it honest, and is the result written down.
- Chapter 4 is the assessment.
How it is scored
Your LMS records each chapter you open, your knowledge-check answers and your assessment score. The element is complete when you have opened every chapter and taken the assessment, and passed at 80% or more. You can retake the assessment.
Knowledge check
What makes a quality clamp different from a rule written in a document?
Chapter 1 · What every change must pass
The ten clamps
Ten checks, each tied to a rule from an earlier session. For each one: what it checks, how it runs, and what goes wrong on the floor without it.
32 min10 clamps9 by machine, 1 by a person
By the end of this chapter you can
- Name the ten clamps and the rule each one enforces.
- Give the command or setting that runs each clamp.
- Explain, for each clamp, the plant-floor failure it prevents.
A control plan for code
A plant control plan lists, for each characteristic of a part, how it is measured, how often and what happens when it is out. Quality clamps are the same list for the platform. They follow the idea of mistake-proofing (poka-yoke): design the process so the mistake cannot pass, instead of relying on someone to notice it [1]. Software engineering reached the same conclusion: secure development frameworks ask for automated checks built into the pipeline, not checks done by memory [2].
The ten
| Clamp | Checks that | Runs as | Rule from |
|---|---|---|---|
| C1 Build | The code compiles and the types agree | npx tsc --noEmit and npm run build | Session 1 |
| C2 Lint | Code follows the agreed rules, including security rules | npm run lint (ESLint) | Session 1 |
| C3 Tests | Every unit and end-to-end test passes on a fresh clone | npm ci then npm test | Session 2B |
| C4 Schema | Every migration applies, in order, to an empty database; applied migrations are never edited | Migrate a throwaway database in CI | Session 3 |
| C5 Access | Every table has row-level security switched on and a test per role | A database test that lists tables without it | Session 7 |
| C6 Deny | Every route is in the role matrix; an unlisted route is refused | A test that requests every route signed out and as each role | Session 8 |
| C7 Secrets | No key, password or token is in the code or its history | gitleaks in CI; GitHub push protection where you have it | Session 1 |
| C8 Packages | No dependency has a known high or critical vulnerability | npm audit --audit-level=high; Dependabot alerts | Session 2B |
| C9 Phone | Every page works at 360 and 390 px: no sideways scroll, 44 px taps, 16 px fields, 15 px text | A Playwright audit at both widths | Session 10 |
| C10 Review | A person other than the author read the change and approved it | A branch ruleset: one approving review, all clamps required | Session 1 |
C1 Build: it compiles
The cheapest clamp catches the most. TypeScript's compiler checks that every value is used as the type it was declared, so a measurement stored as text, or a function called with the wrong argument, fails before anything runs [3]. tsc --noEmit checks types without producing files; the framework's build then proves the whole site can be built the way the host will build it.
C2 Lint: it follows the rules
A linter reads code and flags patterns the team has agreed to forbid [4]. Beyond tidiness, lint rules can enforce safety: no eval, no raw HTML injected from user input, no floating promise that silently swallows an error. Agree the rule set once, commit it, and never let a pull request switch a rule off without a reviewer seeing it.
C3 Tests: they pass on a fresh clone
Tests that pass on your laptop prove your laptop. A fresh clone has no ignored files, no leftover packages and no empty folders, because Git does not store empty folders. C3 installs exactly what the lockfile says with npm ci and runs the whole suite, including the end-to-end tests against a fresh database. Chapter 2 shows how.
C4 Schema: migrations apply
Schema first (Session 3) means every table change is a numbered migration file in the repository. C4 creates an empty database, applies every migration in order and fails on any error. It also fails if a migration that has already been applied is edited: once a change has run on the live database, a correction is a new migration, never an edit to the old one [5].
C5 Access: row-level security on every table
In Postgres, row-level security is off for a new table until someone switches it on, and a table with it switched on but no policy refuses everything [6]. Supabase exposes tables through its API, so a table without row-level security can be read by anyone who has the public key [7]. C5 asks the database itself for every table in the exposed schema where it is off, and fails if the list is not empty. Session 7's per-role tests then prove the policies allow and refuse what the matrix says.
-- C5: list every table in the public schema without row-level security. Must return no rows.
select c.relname as table_without_rls
from pg_class c join pg_namespace n on n.oid = c.relnamespace
where n.nspname = 'public' and c.relkind = 'r' and not c.relrowsecurity;C6 Deny: a new route is closed
Session 8 made sign-in deny by default: a route is closed until the role matrix opens it. C6 keeps it true. It lists every route the app serves, compares the list with the matrix and fails on any route the matrix does not mention; then it requests each route signed out and as each role and checks the answer is the one the matrix gives. Broken access control has been the top web application risk on the OWASP Top 10 since 2021 [8]; this is the test that catches it.
C7 Secrets: no keys in the code
A key committed once stays in the repository's history even after the line is deleted. C7 scans every commit for patterns that look like keys, passwords and tokens. gitleaks is free, open-source and runs anywhere [9]. GitHub's push protection blocks a push that contains a recognised secret before it lands: it is free on public repositories and part of a paid add-on for private ones [10]. If C7 ever goes red on a real key, treat the key as stolen: rotate it the same day, then remove it.
C8 Packages: no known holes
Most of the code your platform runs was written by others and installed as packages. npm audit compares the lockfile with the public advisory database and exits with an error when a package has a known vulnerability at or above the level you set [11]. Dependabot, free on GitHub, raises alerts and opens pull requests that update the package [12]. Every Dependabot pull request still passes all ten clamps.
C9 Phone: the floor uses phones
Operators, crews and supervisors use phones and tablets, often with gloves. C9 opens every page at 360 and 390 px wide in a headless browser and fails on sideways scrolling, a tap target under 44 by 44 px, a form field under 16 px (iPhones zoom into smaller fields) or body text under 15 px [13]. Session 10 explains each number. Playwright drives the browser [14].
C10 Review: a person approves
The last clamp is not a program. A person other than the author reads the change and approves it, and the branch ruleset will not merge without that approval and without clamps C1 to C9 green [15]. When an agent writes the change, you are the reviewer; Session 12 teaches how to read what an agent hands back. The ruleset also blocks force-pushes and deletion of main, so history cannot be rewritten.
Knowledge check
A new table for first-off checks is added without row-level security. Which clamp should go red?
Knowledge check
Why does C3 run the tests on a fresh clone instead of on the developer's laptop?
Knowledge check
C7 finds a real database password committed last week. What comes first?
You need: Your repository on github.com; a text editor
Find out which of the ten clamps your platform already has, and write the clamp register you will keep for the rest of the course.
Outcome: A clamp register with ten rows, showing which clamps exist and which are missing.
References
- ASQ: What is mistake-proofing (poka-yoke)?. https://asq.org/quality-resources/mistake-proofing
- NIST SP 800-218, Secure Software Development Framework (SSDF) 1.1. https://csrc.nist.gov/pubs/sp/800/218/final
- TypeScript documentation. https://www.typescriptlang.org/docs/
- ESLint documentation: Getting started. https://eslint.org/docs/latest/use/getting-started
- Supabase Docs: Database migrations. https://supabase.com/docs/guides/deployment/database-migrations
- PostgreSQL documentation: Row security policies. https://www.postgresql.org/docs/current/ddl-rowsecurity.html
- Supabase Docs: Row Level Security. https://supabase.com/docs/guides/database/postgres/row-level-security
- OWASP Top 10 web application security risks. https://owasp.org/www-project-top-ten/
- gitleaks: find secrets in Git repositories. https://github.com/gitleaks/gitleaks
- GitHub Docs: About secret scanning. https://docs.github.com/en/code-security/secret-scanning/introduction/about-secret-scanning
- npm Docs: npm audit. https://docs.npmjs.com/cli/commands/npm-audit
- GitHub Docs: Dependabot. https://docs.github.com/en/code-security/dependabot
- W3C: Understanding WCAG 2.2 Success Criterion 2.5.5 Target Size (Enhanced). https://www.w3.org/WAI/WCAG22/Understanding/target-size-enhanced.html
- Playwright documentation: Installation. https://playwright.dev/docs/intro
- GitHub Docs: About rulesets. https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/managing-rulesets/about-rulesets
Chapter 2 · From a list to a lock
Wiring them into the pipeline
A clamp that runs but cannot block a merge is a suggestion. This chapter puts the ten clamps into one CI workflow, makes each a required check, and closes the gaps that let a failing clamp read green.
28 min1 workflow file1 ruleset$0 on the free plan, with one limit
By the end of this chapter you can
- Write a CI workflow with one job per clamp, run on a fresh clone.
- Make every clamp a required check with a branch ruleset.
- Spot the three ways a failing clamp can read green.
- State what the clamps cost, the free way first.
One workflow, one job per clamp
GitHub Actions runs a workflow file from .github/workflows/ on every pull request [1]. Each job runs on a fresh virtual machine that starts from a clean checkout of the pull request, which gives C3 its fresh clone for free. Name each job after its clamp. The names are what the ruleset will require, and what a reader sees when one goes red.
name: clamps
on: { pull_request: {}, push: { branches: [main] } }
jobs:
c1-build: { runs-on: ubuntu-latest, steps: [ { uses: actions/checkout@v4 }, { run: npm ci }, { run: npx tsc --noEmit && npm run build } ] }
c2-lint: { runs-on: ubuntu-latest, steps: [ { uses: actions/checkout@v4 }, { run: npm ci }, { run: npm run lint } ] }
c3-tests:
runs-on: ubuntu-latest
services: { postgres: { image: "postgres:17", env: { POSTGRES_PASSWORD: test }, ports: ["5432:5432"] } }
env: { DATABASE_URL: "postgres://postgres:test@localhost:5432/postgres" }
steps: [ { uses: actions/checkout@v4 }, { run: npm ci }, { run: npm run migrate }, { run: npm test } ]
c4-schema: # same Postgres service; applies every migration to an empty database and checks applied files are unchanged
c5-access: # runs the C5 query and the per-role tests from Session 7
c6-deny: # requests every route signed out and as each role; fails on any route missing from the matrix
c7-secrets: { runs-on: ubuntu-latest, steps: [ { uses: actions/checkout@v4, with: { fetch-depth: 0 } }, { run: "docker run --rm -v $PWD:/repo ghcr.io/gitleaks/gitleaks:latest detect --source /repo --redact" } ] }
c8-packages: { runs-on: ubuntu-latest, steps: [ { uses: actions/checkout@v4 }, { run: npm audit --audit-level=high --omit=dev } ] }
c9-phone: { runs-on: ubuntu-latest, steps: [ { uses: actions/checkout@v4 }, { run: npm ci }, { run: npx playwright install --with-deps chromium }, { run: npm run test:phone } ] }The database password in the file is for a throwaway database that exists only inside the CI run and is deleted when the job ends. Real keys never go in a workflow file; they go in the repository's Actions secrets, entered on GitHub's site by a person [2].
Make every clamp a required check
A red job on its own only colours the pull request. To block the merge, add a branch ruleset on main [3]:
- On github.com, open the repository, then Settings, then Rules, then Rulesets, and click New ruleset, then New branch ruleset.
- Name it clamps, set Enforcement status to Active, and under Target branches click Add target, then Include default branch.
- Tick Require a pull request before merging and set Required approvals to 1. This is C10.
- Tick Require status checks to pass, click Add checks and add each clamp job by name, c1-build to c9-phone.
- Tick Block force pushes and leave Restrict deletions ticked. Leave the bypass list empty. Click Create.
Three ways a red clamp reads green
A clamp is only as good as the exit code it returns. A job passes when its last command exits with 0. Three patterns hide a failure:
| Pattern | Looks like | Why it hides a failure | The fix |
|---|---|---|---|
| A pipe | npm test | tail -5 | The shell reports the exit code of the last command in the pipe, here tail | set -o pipefail at the top, or no pipe |
| An escape hatch | npm audit || true, continue-on-error: true | The failure is caught and thrown away on purpose | Remove it; if a check is noisy, fix the noise |
| A skipped test | test.skip(...), it.only(...) | The failing test never runs, so nothing fails | A lint rule that forbids skip and only in committed tests |
tail's 0, and CI shows a green tick.Bash documents the rule: the return status of a pipeline is the exit status of the last command, unless the pipefail option is set [5]. On Linux runners, a run step that names shell: bash runs with -e and -o pipefail; a step that names no shell runs with -e only, without pipefail [6]. Scripts you call from a step inherit neither. Put set -euo pipefail at the top of every committed shell script.
What the clamps cost
| Item | Free way first | When you pay |
|---|---|---|
| CI minutes (GitHub Actions) | Public repositories: free. Private: 2,000 minutes a month on the free plan | Team includes 3,000; beyond that, per minute (see the pricing page) |
| Enforced rulesets on a private repository | Run the clamps and enforce them by discipline | GitHub Team, about $4 per user per month |
| Secret scanning (C7) | gitleaks, free and open-source, in CI | GitHub Secret Protection for push protection on private repositories |
| Dependency alerts (C8) | npm audit and Dependabot, both free | Not needed |
| Phone audit (C9) | Playwright, free and open-source | Not needed |
A typical clamp run takes five to eight minutes of runner time across the jobs. Ten pull requests a working day for a month is about 1,000 to 1,600 minutes: inside the free allowance for most plant teams. Record the real number from your first month in the costs page of your platform [4].
Knowledge check
The clamps run on every pull request but a red one can still be merged. What is missing?
Knowledge check
A step reads npm test | tee test.log inside a script without pipefail. A test fails. What does CI show?
Knowledge check
Your repository is private and on GitHub's free plan. What is true of the clamps?
You need: github.com; your AI coding agent; the clamp register from exercise 1
Have your agent add the missing clamps as named jobs, read the change, then require them. Never paste a key into the agent's chat; the workflow needs none.
Outcome: Ten clamps that run on every pull request, nine as required checks and the tenth as a required approval.
References
- GitHub Docs: GitHub Actions documentation. https://docs.github.com/en/actions
- GitHub Docs: Using secrets in GitHub Actions. https://docs.github.com/en/actions/security-for-github-actions/security-guides/using-secrets-in-github-actions
- GitHub Docs: Creating rulesets for a repository. https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/managing-rulesets/creating-rulesets-for-a-repository
- GitHub pricing. https://github.com/pricing
- GNU Bash Reference Manual: Pipelines. https://www.gnu.org/software/bash/manual/html_node/Pipelines.html
- GitHub Docs: Workflow syntax for GitHub Actions (defaults.run.shell). https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions
Chapter 3 · Prove they still bite
Auditing the clamps
A gauge that has drifted passes bad parts without a sound. So does a clamp. Four questions, a known-bad change called a canary, and a register with dates prove the clamps still work.
25 min4 questions1 canary per clampMonthly
By the end of this chapter you can
- Ask the four audit questions of every clamp.
- Run a canary: a known-bad change that must turn a clamp red, and is never merged.
- Keep a clamp register that shows when each clamp last proved itself.
Why clamps drift
Clamps rarely break loudly. Someone renames a job and the ruleset still requires the old name, which no longer runs. A noisy check gets continue-on-error during a busy week and nobody removes it. A test is skipped to unblock a release. A new folder of routes is added that the deny test does not scan. Each time, the pipeline stays green, and the green means less.
Plants solve the same problem with measurement system analysis and with known-bad master parts: before a shift relies on an error-proofing device or a gauge, someone feeds it a part that must be rejected and confirms it is [1]. Software security frameworks ask for the same evidence: that the checks in the pipeline are configured, run and reviewed, not just present [2].
The four questions
| Question | How to answer it | Evidence |
|---|---|---|
| 1. Required? | Open the ruleset on main and compare its required checks with the jobs in the workflow, name by name | A screenshot or copy of the required-checks list, dated |
| 2. Bites? | Run a canary: a throwaway branch with a known-bad change that this clamp must catch | The red run's web address |
| 3. Honest? | Search the workflow and scripts for || true, continue-on-error, unguarded pipes, and committed .skip or .only | The search command and its empty result |
| 4. Recorded? | Write the date, the canary and the result in docs/clamps.md | The register, in the repository, under review like any change |
Canaries
A canary is a deliberately broken change made on a throwaway branch to prove one clamp still catches what it claims to catch. It is opened as a pull request so CI runs, watched until the clamp goes red, and then closed without merging and deleted. If the clamp goes green, the clamp is broken, and fixing it comes before any other work.
| Clamp | A canary that must turn it red |
|---|---|
| C1 Build | Assign text to a number: const qty: number = "ten"; |
| C2 Lint | Add a line your rules forbid, such as eval("1") |
| C3 Tests | Change one expected value in a function so an existing test fails |
| C4 Schema | Edit a migration that has already been applied, or add one with a syntax error |
| C5 Access | Add a migration: create table canary_c5 (id int primary key); with no row-level security |
| C6 Deny | Add a route the role matrix does not list, such as /api/canary |
| C7 Secrets | Add a fake token in a recognised format: ghp_ followed by 36 random letters and digits you type yourself. Never a real key |
| C8 Packages | Pin a package to an old version with a published high-severity advisory, such as lodash@4.17.15 [3] |
| C9 Phone | Give one button height: 30px, or one field font-size: 14px |
| C10 Review | Try to merge any canary without an approval; the button must refuse |
The clamp register
The register is the file you started in chapter 1, docs/clamps.md. One row per clamp: the rule, the check name, the command, whether it is required, the date of the last canary and its result, with the run's web address. Because it lives in the repository, every change to it goes through the clamps and a review like any other change, and its history shows every audit ever done.
How often: every month, and straight after any change to the workflow files, the ruleset or the test runner. Rotate canaries so each clamp is proved at least once a month; three a week takes about fifteen minutes. The OpenSSF Scorecard project automates some of the same questions for open-source repositories (is branch protection on, are dependencies updated, are tokens least-privilege) and is free to run [4].
Knowledge check
A canary for C6 (an unlisted route) runs, and C6 stays green. What does that tell you?
Knowledge check
Why is the clamp register kept in the repository rather than in a spreadsheet on someone's drive?
Knowledge check
Someone renamed the job c3-tests to tests. The ruleset still requires c3-tests. What happens?
You need: A terminal with Git; github.com; docs/clamps.md
Prove three clamps still bite: C5, C7 and C9. Each canary is a throwaway branch, a pull request that must go red, then closed and deleted.
Outcome: Three clamps proved red by a canary, no escape hatches, and a dated register in the repository.
References
- NIST/SEMATECH e-Handbook of Statistical Methods: Measurement process characterization. https://www.itl.nist.gov/div898/handbook/mpc/mpc.htm
- NIST SP 800-218, Secure Software Development Framework (SSDF) 1.1. https://csrc.nist.gov/pubs/sp/800/218/final
- GitHub Advisory Database: CVE-2020-8203, prototype pollution in lodash. https://github.com/advisories/GHSA-p6mc-m468-83gw
- OpenSSF Scorecard. https://github.com/ossf/scorecard
Chapter 4 · 12 questions · 80% passes
Final assessment
Twelve questions across the element. Score 80% (10 of 12) to pass. Your LMS records your score and each answer; you can review the chapters and try again.
15 min12 questions≈ 15 minutesRetake allowed
Your result
CivOps AI Academy
The Quality Clamps: Ten Checks Every Change Must Pass
Element F09 complete · Learner
Your LMS records this completion. For the CivOps Foundation certificate, finish the Foundation Course at https://civops.io/learn.