In this section

0.2 How the Course Is Built

Module 0

Introduction

Lesson 0.1 showed what the course builds. This lesson shows how it is built, so you know what to trust on every page and how to check it yourself.

The course has a lab that stands in for Microsoft's services, a set of data the lab reads, a test runner that checks a build against written cases, a fixed shape for every lesson, and a rule about where facts come from.

None of this is decoration. Each part exists because automation that acts on incidents has to be tested somewhere other than the incidents, and a course that teaches it has to be able to show that what it says is true.

By the end of the lesson you will know what the harness is, what it does not do, how a case is written and read, and how to find the source of any claim a lesson makes.

1

The Lab Stands In for Six Services

So nothing is tested on a real incident

A playbook calls several Microsoft services, and none of them is a safe place to try things. Isolating a real device to see what happens is not an exercise. So for Modules 1 to 10 every call the playbook makes goes to the harness, a small program in the lab pack that answers as each service does:

{
  "the harness plays": {
    "Microsoft Sentinel": "incidents, entities, comments, labels",
    "Defender for Endpoint": "machines, machine actions, isolation, scans",
    "Microsoft Graph": "alerts, users, sign-in sessions",
    "Log Analytics": "the query API and saved functions",
    "Microsoft Entra ID": "client-credentials tokens, with audiences",
    "the approval service": "requests, answers, expiry"
  }
}

The harness answers each call with the request and response shapes from Microsoft Learn's reference page for that operation: the same URL paths, the same JSON field names, the same status codes for success and for each documented error.

A workflow that works against the harness is calling the real APIs correctly, which is why the same definitions run in a tenant in Module 11 with only their addresses and identities changed.

It is also small enough to read. The harness is one Python file with no dependencies beyond the standard library and the query engine in the pack, and its code cites the Learn pages its operations follow. When a lesson's result surprises you, you can open the harness and see exactly what it returned and why.

It also does what a real service cannot be asked to do on demand. It can fail a call, throttle it, lose its reply, answer an approval for the wrong device or send the same incident twice in a second. Those are the situations a playbook has to survive, and in a tenant you would wait months to see each one once.

The runtime is not a stand-in. Your workflows run in the Logic Apps Standard runtime on your own machine, the same runtime Azure runs, started by Visual Studio Code or from a terminal. Only the services the workflows call are played by the harness, so what you learn about how a workflow behaves is how it behaves in Azure.

The harness is also strict when you ask it to be. From Module 3 it runs with strict tokens: every call must carry a token issued for the right service, and every token carries only the permissions its identity was granted.

A call to Defender with a Graph token is refused with the status Defender returns, and so is an isolation by an identity that may only read. That is how the course tests least privilege before there is a tenant to test it in.

Module 11 is where the harness steps aside. The workflows are rewritten for a tenant by a script, deployed with Bicep, and run against the real services, and Modules 11 and 12 list each thing that only a tenant can show.

2

What the Harness Assumes

Every guess, written down

Microsoft's pages do not document everything. When the harness has to decide something a page leaves open, it records the decision as an assumption, with an id, and marks every response that relied on it:

{
  "assumptions the harness records": 44,
  "two of them": {
    "approval-one-pending": "The approval service holds at most one pending ...",
    "lost-reply": "The lost-reply fault: the machine action is ..."
  }
}

There are 44 of them. Each says what the harness does and why, so that when a lesson's result depends on one, the lesson can say so, and when a tenant behaves differently, the difference has a name.

The approval service assumption, for example, is a decision about a service the course itself defines in Module 8; the lost reply is a fault the harness can inject to test what happens when Defender does the work but its answer never arrives.

Every response the harness gives carries a header that says it is not a Microsoft service, and a response shaped by an assumption carries another naming the assumption. A run history in the lab therefore shows, on every call, which parts were documented behavior and which were the harness's choice.

The assumptions are also the course's list of things to check in a tenant. When Module 11 deploys the playbook, several of its tenant checks are assumptions from this list, and the result of each check either confirms the harness or changes it. A harness that never wrote its guesses down could not be corrected this way.

The faults it can inject, and the operations they can target:

{
  "fault kinds": [
    "busy",
    "deny",
    "fail",
    "lost-reply",
    "malformed",
    "nodata",
    "partial",
    "pending",
    "throttle",
    "timeout",
    "unavailable"
  ],
  "operations a fault can target": [
    "aud",
    "avscan",
    "busy",
    "fnstart",
    "fnstatus",
    "getaction",
    "graphalerts",
    "isolate",
    "kind",
    "laquery",
    "listactions",
    "listmachines",
    "unisolate",
    "userget",
    "userpatch",
    "userrevoke"
  ]
}

Assumptions are not shortcuts. Each one was needed because the playbook depended on behavior the documentation leaves open, and the harness had to do something; writing it down is what keeps the lab honest about the difference between what Microsoft promises and what the course had to decide.

A fault is set up by a case before a run, and applies to the operation it names, once or for every call. Throttle returns a 429 with Retry-After, the way Microsoft's APIs signal too many requests.

