Module 6: Promotion, Rollback, and Documented Delivery

7. CI checks and the portfolio artifact

Description

By the end of this lesson you will be able to set up a CI check in GitHub Actions validating your workflows' JSON on every commit, automatically and at zero cost: an inspector checking every workflow is well-formed and normalized without anyone having to remember. You're going to understand what continuous integration is, a GitHub Actions file's anatomy piece by piece, and how the check connects with Module 3's normalization. And you're going to build the portfolio artifact —the repository with its versioned workflow, plus visual evidence— that job postings ask for as a hiring filter, with the guide for how to defend it in an interview.

This matters for two reasons that come together. The first is about quality: up to now, JSON review depended on you remembering to look at it (lesson 3). A CI check turns "remembering to review" into "the system reviews on its own, always," which is the only way discipline survives rushes and months. The second is about career: the market doesn't ask for a workflow that works —it assumes that— it asks for an artifact proving you know how to deliver like a professional. A repository with CI green on every commit is exactly that proof: it tells whoever's evaluating you "this person doesn't just build, they validate and operate." It's the difference between claiming you know and showing it.

Connection to the module: lesson 3 taught you to review the diff by hand; this one automates part of that review —the JSON validation— so it happens on its own on every commit. It's the same spirit as Module 3's export.sh script (turning a manual step into an automatic one), now on the verification side. And it sets the stage for lesson 8: the CI check and the portfolio artifact you build here are two pieces of the final deliverable. This is the second-to-last stop; next comes the project bringing everything together and closing out the guide.

What continuous integration (CI) is

Let's start with the name, because it sounds bigger than it needs to be.

CI stands for continuous integration. In its simplest form —the one useful here— CI is the practice of: every time you upload a change to the repository, an automatic system runs a series of checks on that change, with you doing nothing. If the checks pass, the change gets marked good; if any fails, the system tells you right away. The word "continuous" is literal: verification happens continuously, on every change, not once a month when someone remembers.

Think of it as your house's smoke detector. You don't have to remember to check for smoke every night: the detector's there, always on, and if smoke shows up, it sounds on its own. Its value isn't that it detects better than you —you'd smell the smoke too— it's that it doesn't depend on you remembering. It watches while you sleep, while you're distracted, while you're in a rush. A CI check is that detector for your repository: it checks every commit, without tiring, without forgetting, even while you're thinking about something else.

Here's the connection with what you already know. In lesson 3 you learned to review a workflow's JSON by hand: reading the diff, looking for surprises, confirming it's normalized. That works when you remember and have time. But on a busy Tuesday, with three urgent things going on, it's easy to commit without reviewing carefully. The CI check catches what slips past you: it validates the JSON on every commit, no exceptions. It doesn't replace your review —business judgment stays yours— it backs it up with a net that doesn't get distracted.

What GitHub Actions is

The tool you're going to do CI with, for free, is called GitHub Actions.

GitHub Actions is GitHub's built-in automation system, running tasks in response to your repository's events: when you push, when you open a pull request, on a fixed schedule, or when you trigger it by hand. Each task runs on a temporary computer GitHub lends you —a runner— does its job, reports whether it passed or failed, and disappears. You don't have to administer any server: GitHub provides the machine, you provide the instructions.

And it's free for what you need. Public repositories get GitHub Actions at no cost, and private ones come with a free monthly quota more than enough for validating a few workflows' JSON on every commit. As with everything in this guide, the default path costs nothing.

A confusing vocabulary detail worth clearing up once and for all: in GitHub Actions, every automation file is also called a "workflow." It's an unfortunate name collision, because in n8n "workflow" is something else. To avoid getting lost, in this lesson I'm going to say "CI workflow" when talking about the GitHub Actions file, and "n8n workflow" —or simply order-triage— when talking about what you build in n8n. Two different "workflows": one automates your repository, the other automates Cumbre's business.

Anatomy of a CI workflow

A GitHub Actions CI workflow is a text file in YAML format, living in a special folder in your repository: .github/workflows/. YAML is a format for writing configuration readably, with structure marked by indentation —similar to how Python uses indentation. GitHub reads any .yml file in that folder and treats it as a CI workflow.

Before writing ours, let's look at the pieces any CI workflow is made of, because there are few of them and they repeat in every one. Take this skeleton:

name: validate-workflows

on:
  push:
    paths:
      - 'workflows/**.json'
  pull_request:
    paths:
      - 'workflows/**.json'

