Skip to content
academia.sh

Lesson 10 / 10

Compliance Requirements

Turning externally imposed constraints into a check: running five constraint types over 15 reported copies, a narrow access definition catching 7 of 10 violations and missing 3, catching rising to 8 and the false alarm to 3 once the definition broadens, producing 27,375 lines of evidence a year instead of relaxing, and the constraint costing the architecture one new component and 11 new edges.

Contents

The previous lesson measured how a rule ages and counted two ways out: updating the configuration or relaxing the rule. Both were in the architecture’s own hands, because the architecture itself had set the rules — the same side chose the gate list, the allow list, and the exceptions, and the same side changed them when they aged.

This lesson’s constraints come from outside. A compliance requirement is the share of an obligation outside the system that falls on the architecture; removing it, relaxing it, or writing your own exception is not an option. The course’s measure stays the same — can the constraint be turned into an executable check, and if not, what takes its place — but it diverges in three places: evidence takes the place of relaxing, the untranslatable part sits in the rule’s interpretation, and the constraint directly changes the architecture’s shape. Constraints are named here by type: a retention-period requirement, a data-residency constraint, the right to erasure, an access-log retention obligation, an auditability obligation.

Five Constraint Types, Five Checks

The example is again the course’s fictional regional library network. GV19 — data-class table: five data classes, each with a retention floor, a retention ceiling, an allowed zone, whether the right to erasure applies, and an access-log obligation. GV20 — reported-copy table: fifteen copies; each row is one instance of a data class sitting in one component, in one zone, and carries the age of its oldest record, whether an access log is kept, and whether it is wired into the erasure flow. GV21 — known violation set: ten hand-labeled violations; two of them are unreported copies that never appear in the table at all. GV22 — two scopes of the access definition: the narrow definition counts only direct reads as access, the broad definition also counts derived reads.

The file below builds the tables, the five checks, and the measurement. It is an in-process model; there is no real store, network, or record.

// compliance/measure.mjs — turning externally imposed constraints into an executable check. In-process
// model: the fictional regional library network; there is no real store, network, or record, each copy is one row.

const parseRows = (s) => s.trim().split("\n").map((l) => l.trim().split("|"));
const printRow = (w, ...s) => console.log(s.map((v, i) =>
  (w[i] < 0 ? String(v).padEnd(-w[i]) : String(v).padStart(w[i]))).join(""));

// Data class: name|retention floor (days)|ceiling (0 = none)|allowed zone|right to erasure|access log
const DATA_CLASS = Object.fromEntries(parseRows(`
  member-identity|0|1825|internal|1|1
  loan-record|365|1095|internal|1|1
  payment-record|3650|0|internal|0|1
  search-history|0|180|internal external|1|1
  access-log|730|2555|internal|0|0`).map(([name, min, max, z, erase, log]) => [name, { min: +min, max: +max,
  zone: z.split(" "), eraseRight: erase === "1", logRequired: log === "1" }]));

// Reported copy: class|component|zone|age of oldest record (days)|access log kept|erasure-wired|read
const COPY = parseRows(`
  member-identity|membership|internal|900|1|1|direct
  member-identity|loan|internal|1200|1|1|direct
  member-identity|notification|internal|400|0|0|direct
  member-identity|archive|external|2000|0|0|direct
  member-identity|backup|internal|2200|0|0|direct
  loan-record|loan|internal|1200|1|1|direct
  loan-record|report|internal|1500|0|0|derived
  loan-record|archive|external|2000|0|0|direct
  payment-record|fee|internal|2000|1|0|direct
  payment-record|backup|internal|2200|0|0|direct
  payment-record|report|internal|3700|0|0|derived
  search-history|catalog-gate|internal|30|0|1|derived
  search-history|report|internal|300|0|0|derived
  access-log|event-log|internal|800|1|0|direct
  access-log|backup|internal|2200|0|0|direct`).map(([cls, component, zone, age, logged, erased, read]) =>
  ({ id: `${cls}|${component}`, class: cls, component, zone, age: +age,
    logged: logged === "1", erasureWired: erased === "1", read }));

// Five constraint types. "scope" is the access definition: narrow counts only direct reads as access.
const checks = (scope) => ({
  "D1 retention ceiling": (k) => DATA_CLASS[k.class].max > 0 && k.age > DATA_CLASS[k.class].max,
  "D2 retention floor": (k) => DATA_CLASS[k.class].min > 0 && k.age < DATA_CLASS[k.class].min,
  "D3 data residency": (k) => !DATA_CLASS[k.class].zone.includes(k.zone),
  "D4 right to erasure": (k) => DATA_CLASS[k.class].eraseRight && !k.erasureWired,
  "D5 access log": (k) => DATA_CLASS[k.class].logRequired && !k.logged
    && (scope === "broad" || k.read === "direct"),
});

// Hand-labeled actual violation set (model input); the last two are unreported copies
const ACTUAL = parseRows(`
  member-identity|notification
  member-identity|archive
  loan-record|loan
  loan-record|report
  loan-record|archive
  payment-record|backup
  search-history|catalog-gate
  search-history|report
  member-identity|report
  payment-record|notification`).map((x) => x.join("|"));

const SCOPES = ["narrow", "broad"];
console.log(`${COPY.length} reported copies, ${Object.keys(DATA_CLASS).length} data classes, ` +
  `${Object.keys(checks("narrow")).length} constraint types, ${ACTUAL.length} hand-labeled violations ` +
  `(${ACTUAL.filter((a) => !COPY.some((k) => k.id === a)).length} of them unreported copies)`);

const CONSTRAINT_W = [-24, 12, 12];
console.log();
printRow(CONSTRAINT_W, "constraint type", "flagged: narrow", "broad");
for (const name of Object.keys(checks("narrow")))
  printRow(CONSTRAINT_W, name, ...SCOPES.map((s) => COPY.filter(checks(s)[name]).length));

const SCOPE_W = [-16, 16, 11, 7, 14, 13], R = {};
console.log();
printRow(SCOPE_W, "access definition", "flagged copies", "caught", "missed", "false alarm", "clean pass");
for (const s of SCOPES) {
  const d = checks(s);
  const flagged = COPY.filter((k) => Object.values(d).some((f) => f(k)));
  const caught = flagged.filter((k) => ACTUAL.includes(k.id)).length;
  R[s] = { flagged: flagged.length, caught, missed: ACTUAL.length - caught,
    falseAlarm: flagged.length - caught,
    cleanPass: COPY.length - flagged.length - (ACTUAL.length - caught
      - ACTUAL.filter((a) => !COPY.some((k) => k.id === a)).length) };
  printRow(SCOPE_W, s, R[s].flagged, `${caught}/${ACTUAL.length}`, R[s].missed, R[s].falseAlarm, R[s].cleanPass);
}

// Evidence: relaxing is not an option, a run log is required. Evidence is itself a data class.
const BYTES_PER_ROW = 120, DAYS = 365, KEEP = DATA_CLASS["access-log"].max;
const rowsPerRun = COPY.length * Object.keys(checks("narrow")).length;
console.log(`\nevidence: ${COPY.length} x 5 = ${rowsPerRun} rows per run; one run a day -> ` +
  `${(rowsPerRun * DAYS).toLocaleString("en-US")} rows a year, ` +
  `${((rowsPerRun * DAYS * BYTES_PER_ROW) / 1e6).toFixed(2)} MB`);
console.log(`evidence is kept for ${KEEP} days (the access-log retention ceiling) -> ` +
  `${(rowsPerRun * KEEP).toLocaleString("en-US")} rows, ${((rowsPerRun * KEEP * BYTES_PER_ROW) / 1e6).toFixed(2)} MB`);

// The constraint's cost to the architecture: components displaced and new edges
const COST_W = [-36, 10, 12];
console.log();
printRow(COST_W, "cost to architecture", "narrow", "broad");
const relocated = new Set(COPY.filter(checks("narrow")["D3 data residency"]).map((k) => k.component));
const eraseEdges = COPY.filter(checks("narrow")["D4 right to erasure"]).length;
printRow(COST_W, "component displaced", relocated.size, relocated.size);
printRow(COST_W, "new component (erasure broadcast)", 1, 1);
printRow(COST_W, "new edge: erasure flow", eraseEdges, eraseEdges);
const logEdges = SCOPES.map((s) => COPY.filter(checks(s)["D5 access log"]).length);
printRow(COST_W, "new edge: access log", ...logEdges);
printRow(COST_W, "new edge total", ...logEdges.map((x) => x + eraseEdges));

// Untranslatable part: the interpretation of the obligation and the boundary of its scope
const [INTERPRETATIONS, REVIEWS, HOURS] = [6, 2, 3];                // model input
console.log(`\nuntranslatable: the obligation's interpretation and the scope's boundary; in their place ` +
  `${INTERPRETATIONS} interpretation records, ${REVIEWS} reviews a year x ${HOURS} person-hours = ${INTERPRETATIONS * REVIEWS * HOURS} person-hours/year`);
console.log(`when the definition moves from narrow to broad: caught ${R.narrow.caught} -> ${R.broad.caught}, ` +
  `missed ${R.narrow.missed} -> ${R.broad.missed}, false alarm ${R.narrow.falseAlarm} -> ${R.broad.falseAlarm}, ` +
  `new edges ${logEdges[0] + eraseEdges} -> ${logEdges[1] + eraseEdges}`);
15 reported copies, 5 data classes, 5 constraint types, 10 hand-labeled violations (2 of them unreported copies)

constraint type         flagged: narrow       broad
D1 retention ceiling               6           6
D2 retention floor                 2           2
D3 data residency                  2           2
D4 right to erasure                6           6
D5 access log                      5           9

access definition  flagged copies     caught missed   false alarm   clean pass
narrow                         9       7/10      3             2            5
broad                         11       8/10      2             3            4

evidence: 15 x 5 = 75 rows per run; one run a day -> 27,375 rows a year, 3.29 MB
evidence is kept for 2555 days (the access-log retention ceiling) -> 191,625 rows, 23.00 MB

cost to architecture                    narrow       broad
component displaced                          1           1
new component (erasure broadcast)            1           1
new edge: erasure flow                       6           6
new edge: access log                         5           9
new edge total                              11          15

untranslatable: the obligation's interpretation and the scope's boundary; in their place 6 interpretation records, 2 reviews a year x 3 person-hours = 36 person-hours/year
when the definition moves from narrow to broad: caught 7 -> 8, missed 3 -> 2, false alarm 2 -> 3, new edges 11 -> 15

The numbers belong to the measurement class; their inputs are the assumptions above. All five constraint types turn into an executable check: each end of the retention period is a comparison, residency is a set membership, the right to erasure is a reachability question, and the access log is a predicate. Under the narrow definition, nine of the fifteen copies are flagged; seven of the ten violations are caught, three are missed, and two clean copies get a false alarm.

Two of the three missed violations sit where the check cannot see them: unreported copies that never appear in the table. The check only reads reported copies, and it produces no number for the unreported — this is not the rule missing something, it is a gap in the data source, and tightening the rule does not close it. Both false alarms are scope defects: the member identity in backup is flagged for exceeding the retention ceiling, but backup is the subject of a separate regime; the payment record in the fee component looks like it is under the retention floor, but the record that satisfies the floor sits in a different store. Neither is the check’s mistake — both come from the narrowness of the table the check is looking at.

No Relaxing, Evidence Instead

In the previous lesson, the behavior a false alarm produced was disabling a rule, and disabling two rules dropped caught from seven to four. There is no such switch here. What sets an externally imposed constraint apart is that what is demanded is not showing that it was violated, but showing that it was honored: evidence instead of an exception.

Evidence is an output, and its cost is counted. Fifteen copies times five constraints per run comes to 75 lines; one run a day comes to 27,375 lines and 3.29 MB a year. The evidence record is itself kept under the auditability obligation — GV23, 2555 days, the access log’s retention ceiling — for a total of 191,625 lines, 23.00 MB. The closed loop here is worth stating: evidence is itself a data class. It has its own retention period, its own residency constraint, and its own access-log obligation; the check has to check the record it produces, too.

The Constraint’s Cost to the Architecture

A constraint does not just add a check; it also displaces components. GV24 — how the constraint is applied: the component carrying copies that sit outside the allowed zone is moved into the zone, an erasure-broadcast component is added for the right to erasure and opens one edge to every copy not already wired into the flow, and one edge opens to the event log for every access whose log is not kept.

The table below counts this: one component is displaced (archive, because it carries two copies outside the zone), one component is born, and eleven new edges open — six for the erasure flow, five for the access log. None of these edges was chosen to raise a quality attribute, and none is the result of a trade-off; here, an external obligation is dictating the architecture’s shape. That is the point of the measure, too: the constraint’s cost does not sit in one line item called “compliance cost” but as displaced components and opened edges.

The Untranslatable Part: Interpreting the Obligation

All five checks assume a definition has already been given. What is “access” — does a report’s derived read count as access? What is “erasure” — does masking count as erasure? The check does not answer these questions; it takes the answer as input. This is the untranslatable part: the interpretation of the obligation and the boundary of its scope.

What takes its place is a written interpretation record, and its cost is counted — GV25: six interpretation records, two reviews a year, three person-hours per item, 36 person-hours total. But the real measurement is the outcome of the interpretation. The same check measures something different once the “access” definition moves from narrow to broad: caught rises from seven to eight, missed falls from three to two, the false alarm rises from two to three, and the new edges in the architecture from eleven to fifteen. A single word’s scope changes four numbers at once. The untranslatable part is not outside the check; it is in its input — when it is not written as explicitly as the rule itself, every number the check produces becomes contestable.

Summary

  • All five external constraint types turned into executable checks: the retention period’s floor and ceiling, data residency, the right to erasure, the access log.
  • Under the narrow access definition, nine of the fifteen reported copies are flagged; seven of ten violations are caught, three are missed, and two clean copies get a false alarm.
  • Two of the three missed violations are unreported copies: the check only reads reported copies and produces no number for the unreported.
  • There is no option to relax; evidence is required in its place: 27,375 lines and 3.29 MB a year, 191,625 lines and 23.00 MB with 2555 days of retention. Evidence is itself a data class and is subject to its own constraints.
  • The constraint’s cost to the architecture was counted: one component is displaced, one component is born, eleven new edges open (fifteen under the broad definition).
  • The untranslatable part is the interpretation of the obligation and the boundary of its scope; the six interpretation records that take its place cost 36 person-hours a year, and broadening the definition moves caught from 7 to 8 and the false alarm from 2 to 3.

Course Wrap-Up

Over ten lessons, the course asked a single question: can a quality attribute or an architectural rule be turned into an executable check, and if not, what takes its place?

Lesson Turned into a check Caught / missed / false alarm Untranslatable, and what replaced it
The Quality Attribute Tree 25 of 30 leaves tied to a number, 20 to a check 11 / 7 / 23 10 leaves; a quarterly measurement session and review catches 4 of the missed with an 8.5-window delay and 100 person-hours
Tension Between Quality Attributes a tension matrix from 12 decisions, threshold sweep 4/1/0 at eight points, 4/1/5 at six points whether the drop is acceptable is a judgment call; in its place, 5 accepted trade-off records, false alarm 5 → 0
Security Architecture 7 zones, 8 boundaries, 18 crossings; the check required per boundary 6 / 4 / 3 (3 open gaps) whether the boundary sits in the right place; a quarterly review of the 8 boundaries, 24 person-hours
Scalability Decisions a replication rule across three scale-unit candidates 5 / 1 / 1 (3 correct silences) an undeclared shared resource; in its place, a replication trial, 60 person-hours over 10 attempts
Maintainability Criteria a file, module, and depth metric across 12 changes; a threshold rule 3 / 1 / 1 (7 correct silences) a change’s difficulty and whether the concept sits in the right module; a quarterly layout review, 12 person-hours a quarter, 72 across six quarters
Architecture Governance Models 5 of 9 governance rules turned into checks; 24 decisions centralized 12 / 0 / 8, federated 9 / 3 / 1, advisory 6 / 6 / 1 4 rules; in their place, a 1/3 sample, 32 manual reviews, missing 7 of 12 violations
Fitness Functions F1 public surface size, F2 dead export ratio F1 5/0/6 at > 2, 1/4/0 at > 10; F2 4/1/1 “a public name describes the module’s job”; in its place, a sample reading 22 of 66 symbols
Dependency and Layer Control five forms of the same layer rule; 13 modules, 34 edges denylist 3 / 6 / 0, layer allowlist 7 / 2 / 1, module allowlist 9 / 0 / 0 an approved migration edge cannot be expressed by a rule; in its place, 2 sanctioned exception records, catching 7 → 6 without an expiry check
Evolutionary Architecture a fitness function measuring openness to change; 12 proposals 7 / 1 / 1 the cheapness of a change not yet requested; in its place, 6 scenarios and 4 reviews a year (24 items), covering 5 of the 12 changes that arrived
Compliance Requirements five external constraint types, 15 reported copies 7/3/2 under the narrow definition, 8/2/3 under the broad the interpretation of the obligation and the boundary of its scope; in its place, 6 interpretation records, 36 person-hours a year

The table’s rule deserves to be named: a quality attribute or an architectural rule is governable to the extent it can be turned into an executable check; the untranslatable part is written down by name and what replaces it is counted. The sentence “this attribute matters” carried no decision anywhere in this course. The three columns do not substitute for one another, either: the second says what the check looks at, the third says how much of it the check gets right, the fourth says what is done where the check cannot look at all. A governance regime that does not write down all three knows only what it has caught.

Two patterns repeat in the table. First, every setting that raises catching also raises the false alarm — the Quality Attribute Tree’s 23 alarms, Tension Between Quality Attributes’ six-point threshold, this lesson’s broad access definition — and the price of a false alarm is a rule getting disabled: in the Quality Attribute Tree, disabling two checks took caught from 11 to 9; in Fitness Functions, once F1 closed on its fourth release it missed 8 of the 9 releases with a violation; in Evolutionary Architecture, disabling two rules took caught from 7 to 4. Second, what replaces the untranslatable is, in every lesson, human time, and it is expensive: 100, 24, 60, 72, and 36 person-hours, 32 manual reviews, a 22-symbol sample, 24 review items.

The question the course leaves behind sits below the table. Rules were written, turned into checks, and their catches and misses were counted. But it is people who write the rule, run it, disable it in the face of a false alarm, and sit down for the review session for the untranslatable part. How teams are split, at which boundary a decision gets made, and how delivery flows directly shape the architecture; none of this was taken up anywhere in this course. The next course, Process, Team and Delivery, takes up that layer.

To keep your progress and take notes, Log in

My notes

Log in to take notes.

Start typing to search.

↑↓ Esc navigate · open · close