Skip to content
academia.sh

Lesson 03 / 11

The Architect's Responsibilities

Building the decision, communication, and oversight responsibilities as a matrix: translating a decision into a machine-auditable constraint form, running the supervised and unsupervised schemes with the same edge sequence on a codebase that grows shortcuts, the share of decisions with no counterpart left in code, and the hour distribution across the three responsibilities.

Contents

The previous two lessons treated the decision as something already made: the decision exists, its scope is measured, its level is determined. Someone carries out the three-level scheme’s 47 consults, and that person’s job is not only to make decisions. This lesson splits the architect’s job into three responsibilities.

Decision is the selection of an option. Communication is the decision reaching the units in its scope in an applicable form. Oversight is the decision’s counterpart in code continuing to survive. There is no order among the three; missing one devalues the others. What gets measured is exactly this: the share of decisions that, despite having been made and announced, have no counterpart left in the code.

The Decision’s Written Form

For a decision to be overseen, it has to be translated into a form the machine can audit. In this model, the form is this: which modules may not directly import which module (WA7). The decision “data access goes through a single layer” means the front modules cannot draw a direct edge to the data access module; if the edge exists, the decision has been violated. This is the same form as the boundary-violation measure established in the Service Architectures course.

Not every decision can be translated into this form. Email being a plain-text body, the daily report being produced in a separate module, and the kiosk front being a separate deployment unit do not show up in the import graph. These are audited by other means; the measurement keeps them separate from the auditable decisions.

The model reuses the previous lesson’s import graph; the graph is repeated so the block runs on its own.

// architecture/model.mjs — MODEL library network codebase: import graph and decision bodies.
// Not a real institution; edges and decision bodies are chosen by hand.
export const IMPORTS = {
  configuration: [], clock: [], logging: ["configuration"],
  "data-access": ["configuration", "logging"], "event-bus": ["configuration", "logging"],
  authentication: ["data-access", "configuration", "logging"], authorization: ["authentication"],
  "catalog-connector": ["configuration", "logging"],
  "catalog-cache": ["catalog-connector", "clock"],
  "loan-rule": ["configuration", "clock"],
  "loan-core": ["loan-rule", "data-access", "event-bus", "clock", "catalog-connector"],
  reservation: ["loan-core", "notification-queue", "data-access"],
  "member-registry": ["data-access", "logging"],
  "member-penalty": ["member-registry", "loan-rule", "clock", "data-access"],
  "branch-inventory": ["data-access", "event-bus"],
  "branch-sync": ["branch-inventory", "event-bus", "catalog-connector", "logging"],
  "notification-queue": ["data-access", "event-bus"],
  "notification-email": ["notification-queue", "configuration"],
  "report-daily": ["data-access", "clock"],
  "report-batch": ["data-access", "branch-inventory", "clock", "logging"],
  "web-front": ["loan-core", "catalog-cache", "member-registry", "reservation", "authentication"],
  "staff-front": ["loan-core", "member-registry", "member-penalty", "branch-inventory", "report-daily",
    "authentication", "authorization"],
  "kiosk-front": ["loan-core", "catalog-cache", "authentication"],
  "batch-job": ["report-batch", "branch-sync", "notification-queue", "notification-email"],
};
export const DECISIONS = [
  { name: "data access goes through a single layer", body: ["data-access"] },
  { name: "settings are read from a single source", body: ["configuration"] },
  { name: "inventory is synced over the event bus", body: ["event-bus", "branch-sync"] },
  { name: "the external catalog is accessed through a single connector", body: ["catalog-connector"] },
  { name: "authentication is consolidated in a single module", body: ["authentication"] },
  { name: "the loan period rule is read from configuration", body: ["loan-rule"] },
  { name: "the clock source is taken from a single module", body: ["clock"] },
  { name: "penalty calculation stays separate from the member record", body: ["member-penalty"] },
  { name: "the daily report is produced in a separate module", body: ["report-daily"] },
  { name: "email notifications go out with a plain-text body", body: ["notification-email"] },
  { name: "the kiosk front is a separate deployment unit", body: ["kiosk-front"] },
];
export const MODULES = Object.keys(IMPORTS);

