feat(portfolio): fold in RAILIANCE-WP-0016 apps-pg evidence

First delegated evidence from RESOURCE-WP-0003-T04 to land. railiance-platform
delivered apps-pg capacity, utilization, consumers, and the apps-pg-dbbytes-v1
allocation driver, and correctly delivered no EUR.

- data/resources/apps-pg.json: real capacity; allocation unattributed -> shared
  under apps-pg-dbbytes-v1; second consumer vergabe-teilnahme registered
- data/control-cycle/apps-pg-2026-09-base.json: first operational control-cycle
  record in the repository
- examples/control-cycle/apps-pg-*.json retired; the invented fixture collided
  with the real record's identifier
- data/portfolio-coverage-2026-08-14.json: gap marked delivered with three
  residual unknowns still open

The real evidence exposed a design gap in the T05 schema: v0.1 required a number
for every cost field, so recording genuine usage without a booked cost meant
inventing one. Schema 0.2 permits null costs, null unattributed_eur, a technical
unattributed_share, and null measurements. Null is unknown, never zero; an
unknown component makes the total null rather than the sum of the known parts;
and the comparator classifies unknown amounts as data_quality instead of
computing a variance. Existing 0.1 records are not rewritten.

apps-pg is now measured (idle at 5.8% of volume) and attributed, and remains
unpriced: delivered technical evidence does not create a booked cost.

86 tests pass.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
tegwick 2026-08-14 09:36:57 +02:00
parent b64b5683df
commit 17de8b831e
16 changed files with 908 additions and 71 deletions

View file

@ -10,7 +10,7 @@
| --- | --- | --- | --- | --- | | --- | --- | --- | --- | --- |
| workplan | RESOURCE-WP-0001 | finished | — | workplans/RESOURCE-WP-0001-statehub-bootstrap.md | | workplan | RESOURCE-WP-0001 | finished | — | workplans/RESOURCE-WP-0001-statehub-bootstrap.md |
| workplan | RESOURCE-WP-0002 | active | — | workplans/RESOURCE-WP-0002-procure-postgres-backup-storage.md | | workplan | RESOURCE-WP-0002 | active | — | workplans/RESOURCE-WP-0002-procure-postgres-backup-storage.md |
| workplan | RESOURCE-WP-0003 | active | — | workplans/RESOURCE-WP-0003-managed-infrastructure-portfolio-control.md | | workplan | RESOURCE-WP-0003 | finished | — | workplans/RESOURCE-WP-0003-managed-infrastructure-portfolio-control.md |
| task | RESOURCE-WP-0001-T01 | done | — | workplans/RESOURCE-WP-0001-statehub-bootstrap.md | | task | RESOURCE-WP-0001-T01 | done | — | workplans/RESOURCE-WP-0001-statehub-bootstrap.md |
| task | RESOURCE-WP-0001-T02 | done | — | workplans/RESOURCE-WP-0001-statehub-bootstrap.md | | task | RESOURCE-WP-0001-T02 | done | — | workplans/RESOURCE-WP-0001-statehub-bootstrap.md |
| task | RESOURCE-WP-0001-T03 | done | — | workplans/RESOURCE-WP-0001-statehub-bootstrap.md | | task | RESOURCE-WP-0001-T03 | done | — | workplans/RESOURCE-WP-0001-statehub-bootstrap.md |
@ -26,5 +26,5 @@
| task | RESOURCE-WP-0003-T03 | done | — | workplans/RESOURCE-WP-0003-managed-infrastructure-portfolio-control.md | | task | RESOURCE-WP-0003-T03 | done | — | workplans/RESOURCE-WP-0003-managed-infrastructure-portfolio-control.md |
| task | RESOURCE-WP-0003-T04 | done | — | workplans/RESOURCE-WP-0003-managed-infrastructure-portfolio-control.md | | task | RESOURCE-WP-0003-T04 | done | — | workplans/RESOURCE-WP-0003-managed-infrastructure-portfolio-control.md |
| task | RESOURCE-WP-0003-T05 | done | — | workplans/RESOURCE-WP-0003-managed-infrastructure-portfolio-control.md | | task | RESOURCE-WP-0003-T05 | done | — | workplans/RESOURCE-WP-0003-managed-infrastructure-portfolio-control.md |
| task | RESOURCE-WP-0003-T06 | todo | — | workplans/RESOURCE-WP-0003-managed-infrastructure-portfolio-control.md | | task | RESOURCE-WP-0003-T06 | done | — | workplans/RESOURCE-WP-0003-managed-infrastructure-portfolio-control.md |
| task | RESOURCE-WP-0003-T07 | todo | — | workplans/RESOURCE-WP-0003-managed-infrastructure-portfolio-control.md | | task | RESOURCE-WP-0003-T07 | done | — | workplans/RESOURCE-WP-0003-managed-infrastructure-portfolio-control.md |

View file

@ -0,0 +1,69 @@
{
"schema_version": "0.2",
"record_id": "forecast:apps-pg:2026-09:base:v1",
"record_type": "forecast",
"resource_id": "resource:railiance:apps-pg",
"resource_class": "shared_platform_service",
"period": "2026-09",
"created_at": "2026-08-14T00:00:00Z",
"scenario": "base",
"forecast_ref": null,
"revision_of": null,
"usage_proxies": {
"consumer_database_gb": { "value": 0.0155, "unit": "GiB" },
"pgdata_gb": { "value": 0.5766, "unit": "GiB" },
"wal_gb": { "value": 0.5469, "unit": "GiB" },
"sessions": { "value": 7, "unit": "count" }
},
"capacity": {
"model": "hybrid",
"provisioned": {
"storage": { "value": 10, "unit": "GiB" },
"instances": { "value": 1, "unit": "count" },
"max_connections": { "value": 100, "unit": "count" }
},
"used": {
"storage": { "value": 0.5766, "unit": "GiB" },
"instances": { "value": 1, "unit": "count" },
"sessions": { "value": 7, "unit": "count" }
}
},
"costs": {
"currency": "EUR",
"infrastructure": null,
"internal_labor": 30.0,
"external_labor": 0,
"total": null,
"booked_cost_refs": []
},
"allocation": {
"method": "proportional",
"driver": "database_gb",
"method_version": "apps-pg-dbbytes-v1",
"unattributed_eur": null,
"unattributed_share": 0.948
},
"service_constraints": {
"rpo_minutes": { "value": null, "unit": "minutes" },
"rto_minutes": { "value": null, "unit": "minutes" }
},
"uncertainty": {
"level": "high",
"notes": [
"First operational control-cycle record for this resource, from railiance-platform RAILIANCE-WP-0016 captured 2026-08-14T00:04:19Z",
"Infrastructure cost is null: apps-pg is a share of resource:hosteurope:railiance01, whose booked price is unknown (RAIL-HO-WP-0008)",
"Internal labour is 0.5 declared operator-hours per month converted at the EUR 60 per hour rate used in data/demand/platform-audit-storage.json; the hours are an estimate, not a timesheet",
"Total is null because one component is unknown; it is not the sum of the known parts",
"94.8 percent of used volume is WAL and engine catalogs attributed to platform:apps-pg-overhead, not to a consumer",
"vergabe_db has never recorded a transaction, so its 0.4614 byte share reflects a reserved empty database rather than realised demand",
"Five days of cluster life and no metrics retention: this is a snapshot carried forward, not a trend",
"RPO is unbounded and RTO undefined — no ScheduledBackup exists and the cluster is not an Option A logical-dump target"
]
},
"evidence": [
"/home/worsch/railiance-platform/docs/evidence/RAILIANCE-WP-0016-apps-pg-observation.json",
"/home/worsch/railiance-platform/docs/evidence/RAILIANCE-WP-0016-apps-pg-resource-evidence.md",
"RAILIANCE-WP-0016",
"data/resources/apps-pg.json"
]
}

