diff --git a/WORK-RECORDS.md b/WORK-RECORDS.md index 86d024e..c3cd20e 100644 --- a/WORK-RECORDS.md +++ b/WORK-RECORDS.md @@ -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 | diff --git a/data/control-cycle/apps-pg-2026-09-base.json b/data/control-cycle/apps-pg-2026-09-base.json new file mode 100644 index 0000000..45949df --- /dev/null +++ b/data/control-cycle/apps-pg-2026-09-base.json @@ -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" + ] +} diff --git a/data/portfolio-coverage-2026-08-14.json b/data/portfolio-coverage-2026-08-14.json new file mode 100644 index 0000000..afa292f --- /dev/null +++ b/data/portfolio-coverage-2026-08-14.json @@ -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" +} diff --git a/data/resources/apps-pg.json b/data/resources/apps-pg.json index b8ddfed..beb1854 100644 --- a/data/resources/apps-pg.json +++ b/data/resources/apps-pg.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" + } + ] } diff --git a/docs/evidence/RESOURCE-WP-0003-apps-pg-evidence-foldin-2026-08-14.md b/docs/evidence/RESOURCE-WP-0003-apps-pg-evidence-foldin-2026-08-14.md new file mode 100644 index 0000000..17b91dd --- /dev/null +++ b/docs/evidence/RESOURCE-WP-0003-apps-pg-evidence-foldin-2026-08-14.md @@ -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`. diff --git a/docs/forecast-actual-control.md b/docs/forecast-actual-control.md index 3879964..3b79aa9 100644 --- a/docs/forecast-actual-control.md +++ b/docs/forecast-actual-control.md @@ -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 diff --git a/docs/portfolio-operating-cadence.md b/docs/portfolio-operating-cadence.md index df2b354..8504295 100644 --- a/docs/portfolio-operating-cadence.md +++ b/docs/portfolio-operating-cadence.md @@ -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. diff --git a/examples/control-cycle/apps-pg-actual.json b/examples/control-cycle/apps-pg-actual.json deleted file mode 100644 index fd14ce2..0000000 --- a/examples/control-cycle/apps-pg-actual.json +++ /dev/null @@ -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"]} diff --git a/examples/control-cycle/apps-pg-forecast.json b/examples/control-cycle/apps-pg-forecast.json deleted file mode 100644 index 60ff678..0000000 --- a/examples/control-cycle/apps-pg-forecast.json +++ /dev/null @@ -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"]} diff --git a/schemas/resource-control-cycle.schema.json b/schemas/resource-control-cycle.schema.json index 94be5aa..683ab58 100644 --- a/schemas/resource-control-cycle.schema.json +++ b/schemas/resource-control-cycle.schema.json @@ -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 + } } } } diff --git a/tests/test_control_cycle.py b/tests/test_control_cycle.py index 0150474..02d3da3 100644 --- a/tests/test_control_cycle.py +++ b/tests/test_control_cycle.py @@ -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() diff --git a/tests/test_portfolio_report.py b/tests/test_portfolio_report.py index efe3563..27a2a27 100644 --- a/tests/test_portfolio_report.py +++ b/tests/test_portfolio_report.py @@ -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() diff --git a/tools/control_cycle.py b/tools/control_cycle.py index 53e4a1e..0f3b9df 100644 --- a/tools/control_cycle.py +++ b/tools/control_cycle.py @@ -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")} diff --git a/tools/portfolio_report.py b/tools/portfolio_report.py index 8d8ac50..d7e7459 100644 --- a/tools/portfolio_report.py +++ b/tools/portfolio_report.py @@ -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 " diff --git a/tools/validate.py b/tools/validate.py index e013a4f..829243b 100644 --- a/tools/validate.py +++ b/tools/validate.py @@ -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" diff --git a/workplans/RESOURCE-WP-0003-managed-infrastructure-portfolio-control.md b/workplans/RESOURCE-WP-0003-managed-infrastructure-portfolio-control.md index 68faea9..651ec5f 100644 --- a/workplans/RESOURCE-WP-0003-managed-infrastructure-portfolio-control.md +++ b/workplans/RESOURCE-WP-0003-managed-infrastructure-portfolio-control.md @@ -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