Measurement

The second file builds the responsibility matrix, changes the codebase over ten rounds, and runs the same sequence of edges across two schemes. The form the change takes is this: a developer draws a direct edge to a module they already reach indirectly — a shortcut (WA8). A shortcut does not create a new access path, it shortens an existing indirect one; so the pool is finite, and every addition removes one edge from the pool. The sequence is generated once and handed unchanged to both schemes.

// architecture/responsibility.mjs — the responsibility matrix, comparing the scheme with and without oversight
import { IMPORTS, DECISIONS, MODULES } from "./model.mjs";

const FRONTS = ["web-front", "staff-front", "kiosk-front", "batch-job"];
// CONSTRAINTS — the decision's auditable counterpart in the import graph: no direct edge from source to target
const CONSTRAINTS = [
  { decision: 0, source: FRONTS, target: "data-access" },
  { decision: 1, source: FRONTS, target: "configuration" },
  { decision: 2, source: ["web-front", "kiosk-front", "staff-front"], target: "branch-inventory" },
  { decision: 3, source: FRONTS, target: "catalog-connector" },
  { decision: 4, source: ["reservation", "member-registry", "member-penalty", "branch-inventory", "report-daily", "report-batch"], target: "authentication" },
  { decision: 5, source: ["web-front", "staff-front", "kiosk-front"], target: "loan-rule" },
  { decision: 6, source: FRONTS, target: "clock" },
  { decision: 7, source: ["member-registry"], target: "member-penalty" },
];
const UNAUDITABLE = [8, 9, 10]; // where the daily report is produced, the email body format, kiosk deployment

// responsibility matrix: the state of the three responsibilities for each decision
// made = the decision has been taken; written = a machine-auditable constraint form exists; overseen = depends on the scheme
const matrix = DECISIONS.map((k, i) => ({
  name: k.name, made: true, written: !UNAUDITABLE.includes(i),
}));
const col = (s, n) => String(s).padEnd(n);
const yn = (b) => (b ? "yes" : "no");
console.log(col("decision", 60) + col("made", 8) + "written constraint");
console.log("-".repeat(86));
for (const m of matrix) console.log(col(m.name, 60) + col(yn(m.made), 8) + yn(m.written));
console.log(`\ndecisions made = ${matrix.length}/${matrix.length}; with a written constraint form = ${matrix.filter((m) => m.written).length}/${matrix.length}`);
console.log(`a decision with no written form cannot be overseen: ${UNAUDITABLE.map((i) => DECISIONS[i].name).join("; ")}`);

// ---- the codebase changes over time: a developer turns an indirect access into a direct edge ----
function prng(seed) { let s = seed >>> 0; return () => (s = (s * 1664525 + 1013904223) >>> 0) / 2 ** 32; }
const reachable = (g, a, b) => {
  const seen = new Set([a]), stack = [a];
  while (stack.length) for (const h of g[stack.pop()]) if (!seen.has(h)) { seen.add(h); stack.push(h); }
  return seen.has(b);
};
const shortcuts = (g) => {
  const c = [];
  for (const a of MODULES) for (const b of MODULES)
    if (a !== b && !g[a].includes(b) && reachable(g, a, b)) c.push([a, b]);
  return c;
};
const violates = (a, b) => CONSTRAINTS.filter((c) => c.target === b && c.source.includes(a));

const ROUNDS = 10, PER_ROUND = 5, SEED = 90210;
const base = Object.fromEntries(MODULES.map((m) => [m, [...IMPORTS[m]]]));
const pool = shortcuts(base);
const rand = prng(SEED);              // seed is visible; the sequence is generated once
const remaining = [...pool], SEQUENCE = [];
for (let i = 0; i < ROUNDS * PER_ROUND; i++) SEQUENCE.push(remaining.splice(Math.floor(rand() * remaining.length), 1)[0]);
console.log(`\nshortcut pool = ${pool.length} edges; ${SEQUENCE.length} edges drawn (seed ${SEED})`);
console.log(`${SEQUENCE.filter(([a, b]) => violates(a, b).length).length} of the drawn edges violate a constraint`);
const initial = [];
for (const a of MODULES) for (const b of IMPORTS[a]) for (const c of violates(a, b)) initial.push(`${a} -> ${b}`);
console.log(`constraints already violated at the moment the decision is made = ${initial.length} (${initial.join(", ") || "none"})`);