jobs:
  validate-json:
    runs-on: ubuntu-latest
    steps:
      - name: Check out the repository
        uses: actions/checkout@v4
      - name: Validate the JSON
        run: echo "the checks go here"

Let's break it down piece by piece, because once you recognize the structure, every CI workflow reads the same way:

  • name — the CI workflow's name, as it'll show up in your GitHub repository's "Actions" tab. Here, validate-workflows. It's purely for you to identify it.
  • on — the trigger: which events make this workflow run. Here we say "run on every push and every pull_request, but only when files matching workflows/**.json change." That paths filter is a courtesy: there's no point validating the workflows' JSON if the commit only touched the README. The ** means "in any subfolder."
  • jobs — the list of jobs to run. A CI workflow can have several; ours has one, called validate-json.
  • runs-on — what kind of machine the job runs on. ubuntu-latest is a standard Linux machine GitHub provides, with common tools already installed —including jq, which we're going to use.
  • steps — the job's steps, in order. Each step either uses a pre-existing action (uses) or runs a shell command (run).
  • uses: actions/checkout@v4 — the first step is almost always this: "bring my repository's content to the temporary machine." Without this, the runner is empty and has none of your files to validate. actions/checkout is an official GitHub action; the @v4 fixes the version.
  • run: — runs a shell command on the machine. This is where you put your real checks. Notice something reassuring: this run is regular shell, the same as your terminal. None of the Code node's n8n restrictions apply here —this runs on a GitHub Linux machine, entirely outside n8n— so you have jq, bash, and everything you need, same as in Module 3's export.sh.

With these pieces you can already read any CI workflow. Now let's write the one validating your n8n workflows.

The CI workflow validating your JSON

This is the lesson's heart: a CI workflow that, on every commit touching your n8n workflows, checks two things —that the JSON is parseable (well-formed) and that it's normalized (no volatile fields, keys sorted), the way Module 3's export.sh left it. It goes in .github/workflows/validate-workflows.yml:

name: validate-workflows

# Runs on every push and pull request touching the workflows' JSON.
on:
  push:
    paths:
      - 'workflows/**.json'
  pull_request:
    paths:
      - 'workflows/**.json'