Timeout and unavailable return 504 and 503. Deny returns 403, the status of a missing permission. Partial and nodata are the query API's ways of returning less than was asked. Each one is a lesson somewhere in the course.

3

The Data It Serves

One month at one organization

The harness does not invent incidents. It serves 540 incidents from the course's data, a month of activity at a fictional organization, and answers queries against the same data:

{
  "corpus tables the harness reads, rows": {
    "SigninLogs": 8203,
    "DeviceProcessEvents": 9578,
    "DeviceInfo": 225,
    "IdentityInfo": 97,
    "SecurityAlert": 710,
    "SecurityIncident": 1278,
    "ThreatIntelIndicators": 252
  }
}

These are the tables the playbook's evidence comes from. Sign-ins and process events answer the evidence queries in Module 4. Device and identity information feed the enrichment in Module 6. Alerts and incidents are what Sentinel would hold.

Threat intelligence indicators back the file and address checks. The lab pack carries these tables and the query engine that reads them, so a query in the lab returns the same rows as the one shown in a lesson.

The data is the same on every machine, which is the point. When a lesson says a query returned one sign-in for an account in a window, your query returns one sign-in. When it says an incident has four entities, yours does. A course that ran against live data could not make that promise.

Times in the data are fixed too. The playbook's evidence windows are built from the incident's own first and last activity, not from the time you run it, so a query for sign-ins around an incident returns the incident's hour of the data's month whenever you run it, and the evidence lines in its comment match the lesson's.

The organization has real-looking problems. Some incidents are true attacks; most are noise; a few are the kind that should end with no action at all. The incident Module 1 starts with is a ransomware alert on one laptop.

The capstone uses a different laptop and a different account, with a failed sign-in from another country five days before its endpoint activity. Lesson 12.8 shows the playbook's evidence step missing that sign-in on purpose, because it falls outside the window the playbook queries.

The data has gaps on purpose, the way real data does. Some accounts have no identity record, some devices are not onboarded, some incidents name a host that matches no device. Each gap is a case the playbook has to handle by saying what it could not find, and several lessons are built around one.

You will also read the data directly. Lessons that make a point about evidence show the query and let you run it in the browser console beside the lesson, against the same tables.

4

Cases and run-spec

How a build is checked

Every change to the playbook is checked against written cases. A case says what to send, how to answer the approval, what faults to inject and what must be true afterward:

{
  "one case, from the capstone": {
    "id": "cap-contained",
    "send": {
      "incident": 100448
    },
    "approval": {
      "answer": "approve"
    },
    "expect": {
      "reply_status": 200,
      "run_status": "Succeeded",
      "comment": {
        "contains": [
          "Incident 100448",
          "NE-THOMPSON-LT",
          "isolated (Full)",
          "verified: no release since."
        ],
        "excludes": [
          "sig=",
          "Bearer",
          "access_token",
          "client_secret"
        ]
      },
      "actions": {
        "Isolate": 1
      },
      "approvals": 1,
      "labels": []
    }
  }
}

That case sends the capstone incident, approves the request, and expects the run to succeed, the reply to be 200, the comments to contain four exact phrases and never a callback signature or a token, one isolation, one approval request and no labels. run-spec, the course's test runner, sets the case up through the harness, runs it against your workflow in the local runtime, and prints PASS or FAIL with the reason.

The phrases a case looks for are chosen to be the ones that matter. A containment case checks for verified, not for isolated, because a playbook that wrote isolated without checking Defender's history would be making the claim the course spends Module 9 teaching it not to make. A case that checked for the wrong phrase would pass a playbook that does the wrong thing.

A case checks what a person would check by reading the incident, but without the person. That is why the expectations are phrases from the comment rather than internal details of the workflow: a case should pass for any build that does the right thing, however its actions are arranged, and fail for any build that writes the wrong thing, however green its run.

Cases accumulate. Module 2 writes the first specification, and each module adds the cases for what it built. A change in Module 10 is run against Module 8's and Module 9's cases as well as its own, so the course finds the change that fixes one thing and breaks another before it moves on.

A failing case prints exactly what it expected and what it saw, such as two isolations where one was expected or a comment missing a phrase. Most failures can be understood from that line alone.

When it is not enough, the run history in the local runtime shows every action of the failing run, its inputs and outputs and the order they ran in, which is the same view an engineer uses in Azure.

You write cases too. Several exercises ask for a new case before a fix, so that the fix has something to pass, and the capstone ships as a file of cases you run against your own build.

5

How a Lesson Is Laid Out

The same order, every time

Every lesson has the same skeleton, so you always know where to find things. Lesson 12.2, counted from the file:

{
  "Lesson 12.2, as built": {
    "introduction": "what the lesson changes and why",
    "sections": 8,
    "code and file blocks": 12,
    "procedures": 1,
    "practice": "one Do this card, with a done-when line",
    "last line": "what the next lesson does"
  }
}

The introduction says what the lesson changes and why it matters. Numbered sections each teach one thing, and each is anchored on something real: a workflow definition, a Bicep file, a query and its result, a run of the playbook, a procedure with the real commands. The prose explains what the artifact shows; the artifact is what you will work with.