View file

@ -0,0 +1,115 @@
{
"schema_version": "0.1",
"observed_at": "2026-08-14T00:04:19Z",
"inventory_records": 7,
"coverage": [
{
"group": "helix-forge",
"status": "covered-by-shared-forge",
"resource_ids": [
"resource:railiance:forgejo",
"resource:railiance:reef-railiance:k3s",
"resource:hosteurope:railiance01"
]
},
{
"group": "coulomb-social",
"status": "covered",
"resource_ids": [
"resource:tenant:coulomb:coulomb-social",
"resource:railiance:apps-pg",
"resource:railiance:reef-railiance:k3s",
"resource:hosteurope:railiance01"
]
},
{
"group": "shared-railiance",
"status": "covered-substrate-and-selected-services",
"resource_ids": [
"resource:railiance:reef-railiance:k3s",
"resource:railiance:forgejo",
"resource:railiance:apps-pg",
"resource:hosteurope:railiance01",
"resource:platform:audit-storage"
]
},
{
"group": "representative-tenant",
"status": "covered",
"tenant_id": "tenant:friendly:binky",
"resource_ids": [
"resource:tenant:friendly:binky:rapp-qonto",
"resource:railiance:reef-railiance:k3s",
"resource:hosteurope:railiance01"
]
}
],
"owned_gaps": [
{
"owner": "railiance-infra",
"gap": "Host Europe product, provider resource ID, account reference, country/region evidence, contract price, tax treatment, order date, renewal and cancellation dates",
"delegated_workplan": "RAIL-HO-WP-0008",
"state_hub_workplan_id": "7122657f-87c8-46b5-a725-a1af1ba0af12",
"status": "open"
},
{
"owner": "railiance-cluster",
"gap": "cluster-wide requested/used capacity history, allocation driver, generic persistent-storage contract, and independent failure domains",
"delegated_workplan": "RAIL-BS-WP-0014",
"state_hub_workplan_id": "ea6ec98a-0d65-4afa-b6c5-6e7ed34011ac",
"status": "open"
},
{
"owner": "railiance-forge",
"gap": "Forgejo resource requests/utilization, storage growth, operations labor, and cost-allocation driver",
"delegated_workplan": "RAILIANCE-WP-0002",
"state_hub_workplan_id": "72f935a5-d921-48a4-96e4-2365556c7374",
"status": "open"
},
{
"owner": "railiance-platform",
"gap": "apps-pg utilization, backup/restore evidence, operations labor, and consumer allocation driver",
"delegated_workplan": "RAILIANCE-WP-0016",
"state_hub_workplan_id": "49084fb8-de63-4f32-a4a9-3a42d4e708ac",
"status": "delivered",
"delivered_on": "2026-08-14",
"interface": [
"/home/worsch/railiance-platform/docs/evidence/RAILIANCE-WP-0016-apps-pg-observation.json",
"/home/worsch/railiance-platform/docs/evidence/RAILIANCE-WP-0016-apps-pg-resource-evidence.md"
],
"refresh": "make apps-pg-evidence",
"residual_unknowns": [
"booked EUR for the apps-pg share of railiance01 (owner: fin-hub, FIN-WP-0004; upstream RAIL-HO-WP-0008)",
"operator hours are an estimate, not a timesheet (owner: railiance-platform)",
"no restore drill, no ScheduledBackup: RPO unbounded and RTO undefined (owner: railiance-platform)"
]
},
{
"owner": "coulomb-social",
"gap": "declared CPU/memory demand, usage proxies, service objectives, and application operations labor",
"delegated_workplan": "CSOC-WP-0005",
"state_hub_workplan_id": "00de1578-6150-40d3-b193-e852666ed945",
"status": "open"
},
{
"owner": "rapp-qonto",
"gap": "request/cold-start frequency, external API costs, runtime utilization, and recurring operator labor",
"delegated_workplan": "RAPP-QONTO-WP-0002",
"state_hub_workplan_id": "25b3b715-9a56-499e-a85c-a09ebd9bbb79",
"status": "open"
},
{
"owner": "fin-hub",
"gap": "booked Host Europe financial facts joined to resource:hosteurope:railiance01",
"delegated_workplan": "FIN-WP-0004",
"state_hub_workplan_id": "67b6de6c-4820-4478-9789-f50260204c27",
"delegated_tasks": [
"FIN-WP-0004-T04",
"FIN-WP-0004-T05",
"FIN-WP-0004-T06"
],
"status": "open"
}
],
"supersedes": "data/portfolio-coverage-2026-08-11.json"
}

View file