function run(oversight) {
  const g = Object.fromEntries(MODULES.map((m) => [m, [...IMPORTS[m]]]));
  let added = 0, reverted = 0, hours = 0;
  for (let t = 0; t < ROUNDS; t++) {
    for (const [a, b] of SEQUENCE.slice(t * PER_ROUND, (t + 1) * PER_ROUND)) { g[a].push(b); added++; }
    if (!oversight) continue;
    hours += 0.5;                        // one audit run per round
    for (const a of MODULES)
      for (const b of [...g[a]]) if (!IMPORTS[a].includes(b) && violates(a, b).length) {
        g[a].splice(g[a].indexOf(b), 1); reverted++; hours += 1; // cost of reverting
      }
  }
  const broken = new Set();
  for (const a of MODULES) for (const b of g[a]) for (const c of violates(a, b)) broken.add(c.decision);
  return { added, reverted, hours, broken: broken.size };
}

const C = CONSTRAINTS.length;
const noOversight = run(false), withOversight = run(true);
console.log(`\n${ROUNDS} rounds, ${PER_ROUND} shortcuts per round; auditable decisions = ${C}`);
console.log(col("scheme", 15) + col("added", 8) + col("reverted", 11) + col("broken decisions", 18) + col("compliant", 11) + "oversight hours");
console.log("-".repeat(78));
for (const [name, r] of [["no oversight", noOversight], ["oversight", withOversight]])
  console.log(col(name, 15) + col(r.added, 8) + col(r.reverted, 11) +
    col(`${r.broken}/${C}`, 18) + col(`${(((C - r.broken) / C) * 100).toFixed(0)}%`, 11) + r.hours);

console.log(`\ndecisions with no counterpart left in code: without oversight ${noOversight.broken}/${C}; with oversight ${withOversight.broken}/${C}`);
console.log(`decisions oversight saves = ${noOversight.broken - withOversight.broken}; cost ${withOversight.hours} hours, ${(withOversight.hours / (noOversight.broken - withOversight.broken)).toFixed(1)} hours per decision`);

// ---- how the architect's time is split ----
const HOURS = { "decision-making": DECISIONS.length * 3, communication: 47 * 0.5, oversight: withOversight.hours };
const total = Object.values(HOURS).reduce((a, b) => a + b, 0);
console.log(`\n${col("responsibility", 16)}hours   share`);
console.log("-".repeat(30));
for (const [k, v] of Object.entries(HOURS)) console.log(col(k, 16) + col(v.toFixed(1), 8) + `${((v / total) * 100).toFixed(0)}%`);
console.log(col("total", 16) + total.toFixed(1));
decision                                                    made    written constraint
--------------------------------------------------------------------------------------
data access goes through a single layer                     yes     yes
settings are read from a single source                      yes     yes
inventory is synced over the event bus                      yes     yes
the external catalog is accessed through a single connector yes     yes
authentication is consolidated in a single module           yes     yes
the loan period rule is read from configuration             yes     yes
the clock source is taken from a single module              yes     yes
penalty calculation stays separate from the member record   yes     yes
the daily report is produced in a separate module           yes     no
email notifications go out with a plain-text body           yes     no
the kiosk front is a separate deployment unit               yes     no

decisions made = 11/11; with a written constraint form = 8/11
a decision with no written form cannot be overseen: the daily report is produced in a separate module; email notifications go out with a plain-text body; the kiosk front is a separate deployment unit

shortcut pool = 58 edges; 50 edges drawn (seed 90210)
19 of the drawn edges violate a constraint
constraints already violated at the moment the decision is made = 1 (staff-front -> branch-inventory)

10 rounds, 5 shortcuts per round; auditable decisions = 8
scheme         added   reverted   broken decisions  compliant  oversight hours
------------------------------------------------------------------------------
no oversight   50      0          6/8               25%        0
oversight      50      19         1/8               88%        24

