Skip to main content

Software Processes & Continuous Delivery

Split pixel-art scene. On the left in warm amber, four engineers strain to push one enormous freight container packed with mismatched crates through a gate, a red warning lamp on top. On the right in cool teal, a conveyor carries small crates through the same gate one at a time, each with a green checkmark, while one engineer rests a hand on a stop cord and four lamps above show a ramp climbing. Tagline: Ten Thousand Suspects, Or Fifty.

Reminders​

  • DUE: Onboarding: Gradebook Column Groups — due Thu Sep 24
  • DUE: Reading Bid — due Thu Sep 24
  • RETURNED: The Ticket Hunt — grades and written feedback went out this morning
  • FEEDBACK: Project Bids — closed Thu Sep 17; feedback coming back shortly

The Ticket Hunt, Returned​

Grades went out this morning. 26 tickets. Every one of them checked against the repository, not against your form.

23 reproduced in full. 2 partially. 1 did not.

Zero duplicates across the whole cohort.

Ten passed. Three come back.

All four roles covered. Nobody converged on the docs, which is the way this usually fails.

Zero duplicates was the criterion I was least able to check by hand, and the one nobody failed.

Where Did Your Triage Item Come From?​

A. A ticket a classmate filed that same morning

B. A TODO, an old unlabeled issue, or something from the axe sweep

Hands up. Both are legal under the handout. The class split exactly in half: 13 verdicts each.

Every verdict that was refuted, empty, or exemplary came from B. Nothing in A was wrong, and nothing in A was informative.

Four Worth Reading​

David, on #911. Pinned staging at a commit, found a clean checkout fails 2 of 108 Jest suites at import, and that one import error was hiding 5 real assertion failures CI has never run. Then found the fix already written in an earlier commit, confirmed it isn't an ancestor of staging, and concluded the work left is splitting a PR, not coding.

Naman, on #910. Closed an accessibility finding as a documented false positive: the commit, the suppression selector, the measured 19.9:1 and 19.06:1 contrast ratios, and an explicit refusal to generalize to the rest of the issue. Then said what the verdict does not cover.

Adhiraj, on a stale TODO. Started from a comment somebody left months ago and ended at a live defect, with the dead-branch argument worked out line by line, plus an honest note that he never loaded the UI to watch it fail.

Abhinav, on #1005. Reproduced a duplicate-question-name bug and found it worse than reported: the two questions share input state live, so a required question can submit empty. Traced to one jsonb blob keyed by name.

CS 4535: Software Design & Delivery

Software Processes & Continuous Delivery​

©2026 Jonathan Bell, CC-BY-SA

Learning Objectives​

After this session, you'll be able to:

  1. Describe how continuous development catches errors sooner, and what each stage of the pipeline protects against
  2. Contrast trunk-based development with long-lived feature branches
  3. Explain what a feature flag separates, and design a ramp for a change
  4. Tell from a ramp and an error rate whether a release is going badly
  5. Compare continuous delivery with test-driven development as quality strategies

Good Process Helps You Design Better Programs​

Software design process: Requirements, Design, Implementation, Validation and Operations, each stage feeding forward with feedback flowing backward.

"Shift Left" on bugs: find issues faster to reduce the cost of fixing them

Staging Has Been Red Since Yesterday​

Real, this week, in the repository you are about to merge into. e2e-local has failed on every push since PR #1016 landed. It isn't your commit.

no-submission-grading-ui.test.tsx:100 › the staff grading route also
redirects to Grade for a no-submission assignment

Expected pattern: /\/grade(?:\?.*)?$/
Received string: ".../grade/assignments/103/submissions/72"

A. Revert #1016. It broke the build.

B. Fix the test. C. Fix the app. D. Not yours. Keep working on your branch.

Continuous Integration​

Everyone merges to one shared branch often, and every change is built and tested automatically, triggered by the commit and by the pull request.

Never leave the build broken. A red trunk blocks everybody, not just you. Fixing it outranks whatever you were doing.

Run on every change. Not nightly, not before a release, and actually run: #1016's suite was skipped four times and the merge button stayed green.