@ -1,15 +1,162 @@
{ {
"schema_version": "0.2", "record_scope": "inventory", "schema_version": "0.2",
"id": "resource:railiance:apps-pg", "resource_class": "database", "record_scope": "inventory",
"status": "active", "management_model": "self_managed", "id": "resource:railiance:apps-pg",
"provider": {"name": "Railiance", "account_ref": null, "product_ref": "cloudnative-pg", "provider_resource_id": "kubernetes:databases/cluster/apps-pg"}, "resource_class": "database",
"service": {"name": "Shared applications PostgreSQL", "service_id": "apps-pg", "class": "CloudNativePG database service", "capacity_model": "shared"}, "status": "active",
"location": {"region": null, "country": null, "failure_domains": ["cluster:reef-railiance", "host:railiance01", "storage:local-path"], "residency": null}, "management_model": "self_managed",
"capacity": [{"metric": "instances", "value": 1, "unit": "count", "kind": "observed", "observed_at": "2026-08-11"}, {"metric": "storage", "value": 10, "unit": "GiB", "kind": "provisioned", "observed_at": "2026-08-11"}], "provider": {
"ownership": {"owner": "railiance-platform", "environment": "production", "workload_ids": ["coulomb-social"], "tenant_id": null, "allocation": {"mode": "unattributed", "cost_attribution_key": null, "driver": null, "method_version": null}}, "name": "Railiance",
"cost": {"currency": "EUR", "tax_status": "not_applicable", "billing_model": "shared cluster and operations cost; allocation method unknown", "commitment_ref": null, "price_evidence": null}, "account_ref": null,
"lifecycle": {"proposed_on": null, "ordered_on": null, "commissioned_on": "2026-08-09", "renews_on": null, "cancel_by": null, "retired_on": null, "exit_path": "Restore databases to a replacement PostgreSQL service, rotate application connections, verify consumers, then retire the cluster and volume."}, "product_ref": "cloudnative-pg",
"relationships": [{"type": "hosted_on", "resource_id": "resource:railiance:reef-railiance:k3s"}], "provider_resource_id": "kubernetes:databases/cluster/apps-pg"
"requirements": [{"kind": "recovery", "ref": "/home/worsch/railiance-platform/docs/apps-pg.md"}], },
"evidence": [{"kind": "telemetry", "ref": "kubernetes:reef-railiance/databases/cluster/apps-pg@2026-08-11", "observed_at": "2026-08-11", "authority": "reef-railiance Kubernetes API"}, {"kind": "workload", "ref": "/home/worsch/railiance-platform/helm/apps-pg-cluster.yaml", "observed_at": "2026-08-11", "authority": "railiance-platform"}] "service": {
"name": "Shared applications PostgreSQL",
"service_id": "apps-pg",
"class": "CloudNativePG database service",
"capacity_model": "shared"
},
"location": {
"region": null,
"country": null,
"failure_domains": [
"cluster:reef-railiance",
"host:railiance01",
"storage:local-path"
],
"residency": null
},
"capacity": [
{
"metric": "instances",
"value": 1,
"unit": "count",
"kind": "provisioned",
"observed_at": "2026-08-14"
},
{
"metric": "instances",
"value": 1,
"unit": "count",
"kind": "observed",
"observed_at": "2026-08-14"
},
{
"metric": "storage",
"value": 10,
"unit": "GiB",
"kind": "provisioned",
"observed_at": "2026-08-14"
},
{
"metric": "storage_used",
"value": 0.5766,
"unit": "GiB",
"kind": "observed",
"observed_at": "2026-08-14"
},
{
"metric": "wal_since_init",
"value": 0.5469,
"unit": "GiB",
"kind": "observed",
"observed_at": "2026-08-14"
},
{
"metric": "cpu_usage",
"value": 0.009,
"unit": "vCPU",
"kind": "observed",
"observed_at": "2026-08-14"
},
{
"metric": "memory_usage",
"value": 0.0996,
"unit": "GiB",
"kind": "observed",
"observed_at": "2026-08-14"
},
{
"metric": "max_connections",
"value": 100,
"unit": "count",
"kind": "limit",
"observed_at": "2026-08-14"
},
{
"metric": "sessions",
"value": 7,
"unit": "count",
"kind": "observed",
"observed_at": "2026-08-14"
}
],
"ownership": {
"owner": "railiance-platform",
"environment": "production",
"workload_ids": [
"coulomb-social",
"vergabe-teilnahme"
],
"tenant_id": null,
"allocation": {
"mode": "shared",
"cost_attribution_key": "platform:apps-pg",
"driver": "database_gb",
"method_version": "apps-pg-dbbytes-v1"
}
},
"cost": {
"currency": "EUR",
"tax_status": "not_applicable",
"billing_model": "shared cluster and operations cost allocated by declared-consumer database bytes (apps-pg-dbbytes-v1: coulomb-social 0.5386, vergabe-teilnahme 0.4614); the 94.8 percent WAL and catalog residual is attributed to platform:apps-pg-overhead and no booked cost exists yet",
"commitment_ref": null,
"price_evidence": null
},
"lifecycle": {
"proposed_on": null,
"ordered_on": null,
"commissioned_on": "2026-08-09",
"renews_on": null,
"cancel_by": null,
"retired_on": null,
"exit_path": "Restore databases to a replacement PostgreSQL service, rotate application connections, verify consumers, then retire the cluster and volume."
},
"relationships": [
{
"type": "hosted_on",
"resource_id": "resource:railiance:reef-railiance:k3s"
}
],
"requirements": [
{
"kind": "recovery",
"ref": "/home/worsch/railiance-platform/docs/evidence/RAILIANCE-WP-0016-apps-pg-resource-evidence.md#t02--recovery-and-operations"
},
{
"kind": "other",
"ref": "/home/worsch/railiance-platform/docs/apps-pg.md"
}
],
"evidence": [
{
"kind": "telemetry",
"ref": "/home/worsch/railiance-platform/docs/evidence/RAILIANCE-WP-0016-apps-pg-observation.json",
"observed_at": "2026-08-14",
"authority": "railiance-platform (reef-railiance Kubernetes API + postgres catalog)"
},
{
"kind": "workload",
"ref": "/home/worsch/railiance-platform/docs/evidence/RAILIANCE-WP-0016-apps-pg-resource-evidence.md",
"observed_at": "2026-08-14",
"authority": "railiance-platform, RAILIANCE-WP-0016"
},
{
"kind": "workload",
"ref": "/home/worsch/railiance-platform/helm/apps-pg-cluster.yaml",
"observed_at": "2026-08-11",
"authority": "railiance-platform"
}
]
} }

View file

