Lesson 17 / 21
Tagging and Cost Allocation
Ownership information comes from outside the bill, and it does not come in full: in a fictional inventory of 148 resources, visible tag coverage is 77.7% while valid coverage is 75.0%, and 25.1% of the monthly 4,214.3 fictional units stays unallocatable, entering no rule at all. When the shared 1,584.9 units is split across three rules, the same team's bill swings between 8.7% and 18.6%. A tag mandate zeroes the unallocatable share but rejects 37 resources and stalls 14 legitimate jobs worth 511.9 units; even once tags reach 95.9% coverage by month's end, that month's 568.6 units (13.5%) never correct retroactively.
Published Updated
In this lesson
- Measures tag coverage as two separate numbers: resources that show up tagged in the coverage report and resources counted valid in allocation
- Distributes cost from the tag to teams and counts the direct, shared, and unallocatable shares separately
- Counts the cost of a tag mandate: rejected resources, stalled legitimate work, and the tag's failure to reach the past bill
Contents
The previous lesson measured that the bill gives fourteen splits while the network requires ninety units of separation. That gap does not close from the bill; ownership information has to come from somewhere else. On the cloud side, that place is the resource itself: a tag is attached to a resource, and cost is distributed from that tag.
This lesson’s question is not how the distribution should be done, it is where it does not hold. Team-based accounting and the unsplittable item were measured in M22/K08; what is measured here is the tag, its input.
Tagged Resource Inventory
The example is the fictional regional measurement network: five teams, three environments, a
rented machine, network, store, function, and edge layer. Resources, tags, and amounts are
fictional; the currency is fictional too and is called unit, corresponding to no real price
list.
CC7: the inventory holds 148 resources; eight types, three of which are used by more than
one team and carry a shared tag. CC8: whether a resource has an owner tag depends on how
it was created; there are four creation paths. CC9: a misspelled owner tag shows up
tagged in the coverage report but has no counterpart in the team roster; it is invalid in
allocation. CC10: a shared resource’s real driver is unknown — there is no true share to
compare against in this lesson, what is measured is not deviation but rule-driven swing. CC11:
a resource can carry at most ten tag keys; the network uses seven. CC12: a tag is completed
on some day; bill lines cut before that day are untagged and never correct.
The inventory is a process-internal model derived from a generator; the seed is visible.
// cost/inventory.mjs — tagged resource inventory model: no real inventory. Resources are // derived with a generator, the seed is visible, and the run repeats. The currency is fictional // and is called `unit`. Whether a tag *exists* is decided by how the resource was created; // a misspelled tag is a separate case: tagged in the coverage report, invalid in allocation. export const SEED = 6108; export const COUNT = 148; export const TEAM = ["meter-reading", "validation", "billing", "field", "reporting"]; export const ENV = ["production", "staging", "development"]; export const TAG_LIMIT = 10; // tag keys per resource, at most export const USED_KEY = ["owner", "environment", "service", "version", "temporary", "responsible", "cost-center"]; // type name, monthly unit lo-hi, shared (used by more than one team) export const TYPE = [ ["rented machine", 18, 96, false], ["block storage", 4, 34, false], ["object store", 6, 52, false], ["function", 1, 12, false], ["edge node", 3, 21, false], ["network gateway", 12, 40, true], ["shared message bus", 20, 64, true], ["monitoring collector", 9, 30, true], ]; // creation path: name, share, probability of an owner tag, probability the tag is spelled right export const PATH = [ ["template", 0.40, 1.00, 1.00], ["auto-scaling", 0.26, 0.71, 0.94], ["manual", 0.22, 0.55, 0.78], ["legacy migration", 0.12, 0.08, 0.60], ]; const pick = (table, u) => { let a = 0; for (const s of table) { a += s[1]; if (u < a) return s; } return table[table.length - 1]; }; // misspelling shapes: tagged in the coverage report, no counterpart in the team roster const VARIANT = [(e) => e.toUpperCase(), (e) => e.replace(/-/g, ""), (e) => e + "-team"]; export const inventory = () => { let s = SEED >>> 0; const r = () => { s = (Math.imul(s, 1664525) + 1013904223) >>> 0; return s / 4294967296; }; const list = []; for (let i = 0; i < COUNT; i++) { const [typeName, lo, hi, shared] = TYPE[Math.floor(r() * TYPE.length)]; const [pathName, , pOwned, pCorrect] = pick(PATH, r()); const env = ENV[Math.floor(r() * ENV.length)]; const unit = Math.round((lo + r() * (hi - lo)) * 10) / 10; const team = TEAM[Math.floor(r() * TEAM.length)]; let owner = null, valid = false; if (shared) { owner = "shared"; valid = true; } else if (r() < pOwned) { if (r() < pCorrect) { owner = team; valid = true; } else owner = VARIANT[Math.floor(r() * VARIANT.length)](team); } const day = 1 + Math.floor(r() * 34); // day the tag is completed; >30 = never const urgent = pathName === "manual" && r() < 0.18; // urgent repair job list.push({ id: i, type: typeName, shared, path: pathName, env, unit, owner, valid, fixDay: owner && valid ? 0 : day, urgent }); } return list; }; export const sum = (v) => v.reduce((a, b) => a + b, 0); export const sumUnits = (v) => sum(v.map((k) => k.unit));
// cost/allocate.mjs — tag coverage, cost allocation to teams (three rules), the resources a tag // mandate rejects, and tagging's failure to reach back into the past. What is measured is not // whether the tag is correct, it is *where the allocation does not hold*. import { inventory, TEAM, TYPE, PATH, TAG_LIMIT, USED_KEY, SEED, sum, sumUnits } from "./inventory.mjs"; const write = (w, ...s) => console.log(s.map((v, i) => (w[i] < 0 ? String(v).padEnd(-w[i]) : String(v).padStart(w[i]))).join("")); const t0 = process.hrtime.bigint(); const R = inventory(), T = sumUnits(R), share = (x) => `${(100 * x / T).toFixed(1)}%`; console.log(`seed ${SEED}: ${R.length} resources, monthly ${T.toFixed(1)} units (fictional currency)`); console.log("\n1. tag coverage by creation path"); const A = [-20, 11, 10, 10, 10, 11, 12]; write(A, "creation path", "resources", "tagged", "valid", "invalid", "untagged", "unit share"); for (const [name] of PATH) { const g = R.filter((k) => k.path === name), e = g.filter((k) => k.owner); write(A, name, g.length, e.length, g.filter((k) => k.valid).length, e.filter((k) => !k.valid).length, g.filter((k) => !k.owner).length, share(sumUnits(g))); } const tagged = R.filter((k) => k.owner), valid = R.filter((k) => k.valid); const invalid = R.filter((k) => k.owner && !k.valid), blank = R.filter((k) => !k.owner); write(A, "total", R.length, tagged.length, valid.length, invalid.length, blank.length, "100%"); console.log(` visible coverage ${(100 * tagged.length / R.length).toFixed(1)}%, ` + `valid coverage ${(100 * valid.length / R.length).toFixed(1)}%`); console.log(` invalid tag ${invalid.length} resources ${sumUnits(invalid).toFixed(1)} units ` + `(${share(sumUnits(invalid))}): shows up tagged in the coverage report`); console.log(` tag key limit ${TAG_LIMIT}, used ` + `${USED_KEY.length}, remaining ${TAG_LIMIT - USED_KEY.length}`); console.log("\n2. allocation: direct, shared, unallocated"); const direct = R.filter((k) => k.valid && k.owner !== "shared"); const shared = R.filter((k) => k.owner === "shared"); const unallocated = R.filter((k) => !k.valid); const D = sumUnits(direct), P = sumUnits(shared), X = sumUnits(unallocated); const B = [-14, 11, 15, 9]; write(B, "bucket", "resources", "monthly units", "share"); write(B, "direct", direct.length, D.toFixed(1), share(D)); write(B, "shared", shared.length, P.toFixed(1), share(P)); write(B, "unallocated", unallocated.length, X.toFixed(1), share(X)); const teamUnit = TEAM.map((e) => sumUnits(direct.filter((k) => k.owner === e))); const teamCount = TEAM.map((e) => direct.filter((k) => k.owner === e).length); const ratio = (v) => { const t = sum(v); return v.map((x) => x / t); }; const RULE = { equal: TEAM.map(() => 1 / TEAM.length), cost: ratio(teamUnit), "resource count": ratio(teamCount) }; console.log(` shared ${P.toFixed(1)} units split by a rule;`); console.log(` unallocated ${X.toFixed(1)} units (${share(X)}) enters no rule`); console.log("\n3. three split rules: how much the same team's bill swings by rule"); const C = [-16, ...TEAM.map(() => 15)]; write(C, "rule", ...TEAM); const result = {}; for (const [name, p] of Object.entries(RULE)) result[name] = TEAM.map((_, i) => teamUnit[i] + P * p[i]); write(C, "direct", ...teamUnit.map((x) => x.toFixed(1))); for (const [name, v] of Object.entries(result)) write(C, name, ...v.map((x) => x.toFixed(1))); const swing = TEAM.map((_, i) => { const v = Object.values(result).map((s) => s[i]); return [Math.min(...v), Math.max(...v)]; }); write(C, "widest gap", ...swing.map(([a, b]) => (b - a).toFixed(1))); write(C, "as % of bill", ...swing.map(([a, b]) => `${(100 * (b - a) / a).toFixed(1)}%`)); const en = swing.map(([a, b]) => (b - a) / a); const i0 = en.indexOf(Math.max(...en)); console.log(` no real share exists to compare against: what is measured is not deviation, it is rule-driven swing`); console.log(` most swing ${TEAM[i0]}: ${(100 * en[i0]).toFixed(1)}%; ` + `unallocated ${share(X)} is off the bill under all three rules`); console.log("\n4. tag mandate: how many resources are rejected, how many are legitimate work"); const rejected = R.filter((k) => !k.valid); const E = [-20, 11, 15, 12, 15]; write(E, "creation path", "rejected", "monthly units", "legitimate", "stalled units"); let legitCount = 0, legitUnit = 0; for (const [name] of PATH) { const g = rejected.filter((k) => k.path === name); const m = g.filter((k) => k.path === "auto-scaling" || k.urgent); legitCount += m.length; legitUnit += sumUnits(m); write(E, name, g.length, sumUnits(g).toFixed(1), m.length, sumUnits(m).toFixed(1)); } write(E, "total", rejected.length, sumUnits(rejected).toFixed(1), legitCount, legitUnit.toFixed(1)); console.log(` ${rejected.length} resources rejected (${(100 * rejected.length / R.length).toFixed(1)}%); ` + `${legitCount} of these are capacity or urgent repair work`); console.log(` the mandate zeroes the unallocated ${share(X)} share, in exchange ` + `${legitUnit.toFixed(1)} units' worth of work, ${legitCount} items, stalls`); console.log("\n5. tagging added later: the past bill does not correct itself"); const DAYS = 30, MILESTONE = [1, 5, 10, 15, 20, 25, 30]; const F = [6, 14, 10, 16, 19]; write(F, "day", "tagged", "coverage", "units/day", "accumulated loss"); const dailyLoss = (g) => sumUnits(R.filter((k) => !k.valid && k.fixDay > g)) / DAYS; let accumulated = 0; for (let g = 1; g <= DAYS; g++) { accumulated += dailyLoss(g); if (!MILESTONE.includes(g)) continue; const closed = R.filter((k) => k.valid || k.fixDay <= g); write(F, g, closed.length, `${(100 * closed.length / R.length).toFixed(1)}%`, (sumUnits(closed) / DAYS).toFixed(2), accumulated.toFixed(1)); } const never = R.filter((k) => !k.valid && k.fixDay > DAYS); console.log(` end-of-month coverage ${(100 * (R.length - never.length) / R.length).toFixed(1)}%, ` + `but the month's unallocated share is ${accumulated.toFixed(1)} units (${(100 * accumulated / T).toFixed(1)}%)`); console.log(` ${never.length} resources are never tagged: ${sumUnits(never).toFixed(1)} units/month ` + `stay unallocated every month`); const ms = Number(process.hrtime.bigint() - t0) / 1e6; // raw measurement is not printed, only the band is checked console.log(`\nfive tables within the 2000 ms band: ${ms < 2000 ? "yes" : "no"}`);
seed 6108: 148 resources, monthly 4214.3 units (fictional currency)
1. tag coverage by creation path
creation path resources tagged valid invalid untagged unit share
template 58 58 58 0 0 40.8%
auto-scaling 40 31 29 2 9 30.6%
manual 27 16 14 2 11 14.5%
legacy migration 23 10 10 0 13 14.1%
total 148 115 111 4 33 100%
visible coverage 77.7%, valid coverage 75.0%
invalid tag 4 resources 123.6 units (2.9%): shows up tagged in the coverage report
tag key limit 10, used 7, remaining 3
2. allocation: direct, shared, unallocated
bucket resources monthly units share
direct 58 1572.2 37.3%
shared 53 1584.9 37.6%
unallocated 37 1057.2 25.1%
shared 1584.9 units split by a rule;
unallocated 1057.2 units (25.1%) enters no rule
3. three split rules: how much the same team's bill swings by rule
rule meter-reading validation billing field reporting
direct 286.6 250.3 267.8 309.8 457.7
equal 603.6 567.3 584.8 626.8 774.7
cost 575.5 502.6 537.8 622.1 919.1
resource count 641.8 550.9 568.4 555.7 840.3
widest gap 66.3 64.7 47.0 71.0 144.4
as % of bill 11.5% 12.9% 8.7% 12.8% 18.6%
no real share exists to compare against: what is measured is not deviation, it is rule-driven swing
most swing reporting: 18.6%; unallocated 25.1% is off the bill under all three rules
4. tag mandate: how many resources are rejected, how many are legitimate work
creation path rejected monthly units legitimate stalled units
template 0 0.0 0 0.0
auto-scaling 11 444.3 11 444.3
manual 13 222.8 3 67.6
legacy migration 13 390.1 0 0.0
total 37 1057.2 14 511.9
37 resources rejected (25.0%); 14 of these are capacity or urgent repair work
the mandate zeroes the unallocated 25.1% share, in exchange 511.9 units' worth of work, 14 items, stalls
5. tagging added later: the past bill does not correct itself
day tagged coverage units/day accumulated loss
1 112 75.7% 106.85 33.6
5 115 77.7% 108.90 161.3
10 119 80.4% 112.15 306.2
15 123 83.1% 115.44 434.3
20 133 89.9% 129.34 513.4
25 139 93.9% 136.04 552.4
30 142 95.9% 137.50 568.6
end-of-month coverage 95.9%, but the month's unallocated share is 568.6 units (13.5%)
6 resources are never tagged: 89.3 units/month stay unallocated every month
five tables within the 2000 ms band: yes
The numbers belong to the measurement class; their inputs are the assumptions above.
Coverage Is Not One Number
The first table splits tag coverage by creation path and produces two different numbers. Visible coverage is 77.7%: 115 of 148 resources carry an owner tag. Valid coverage is 75.0%: on four of those, the tag value matches no team at all. Four resources, 123.6 units, 2.9% of the bill — clean in the coverage report, lost in allocation.
Where coverage comes from is written into the path itself. All 58 resources created from a template are tagged correctly; the tag is written by the template, not a person. Thirteen of the 23 resources from legacy migration carry no tag at all, and 27 manually created resources show both a missing tag (11) and a misspelling (2). Nine of the 40 resources from auto-scaling carry no tag: scaling that does not inherit the resource does not inherit the tag either.
The limit is also counted: a resource can carry ten tag keys, the network uses seven, three remain; adding one more split spends one of those three slots.
Three Rules, Three Different Bills
The second table splits allocation three ways: 1,572.2 units directly assigned (37.3%), 1,584.9 units shared (37.6%), and 1,057.2 units unallocated (25.1%). The unallocated share is not a splitting problem; the input is missing, and no rule accepts it.
The third table splits the shared amount three ways: equal share, the direct-cost ratio, and the resource-count ratio. All three read only from the tag. Unlike M22/K08, there is no real share here; what is measured is not deviation, it is the same team’s bill swinging by rule.
Reporting swings the most: 774.7 at equal share, 840.3 at resource count, 919.1 at cost ratio — a gap of 144.4 units, 18.6% of its bill. Billing swings least (8.7%). A team can watch its share swing by a sixth without changing anything about its own resources, purely because one rule changed centrally. The unallocated 25.1% shows up in none of the three rules; the teams’ totals never add up to the total bill.
The Work the Mandate Stalls
The fourth table applies the obvious fix: a resource with no valid owner tag cannot be created. The mandate zeroes the unallocated 25.1%, because those resources never exist.
Its cost sits in the same table. 37 resources are rejected, and 14 of them are legitimate work: 11 resources opened by auto-scaling are capacity, three of the manually opened ones are urgent repairs. The stalled work is 511.9 units. All of auto-scaling’s rejections are legitimate — the rule bites hardest exactly where no one is at a keyboard. The 13 legacy-migration rejections (390.1 units) stall no legitimate work at all: those resources already exist, and the mandate only reaches new ones.
Tags Do Not Reach Backward
The fifth table completes tags over the course of the month. Coverage climbs from 75.7% on day 1 to 95.9% on day 30; by month’s end the inventory report reads nearly clean. The month’s bill is not clean: the accumulated unallocated amount is 568.6 units, 13.5% of the month.
The reason is one sentence: bill lines are cut day by day, and a cut line carries whatever tag the resource wore that day. A resource tagged on day 20 leaves its first nineteen days unallocated. Some providers let a tag the resource already wore be activated retroactively for cost allocation; none writes a tag that was never attached back into the past. Coverage is instantaneous, the bill is continuous; putting both in the same report produces the picture of a month that has already been fixed. Six resources are never tagged at all: 89.3 units stay unallocated again every month.
Summary
- In the fictional inventory of 148 resources, visible coverage is 77.7%, valid coverage is 75.0%; the four-resource gap between them (123.6 units, 2.9%) shows up tagged in the report and gets lost in allocation. Coverage depends on the creation path: all 58 template resources are tagged correctly, 13 of 23 legacy-migration resources carry no tag.
- Of the monthly 4,214.3 units, 37.3% is directly assigned, 37.6% is shared, and 25.1% enters no rule at all; the teams’ totals never add up to the total bill.
- Split across three rules, the shared share makes the same team’s bill swing between 8.7% and 18.6%; reporting’s bill sits in a 144.4-unit band, between 774.7 and 919.1.
- A tag mandate zeroes the unallocated share but rejects 37 resources; 14 of them are legitimate work, and 511.9 units’ worth stalls. All 11 of auto-scaling’s rejections are capacity work.
- Even once tags reach 95.9% coverage by month’s end, that month’s 568.6 units (13.5%) stay unallocated: coverage is instantaneous, the bill is continuous.
Next Step
Items were split and one attempt at ownership was made; both read the bill after it arrives. The next lesson moves the question onto the time axis: two alert types run over a spending curve, the day each is noticed is counted, and the bill’s own delay enters the table as a constraint — by the time an alert fires, how much of the spending has already happened.
To keep your progress and take notes, Log in
My notes
Log in to take notes.