It does not replace testing before you push. CI is the second opinion, not the first.

Feedback in minutes. A suite that takes an hour gets ignored, worked around, and eventually turned off.

One Change Per Run​

If a run contains one change and it fails, you know who broke it.

If a run contains fifty, you have fifty suspects and a bisect ahead of you.

Google's Test Automated Platform, as of the numbers in the literature: about 50,000 changes a day, roughly 4 billion test cases a day, and an average wait of 11 minutes for a result.

Even Google can't run every test on every change. TAP runs only the tests a change can affect, and batches changes when it's busy. When a batch fails, it splits the batch and reruns each change alone, because a failure has to be attributable to one change.

Blame attribution isn't about who to be annoyed with. It's the difference between a ten-minute fix and a ten-hour hunt.

Passes Here. Fails In CI. Why?​

await page.click('text=Add column group');
await page.waitForTimeout(500);
expect(await page.locator('.group-row').count()).toBe(3);

Green every time on your laptop. Red about one run in six in CI, on the same commit.

90 seconds, with the person next to you. What's different about CI?

When The Suite Lies​

A flaky test passes and fails on the same code. Same commit, same machine, different answer.

Where it comes from

Waiting on something async and guessing how long. Two tests sharing state and running in a different order. Concurrency. A clock, a network, a random seed.

What it costs

Not the compute. The habit. A suite that cries wolf teaches the team to press rerun, and pressing rerun is how a genuine failure gets shipped.

Flaky red says broken when nothing is. A skipped job says fine when nothing was checked. Both are the suite lying.

How Facebook Shipped Before 2016​

Your branch lived about a week. You developed on it, alone, while master moved underneath you.

A stabilize branch, weekly. Everybody's week of work, merged together, then made to work as a set.

A release branch, cut from that. Whatever survived stabilization went out.

And the rule that gives it away: your change did not go out unless you were physically there that day to support it.

Read that last rule as a confession: nobody trusted the merge.

Trunk-Based Development​

Long-lived branches

Integration is an event. It happens late, all at once, and it is somebody's job.

Divergence grows all week. Conflicts are found at merge time.

The branch is how unfinished work is hidden.

Trunk-based

Integration is continuous. Branches live hours or a day or two, then merge.

Divergence stays small. Conflicts are found while they are still small.

A flag is how unfinished work is hidden.

Trunk-based costs you something real: your half-finished feature is now on master, running, in front of nobody.

Which only works if shipping the code and showing the feature are two separate acts.

Deploy Is Not Release​

Deploy

The code is on the server. Every user is running it.

An engineering event.

Release

A user can reach the feature.

A product decision, made per course, changeable in seconds.

In Pawtograder that switch is a row in classes.features: a name and a boolean, per course.

So a feature can be on for one course and off for the other nine, with one deployed build.

CI, Continuous Delivery, Continuous Deployment​

Continuous integration. Everyone merges to one shared branch often, and every merge is built and tested automatically. That's the CI already running on your pull requests.

Continuous delivery. Every commit that passes is releasable. Shipping is a decision somebody makes, and a button somebody presses.

Continuous deployment. Every commit that passes ships by itself. No button, no decision, no human in the path.

Pawtograder does continuous delivery. Changes are promoted to production deliberately, not automatically. The button is a person.

Why Facebook can't have a button. By 2016 they took 1,000+ diffs a day, so a weekly push carried up to 10,000. When that breaks, you have 10,000 suspects. They moved to pushing from master every few hours instead.

Shift left: same defect, four prices. Your editor costs seconds, CI costs minutes, staging costs an hour, a student at 11pm costs an afternoon and their trust.

Dark Launch, Then Ramp​

Dark launch. Deploy it with the flag off. The code is live, nobody can reach it, and you find out whether it breaks the build and the tests before it can break a user.

Ramp. Turn it on for a growing fraction, on a schedule you decide: yourself, then staff, then one course, then more. 1% · 5% · 25% · 100%

The cost. Both paths now exist in production, so both have to work. Your test matrix doubled the moment you added the flag.

How Would You Know?​

The flag is on for 25%. Somewhere, a student just hit a TypeError. Nothing on your machine will ever tell you that.