decisions with no counterpart left in code: without oversight 6/8; with oversight 1/8
decisions oversight saves = 5; cost 24 hours, 4.8 hours per decision

responsibility  hours   share
------------------------------
decision-making 33.0    41%
communication   23.5    29%
oversight       24.0    30%
total           80.5

The Compliance Rate of an Unsupervised Decision

All eleven decisions have been made, and eight of them have a written, auditable constraint form. Over ten rounds, 50 shortcuts were added; 19 of them violate a constraint. In the scheme without oversight, these 19 edges became permanent and six of the eight decisions lost their counterpart in the code — a compliance rate of 25%. In the scheme with oversight, the violating edges were reverted at the end of every round, and in the end only one decision was left broken; a compliance rate of 88%.

That one remaining decision is the measurement’s most instructive part. The staff app was already looking directly at branch inventory on the very day the decision was made; oversight reverts only the edges added after the decision. A decision already violated the moment it was made is not fixed by oversight — it needs a separate migration effort. When this distinction is not written down, the oversight report only says “one decision is still broken,” and the reason stays invisible.

The cost of the difference is counted too: oversight saved five decisions and cost 24 hours — 4.8 hours per decision. This number is a justification. The sentence “the architect should track decisions” is not a decision; the sentence “in the scheme without oversight, six of eight decisions lose their counterpart in the code over ten rounds, and oversight saves five of them at 4.8 hours per decision” is verifiable.

The state of the three unauditable decisions should be noted separately. The measurement never counted them, because they leave no trace in the import graph. A decision with no written form cannot be overseen; the compliance rate of a decision that cannot be overseen cannot be measured. The responsibility matrix’s “written constraint” column is therefore more informative than its decision column: 11 of 11 decisions have been made, 8 are auditable.

Time Distribution

The final table totals the hours of the three responsibilities. Decision-making, at three hours per decision, is 33 hours; communication, at half an hour for each of the 47 consults counted in the previous lesson’s three-level scheme, is 23.5 hours; oversight is the measured 24 hours (WA9). Of the total 80.5 hours, 41% is decision-making, 29% is communication, 30% is oversight.

What the distribution says is this: decision-making is less than half the architect’s job. Communication and oversight together take up 59%, and cutting either one produces nothing visible at the time — cut communication produces the previous lesson’s 31 conflicting implementations, cut oversight produces this lesson’s six broken decisions. The cost of both is paid not at the moment the decision is made, but months later.

Summary

  • The architect’s job is three responsibilities: decision, communication, and oversight; for it to be measurable, the decision has to be translated into a machine-auditable constraint form.
  • In the model codebase, 11 of 11 decisions have been made, but only 8 have an auditable counterpart in the import graph; the remaining three cannot be overseen and their compliance rate cannot be measured.
  • Over ten rounds, 50 shortcuts were added, 19 of which violated a constraint; in the scheme without oversight, six of eight decisions lost their counterpart in code (25% compliance), in the scheme with oversight, one decision stayed broken (88%).
  • The one remaining broken decision comes from an edge that existed before the decision; oversight protects only what comes after the decision, and a pre-existing violation is a separate migration effort.
  • Oversight saved five decisions at 4.8 hours per decision; of the three responsibilities’ 80.5-hour total, decision-making took 41%, communication 29%, oversight 30%.

Next Step

This lesson’s oversight scheme silently assumed one thing: that the person making the decision can actually see the import graph. The same person who writes the constraints, counts the violations, and reverts them knows what state the code is in at that moment. This assumption does not hold in every scheme. If the person making the decision never touches the code, a gap accumulates between the graph at the moment they make the decision and the graph months later, and the decision is built on top of that gap. The next lesson models that gap: the sets of modules known to the decision-maker and to the implementer are kept separate, the hands-on and hands-off schemes are run on the same sequence of changes, and two numbers are compared — the decision’s feasibility rate and the number of rounds it takes for a violation to be noticed.

To keep your progress and take notes, Log in

My notes

Log in to take notes.

Start typing to search.

↑↓ Esc navigate · open · close