@ -0,0 +1,90 @@
# RESOURCE-WP-0003 apps-pg evidence fold-in — 2026-08-14
## Result
`railiance-platform` finished `RAILIANCE-WP-0016` and published the apps-pg
evidence that `RESOURCE-WP-0003-T04` delegated. This is the first delegated
evidence to land, and folding it in exercised the whole loop end to end:
delegation, delivery, ingestion, allocation, control record, and report.
Source, captured 2026-08-14T00:04:19Z, refreshed with `make apps-pg-evidence`:
- `/home/worsch/railiance-platform/docs/evidence/RAILIANCE-WP-0016-apps-pg-observation.json`
- `/home/worsch/railiance-platform/docs/evidence/RAILIANCE-WP-0016-apps-pg-resource-evidence.md`
## What changed here
| Artifact | Change |
|---|---|
| `data/resources/apps-pg.json` | Real capacity and utilization; allocation moved from `unattributed` to `shared` under `apps-pg-dbbytes-v1`; second consumer `vergabe-teilnahme` registered; evidence repointed at the platform interface |
| `data/control-cycle/apps-pg-2026-09-base.json` | **New.** First operational control-cycle record in the repository |
| `examples/control-cycle/apps-pg-{forecast,actual}.json` | **Retired.** The invented fixture collided with the real record's identifier — the clearest possible signal it had been superseded |
| `schemas/resource-control-cycle.schema.json` | Widened to 0.2: nullable costs, nullable `unattributed_eur`, new `unattributed_share`, nullable measurement values |
| `tools/control_cycle.py` | Unknown amounts return `status: "unknown"` and classify as `data_quality` instead of computing a variance |
| `data/portfolio-coverage-2026-08-14.json` | **New.** Supersedes the 2026-08-11 observation; the railiance-platform gap is `delivered` with three residual unknowns |
| `tools/portfolio_report.py` | Delivered gaps stay visible with their residuals; `storage`/`storage_used` added to the measurable pairs |
## The schema gap the real evidence exposed
railiance-platform delivered technical facts and, correctly, no EUR — the host
bill belongs to `resource:hosteurope:railiance01`, whose price is unknown, and
they wrote plainly: "resource-control may convert hours at its own labor rate.
Do not treat these hours as booked cost."
Schema 0.1 could not represent that. Every cost field required a number, so the
only way to record real usage was to invent a cost — precisely what this
repository refuses to do everywhere else. That is a genuine design gap, found
by real evidence rather than by review.
Schema 0.2 permits `null` for cost components, `unattributed_eur`, and
measurement values, and adds `unattributed_share` so a residual can be recorded
from technical evidence alone. `null` means unknown, never zero; if any
component is unknown the total is `null` rather than the sum of the known
parts; and the comparator classifies an unknown amount as `data_quality`,
overriding any explicit attribution, because a missing amount is an evidence
gap and not a price movement.
Existing 0.1 records were not rewritten. `schema_version` accepts both:
immutability applies to the record, not to the schema.
## The first operational record
`forecast:apps-pg:2026-09:base:v1` carries what is known and refuses the rest:
| Field | Value | Basis |
|---|---|---|
| `consumer_database_gb` | 0.0155 GiB | `pg_database_size` of the two declared consumer databases |
| `pgdata_gb` | 0.5766 GiB | `du -sb` on PGDATA |
| storage used / provisioned | 0.5766 of 10 GiB | PVC `apps-pg-1`, `local-path` |
| `internal_labor` | EUR 30.00 | 0.5 declared operator-hours at the EUR 60/hour rate already used in `data/demand/platform-audit-storage.json` |
| `infrastructure` | **null** | Share of railiance01; price unknown (`RAIL-HO-WP-0008`) |
| `total` | **null** | One component unknown; not the sum of the known parts |
| `unattributed_share` | 0.948 | WAL and engine catalogs under `platform:apps-pg-overhead` |
| `rpo_minutes`, `rto_minutes` | **null** | No ScheduledBackup, no restore drill, not an Option A target |
Uncertainty is `high` and stated in eight notes, including the one that matters
most for allocation: `vergabe_db` has never recorded a transaction, so its
0.4614 byte share reflects a reserved empty database rather than realised
demand. The method is recorded as delivered; whether it survives contact with a
second month is a question for the quarterly calibration.
## Effect on the portfolio report
- apps-pg leaves `utilization.unmeasured` and enters `utilization.idle` at
5.8 percent of its volume — a third idle resource alongside railiance01 and
the k3s cluster.
- apps-pg leaves `cost.unattributed_allocation`, which drops from four
resources to three.
- apps-pg remains in `cost.unpriced`, and `known_monthly_spend_eur` remains
`null`. Delivered technical evidence does not create a booked cost.
- The railiance-platform gap moves to `delivered_gaps` and still generates
three next actions from its residual unknowns.
## Boundary held
No booked cost was originated here. The labour figure is a conversion of
declared hours at a rate this repository already publishes, recorded as an
estimate with its basis, not as a ledger fact. Recovery risk was recorded, not
resolved: apps-pg has RPO unbounded and RTO undefined on single-host local-path
storage, which stays railiance-platform's to close and is gated on the same
object-store decision as `RESOURCE-WP-0002`.

View file

@ -98,10 +98,41 @@ make control-cycle FORECAST=examples/control-cycle/storage-forecast.json \
ACTUAL=examples/control-cycle/storage-actual.json ACTUAL=examples/control-cycle/storage-actual.json
``` ```
The examples cover storage, `reef-railiance` cluster compute, and the shared The examples cover storage and `reef-railiance` cluster compute. Their numbers
`apps-pg` service. Their numbers are illustrative contract fixtures, not booked are illustrative contract fixtures, not booked facts. Operational records
facts. Operational records replace their evidence references after the owner replace them once the owner workplans publish observations — the illustrative
workplans publish observations. `apps-pg` pair was retired on 2026-08-14 when `RAILIANCE-WP-0016` delivered the
real one to `data/control-cycle/apps-pg-2026-09-base.json`.
Illustrative fixtures live in `examples/control-cycle/`; operational records
live in `data/control-cycle/` and must cite the authoritative repository
evidence they came from and state their uncertainty. The validator enforces
both.
### Schema 0.2 — unknown is not zero
Real evidence arrives incomplete. `RAILIANCE-WP-0016` supplied apps-pg
capacity, utilization, consumers, and an allocation driver, and correctly
supplied no EUR: the host bill belongs to `resource:hosteurope:railiance01`,
whose price is still unknown. Schema 0.1 could not express that — every cost
field required a number, so the only way to record real usage was to invent a
cost.
Schema 0.2 permits `null` for each cost component, for `unattributed_eur`, and
for any measurement value, and adds `unattributed_share` so a shared-cost
residual can be recorded from technical evidence before any EUR exists. The
rules:
- `null` means unknown or unbounded, never zero;
- if any cost component is unknown, `total` is `null` — never the sum of the
parts that happen to be known;
- the comparator returns `status: "unknown"` for that component and classifies
it `data_quality`, overriding any explicit attribution, because a missing
amount is an evidence gap rather than a price or labour movement;
- known components still compute their variance alongside unknown ones.
Existing 0.1 records stay valid and are never rewritten. Immutability applies
to the record, not to the schema, so `schema_version` accepts both.
## Variance attribution ## Variance attribution

View file

