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:
parent
b64b5683df
commit
17de8b831e
16 changed files with 908 additions and 71 deletions
|
|
@ -10,7 +10,7 @@
|
|||
| --- | --- | --- | --- | --- |
|
||||
| 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-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-T02 | 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-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-T06 | todo | — | 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-T06 | done | — | workplans/RESOURCE-WP-0003-managed-infrastructure-portfolio-control.md |
|
||||
| task | RESOURCE-WP-0003-T07 | done | — | workplans/RESOURCE-WP-0003-managed-infrastructure-portfolio-control.md |
|
||||
|
|
|
|||
69
data/control-cycle/apps-pg-2026-09-base.json
Normal file
69
data/control-cycle/apps-pg-2026-09-base.json
Normal 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"
|
||||
]
|
||||
}
|
||||
115
data/portfolio-coverage-2026-08-14.json
Normal file
115
data/portfolio-coverage-2026-08-14.json
Normal 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"
|
||||
}
|
||||
|
|
@ -1,15 +1,162 @@
|
|||
{
|
||||
"schema_version": "0.2", "record_scope": "inventory",
|
||||
"id": "resource:railiance:apps-pg", "resource_class": "database",
|
||||
"status": "active", "management_model": "self_managed",
|
||||
"provider": {"name": "Railiance", "account_ref": null, "product_ref": "cloudnative-pg", "provider_resource_id": "kubernetes:databases/cluster/apps-pg"},
|
||||
"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": "observed", "observed_at": "2026-08-11"}, {"metric": "storage", "value": 10, "unit": "GiB", "kind": "provisioned", "observed_at": "2026-08-11"}],
|
||||
"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}},
|
||||
"cost": {"currency": "EUR", "tax_status": "not_applicable", "billing_model": "shared cluster and operations cost; allocation method unknown", "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/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"}]
|
||||
"schema_version": "0.2",
|
||||
"record_scope": "inventory",
|
||||
"id": "resource:railiance:apps-pg",
|
||||
"resource_class": "database",
|
||||
"status": "active",
|
||||
"management_model": "self_managed",
|
||||
"provider": {
|
||||
"name": "Railiance",
|
||||
"account_ref": null,
|
||||
"product_ref": "cloudnative-pg",
|
||||
"provider_resource_id": "kubernetes:databases/cluster/apps-pg"
|
||||
},
|
||||
"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"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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`.
|
||||
|
|
@ -98,10 +98,41 @@ make control-cycle FORECAST=examples/control-cycle/storage-forecast.json \
|
|||
ACTUAL=examples/control-cycle/storage-actual.json
|
||||
```
|
||||
|
||||
The examples cover storage, `reef-railiance` cluster compute, and the shared
|
||||
`apps-pg` service. Their numbers are illustrative contract fixtures, not booked
|
||||
facts. Operational records replace their evidence references after the owner
|
||||
workplans publish observations.
|
||||
The examples cover storage and `reef-railiance` cluster compute. Their numbers
|
||||
are illustrative contract fixtures, not booked facts. Operational records
|
||||
replace them once the owner workplans publish observations — the illustrative
|
||||
`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
|
||||
|
||||
|
|
|
|||
|
|
@ -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 |
|
||||
| **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
|
||||
|
||||
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
|
||||
storage carries price evidence. Every other resource is waiting on
|
||||
`RAIL-HO-WP-0008` and `FIN-WP-0004`.
|
||||
- **Idle or saturated capacity?** `railiance01` and the `reef-railiance` k3s
|
||||
cluster are idle on every measured metric — 14 percent CPU, 37 percent memory.
|
||||
Nothing is saturated. Five resources cannot be measured at all.
|
||||
- **Forecast error?** Not yet computable. The control-cycle mechanism is proven
|
||||
on paired examples, but no operational actual observation exists, so there is
|
||||
no variance to report and none is fabricated.
|
||||
- **Idle or saturated capacity?** `railiance01`, the `reef-railiance` k3s
|
||||
cluster, and `apps-pg` are idle on every measured metric — 14 percent CPU,
|
||||
37 percent memory, and 5.8 percent of the apps-pg volume. Nothing is
|
||||
saturated. Four resources cannot be measured at all.
|
||||
- **Forecast error?** Not yet computable. The first operational forecast record
|
||||
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:
|
||||
six active resources have no renewal or cancellation date recorded, so the
|
||||
cancellation window cannot be respected for any of them.
|
||||
|
|
|
|||
|
|
@ -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"]}
|
||||
|
|
@ -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"]}
|
||||
|
|
@ -4,81 +4,286 @@
|
|||
"title": "Immutable resource forecast or actual observation",
|
||||
"type": "object",
|
||||
"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": {
|
||||
"schema_version": {"const": "0.1"},
|
||||
"record_id": {"type": "string", "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"]},
|
||||
"schema_version": {
|
||||
"enum": [
|
||||
"0.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."
|
||||
},
|
||||
"record_id": {
|
||||
"type": "string",
|
||||
"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": {
|
||||
"type": "object",
|
||||
"minProperties": 1,
|
||||
"additionalProperties": {"$ref": "#/$defs/measurement"}
|
||||
"additionalProperties": {
|
||||
"$ref": "#/$defs/measurement"
|
||||
}
|
||||
},
|
||||
"capacity": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": ["model", "provisioned", "used"],
|
||||
"required": [
|
||||
"model",
|
||||
"provisioned",
|
||||
"used"
|
||||
],
|
||||
"properties": {
|
||||
"model": {"enum": ["fixed", "elastic", "hybrid"]},
|
||||
"provisioned": {"type": "object", "additionalProperties": {"$ref": "#/$defs/measurement"}},
|
||||
"used": {"type": "object", "additionalProperties": {"$ref": "#/$defs/measurement"}}
|
||||
"model": {
|
||||
"enum": [
|
||||
"fixed",
|
||||
"elastic",
|
||||
"hybrid"
|
||||
]
|
||||
},
|
||||
"provisioned": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"$ref": "#/$defs/measurement"
|
||||
}
|
||||
},
|
||||
"used": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"$ref": "#/$defs/measurement"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"costs": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": ["currency", "infrastructure", "internal_labor", "external_labor", "total"],
|
||||
"required": [
|
||||
"currency",
|
||||
"infrastructure",
|
||||
"internal_labor",
|
||||
"external_labor",
|
||||
"total"
|
||||
],
|
||||
"properties": {
|
||||
"currency": {"const": "EUR"},
|
||||
"infrastructure": {"type": "number", "minimum": 0},
|
||||
"internal_labor": {"type": "number", "minimum": 0},
|
||||
"external_labor": {"type": "number", "minimum": 0},
|
||||
"total": {"type": "number", "minimum": 0},
|
||||
"booked_cost_refs": {"type": "array", "items": {"type": "string"}, "uniqueItems": true}
|
||||
}
|
||||
"currency": {
|
||||
"const": "EUR"
|
||||
},
|
||||
"infrastructure": {
|
||||
"type": [
|
||||
"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": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": ["method", "driver", "method_version", "unattributed_eur"],
|
||||
"required": [
|
||||
"method",
|
||||
"driver",
|
||||
"method_version",
|
||||
"unattributed_eur"
|
||||
],
|
||||
"properties": {
|
||||
"method": {"type": "string", "minLength": 1},
|
||||
"driver": {"type": "string", "minLength": 1},
|
||||
"method_version": {"type": "string", "minLength": 1},
|
||||
"unattributed_eur": {"type": "number", "minimum": 0}
|
||||
"method": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"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": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": ["level", "notes"],
|
||||
"required": [
|
||||
"level",
|
||||
"notes"
|
||||
],
|
||||
"properties": {
|
||||
"level": {"enum": ["low", "medium", "high"]},
|
||||
"notes": {"type": "array", "items": {"type": "string"}}
|
||||
"level": {
|
||||
"enum": [
|
||||
"low",
|
||||
"medium",
|
||||
"high"
|
||||
]
|
||||
},
|
||||
"notes": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"variance_attribution": {
|
||||
"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": {
|
||||
"measurement": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": ["value", "unit"],
|
||||
"required": [
|
||||
"value",
|
||||
"unit"
|
||||
],
|
||||
"properties": {
|
||||
"value": {"type": "number", "minimum": 0},
|
||||
"unit": {"type": "string", "minLength": 1}
|
||||
"value": {
|
||||
"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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import json
|
||||
import sys
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
|
@ -50,5 +51,77 @@ class ControlCycleTest(unittest.TestCase):
|
|||
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__":
|
||||
unittest.main()
|
||||
|
|
|
|||
|
|
@ -159,5 +159,40 @@ class LivePortfolioTest(unittest.TestCase):
|
|||
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__":
|
||||
unittest.main()
|
||||
|
|
|
|||
|
|
@ -11,7 +11,11 @@ COST_FIELDS = ("infrastructure", "internal_labor", "external_labor", "total")
|
|||
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
|
||||
return {
|
||||
"forecast": forecast,
|
||||
|
|
@ -50,7 +54,12 @@ def compare(forecast: dict, actual: dict) -> dict:
|
|||
costs = {}
|
||||
for name in COST_FIELDS:
|
||||
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"]:
|
||||
costs["allocation_method"] = {"status": "changed", "category": attribution.get("allocation", "allocation")}
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ USAGE_PAIRS = {
|
|||
"cpu": "cpu_usage",
|
||||
"memory": "memory_usage",
|
||||
"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"])}
|
||||
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": [
|
||||
{"owner": gap["owner"], "gap": gap["gap"], "delegated_workplan": gap["delegated_workplan"]}
|
||||
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 = []
|
||||
for gap in report["coverage"]["unresolved_gaps"]:
|
||||
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"]:
|
||||
actions.append(
|
||||
"resource-control: no portfolio spend figure exists until at least one booked cost arrives "
|
||||
|
|
|
|||
|
|
@ -44,16 +44,30 @@ def main() -> int:
|
|||
for resource in resources:
|
||||
assert not required - resource.keys(), f"missing fields: {required - resource.keys()}"
|
||||
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}
|
||||
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:
|
||||
assert record["schema_version"] == "0.1"
|
||||
assert record["schema_version"] in {"0.1", "0.2"}
|
||||
assert record["resource_id"].startswith("resource:")
|
||||
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":
|
||||
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")
|
||||
assert case_schema["$schema"].endswith("2020-12/schema")
|
||||
assert case_schema["properties"]["schema_version"]["const"] == "0.1"
|
||||
|
|
|
|||
|
|
@ -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] 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
|
||||
|
||||
Both optimization cases are `blocked_on_evidence` by design: the mechanism is
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue