Lesson 10 / 10
Data Governance
Counting the ownership gap and the retention policy: in a pipeline with six positions counting the source, three of the five stages carry an owner, yet all six corruptions are caught at an ownerless position, because the checks run only at the source and at the summary layer. A finding walks downstream until it reaches an owner, and three of the six findings go to an owner who can repair them; in the other three the recipient is one to five stages away from the repair point and has no access to the evidence. The three classes whose repair point is ownerless are exactly the three classes the flawed check set never catches. A six-period retention window deletes six periods but raises the periods that can no longer be audited retroactively to seven, the summary falls from 55 cells to 25, 2200 of the lineage's 4400 consumption links point to a deleted row, and the correct partition check raises a false alarm on intact data. The lesson is the course wrap-up.
Published Updated
In this lesson
- Defines ownership as the authority to change the output of a position and counts how many positions of the pipeline carry an owner.
- Measures with three numbers where a finding caught at an ownerless position goes: the recipient, the distance to the repair point and the access to the evidence.
- Computes the number of periods that can no longer be audited retroactively and the number of dangling lineage links when the retention window narrows.
- Collects the uninstrumented and instrumented numbers of the course's ten lessons in a single table.
Contents
In the previous lesson the search went down to the source and stopped there. Lineage said which
rows were missing, wrote that the loss stage was source and fell silent. Why forty rows never
arrived and who should be told the record could not say. The pipeline has five stages, six counting
the source; how many carry an owner.
This lesson measures a gap: the distance between where a corruption is caught and where it can be repaired. The gap is read with three numbers: who the finding goes to, whether that role can make the repair, whether it can look at the evidence. The second measure reads the same gap on the time axis: how many periods can no longer be audited retroactively when the retention window narrows. All the numbers are counts; no ratio is printed.
- QP47. The source, the five-stage pipeline, the corruption classes and the ownership map are fictional and are defined within the lesson. No store, catalog product, orchestrator or policy text is called. Seed 20260218.
- QP48. An owner is the role class with the authority to change the output of a position; it is not a person or an institution. Access is which positions’ rows a role can read.
- QP49. A finding is born at the position where it is caught. If that position has no owner, the finding walks downstream to the first owned position; if none remains, it goes to no one.
- QP50. The repair point is the earliest position at which a corruption can be fixed; it need not be the position where the finding was received. The distance is the number of stages between the two.
- QP51. The detecting check and the detection lag were measured in the first lesson of this course; here they are taken as input and not measured again. Access control, the retention policy and the personal data procedure were measured in the Identity, Access and Cryptography and the Governance, Risk and Compliance courses of the Cybersecurity curriculum; they are not repeated.
- QP52. The retention window keeps the last N periods and deletes what came before. A period is retroactively auditable if its consumption row can be recomputed from the remaining source.
Ownership Map and the Path a Finding Walks
Ownership is not a document but a mapping: who can change the output of which position. The map below recognizes two roles; the remaining positions are empty. The gap is not contrived but a result of the pipeline; intermediate layers are no one’s product.
# FICTIONAL. The ownership, access and repair map is defined within the lesson; no # regulation text, institution, catalog product or orchestrator is called. POSITION = ["source", "screen", "dedupe", "consumption", "summary", "serve"] # An owner is not a person but a role class; the value of an ownerless position is None. OWNER = {"source": None, "screen": "pipeline team", "dedupe": "pipeline team", "consumption": None, "summary": None, "serve": "reporting team"} ACCESS = {"pipeline team": {"screen", "dedupe", "consumption", "summary"}, "reporting team": {"summary", "serve"}} # The detecting check and the detection lag (stage) were measured in the first lesson of this course. DETECTION = {"late_arrival": ("distribution", 4), "duplicated": ("uniqueness", 0), "schema_changed": ("schema", 0), "unit_changed": ("distribution", 4), "missing_partition": ("partition_period", 0), "type_drift": ("type", 0)} # The earliest position at which the corruption can really be repaired. REPAIR = {"late_arrival": "source", "duplicated": "dedupe", "schema_changed": "screen", "unit_changed": "source", "missing_partition": "source", "type_drift": "screen"} # The classes the flawed check set never catches, again from the first lesson. MISSED = {"late_arrival", "unit_changed", "missing_partition"} def route(i): """A finding is born at the position where it is caught; if the position is ownerless it walks to the first owned one.""" for j in range(i, len(POSITION)): if OWNER[POSITION[j]]: return j, OWNER[POSITION[j]] return None, None def yn(x): return "yes" if x else "no" print(f"{sum(1 for a in POSITION[1:] if OWNER[a])} of the five stages are owned ;" " the source position is outside the pipeline and ownerless") print("\ncorruption detector lag position owner finding goes to repair" " distance authority access in flawed set") authorized = accessed = 0 for cls, (det, lag) in DETECTION.items(): j, recipient = route(lag) o = REPAIR[cls] can_fix, can_see = OWNER[o] == recipient, o in ACCESS[recipient] authorized, accessed = authorized + can_fix, accessed + can_see print(f"{cls:<17} {det:>17} {lag:>8} {OWNER[POSITION[lag]] or 'none':>14} {recipient:<17}" f" {o:<7} {abs(POSITION.index(o) - j):>8} {yn(can_fix):>9} {yn(can_see):>6}" f" {'escapes' if cls in MISSED else 'caught':>14}") ownerless = {c for c in REPAIR if OWNER[REPAIR[c]] is None} print(f"\nthe owner receiving the finding can repair in {authorized} corruptions ," f" {accessed} corruptions have access to the evidence") print(f"{len(ownerless)} classes with an ownerless repair point , {len(MISSED)} classes the flawed set never catches" f" , same set: {yn(ownerless == MISSED)}")
3 of the five stages are owned ; the source position is outside the pipeline and ownerless corruption detector lag position owner finding goes to repair distance authority access in flawed set late_arrival distribution 4 none reporting team source 5 no no escapes duplicated uniqueness 0 none pipeline team dedupe 1 yes yes caught schema_changed schema 0 none pipeline team screen 0 yes yes caught unit_changed distribution 4 none reporting team source 5 no no escapes missing_partition partition_period 0 none pipeline team source 1 no no escapes type_drift type 0 none pipeline team screen 0 yes yes caught the owner receiving the finding can repair in 3 corruptions , 3 corruptions have access to the evidence 3 classes with an ownerless repair point , 3 classes the flawed set never catches , same set: yes
The harshest column is the fourth, and it holds a single value: none. Although three of the five stages are owned, not one of the six corruptions is caught at an owned position. The reason is where the checks sit: of the seven checks, six run at the source and one at the summary layer, and both positions are ownerless. Ownership was written where the pipeline produces, not where the check looks. Owning a position and running a check there are two separate decisions; that they do not overlap is a measurable flaw.
Because the finding walks, it still finds a recipient, but the recipient is most often the wrong
role. In three corruptions the pipeline team is both authorized and has access: duplicated is
repaired in deduplication, schema_changed and type_drift in screening. In the other three the
repair point is source, which has no owner. The missing_partition finding lands one stage away;
late_arrival and unit_changed go five stages away, to the reporting team, which holds only a
deviated served value.
The last line is the narrowest finding of this lesson. The three classes whose repair point is ownerless are exactly the same as the three the flawed check set never catches. The two lists came out independently, one from the coverage of the checks, the other from the role map. That they match shows the least audited position is the least owned position.
Retention Window and Retroactive Audit
The ownership gap has a second face in time. Deleted rows free space and also take away the basis of the retroactive audit. The block below runs the same fictional source with five retention windows.
# FICTIONAL. The meter readings of the measurement network are generated within the lesson. In an # intact source the first two stages drop no rows, so consumption is computed directly from the source. SEED, M32 = 20260218, 0xFFFFFFFF def rng(t): x = ((t ^ (t >> 16)) * 2246822507) & M32 x = ((x ^ (x >> 13)) * 3266489909) & M32 s = [(x ^ (x >> 16)) & M32] def next_value(): s[0] = (s[0] * 1664525 + 1013904223) & M32 return s[0] / 4294967296 return next_value REGION = ["east", "west", "north", "south", "central"] TYPE = ["residential", "commercial"] PERIODS = [f"2026-{a:02d}" for a in range(1, 13)] SUBSCRIBERS = {} for i in range(200): r = rng(SEED + 37 * i) SUBSCRIBERS[f"A{i + 1:03d}"] = {"region": REGION[int(r() * 5)], "type": TYPE[int(r() * 2)], "start": 10000 + int(r() * 60000)} def generate_source(): records = [] for i, (sub, a) in enumerate(SUBSCRIBERS.items()): e = a["start"] for p in PERIODS: r = rng(SEED + 101 * i + 7 * PERIODS.index(p)) e += 4 + int(r() * 46) records.append({"subscriber": sub, "period": p, "region": a["region"], "type": a["type"], "index": e, "unit": "m3"}) return records def key_of(k): return (k["subscriber"], k["period"]) def s3_consumption(v): """Consumption from the period difference; a row cannot be computed if there is no previous period.""" table, out = {key_of(k): k for k in v}, [] for k in v: i = PERIODS.index(k["period"]) prev = table.get((k["subscriber"], PERIODS[i - 1])) if i else None if prev is not None: out.append(dict(k, m3=k["index"] - prev["index"])) return out def c_region_period(v): """Correct partition check: has every region been written in every period.""" return {(k["region"], k["period"]) for k in v} != {(b, d) for b in REGION for d in PERIODS} def retain(records, window): """Retention policy: only the last `window` periods are kept, the rest is deleted.""" kept = set(PERIODS[len(PERIODS) - window:]) return [k for k in records if k["period"] in kept], kept source = generate_source() # Every consumption row has two ancestors: its own period and the previous period. LINKS = [x for k in s3_consumption(source) for x in (key_of(k), (k["subscriber"], PERIODS[PERIODS.index(k["period"]) - 1]))] print(f"source {len(source)} rows , consumption rows {len(s3_consumption(source))} ," f" consumption links {len(LINKS)}") print("\n window deleted kept recomputable unauditable 2026-07 alarm dangling") for p in (12, 9, 6, 3, 1): remaining, kept = retain(source, p) recomputable = sum(1 for i, d in enumerate(PERIODS) if i and d in kept and PERIODS[i - 1] in kept) keys = {key_of(k) for k in remaining} status = ("deleted" if "2026-07" not in kept else "ancestor deleted" if "2026-06" not in kept else "auditable") print(f"{p:>7} {12 - p:>7} {len(remaining):>5} {recomputable:>20}" f" {12 - recomputable:>12} {status:>16}" f" {yn(c_region_period(remaining)):>5} {sum(1 for b in LINKS if b not in keys):>8}")
source 2400 rows , consumption rows 2200 , consumption links 4400
window deleted kept recomputable unauditable 2026-07 alarm dangling
12 0 2400 11 1 auditable no 0
9 3 1800 8 4 auditable yes 1000
6 6 1200 5 7 ancestor deleted yes 2200
3 9 600 2 10 deleted yes 3400
1 11 200 0 12 deleted yes 4200
The retention policy makes one more period unauditable than it deletes: with six periods deleted
the unauditable periods are seven, with nine deleted ten. The extra period is always the oldest of
the window; its rows remain, but consumption is a difference and the other end of that difference
is deleted. In the six-period window the 2026-07 rows remain, yet they cannot
be recomputed and the summary layer falls to 25 cells. Reading a retention period as
“the last six periods are auditable” is a misreading; five periods are auditable.
The last column answers the question the previous lesson left open. Lineage had written 4,400 consumption links; in the six-period window 2,200 of them now point to a row that no longer exists, and 200 of those are precisely the ancestors of the boundary period. Keeping the record does not keep traceability; if a link’s row is deleted, the record only reports where the loss is.
The alarm column is the second cost. The period-aware partition check was built in this course as the
correct check. When retention is applied, the set of periods it expects stays larger than the set
that exists, and the check fires on intact data: no corruption, yet a finding. A correct check
becomes a false alarm generator when the field it must look at is narrowed, because no one updated
the expected period set with the policy. Whose job that is is the first section’s question: the
position where the policy is applied is source, and it has no owner.
Summary
- Ownership is the authority to change the output of a position; although three of the five stages are owned, all six corruptions are caught at an ownerless position, because the checks run at the source and at the summary layer while ownership was written to the produced layers.
- A finding finds a recipient by walking downstream, but in three corruptions the recipient cannot
make the repair;
late_arrivalandunit_changedgo to a role five stages from the repair point, with no access to the evidence. - The three classes whose repair point is ownerless are exactly the three classes the flawed check set never catches: the least audited position is the least owned position.
- The retention policy makes one more period unauditable than it deletes; with six periods deleted the unauditable periods become 7, the summary falls to 25 cells and the window’s oldest period cannot be recomputed though its rows remain.
- The record is not independent of retention: 2,200 of the 4,400 consumption links point to a deleted row, and the correct partition check turns into a false alarm.
Course Wrap-Up
| Lesson | Uninstrumented pipeline | Instrumented pipeline | Cost |
|---|---|---|---|
domain-and-lifecycle/01 — the corruption caught |
0 detections, lag undefined | not measured — the lesson only builds the measurement axis, it runs no check | trace 2400/2400/2200/55/5; 480 rows per value (588 / 372) |
domain-and-lifecycle/02 — the stage that sees the corruption (6 classes) |
serve holds 5 values in every class: 0 |
screen 1, dedupe 1 (+2) |
consumption doubles the missing rows: 40→80, 46→92, 200→400 |
domain-and-lifecycle/03 — two measures: rows and deviation (m3) |
rows: schema_changed 2200→1800, the noisiest |
deviation: the same class 0.003, late_arrival 0.105 — the ranking reverses |
missing_partition lowers the deviation 10.832 → 10.829 |
domain-and-lifecycle/04 — the corruption class carried by the source class |
tolerant external service 6 classes, lag 8 stages | declared type, primary key, consistent read 3 classes (−3), 4 stages (−4) | tolerant parsing corrupts 200 rows, no one catches it; the row count check with a 0.05 share catches none |
domain-and-lifecycle/05 — the period in which the corruption is seen (mode) |
batch: period lag 1, 5 stages, 13200 rows | stream: 0 (−1), 8 stages (+3), 2400 rows (−10800) | the stream carries 2600 inputs; on a late-arriving row batch leaves 55 cells correct, stream pollutes 10 of them |
quality-and-principles/01 — detection lag (stage) |
0 detections, lag undefined | six of six classes are caught (+6), lag 4/0/0/4/0/0 | the flawed set never catches three classes; partition stays green with 46 rows missing |
quality-and-principles/02 — the stage where the loss is caught |
no contract: 200 + 200 rows silently dropped, summary 55→45 | with contract: stage 0, summary still 45 — the output does not get fixed | the strict contract blocks 1200 good rows (summary 25); load-level enforcement blocks 2200, no output |
quality-and-principles/03 — raw rows and served value |
appending store 2400 → 4800 → 7200 | keyed write 2400 constant, duplicated rows 0 (−4800) | north is 1212.55 in all three runs; 2425.09 and 3637.64 with the flawed deduplication |
quality-and-principles/04 — rows looked at in a search back to the source |
no record: 9260 rows, independent of the target | with lineage 94–155 rows (60–99 times fewer) | 11455 links, 1.21 per row; a link cannot be written to a row that never arrived |
quality-and-principles/05 — ownership gap and unauditable period |
no ownership written: the finding has no recipient, unauditable period 1 | 3 of the 5 stages owned, 3 of the 6 findings go to an owner who can repair (+3) | 0 of the six detection positions are owned; a six-period retention raises the unauditable period to 7 (+6), dangling links 2200 |
The table is one rule written ten times. A data pipeline’s number is not the rows it carries but how many steps later a wrong row is caught; a pipeline whose check for catching a corruption is not written counts as unmeasured. No number in the uninstrumented column comes from bad code; they are the numbers of an arrangement that counts the carried rows but never asks whether they are correct.
The second claim is also read from the same table: an added check also corrupts. The rows that
say so by name are these. In quality-and-principles/02 the strict contract blocks 1200 good rows on
a harmless field addition and drops the summary to 25 cells, though the corruption caught is zero;
in the same lesson load-level enforcement blocks 2200 good rows and yields no output. In
quality-and-principles/01 the partition check stays green while 46 rows are unwritten: false
assurance costs more than no check. In domain-and-lifecycle/04 the row count check with a 0.05
share catches no class. In domain-and-lifecycle/05 stream mode permanently pollutes 10 summary
cells on a late-arriving row, while batch mode leaves 55 correct. In quality-and-principles/03
deduplication absorbs the corrected load. In quality-and-principles/05 the retention policy turns
the correct partition check into a false alarm.
The third claim is why looking at the served number is not enough, and it is counted with three
corruptions by name. duplicated leaves all five values exactly the same, because deduplication
absorbs the surplus; domain-and-lifecycle/02, domain-and-lifecycle/04 and
quality-and-principles/01 each measured this separately. type_drift does the same in a type
conversion. unit_changed is the quietest: it moves ten summary cells between 1812946491 and
−1812944121, the two periods cancel each other, and the value of north stays at 1212.55. None of the
three can be seen by observing the served output.
Across ten lessons the rows the pipeline carries were counted and how many stages later a corruption was caught was measured. One thing was never asked: where those rows sit. The source was cited as 2400 rows, the summary as 55 cells, the links as 11455 records; but the layout they are written in, its partitioning, and how much it multiplies a query were never counted. The next course turns this layout into an object of measurement.
To keep your progress and take notes, Log in
My notes
Log in to take notes.