Lesson map
Lesson 14 published. Map, example, EX053.
flowchart TD
N0["Learning outcomes and connection"]
N1["14.1 The data contract is the control"]
N2["14.2 Offline lab: a deterministic review packet"]
N3["14.3 Failure injection and review gate"]
N4["Subject synthesis"]
N0 --> N1 --> N2 --> N3 --> N4
N4 --> EX["Worked example"]
EX --> Q["Exercise EX053"]
Logical drawing
flowchart LR
N0["Learning outcomes and connection"]
N1["14.1 The data contract is the control"]
N2["14.2 Offline lab: a deterministic review packet"]
N3["14.3 Failure injection and review gate"]
N4["Subject synthesis"]
N0 --> N1 --> N2 --> N3 --> N4 --> Q["EX053"]
[Learning outcomes and connection]
|
v
[14.1 The data contract is the control]
|
v
[14.2 Offline lab: a deterministic review packet]
|
v
[14.3 Failure injection and review gate]
|
v
[Subject synthesis]
|
v
[Worked example] --> [Exercise EX053]
Learning outcomes and connection
The learner will turn the K12 intake and K13 predicate review into a repeatable, read-only triage calculation. The result is a proposal for human review, not an alert adjudication or a command to contain a session. By the end, the learner can specify an allowlist, reject an unexpected field or value, reproduce a result, explain an exception queue and identify the approval boundary. Chapter 13 showed that two alerts can both match their predicates while the service outcome remains unknown. Automation must preserve that distinction. NIST SP 800-61 Rev. 3 is a response-process reference; the K14 script and labels are original teaching fixtures.
14.1 The data contract is the control
Imagine a responder who receives a folder called “incident logs” and writes a script that recursively reads every file, extracts any line containing svc-book and posts a conclusion. That is fast but indefensible: the folder might contain personal data outside the approved scope, old copies, malformed lines, or records from unrelated producers. A bounded triage job starts with a manifest of specific accepted input objects, their version, expected schema, source custodian and permitted use. The job must never infer authorization from the existence of a file. For K14 the inputs are the synthetic, accepted K12 rows I1, I2, H1 and A1, represented below as fixed literals. K12's rejected A2 and duplicate A1 remain in the separately retained exception report. They must not disappear from the case record merely because this job consumes an accepted view.
An input allowlist has at least three levels. Object allowlist: K12-derived-v1 is the only approved dataset for this exercise. Schema allowlist: each record has id, source, time, kind and optional session/request; a producer cannot silently add an actor or “confirmed compromise” field. Value allowlist: source must be idp, host or app; kind is interpreted only in that source's namespace. A row called “commit” from the app is not a database commit. Namespaces and source semantics matter more than attractive column names. In a real incident, the custodian, incident lead, data minimization rule and access receipt must be documented before collection or processing.
The output contract is similarly narrow: accepted IDs, exception IDs with reasons, and review flags with the input IDs that caused each flag. A flag must not say “compromised account” when the test is “an app row is queued.” The audit record names the algorithm version, input version, result digest, run time and reviewer state. The digest detects changes to this derived output when recomputed; it does not authenticate source systems. Record the raw input's own provenance separately. Avoid logging secrets or full personal-data rows in an operational audit line.
Idempotence here means the same immutable input and same algorithm version produce the same decision payload. Do not include wall-clock time in that payload; capture execution time in a separate receipt. “Rerun” does not mean a new alert may be appended twice, a ticket reopened, or an external control executed twice. Any downstream side effect needs its own idempotency key and owner-approved policy. The Python JSON and hashlib documentation explains the local serialization and digest interfaces; it does not certify K12 provenance.
14.2 Offline lab: a deterministic review packet
Run the following Python 3 listing locally. It does not read a file, use a network or mutate a service. The manifest and rows are copied as classroom literals. In a production implementation, cap input bytes and row counts before parsing, pin schemas to reviewed versions, preserve raw bytes and rejection reasons, and protect the audit stream against modification. A general JSON parser alone does not impose a safe application-specific resource limit.
import hashlib
import json
VERSION = "K14-v1"
ALLOWED = {
"idp": {"success", "denied"},
"host": {"boot_b7"},
"app": {"queued"},
}
rows = [
{"id": "I1", "source": "idp", "time": "2026-10-11T11:59:59Z", "kind": "success", "session": "s-7"},
{"id": "I2", "source": "idp", "time": "2026-10-11T12:00:01Z", "kind": "denied", "session": "s-8"},
{"id": "H1", "source": "host", "time": "2026-10-11T12:00:02Z", "kind": "boot_b7"},
{"id": "A1", "source": "app", "time": "2026-10-11T12:00:03Z", "kind": "queued", "session": "s-7", "request": "r-601"},
]
fields = {"id", "source", "time", "kind", "session", "request"}
def triage(packet):
accepted, rejected, seen = [], [], set()
for row in packet:
ident = row.get("id", "?")
reason = None
if not set(row) <= fields:
reason = "unexpected_field"
elif row.get("source") not in ALLOWED:
reason = "unknown_source"
elif row.get("kind") not in ALLOWED[row["source"]]:
reason = "unknown_kind"
elif not all(isinstance(row.get(k), str) for k in ("id", "time", "kind")):
reason = "bad_required_field"
elif (row["source"], ident) in seen:
reason = "duplicate_source_id"
if reason:
rejected.append({"id": ident, "reason": reason})
continue
seen.add((row["source"], ident))
accepted.append(row)
flags = []
for row in accepted:
if row["source"] == "app" and row["kind"] == "queued":
flags.append({"input": row["id"], "claim": "app_reports_queued"})
if row["source"] == "idp" and row["kind"] == "denied":
flags.append({"input": row["id"], "claim": "idp_reports_denial"})
return {"version": VERSION, "accepted": [r["id"] for r in accepted],
"rejected": rejected, "flags": flags}
result = triage(rows)
canonical = json.dumps(result, sort_keys=True, separators=(",", ":")).encode()
print("accepted:", result["accepted"])
print("rejected:", result["rejected"])
print("flags:", [f["claim"] + ":" + f["input"] for f in result["flags"]])
print("same_payload:", canonical == json.dumps(triage(rows), sort_keys=True, separators=(",", ":")).encode())
print("digest_prefix:", hashlib.sha256(canonical).hexdigest()[:12])
The first three lines are the decision payload; the fourth checks repeatability; the fifth is a local fingerprint to record with algorithm and input versions. A changed output value or VERSION changes the canonical payload, but changing an accepted input time without changing these output fields can leave this digest unchanged. Therefore retain a separate raw-input digest and manifest; the output digest is not an input-integrity check. Do not place a dynamic collection timestamp inside canonical. The correct result contains four accepted IDs, zero new rejections, and two flags: queued A1 and denied I2. It contains neither a DB result nor a validated link between the two session namespaces. K12's rejected raw rows are still part of the separate upstream exception report.
14.3 Failure injection and review gate
Make a copy of rows and append an app row with an extra field such as actor. That row must be rejected as unexpected_field before any review flag. Append a second row reusing app A1 and it must be rejected as duplicate_source_id. Change the original A1 kind to commit and it must be rejected as unknown_kind; the code cannot invent a DB interpretation. These are rejection tests, not new K12 evidence. The current sample does not validate timestamps, optional-field types, row limits or identifier grammar; those belong in a reviewed production contract. A deceptive input such as a duplicate key inside raw JSON must be handled at the parser boundary before reaching this dictionary-based function. Mark these limits in the case review.
The human review point asks four questions: do the accepted records still match their manifest and source receipts; are the K12 raw rejections visible; do the flags quote only literal predicates; and does a proposed real action have a separate approved control contract? If any answer is no, stop at “unreviewed packet,” preserve the result version and request the missing owner evidence. A script cannot approve access or a service change.
Exercises — subject 14
EX053 — Contract and lineage. Write an input/output contract for K14 with object, schema, source-specific meanings, output fields, resource limits, raw/derived pointers and owner approval. Identify two facts the two flags cannot establish. Evidence: one-page contract. Rubric: 2 = all boundaries and limits, 1 = useful schema but missing provenance, 0 = grants authority to a script.
EX054 — Reproducible transcript. Execute the listing twice; record the five output lines, especially the digest prefix, and explain why identical bytes depend on both VERSION and canonical serialization. Provide a separate transcript with one extra-field row. Evidence: original and variant runs. Rubric: 2 = exact output and variant rejection without overwriting original, 1 = only counts, 0 = says digest authenticates a producer.
EX055 — Exception audit. Add one duplicate app:A1 row and one app row with kind commit to a copy. Predict rejection order, accepted count and flag count. Reconcile these two new rejections with K12's earlier two raw rejections without treating them as one pipeline event. Evidence: two-stage exception ledger. Rubric: 2 = exact code behavior and distinct stages, 1 = correct counts but merged provenance, 0 = silently drops invalid input.
EX056 — Human review gate. Draft a six-line review note for a colleague who proposes to auto-disable svc-book from the two flags. State what extra source and service checks would discriminate harm from ordinary failure, who decides, one business check and the status of the action. Evidence: review note. Rubric: 2 = bounded flags, owner, DB outcome and safe gate, 1 = general caution, 0 = triggers unapproved revocation.
Subject synthesis
The safe product of K14 is a deterministic, reviewable packet with explicit exceptions. Its highest value is exposing the gap between literal flags and an incident decision. Chapter 15 joins partial views while keeping the same provenance and uncertainty boundaries.