@ -47,6 +47,19 @@ reason, rather than being silently omitted from the ratios.
| **Pre-renewal review** | At least 60 days before the earliest `renews_on` or `cancel_by` | Contract evidence, utilization, open cases | Human financial authority | A `renewal` case, decided while cancellation is still possible | | **Pre-renewal review** | At least 60 days before the earliest `renews_on` or `cancel_by` | Contract evidence, utilization, open cases | Human financial authority | A `renewal` case, decided while cancellation is still possible |
| **Event-driven** | Sustained variance beyond threshold, an incident exposing a failure domain, or a workload request | The triggering evidence | Owning repository | A case of the matching type per `docs/optimization-cases.md` | | **Event-driven** | Sustained variance beyond threshold, an incident exposing a failure domain, or a workload request | The triggering evidence | Owning repository | A case of the matching type per `docs/optimization-cases.md` |
## Delivered evidence stays visible
A delegated gap that is answered does not vanish from the report. It moves from
`coverage.unresolved_gaps` to `coverage.delivered_gaps`, keeping the interface
that answered it and the **residual unknowns** it did not, and each residual
still generates a next action. Partial delivery must not read as full coverage.
`RAILIANCE-WP-0016` is the first example: railiance-platform delivered apps-pg
capacity, utilization, consumers, and the `apps-pg-dbbytes-v1` allocation
driver, and deliberately delivered no EUR. Three residuals remain open — the
booked host share, the labour estimate, and the absent recovery objectives — so
apps-pg is now measurable and attributed while still counting as unpriced.
## Reading the current report ## Reading the current report
As of 2026-08-14 an operator asking the four questions this cadence exists to As of 2026-08-14 an operator asking the four questions this cadence exists to
@ -55,12 +68,15 @@ answer gets these answers, and the honest ones are the useful ones:
- **Material spend?** Unknown, and explicitly so. Only the proposed backup - **Material spend?** Unknown, and explicitly so. Only the proposed backup
storage carries price evidence. Every other resource is waiting on storage carries price evidence. Every other resource is waiting on
`RAIL-HO-WP-0008` and `FIN-WP-0004`. `RAIL-HO-WP-0008` and `FIN-WP-0004`.
- **Idle or saturated capacity?** `railiance01` and the `reef-railiance` k3s - **Idle or saturated capacity?** `railiance01`, the `reef-railiance` k3s
cluster are idle on every measured metric — 14 percent CPU, 37 percent memory. cluster, and `apps-pg` are idle on every measured metric — 14 percent CPU,
Nothing is saturated. Five resources cannot be measured at all. 37 percent memory, and 5.8 percent of the apps-pg volume. Nothing is
- **Forecast error?** Not yet computable. The control-cycle mechanism is proven saturated. Four resources cannot be measured at all.
on paired examples, but no operational actual observation exists, so there is - **Forecast error?** Not yet computable. The first operational forecast record
no variance to report and none is fabricated. exists for apps-pg, but its period is 2026-09 and no actual has been observed
against it, so there is no variance to report and none is fabricated. Its
infrastructure cost is `null`, so even when the actual lands, cost variance
will read `unknown` until a booked fact arrives.
- **Approaching commitments?** None visible — and that is itself the finding: - **Approaching commitments?** None visible — and that is itself the finding:
six active resources have no renewal or cancellation date recorded, so the six active resources have no renewal or cancellation date recorded, so the
cancellation window cannot be respected for any of them. cancellation window cannot be respected for any of them.

View file

@ -1 +0,0 @@
{"schema_version":"0.1","record_id":"actual:apps-pg:2026-09","record_type":"actual","resource_id":"resource:railiance:apps-pg","resource_class":"shared_platform_service","period":"2026-09","created_at":"2026-10-02T00:00:00Z","scenario":"observed","forecast_ref":"forecast:apps-pg:2026-09:base:v1","revision_of":null,"usage_proxies":{"database_gb":{"value":6,"unit":"GB"},"connections":{"value":18,"unit":"peak"}},"capacity":{"model":"hybrid","provisioned":{"storage":{"value":20,"unit":"GiB"}},"used":{"storage":{"value":6,"unit":"GB"}}},"costs":{"currency":"EUR","infrastructure":15,"internal_labor":90,"external_labor":0,"total":105,"booked_cost_refs":["financial-fact:example-shared-allocation-2026-09"]},"allocation":{"method":"proportional","driver":"database_gb","method_version":"1","unattributed_eur":5},"service_constraints":{"rpo_minutes":{"value":5,"unit":"minutes"},"rto_minutes":{"value":60,"unit":"minutes"}},"variance_attribution":{"database_gb":"demand","connections":"model","costs.internal_labor":"labor"},"evidence":["illustrative actual; replace with RAILIANCE-WP-0016 evidence"]}

View file

@ -1 +0,0 @@
{"schema_version":"0.1","record_id":"forecast:apps-pg:2026-09:base:v1","record_type":"forecast","resource_id":"resource:railiance:apps-pg","resource_class":"shared_platform_service","period":"2026-09","created_at":"2026-08-11T00:00:00Z","scenario":"base","forecast_ref":null,"revision_of":null,"usage_proxies":{"database_gb":{"value":5,"unit":"GB"},"connections":{"value":20,"unit":"peak"}},"capacity":{"model":"hybrid","provisioned":{"storage":{"value":20,"unit":"GiB"}},"used":{"storage":{"value":5,"unit":"GB"}}},"costs":{"currency":"EUR","infrastructure":15,"internal_labor":60,"external_labor":0,"total":75,"booked_cost_refs":[]},"allocation":{"method":"proportional","driver":"database_gb","method_version":"1","unattributed_eur":5},"service_constraints":{"rpo_minutes":{"value":5,"unit":"minutes"},"rto_minutes":{"value":60,"unit":"minutes"}},"uncertainty":{"level":"high","notes":["Illustrative until platform evidence arrives"]},"evidence":["RAILIANCE-WP-0016"]}

View file