At the end is a Do this card: a short list of steps that apply the lesson to your own build, with a done-when line that says how you know you have finished. The last line names what the next lesson does, so a lesson always ends pointing forward.

Sections are numbered and titled so you can return to one. Each has a short subtitle that says what it covers, and the quicklinks at the top of every lesson list the module's other lessons, so moving between them never needs the course page.

Exercises are the eighth lesson of each module. They give a reference build and two drafts, run against the module's cases, and ask you to find what each draft gets wrong before the lesson shows it. Module 0 has no exercise; its practice is getting the lab running.

Some lessons open with a predict before the first section: a short question about what a change will do, answered by the run later in the lesson. Others open on the workflow drawn with the change marked. Either way the first thing a lesson shows you is the thing it is about to change, before any explanation of it.

Most lessons are between three and four thousand words. That is long for a page and short for what a module teaches, so the sections are written to be read one at a time, each starting with what it covers and ending with what follows from it.

6

Where Facts Come From

Learn first, then the lab

Every statement a lesson makes about how a Microsoft service behaves comes from Microsoft Learn, read when the lesson was written, or from a run in the lab. Each lesson's generator records its sources:

{
  "Lesson 12.2's sources, from its generator": {
    "Learn: collect diagnostic data": "the Standard category, table and events",
    "Learn: LogicAppWorkflowRuntime": "the columns, and which resources send them",
    "Learn: monitor automation health": "the join from the health log to the runs",
    "the corpus engine, planted rows": "that the function's two joins match"
  }
}

Learn is the source because it is Microsoft's own documentation of how its services behave, and because it changes when they do. A lesson that cited a blog post or a memory of how something used to work would be teaching something nobody can check. Where only a third party documents a behavior, the course names the third party and treats the claim as something to confirm in a tenant.

When Learn and the lab disagree, or when Learn says nothing, the lesson says so. Some behavior is only knowable in a tenant, and Modules 11 and 12 list those as checks for the first deployment rather than presenting a guess as fact. Where a page is ambiguous, the course quotes what it says and names the ambiguity.

Numbers are never typed. A count in a lesson, a row total, a run time, a number of actions, is read from the data, the run or the file when the lesson is generated. If the data changes, the number changes with it, and the generator stops if a number it depends on no longer holds.

The same rule applies to the playbook's own output. The comments quoted in lessons are the ones a run wrote, and the drafts in each exercise were actually run against the cases. When a run produced something the course did not expect, the lesson reports it: Lesson 12.8 shows a duplicate case that failed once and explains the timing gap it exposed.

The rule also governs what the course does not claim. It does not state prices as current, because they change and differ by region; it teaches how to read them. It does not state limits it has not seen documented. And it does not describe a tenant's behavior from the lab's.

You can apply the rule yourself. If a lesson's claim matters to your work, its source is named and can be opened, and its run can be repeated in your lab.

7

The Aids, and Why They Vary

Different tools for different jobs

Lessons use a range of visual aids, each where it fits rather than in every lesson. How often each appears across Modules 1 to 12:

{
  "lessons using each aid, of 96": {
    "procedure": 76,
    "annotated file": 20,
    "predict": 26,
    "comparison grid": 35,
    "workflow canvas": 58,
    "check": 12
  }
}

Procedures and annotated files are the most common, because the course is about building and those are how building is taught: steps with the real commands, and real files with their important parts explained in the margin.

Predicts ask you to commit to an answer before the lesson shows it, which is how a surprising result sticks. Comparison grids set options side by side. Workflow canvases draw the playbook with the change marked.

The canvases are drawn from the workflow definitions, not by hand. A lesson that changes the playbook shows the changed part highlighted and the rest of the playbook around it, so you always see where a change sits in the whole and which steps run before and after it.

The variety is deliberate. Lessons that all looked the same would read like a template and teach like one. Each aid is there because the content needed that shape, and no aid appears in more than half the lessons of any module except the procedures and annotated files, which are the course's core.

All of them use one color scheme, with each color meaning one thing: the workflow and your work in indigo, data read in teal, anything that changes something in rust, a wait or a limit in amber, success in green and failure in red. Once you know it, a diagram tells you at a glance which steps read and which steps act.

None of the aids is required reading in the sense of a test. A predict that you get wrong is doing its job: the gap between what you expected and what happened is the thing worth remembering, and the reveal explains it.

Code and file panes stay dark, and are the exact text of the file or command: copying one into your project gives you what the lesson ran. Where a pane shows only part of a file, the lesson says which part, and names the file, so you can open the whole of it in your own project or the template pack.

Do

Practice

Find the source of one claim
your tenant

Pick a claim from Lesson 0.1 and trace it to where it came from.

Do this Find the source of one claim
  1. Pick a sentence in Lesson 0.1 that states a fact about the playbook or the data.
  2. Find which module builds it, and which file or run shows it.
  3. Read the harness assumptions list in the lab pack, and pick one that surprises you.
  4. Note what you would check in a tenant to confirm it.
Done when you can say, for one claim, whether it comes from Learn, the lab or an assumption.

Lesson 0.3 names each tool the course uses, the version it was built with, and what each one does.