jobs:
  validate-json:
    runs-on: ubuntu-latest
    steps:
      # 1. Bring the repository to the temporary machine.
      - name: Check out the repository
        uses: actions/checkout@v4

      # 2. Verify every workflow is parseable JSON (well-formed).
      - name: Every workflow must be parseable JSON
        run: |
          for f in workflows/*.json; do
            echo "Checking $f is valid JSON"
            jq empty "$f"
          done

      # 3. Verify every workflow is normalized, the way export.sh leaves it.
      - name: Every workflow must be normalized
        run: |
          for f in workflows/*.json; do
            normalized=$(jq -S 'del(.pinData, .versionId, .active, .triggerCount, .meta.instanceId)' "$f")
            if [ "$normalized" != "$(cat "$f")" ]; then
              echo "::error file=$f::$f is not normalized. Run ./scripts/export.sh and commit again."
              exit 1
            fi
          done

Read it in blocks, because each has a clear purpose:

The trigger (on). Runs on every push and pull_request, filtered to workflows/**.json changes. So, every time you promote an order-triage change to the repository —or when you open a pull request to review it, like in lesson 3— the check runs on its own. Automatic review and manual review combine: you read the diff, GitHub validates the JSON, and both have to pass before merging.

The first step: checkout. Brings your files to GitHub's machine. Mandatory; without it there's nothing to validate.

The second step: parseable JSON. The loop goes through every workflows/*.json and runs jq empty "$f". Here jq empty is a precise trick: it tries to parse the file and produces no output if the JSON is well-formed, but fails with an error if the JSON is broken —an unclosed brace, an extra comma. And remember from export.sh: in a script (and in CI it's the same), a failing command stops everything. So, if any workflow has invalid JSON, this step fails and the check turns red. It's the minimal check: "this is at least real JSON."

The third step: normalized JSON. This one's finer and connects directly with Module 3. For each workflow, it calculates how it would look normalized —removing the same volatile fields as export.sh (pinData, versionId, active, triggerCount, meta.instanceId) and sorting the keys with jq -S— and compares it against what's committed. If they don't match, it means someone committed unnormalized JSON (probably downloaded from the editor by hand instead of using the script), and the step fails with a message saying exactly how to fix it: Run ./scripts/export.sh and commit again. The ::error file=...:: is GitHub Actions' way of flagging the error pointing at the culprit file, so in the pull request you see exactly where the problem is.

What to expect once this is set up: every time you commit or open a pull request touching a workflow, a mark shows up on GitHub —a green check if everything passed, a red X if something failed. If you try to commit a broken or unnormalized order-triage.json, the check turns red and tells you before that bad JSON reaches prod. It's the smoke detector working: you don't even notice most of the time, because it's almost always green, and that's exactly why it's worth it, because the day it turns red it saved you from a problem you never saw.

Why this is worth so much for so little

Stop on the economics of what you just set up. It's about twenty lines of YAML, written once, and from then on every commit, forever, gets validated with nobody doing anything. Compare that to the alternative: trusting every person on the team, on every commit, to remember to run the checks by hand. The first is a guarantee; the second is a hope. And the guarantee cost twenty lines and zero pesos.

There's a deeper reason this matters on a team: the CI check makes quality independent of each person's discipline. It doesn't matter whether the person who committed today is careful or distracted, expert or new: the bad JSON doesn't pass, because the detector doesn't depend on them. That's what turns a good personal practice into a team guarantee. And it's, not by coincidence, exactly what an employer wants to see: not that you're careful, but that you built a system that doesn't depend on anyone being.

Closing the door: CI as a merge requirement

There's one more step turning the check from "a notice" into "a real barrier," and it connects CI with lesson 3's pull request. GitHub lets you mark a CI check as required to merge (required status check): with that enabled, a pull request whose check is red can't be merged into the main branch. It isn't that an X shows up and people decide to ignore it; the merge button gets blocked until the check passes.

This —part of what GitHub calls branch protection— closes the loop between the module's two nets. Lesson 3's human review approves the judgment; mandatory CI guarantees the form. A change only reaches the main branch —and from there, promotion— if a human approved the diff and CI is green. Neither net alone is enough; together and mandatory, nothing broken or unreviewed reaches production.

The analogy: it's the difference between a smoke detector that only sounds and one connected to a system that, besides sounding, shuts off the gas valve. The first warns and trusts someone reacts; the second acts. Marking CI as required is connecting your detector to the valve: bad JSON doesn't just trigger the alarm, it's physically prevented from moving forward. For a personal project you might not need it; for a team, it's what makes the guarantee real and not a suggestion.

The portfolio artifact

Now the lesson's second half, the one connecting all your work to your career.

A portfolio artifact is something concrete you can show, demonstrating a capability instead of just claiming it. It isn't a résumé saying "I know how to version workflows"; it's a repository someone can open and verify you know. The difference is huge in an interview: anyone can say they know how to deliver professional automations; very few can open a real repository and point at the evidence.

The market asks for it that directly. Remember the phrase running through the whole guide —"as version-controlled, documented JSON"— and add the attitude many postings pair it with: "no prototype = no conversation." It isn't cruelty; it's an efficiency filter. Whoever's hiring knows a real artifact separates someone who did from someone who read about, and prefers starting the conversation there. Your portfolio artifact is your entry into that conversation.

What makes up the artifact? Two things reinforcing each other:

  • The versioned repositorycumbre-automations—: the exported, normalized, documented order-triage workflow, with credentials out, the environments via Docker Compose, the runbook, and —now— the CI check with its green mark. It's the substance.
  • The visual evidence —a screenshot of the order-triage workflow open in n8n's editor, showing its Webhook, its AI Agent node, and its connected HTTP Request node. It's proof the versioned JSON corresponds to a real, working workflow. The screenshot shows it works; the repo shows you know how to deliver it. Together they tell the whole story: "I build this, and I deliver it like a professional."

The screenshot matters more than it seems. A JSON repository, on its own, is abstract to many evaluators; an image of the assembled workflow makes it tangible at a glance. Put the screenshot in the root README, at the top, as a cover: the first thing whoever opens your repo sees is the real workflow, and below it, the proof you know how to version, test, and operate it.

How to defend it in an interview

The artifact doesn't speak for itself; you defend it. And the good news is the questions you'd get are the same ones this module —and the whole guide— answered. Prepare these defenses, because they're what separates someone showing a repo from someone who understands it:

  • "How do you get a change to production?" — You open the promotion runbook and explain lesson 2's flow: pull from the repo, review the diff, import inactive, verify credentials, activate by hand. You don't describe theory; you point at your own written procedure.
  • "And if the change breaks production?" — You open lesson 5's rollback runbook and explain the two halves —repo and instance— and that you rehearsed it in staging. Very few candidates have a rehearsed rollback to show.
  • "How do you know the delivered JSON is correct?" — You show the "Actions" tab with the CI check green and explain every commit gets validated on its own, so quality doesn't depend on anyone remembering. You point at the smoke detector working.
  • "If you use AI to build, how do you make sure it doesn't break anything?" — You explain lesson 4's safe loop: the AI builds in dev via MCP, Git records it, you review the diff, and only what's approved gets promoted. You demonstrate you know how to use AI without losing control.
  • "Could someone else pick this up without you?" — You open the handoff README and show someone could start the system with just the documentation. The stranger test, live.

Notice the pattern, which you already saw in Module 3's project and here gets completed: every question a good interviewer would ask, this guide turned into a design decision you can point to in your own repository. You're not describing what you know; you're showing evidence of what you did. That's the difference between "I know how to use n8n" and "I own an automation system" —and it's, word for word, what the best job postings ask for.

Common mistakes

Confusing GitHub Actions' "workflow" with n8n's workflow (conceptual). What happens: someone reads "workflow" in GitHub Actions' docs and believes they're configuring their order-triage, or the other way around. They get confused about which file they're editing and where. Why it happens: the name collision is real and unfortunate; both are called "workflow." How to spot it: if you're not clear on whether you're touching the CI .yml or n8n's .json, you have the confusion. How to fix it: the CI workflow is the .yml in .github/workflows/, automates your repository, and GitHub reads it. The n8n workflow is the .json in workflows/, automates Cumbre's business, and n8n runs it. They're two different things that happen to share a name; keep that in mind and don't mix them up.

Believing the CI check replaces human review (conceptual). What happens: someone sets up the CI workflow and concludes they no longer need to read diffs, because "the system validates on its own." They later promote a change with perfect JSON but disastrous business logic —the threshold at 100— which CI approved because the JSON was valid. Why it happens: green CI gives an "everything's fine" feeling confused with "it's reviewed." How to spot it: if you stopped reading diffs because you have CI, you delegated a human judgment to the machine. How to fix it: CI validates the JSON is well-formed and normalized; it doesn't judge whether the change is a good idea. That business judgment remains yours, with lesson 3's diff review. CI and human review are two different nets: one catches broken JSON, the other catches a bad decision. Both are needed.

Putting the CI file in the wrong place (practical). What happens: someone creates validate-workflows.yml in the repo's root, or in workflows/, and GitHub Actions never runs it. They get frustrated because "CI isn't working." Why it happens: GitHub only looks for CI workflows in a specific folder, and it's easy not to know that. How to spot it: if your check never shows up in the "Actions" tab, check where you put the file. How to fix it: the file has to be in .github/workflows/ —that exact path, with the leading dot. GitHub only reads from there. It's a strict platform requirement, not an optional convention.

Showing a repo with no visual evidence of the workflow (practical, portfolio). What happens: someone shares their versioned JSON repository in an interview, and the evaluator —who might not read JSON fluently— can't "see" the workflow and loses interest. The work was there, but it wasn't communicated. Why it happens: to whoever built it, the JSON is the workflow; to whoever evaluates it from outside, it's abstract text. How to spot it: if your repo has no screenshot of the assembled workflow, it's missing the half that makes it tangible. How to fix it: put a screenshot of order-triage open in the editor —with its nodes connected— at the top of the root README. The image shows it works; the repo shows you know how to deliver it. Together they tell the story; separately, each falls short.

Exercises

Exercise 1 — Read the CI workflow. Without running it, read this lesson's validate-workflows.yml and answer: (a) which events make it run, and why is it filtered by paths? (b) What exactly does jq empty "$f" do and why does it work for validation? (c) What does the third step detect that the second doesn't, and with what message does it help whoever made the mistake?

See solution

(a) It runs on every push and every pull_request, but only when the change touches files matching workflows/**.json. The paths filter avoids running the validation when the commit only touched, say, the README: there's no point validating the workflows' JSON if no workflow changed. It's a courtesy saving useless runs.

(b) jq empty "$f" tries to parse the file as JSON and produces no output if it's well-formed, but fails with an error if the JSON is broken (an unclosed brace, an extra comma). Since in CI a failing command stops the step and turns the check red, jq empty works as a minimal proof of "this is at least valid JSON."

(c) The third step detects that the JSON, even if valid, isn't normalized: it compares the committed file against how it'd look after removing volatile fields and sorting keys (what export.sh does). If they don't match, it means someone committed JSON without going through the script —probably downloaded from the editor by hand— and it fails with the message Run ./scripts/export.sh and commit again, telling them exactly how to fix it.

Why it works: if you could answer all three, you can already read a CI workflow, which is 80% of knowing how to write them. And you see how the check automates exactly the two verifications you did by hand in Module 3 —valid and normalized JSON— now on every commit with nobody remembering.

Exercise 2 — Tell apart the two nets. For each problem, say whether the CI check would catch it, lesson 3's human diff review, or neither: (a) an order-triage.json with an unclosed brace; (b) a change lowering the manual review threshold to 100, swamping Cumbre's team; (c) valid but unnormalized JSON, downloaded from the editor by hand; (d) a CRM secret pasted in plain text inside the JSON.

See solution

(a) CI. An unclosed brace makes the JSON unparseable; jq empty fails and the check turns red. It's exactly what the second step catches.

(b) Human review. The JSON is valid and would be normalized, so CI approves it without a problem —to the machine, a threshold of 100 is as legitimate as one of 5000. Only a human who knows Cumbre's business knows 100 is absurd. It's lesson 3's business judgment.

(c) CI. The third step compares against the normalized form and fails if it doesn't match, asking to run export.sh. Human review could also notice it, but CI guarantees it on every commit.

(d) Both, ideally. Human diff review would catch it (you search for sk-, Bearer, long keys). A CI check could be extended to also search for secret patterns —a reasonable improvement— but this lesson's validate-workflows.yml, as it stands, doesn't: it validates form and normalization, not secret hunting. The first real net against secrets remains Module 3's .gitignore and diff review.

Why it works: the exercise makes the division of labor clear. CI catches mechanical problems (broken, unnormalized JSON) automatically and infallibly; human review catches judgment problems (a bad business decision, a leaked secret) requiring understanding the context. Confusing the two —believing green CI means "reviewed"— is the lesson's mistake. Both nets are different and both are needed.

Exercise 3 — Prepare an interview defense. An interviewer opens your cumbre-automations and asks: "I see you use AI to build some workflows. How do you make sure the AI doesn't put something bad into production?" Write the answer you'd give in four or five sentences, pointing at concrete pieces of your repository.

See solution

A possible answer:

"The AI never touches production; it only builds in my dev environment, which is isolated via Docker Compose and, in fact, has the MCP module disabled in prod with N8N_DISABLED_MODULES=mcp, so it isn't even possible to connect it there. When the AI proposes a change in dev, I export it and commit it, and then I review the diff —not what the AI says it did, but what actually changed— with a pull request, right here in the repo. Plus, this green CI check validates the JSON on every commit, and this rollback runbook lets me go back in minutes if something slips through. So: the AI speeds up building, but the diff I review and the promotion flow are what decide what reaches prod. The AI proposes; I approve."

Why it works: the answer turns an abstract question into a walkthrough of concrete evidence from the repo —the per-environment MCP lock, the pull request with the diff, green CI, the runbook. It doesn't describe good intentions; it points at mechanisms the interviewer can verify by opening the repository. That's the difference between defending an artifact and just talking about one.

Summary and next step

In this lesson you automated verification and built your professional cover letter. You understood CI means running automatic checks on every commit —the smoke detector not depending on you remembering— and that GitHub Actions is the free tool doing it, with its CI workflows in .github/workflows/. You learned a CI workflow's anatomy piece by piece —name, on, jobs, runs-on, steps, uses, run— and wrote validate-workflows.yml, which on every commit checks your n8n workflows are parseable JSON (with jq empty) and normalized (comparing against the form Module 3's export.sh leaves), turning the check red with a useful message if something fails. You saw why this is worth so much for so little: it makes quality independent of each person's discipline, exactly what an employer wants to see. And you built the portfolio artifact —the versioned repository plus the workflow's visual evidence— that job postings ask for as a filter ("no prototype = no conversation"), with the interview defenses prepared: every good interviewer's question is a design decision you can point to in your repo.

Before moving on you should be able to: explain what CI is and why its value is in not depending on you remembering; read a CI workflow and say what each piece does; tell apart what CI catches from what human review catches; and name what makes up the portfolio artifact and how you'd defend it.

Lesson 8 is the final project, and it closes out the guide. You're going to integrate everything —the six modules— into the complete, defensible deliverable: the cumbre-automations repository with the normalized, documented JSON, the three environments via Docker Compose, the executed sandbox pass, the promotion and rollback runbook, and the CI check you just set up. It gets delivered exactly as the market asks: "as version-controlled, documented JSON." It's the complete dress rehearsal, the proof you crossed from workflow builder to automation system owner, and the artifact you're going to defend in the interview waiting for you.

Resources