@ -4,81 +4,286 @@
"title": "Immutable resource forecast or actual observation", "title": "Immutable resource forecast or actual observation",
"type": "object", "type": "object",
"additionalProperties": false, "additionalProperties": false,
"required": ["schema_version", "record_id", "record_type", "resource_id", "resource_class", "period", "created_at", "usage_proxies", "capacity", "costs", "allocation", "service_constraints", "evidence"], "required": [
"schema_version",
"record_id",
"record_type",
"resource_id",
"resource_class",
"period",
"created_at",
"usage_proxies",
"capacity",
"costs",
"allocation",
"service_constraints",
"evidence"
],
"properties": { "properties": {
"schema_version": {"const": "0.1"}, "schema_version": {
"record_id": {"type": "string", "minLength": 1}, "enum": [
"record_type": {"enum": ["forecast", "actual"]}, "0.1",
"resource_id": {"type": "string", "pattern": "^resource:"}, "0.2"
"resource_class": {"enum": ["storage", "vm", "cluster_compute", "shared_platform_service", "managed_service", "other"]}, ],
"period": {"type": "string", "pattern": "^[0-9]{4}-(0[1-9]|1[0-2])$"}, "description": "0.2 permits null cost components for facts the authoritative repository has not supplied. Existing 0.1 records stay valid and are never rewritten; immutability applies to the record, not to the schema."
"created_at": {"type": "string", "format": "date-time"}, },
"scenario": {"enum": ["low", "base", "high", "observed"]}, "record_id": {
"forecast_ref": {"type": ["string", "null"]}, "type": "string",
"revision_of": {"type": ["string", "null"]}, "minLength": 1
},
"record_type": {
"enum": [
"forecast",
"actual"
]
},
"resource_id": {
"type": "string",
"pattern": "^resource:"
},
"resource_class": {
"enum": [
"storage",
"vm",
"cluster_compute",
"shared_platform_service",
"managed_service",
"other"
]
},
"period": {
"type": "string",
"pattern": "^[0-9]{4}-(0[1-9]|1[0-2])$"
},
"created_at": {
"type": "string",
"format": "date-time"
},
"scenario": {
"enum": [
"low",
"base",
"high",
"observed"
]
},
"forecast_ref": {
"type": [
"string",
"null"
]
},
"revision_of": {
"type": [
"string",
"null"
]
},
"usage_proxies": { "usage_proxies": {
"type": "object", "type": "object",
"minProperties": 1, "minProperties": 1,
"additionalProperties": {"$ref": "#/$defs/measurement"} "additionalProperties": {
"$ref": "#/$defs/measurement"
}
}, },
"capacity": { "capacity": {
"type": "object", "type": "object",
"additionalProperties": false, "additionalProperties": false,
"required": ["model", "provisioned", "used"], "required": [
"model",
"provisioned",
"used"
],
"properties": { "properties": {
"model": {"enum": ["fixed", "elastic", "hybrid"]}, "model": {
"provisioned": {"type": "object", "additionalProperties": {"$ref": "#/$defs/measurement"}}, "enum": [
"used": {"type": "object", "additionalProperties": {"$ref": "#/$defs/measurement"}} "fixed",
"elastic",
"hybrid"
]
},
"provisioned": {
"type": "object",
"additionalProperties": {
"$ref": "#/$defs/measurement"
}
},
"used": {
"type": "object",
"additionalProperties": {
"$ref": "#/$defs/measurement"
}
}
} }
}, },
"costs": { "costs": {
"type": "object", "type": "object",
"additionalProperties": false, "additionalProperties": false,
"required": ["currency", "infrastructure", "internal_labor", "external_labor", "total"], "required": [
"currency",
"infrastructure",
"internal_labor",
"external_labor",
"total"
],
"properties": { "properties": {
"currency": {"const": "EUR"}, "currency": {
"infrastructure": {"type": "number", "minimum": 0}, "const": "EUR"
"internal_labor": {"type": "number", "minimum": 0}, },
"external_labor": {"type": "number", "minimum": 0}, "infrastructure": {
"total": {"type": "number", "minimum": 0}, "type": [
"booked_cost_refs": {"type": "array", "items": {"type": "string"}, "uniqueItems": true} "number",
} "null"
],
"minimum": 0,
"description": "EUR, or null when the amount is not yet known. Null is unknown, never zero."
},
"internal_labor": {
"type": [
"number",
"null"
],
"minimum": 0,
"description": "EUR, or null when the amount is not yet known. Null is unknown, never zero."
},
"external_labor": {
"type": [
"number",
"null"
],
"minimum": 0,
"description": "EUR, or null when the amount is not yet known. Null is unknown, never zero."
},
"total": {
"type": [
"number",
"null"
],
"minimum": 0,
"description": "EUR, or null when the amount is not yet known. Null is unknown, never zero."
},
"booked_cost_refs": {
"type": "array",
"items": {
"type": "string"
},
"uniqueItems": true
}
},
"description": "Cost components split so a cheaper unit price that raises operator hours is visible. Any component may be null when the authoritative repository or fin-hub has not supplied it; total must then also be null, and variance for that component is reported as unknown."
}, },
"allocation": { "allocation": {
"type": "object", "type": "object",
"additionalProperties": false, "additionalProperties": false,
"required": ["method", "driver", "method_version", "unattributed_eur"], "required": [
"method",
"driver",
"method_version",
"unattributed_eur"
],
"properties": { "properties": {
"method": {"type": "string", "minLength": 1}, "method": {
"driver": {"type": "string", "minLength": 1}, "type": "string",
"method_version": {"type": "string", "minLength": 1}, "minLength": 1
"unattributed_eur": {"type": "number", "minimum": 0} },
"driver": {
"type": "string",
"minLength": 1
},
"method_version": {
"type": "string",
"minLength": 1
},
"unattributed_eur": {
"type": [
"number",
"null"
],
"minimum": 0,
"description": "EUR that reaches no consumer under the stated method, or null when the underlying cost itself is unknown. Null is unknown, never zero."
},
"unattributed_share": {
"type": [
"number",
"null"
],
"minimum": 0,
"maximum": 1,
"description": "Fraction of the allocation driver that reaches no consumer. Recordable from technical evidence alone, before any EUR amount exists."
}
}
},
"service_constraints": {
"type": "object",
"additionalProperties": {
"$ref": "#/$defs/measurement"
} }
}, },
"service_constraints": {"type": "object", "additionalProperties": {"$ref": "#/$defs/measurement"}},
"uncertainty": { "uncertainty": {
"type": "object", "type": "object",
"additionalProperties": false, "additionalProperties": false,
"required": ["level", "notes"], "required": [
"level",
"notes"
],
"properties": { "properties": {
"level": {"enum": ["low", "medium", "high"]}, "level": {
"notes": {"type": "array", "items": {"type": "string"}} "enum": [
"low",
"medium",
"high"
]
},
"notes": {
"type": "array",
"items": {
"type": "string"
}
}
} }
}, },
"variance_attribution": { "variance_attribution": {
"type": "object", "type": "object",
"additionalProperties": {"enum": ["demand", "provider_price", "allocation", "labor", "model", "data_quality"]} "additionalProperties": {
"enum": [
"demand",
"provider_price",
"allocation",
"labor",
"model",
"data_quality"
]
}
}, },
"evidence": {"type": "array", "minItems": 1, "items": {"type": "string"}} "evidence": {
"type": "array",
"minItems": 1,
"items": {
"type": "string"
}
}
}, },
"$defs": { "$defs": {
"measurement": { "measurement": {
"type": "object", "type": "object",
"additionalProperties": false, "additionalProperties": false,
"required": ["value", "unit"], "required": [
"value",
"unit"
],
"properties": { "properties": {
"value": {"type": "number", "minimum": 0}, "value": {
"unit": {"type": "string", "minLength": 1} "type": [
"number",
"null"
],
"minimum": 0,
"description": "Null means the quantity is unknown or unbounded, never zero. A declared service constraint with a null value records that no objective exists yet."
},
"unit": {
"type": "string",
"minLength": 1
}
} }
} }
} }

View file

@ -1,3 +1,4 @@
import json
import sys import sys
import unittest import unittest
from pathlib import Path from pathlib import Path
@ -50,5 +51,77 @@ class ControlCycleTest(unittest.TestCase):
self.assertEqual("data_quality", result["usage_proxies"]["stored_gb"]["category"]) self.assertEqual("data_quality", result["usage_proxies"]["stored_gb"]["category"])
class UnknownCostTest(unittest.TestCase):
"""Schema 0.2: a cost the authoritative repository has not supplied is
unknown, not zero, and must not produce a fabricated variance."""
def test_unknown_cost_reports_status_rather_than_an_error_figure(self):
forecast, actual = record(), record("actual")
forecast["costs"].update(infrastructure=None, total=None)
actual["costs"].update(infrastructure=None, total=None)
result = compare(forecast, actual)["costs"]["infrastructure"]
self.assertEqual("unknown", result["status"])
self.assertNotIn("error", result)
def test_unknown_cost_is_a_data_quality_gap_not_a_price_movement(self):
forecast, actual = record(), record("actual")
actual["costs"].update(infrastructure=None, total=None)
actual["variance_attribution"] = {"costs.infrastructure": "provider_price"}
result = compare(forecast, actual)["costs"]["infrastructure"]
self.assertEqual("data_quality", result["category"])
def test_known_components_still_compute_beside_an_unknown_one(self):
forecast, actual = record(), record("actual")
forecast["costs"].update(infrastructure=None, total=None)
actual["costs"].update(infrastructure=None, internal_labor=90, total=None)
result = compare(forecast, actual)["costs"]
self.assertEqual("unknown", result["infrastructure"]["status"])
self.assertEqual(30, result["internal_labor"]["error"])
def test_one_sided_unknown_does_not_subtract_against_a_missing_amount(self):
forecast, actual = record(), record("actual")
actual["costs"].update(infrastructure=None, total=None)
result = compare(forecast, actual)["costs"]["infrastructure"]
self.assertEqual("unknown", result["status"])
self.assertEqual(10, result["forecast"])
self.assertIsNone(result["actual"])
class OperationalRecordTest(unittest.TestCase):
"""The first real apps-pg record, folded in from RAILIANCE-WP-0016."""
def setUp(self):
path = Path(__file__).parents[1] / "data" / "control-cycle" / "apps-pg-2026-09-base.json"
self.record = json.loads(path.read_text())
def test_usage_is_known_while_infrastructure_cost_is_not(self):
costs = self.record["costs"]
self.assertIsNone(costs["infrastructure"])
self.assertIsNone(costs["total"])
self.assertEqual(30.0, costs["internal_labor"])
self.assertTrue(self.record["usage_proxies"])
def test_total_is_not_the_sum_of_the_known_parts(self):
costs = self.record["costs"]
known = costs["internal_labor"] + costs["external_labor"]
self.assertNotEqual(known, costs["total"])
self.assertIsNone(costs["total"])
def test_shared_overhead_is_recorded_as_a_share_before_any_eur_exists(self):
allocation = self.record["allocation"]
self.assertEqual("apps-pg-dbbytes-v1", allocation["method_version"])
self.assertIsNone(allocation["unattributed_eur"])
self.assertEqual(0.948, allocation["unattributed_share"])
def test_absent_recovery_objectives_are_null_not_optimistic_defaults(self):
constraints = self.record["service_constraints"]
self.assertIsNone(constraints["rpo_minutes"]["value"])
self.assertIsNone(constraints["rto_minutes"]["value"])
def test_record_cites_the_authoritative_platform_evidence(self):
self.assertIn("RAILIANCE-WP-0016", self.record["evidence"])
self.assertEqual("high", self.record["uncertainty"]["level"])
if __name__ == "__main__": if __name__ == "__main__":
unittest.main() unittest.main()

View file

@ -159,5 +159,40 @@ class LivePortfolioTest(unittest.TestCase):
self.assertEqual(self.report, build(ROOT, TODAY)) self.assertEqual(self.report, build(ROOT, TODAY))
class DeliveredEvidenceTest(unittest.TestCase):
"""RAILIANCE-WP-0016 delivered apps-pg evidence; partial delivery must not
read as full coverage."""
@classmethod
def setUpClass(cls):
cls.report = build(ROOT, TODAY)
def test_delivered_gap_leaves_the_open_list(self):
owners = {gap["owner"] for gap in self.report["coverage"]["unresolved_gaps"]}
self.assertNotIn("railiance-platform", owners)
def test_delivered_gap_stays_visible_with_its_interface(self):
delivered = self.report["coverage"]["delivered_gaps"]
self.assertEqual(1, len(delivered))
self.assertEqual("railiance-platform", delivered[0]["owner"])
self.assertTrue(delivered[0]["interface"])
def test_residual_unknowns_still_produce_next_actions(self):
residuals = [a for a in self.report["next_actions"] if "delivered, still open" in a]
self.assertEqual(3, len(residuals))
def test_apps_pg_is_now_measurable_and_idle(self):
self.assertIn("resource:railiance:apps-pg", self.report["utilization"]["idle"])
unmeasured = {row["resource_id"] for row in self.report["utilization"]["unmeasured"]}
self.assertNotIn("resource:railiance:apps-pg", unmeasured)
def test_apps_pg_cost_is_now_attributed_but_still_unpriced(self):
unattributed = {row["resource_id"] for row in self.report["cost"]["unattributed_allocation"]}
self.assertNotIn("resource:railiance:apps-pg", unattributed)
unpriced = {row["resource_id"] for row in self.report["cost"]["unpriced"]}
self.assertIn("resource:railiance:apps-pg", unpriced)
self.assertIsNone(self.report["cost"]["known_monthly_spend_eur"])
if __name__ == "__main__": if __name__ == "__main__":
unittest.main() unittest.main()

View file