What we run

@sentry/nextjs, reporting to a self-hosted Bugsink. Every event carries the exception, the stack, the release (the commit SHA) and the environment.

What it won't tell you

tracesSampleRate: 0. It catches what threw, not what was slow. And it only sees what reaches the client.

60 seconds, with the person next to you. An error shows up. What has to be attached to it before you can act?

Errors Per Exposed User​

Raw error counts rise when exposure rises. That tells you nothing. Divide, then compare across ramp steps.

Ramp stepErrorsExposedPer user
1%2200.10
10%192000.10count went up 10x, this is fine
50%2401,0000.24rate went up 2.4x, this is not

A count that tracks exposure is arithmetic. A rate that tracks the ramp is your bug.

It's 4:15pm on a Wednesday​

CARD A

Flag        gradebook-what-if
Ramp 1% Mon · 5% Tue · 25% Wed 2:00pm

Sentry TypeError: Cannot read properties
of null (reading 'score')
3 events Mon
11 events Tue
340 events since 2:00pm today

Exposed ~15 students Mon
~75 students Tue
~380 students now

Affected students only, 3 courses

Also today dependency bump deployed 1:50pm
Due tonight CS 2100 problem set, 11:59pm

CARD B

Flag        discord-student-join
Ramp 5% Mon · 25% Wed 10:00am

Sentry DiscordAPIError: Missing Access
2 events Mon-Tue
8 events since 10:00am today

Exposed ~40 students Mon
~200 students now

Affected all 8 events from one course,
6 students

Also that course's Discord server
changed owner last Thursday

5 minutes, in pairs. One of these is your bug. One isn't.

Which one, and what number tells you? I'll take it from every pair.

The Number That Decided It​

1st step2nd stepnow
A3/15 = 0.2011/75 = 0.15340/380 = 0.89rate jumped
B2/40 = 0.058/200 = 0.04rate flat

A: roll back to 5%. Two candidate causes ten minutes apart, and no dashboard can separate them. Rolling back costs thirty seconds and answers the question.

B: hold and fix the course. The count grew because exposure grew. Rolling back takes the feature from 200 students to fix a config problem affecting 6.

Two Ways To Buy Confidence​

Test-driven development

Write and maintain a test per feature.

Bugs are located at the unit level, by a test that names them.

You find out before it ships.

Pays in: tests written, and tests kept working.

Continuous delivery

Write and maintain high-level metrics.

Bugs are located by a rate that moved, on a feature you shipped alone.

You find out after it ships, from a few users.

Pays in: telemetry built, and telemetry kept honest.

Ship one feature at a time and you get the same thing a unit test gives you: one suspect.

This is a budget rather than a choice. Every team spends on both, and most spend badly by never deciding.

Up Next​

Thu Sep 24 — Defense in Depth: Prevent, Test, Detect, Recover

Today was the half that watches production. Tomorrow puts it next to everything else you can do about a failure: prevent it, test for it, detect it, recover from it, or accept it. And how to decide which ones a failure deserves.

Before then:

  • Onboarding: Gradebook Column Groups — due Thu Sep 24
  • Reading Bid — due Thu Sep 24

Four Projects​

Paper Exams

Generate, scan and grade paper exams in Pawtograder. Starts from a working pipeline (PR #814) and adds LLM-assisted grading with bring-your-own-key, comments pinned to a spot on the scanned page, and exam variants.

Cloud Workspaces

A replacement for GitHub Codespaces: Coder workspaces on a self-hosted Forgejo, in a Kubernetes namespace you run. Target: one intro-course assignment, from repo to running workspace to graded submission.

Office Hours

A usability sweep of the help queue, then a user study with students and TAs, then a new Discord integration that builds on the existing staff-side bot.

Usability, Accessibility & Permissions

Pages where the UI offers an action the database then refuses, accessibility gaps the automated scans skip, and finer-grained staff roles: read-only observers, TAs who can manage grading assignments, TAs who cannot push to solution repos.

Next: details and preliminary teams on Discord after class, then discussion in class Thu Sep 24, then Discord again, then final teams Mon Sep 28