Build a RACI for the lab and test it for separation of duties

short · 40 min · Objective 4.1

Task

Write a RACI matrix covering the real tasks in your lab system, using the ten roles from the lesson plus the business owner, and test it two ways: that every activity has exactly one accountable role, and that no single role holds responsibility for both halves of a separation-of-duties pair. Then face the fact that you hold every role yourself, and record the compensating control for each pair that collapses onto one person.

Steps

  1. Create lab/raci.csv with header activity,data_scientist,data_engineer,ml_engineer,ai_architect,platform_engineer,mlops_engineer,ai_security_architect,ai_governance_engineer,ai_risk_analyst,ai_auditor,business_owner. Each cell holds R, A, RA, C, I or nothing.
  2. Add at least twelve activities drawn from your lab, worded as you will reuse them: add a document to docs/ and the index; check a new source's provenance; judge whether the model is fit for purpose; hold and edit the regression set; approve a model change for release; deploy a model to the runner; change gateway rules; retain and protect logs/gateway.jsonl; threat-model a design change; build the check that blocks unregistered use; score a new AI risk; accept residual risk; verify that approvals followed the procedure; monitor drift.
  3. Give every row exactly one A. Use the lesson's matching: the data engineer owns ingestion, the MLOps engineer drift monitoring, the governance engineer the enforcing check, the business owner risk acceptance, the auditor verification.
  4. Create lab/sod-pairs.csv with header pair_id,activity_a,activity_b,reason and at least five pairs from the lesson, using activity names exactly as they appear in the RACI: build versus approve release, train versus hold the regression set, deploy versus protect the log, add data versus approve the model, anything versus verify.
  5. Create lab/role-holders.csv with header role,holder, one row per role. In this lab every holder is me.
  6. Create lab/sod-compensating.csv with header pair_id,compensating_control,evidence: for each pair, the control that stands in for a second person, such as an immutable copy of the log, an automated gate you cannot bypass, or a review by someone outside the lab.
  7. Plant a conflict: give data_engineer an A on approving a model change for release, run the verify block, see the role-level conflict list fill, then remove it.

Verify

cd ~/secai-lab
python - <<'EOF'
import csv
raci = {r["activity"]: r for r in csv.DictReader(open("lab/raci.csv", encoding="utf-8"))}
roles = [k for k in next(iter(raci.values())) if k != "activity"]
cell = lambda row, x: (row.get(x) or "").strip()
print(len(raci), "activities,", len(roles), "roles")
print("rows without exactly one A:",
      [a for a, r in raci.items() if sum("A" in cell(r, x) for x in roles) != 1])
holder = {r["role"]: r["holder"] for r in csv.DictReader(open("lab/role-holders.csv", encoding="utf-8"))}
comp = {r["pair_id"] for r in csv.DictReader(open("lab/sod-compensating.csv", encoding="utf-8"))
        if r["compensating_control"] and r["evidence"]}
role_conf, person_conf, n = [], [], 0
for p in csv.DictReader(open("lab/sod-pairs.csv", encoding="utf-8")):
    n += 1
    a, b = raci.get(p["activity_a"]), raci.get(p["activity_b"])
    if a is None or b is None:
        print("unknown activity in", p["pair_id"])
        continue
    ra = {x for x in roles if set(cell(a, x)) & set("RA")}
    rb = {x for x in roles if set(cell(b, x)) & set("RA")}
    if ra & rb:
        role_conf.append(p["pair_id"])
    if {holder.get(x) for x in ra} & {holder.get(x) for x in rb}:
        person_conf.append(p["pair_id"])
print(n, "separation pairs; role-level conflicts:", role_conf)
print("person-level conflicts:", len(person_conf))
print("person-level conflicts with no compensating control:",
      [x for x in person_conf if x not in comp])
EOF

Twelve or more activities and eleven roles, no row without exactly one A, no unknown activity, five or more pairs with an empty role-level conflict list, person-level conflicts equal to the number of pairs (you hold every role), and an empty uncompensated list. A person-level count of zero means role-holders.csv does not match the RACI's column names.

Notes

The two conflict counts answer different questions. The role-level count says whether the design separates duties; the person-level count says whether the staffing does. A small team can have a perfect design and no separation at all, which is why the lesson's answer for that case is compensating controls rather than pretending.

This is an independent study companion for CompTIA SecAI+ CY0-001 and is not produced by or endorsed by CompTIA.