@ -11,7 +11,11 @@ COST_FIELDS = ("infrastructure", "internal_labor", "external_labor", "total")
ATTRIBUTIONS = {"demand", "provider_price", "allocation", "labor", "model", "data_quality"} ATTRIBUTIONS = {"demand", "provider_price", "allocation", "labor", "model", "data_quality"}
def delta(forecast: float, actual: float) -> dict: def delta(forecast: float | None, actual: float | None) -> dict:
# A missing amount is unknown, not zero: subtracting against it would
# manufacture a variance the evidence does not support.
if forecast is None or actual is None:
return {"forecast": forecast, "actual": actual, "status": "unknown"}
error = actual - forecast error = actual - forecast
return { return {
"forecast": forecast, "forecast": forecast,
@ -50,7 +54,12 @@ def compare(forecast: dict, actual: dict) -> dict:
costs = {} costs = {}
for name in COST_FIELDS: for name in COST_FIELDS:
default_category = "labor" if "labor" in name else "provider_price" default_category = "labor" if "labor" in name else "provider_price"
costs[name] = {**delta(forecast["costs"][name], actual["costs"][name]), "currency": "EUR", "category": attribution.get(f"costs.{name}", default_category)} result = delta(forecast["costs"][name], actual["costs"][name])
# An unknown amount is a data-quality gap, not a price or labour movement.
category = "data_quality" if result.get("status") == "unknown" else attribution.get(
f"costs.{name}", default_category
)
costs[name] = {**result, "currency": "EUR", "category": category}
if forecast["allocation"] != actual["allocation"]: if forecast["allocation"] != actual["allocation"]:
costs["allocation_method"] = {"status": "changed", "category": attribution.get("allocation", "allocation")} costs["allocation_method"] = {"status": "changed", "category": attribution.get("allocation", "allocation")}

View file

@ -27,6 +27,7 @@ USAGE_PAIRS = {
"cpu": "cpu_usage", "cpu": "cpu_usage",
"memory": "memory_usage", "memory": "memory_usage",
"root_filesystem": "root_filesystem_used", "root_filesystem": "root_filesystem_used",
"storage": "storage_used",
} }
@ -59,9 +60,23 @@ def coverage_section(coverage: dict | None) -> dict:
{"group": group["group"], "status": group["status"], "resources": len(group["resource_ids"])} {"group": group["group"], "status": group["status"], "resources": len(group["resource_ids"])}
for group in coverage["coverage"] for group in coverage["coverage"]
], ],
# A delivered gap stays visible with its residual unknowns rather than
# disappearing, so partial delivery is not read as full coverage.
"unresolved_gaps": [ "unresolved_gaps": [
{"owner": gap["owner"], "gap": gap["gap"], "delegated_workplan": gap["delegated_workplan"]} {"owner": gap["owner"], "gap": gap["gap"], "delegated_workplan": gap["delegated_workplan"]}
for gap in coverage["owned_gaps"] for gap in coverage["owned_gaps"]
if gap.get("status", "open") == "open"
],
"delivered_gaps": [
{
"owner": gap["owner"],
"delegated_workplan": gap["delegated_workplan"],
"delivered_on": gap.get("delivered_on"),
"interface": gap.get("interface", []),
"residual_unknowns": gap.get("residual_unknowns", []),
}
for gap in coverage["owned_gaps"]
if gap.get("status", "open") == "delivered"
], ],
} }
@ -250,6 +265,9 @@ def next_actions(report: dict) -> list[str]:
actions = [] actions = []
for gap in report["coverage"]["unresolved_gaps"]: for gap in report["coverage"]["unresolved_gaps"]:
actions.append(f"{gap['owner']}: deliver {gap['delegated_workplan']}{gap['gap']}") actions.append(f"{gap['owner']}: deliver {gap['delegated_workplan']}{gap['gap']}")
for gap in report["coverage"].get("delivered_gaps", []):
for residual in gap["residual_unknowns"]:
actions.append(f"{gap['owner']}: {gap['delegated_workplan']} delivered, still open — {residual}")
if report["cost"]["unpriced"]: if report["cost"]["unpriced"]:
actions.append( actions.append(
"resource-control: no portfolio spend figure exists until at least one booked cost arrives " "resource-control: no portfolio spend figure exists until at least one booked cost arrives "

View file

@ -44,16 +44,30 @@ def main() -> int:
for resource in resources: for resource in resources:
assert not required - resource.keys(), f"missing fields: {required - resource.keys()}" assert not required - resource.keys(), f"missing fields: {required - resource.keys()}"
validate_record(resource) validate_record(resource)
operational_records = [load(str(path)) for path in Path("data/control-cycle").glob("*.json")]
control_records += operational_records
record_ids = {record["record_id"] for record in control_records} record_ids = {record["record_id"] for record in control_records}
assert len(record_ids) == len(control_records) assert len(record_ids) == len(control_records)
assert {record["resource_class"] for record in control_records} == {"storage", "cluster_compute", "shared_platform_service"} assert {record["resource_class"] for record in control_records} >= {"storage", "cluster_compute", "shared_platform_service"}
for record in control_records: for record in control_records:
assert record["schema_version"] == "0.1" assert record["schema_version"] in {"0.1", "0.2"}
assert record["resource_id"].startswith("resource:") assert record["resource_id"].startswith("resource:")
costs = record["costs"] costs = record["costs"]
assert costs["total"] == round(costs["infrastructure"] + costs["internal_labor"] + costs["external_labor"], 2) components = [costs["infrastructure"], costs["internal_labor"], costs["external_labor"]]
if any(component is None for component in components):
# An unknown component makes the total unknown; it is never the sum
# of the parts that happen to be known.
assert record["schema_version"] == "0.2", "null costs require schema 0.2"
assert costs["total"] is None
else:
assert costs["total"] == round(sum(components), 2)
if record["record_type"] == "actual": if record["record_type"] == "actual":
assert record["forecast_ref"] in record_ids assert record["forecast_ref"] in record_ids
# Operational records assert real facts and must cite the authoritative
# repository evidence they came from.
for record in operational_records:
assert record["evidence"], f"{record['record_id']} cites no evidence"
assert record["uncertainty"]["notes"], f"{record['record_id']} states no uncertainty"
case_schema = load("schemas/optimization-case.schema.json") case_schema = load("schemas/optimization-case.schema.json")
assert case_schema["$schema"].endswith("2020-12/schema") assert case_schema["$schema"].endswith("2020-12/schema")
assert case_schema["properties"]["schema_version"]["const"] == "0.1" assert case_schema["properties"]["schema_version"]["const"] == "0.1"

View file

@ -287,6 +287,23 @@ resources with no contract dates at all, and six of seven resources sharing
- [x] At least two optimization cases complete the evidence-to-decision loop. - [x] At least two optimization cases complete the evidence-to-decision loop.
- [x] Portfolio reporting exposes unknown and unattributed values explicitly. - [x] Portfolio reporting exposes unknown and unattributed values explicitly.
## Post-completion record
2026-08-14: `railiance-platform` delivered `RAILIANCE-WP-0016`, the first
delegated evidence from T04 to land. Folded in: the apps-pg resource record now
carries real capacity and the `apps-pg-dbbytes-v1` allocation driver, the first
operational control-cycle record replaced the illustrative apps-pg fixture, and
coverage moved to `data/portfolio-coverage-2026-08-14.json` with the gap marked
delivered and its three residual unknowns still open.
The real evidence exposed a design gap in the T05 schema: v0.1 required a number
for every cost field, so recording genuine usage without a booked cost was
impossible without inventing one. Schema 0.2 permits null costs, a null
unattributed amount, a technical `unattributed_share`, and null measurements;
the comparator reports unknown amounts as `data_quality` rather than computing a
variance. Existing 0.1 records were not rewritten. Evidence:
`docs/evidence/RESOURCE-WP-0003-apps-pg-evidence-foldin-2026-08-14.md`.
## Standing gate ## Standing gate
Both optimization cases are `blocked_on_evidence` by design: the mechanism is Both optimization cases are `blocked_on_evidence` by design: the mechanism is