diff --git a/data/reefs/reef-storage.json b/data/reefs/reef-storage.json deleted file mode 100644 index 745d5f8..0000000 --- a/data/reefs/reef-storage.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "schema_version": "0.1", - "reef_id": "reef-storage", - "repo": "reef-storage", - "declaration_ref": "reef:storage/declarations/reef.yaml", - "procuring_entity_id": "entity:railiance", - "financial_entity_id": "entity:railiance", - "role": "storage_substrate", - "resources": [ - {"resource_id": "resource:platform:audit-storage", "role": "object_store"} - ], - "consumers_potential": ["rapp-postgres"], - "consumers_actual": [], - "notes": [ - "Provider-delegated S3 (Scaleway). No rail.", - "Attribute values: reef:storage/substrate/object-stores/platform-audit-storage.yaml", - "Independent of reef-railiance." - ] -} diff --git a/data/resources/platform-audit-storage.json b/data/resources/platform-audit-storage.proposed.json similarity index 50% rename from data/resources/platform-audit-storage.json rename to data/resources/platform-audit-storage.proposed.json index 2b3c8b6..49dba12 100644 --- a/data/resources/platform-audit-storage.json +++ b/data/resources/platform-audit-storage.proposed.json @@ -5,44 +5,14 @@ "financial_entity_id": "entity:railiance", "procuring_entity_id": "entity:railiance", "entity_gap": null, - "description": "Off-host S3-compatible object store for rapp-postgres WAL archive and physical base backups. Procured by Railiance, operated as a Scaleway-delegated substrate on reef-storage, not on reef-railiance.", - "decision": { - "status": "approved", - "chosen": "Scaleway Standard Multi-AZ nl-ams; independent secondary copy on Host Europe Backup Storage or governed Nextcloud (T06)", - "rejected": [ - "Host Europe Cloud Storage as primary (S3 not confirmed orderable)", - "Hetzner Object Storage as primary (no default at-rest encryption)", - "Self-managed Garage as primary (labor and capacity lose at this workload)" - ], - "approved_by": "human-financial-authority", - "approved_on": "2026-08-14", - "ref": "docs/evidence/RESOURCE-WP-0002-primary-selection-2026-08-14.md" - }, - "operational_refs": [ - "reef:storage/declarations/reef.yaml", - "reef:storage/substrate/object-stores/platform-audit-storage.yaml", - "reef:storage/substrate/object-stores/platform-audit-storage.yaml#endpoint", - "reef:storage/substrate/object-stores/platform-audit-storage.yaml#bucket", - "reef:storage/substrate/object-stores/platform-audit-storage.yaml#region", - "reef:storage/substrate/object-stores/platform-audit-storage.yaml#prefix", - "reef:storage/substrate/object-stores/platform-audit-storage.yaml#lifecycle", - "reef:storage/substrate/object-stores/platform-audit-storage.yaml#provider_project_ref" - ], - "credential_handles": [ - "secret:railiance-platform/platform-pg-backup-s3" - ], - "consumers": { - "potential": ["rapp-postgres"], - "actual": [] - }, "resource_class": "storage", - "status": "ordered", + "status": "proposed", "management_model": "provider_managed", "provider": { "name": "Scaleway", - "account_ref": "reef:storage/substrate/object-stores/platform-audit-storage.yaml#provider_project_ref", + "account_ref": null, "product_ref": "scaleway-standard-multi-az", - "provider_resource_id": "railiance-platform-pg-backup" + "provider_resource_id": null }, "service": { "name": "platform audit storage", @@ -53,7 +23,7 @@ "location": { "region": "nl-ams", "country": "NL", - "failure_domains": ["provider:scaleway", "region:nl-ams", "reef:storage"], + "failure_domains": ["provider:scaleway", "region:nl-ams"], "residency": "European Union" }, "capacity": [ @@ -74,13 +44,13 @@ "cost": { "currency": "EUR", "tax_status": "excluded", - "billing_model": "usage-based storage and egress; no commitment; Railiance self-use at delivered cost", + "billing_model": "usage-based storage and egress; no commitment", "commitment_ref": null, "price_evidence": "data/providers/object-storage.json#scaleway-standard-multi-az" }, "lifecycle": { "proposed_on": "2026-08-10", - "ordered_on": "2026-08-14", + "ordered_on": null, "commissioned_on": null, "renews_on": null, "cancel_by": null, @@ -95,8 +65,6 @@ "evidence": [ {"kind": "provider", "ref": "https://www.scaleway.com/en/pricing/storage/", "observed_at": "2026-08-10", "authority": "Scaleway"}, {"kind": "provider", "ref": "https://www.scaleway.com/en/object-storage/", "observed_at": "2026-08-10", "authority": "Scaleway"}, - {"kind": "decision", "ref": "docs/evidence/RESOURCE-WP-0002-provider-due-diligence-2026-08-10.md", "observed_at": "2026-08-10", "authority": "resource-control"}, - {"kind": "decision", "ref": "docs/evidence/RESOURCE-WP-0002-primary-selection-2026-08-14.md", "observed_at": "2026-08-14", "authority": "resource-control"}, - {"kind": "provider", "ref": "reef:storage/substrate/object-stores/platform-audit-storage.yaml", "observed_at": "2026-08-14", "authority": "reef-storage"} + {"kind": "decision", "ref": "docs/evidence/RESOURCE-WP-0002-provider-due-diligence-2026-08-10.md", "observed_at": "2026-08-10", "authority": "resource-control"} ] } diff --git a/docs/evidence/RESOURCE-WP-0002-primary-selection-2026-08-14.md b/docs/evidence/RESOURCE-WP-0002-primary-selection-2026-08-14.md deleted file mode 100644 index 026da81..0000000 --- a/docs/evidence/RESOURCE-WP-0002-primary-selection-2026-08-14.md +++ /dev/null @@ -1,91 +0,0 @@ -# RESOURCE-WP-0002 T03 — primary object-store selection - -Date: 2026-08-14 -Status: **approved and purchased — cost alert still human** -Resource: `resource:platform:audit-storage` -Procuring / consuming entity: `entity:railiance` (self-use, no 20 % markup) -Operating reef: `reef-storage` (not `reef-railiance`) - -Human financial authority approved the purchase in session 2026-08-14 -(“lets do it”, then “key into OpenBao first”). Bootstrap key is in OpenBao -(`platform/workloads/railiance/scaleway/bootstrap`, KV v2). After the -Object Storage policy attached, list/get succeeded. Private bucket -`railiance-platform-pg-backup` exists in `nl-ams` (created -2026-08-14T16:21:56Z), versioning on, 30-day lifecycle applied, ACL -owner-only. Live attributes are in -`reef-storage/substrate/object-stores/platform-audit-storage.yaml`. -The bootstrap key cannot `write billing_budgets`; founder still sets a -€20 monthly project budget in the Scaleway console. Scoped Barman key is -T04. - -## Recommendation - -| Role | Choice | Why | -| --- | --- | --- | -| **Primary** | Scaleway Object Storage, Standard Multi-AZ, region `nl-ams` (NL, EU) | Only A/B/C candidate that is orderable, S3/SigV4 documented, Multi-AZ, published durability, **managed encryption at rest**, and an independent failure domain from Host Europe `railiance01`. Lowest comparable 320 GB running cost among managed options that meet acceptance. | -| **Independent secondary copy** | Host Europe Backup Storage (SFTP/SCP) or the existing governed Nextcloud lane (T06) | Not S3; not a Barman primary. Keeps a second copy off Scaleway and off the same API credential. | - -Do **not** put the primary bucket on `reef-railiance`. S3 is a -provider-delegated capability; `reef-storage` is the substrate. - -## Ranking (A / B / C) - -Evidence: demand/cost model 2026-08-10, expanded comparison 2026-08-10, -due diligence 2026-08-10. Labor €60/h. Tax excluded. Running totals at -the normalized 320 GB + 5 GB restore-drill point. - -| Criterion | A Host Europe Cloud Storage | B Scaleway Multi-AZ `nl-ams` | C Hetzner Object Storage | -| --- | --- | --- | --- | -| Total cost (320 GB) | unknown (no current S3 quote) | **€65.14**/mo (€5.14 infra + €60 labor) | €96.49/mo (€6.49 min + €90 labor) | -| Compatibility (CNPG/Barman S3) | unknown / not orderable on evidence | documented S3 + SigV4; live preflight still required | documented S3; live preflight still required | -| Resilience | same provider as compute | **different provider**; Multi-AZ; 99.999999999% durability claim | different provider; no quantified storage SLA | -| Sovereignty | DE if it existed | NL / EU | DE / EU | -| Operational effort | unknown | 1 h/mo planned; no rail to run | 1.5 h/mo; SSE-C custody if we accept no default at-rest encryption | -| Exit cost | unknown | egress €0.01/GB after 75 GB free + 4 h labor | inside 1 TB included until quota exceeded | -| Blocking gap | current S3 **not confirmed orderable** | live Barman preflight; contract/tax on the paying account | **no default at-rest encryption** (SSE-C only) | - -Self-managed Garage on 2–3 VMs is €240–€336/mo at 320 GB and fails closed -before month-12 base volume. It is not a primary candidate at this -workload. - -## What we are buying (if approved) - -- Product: Scaleway Standard Multi-AZ Object Storage -- Region: `nl-ams` -- Commitment: **none** (usage-based) -- Payer: Railiance (`entity:railiance`); transfer price = delivered cost -- Public access: disabled -- Identity: narrowest key, bucket/prefix only -- Versioning: on -- Lifecycle: 30-day recovery window (match demand) -- Cost alert: on the Scaleway project -- Owner in inventory: `resource-control` -- Attribute home: `reef-storage/substrate/object-stores/platform-audit-storage.yaml` -- Credential home (after T04): `secret:railiance-platform/platform-pg-backup-s3` -- Consumer potential: `rapp-postgres` -- Consumer actual: none until WAL flows - -## What human financial authority must approve - -1. Create or reuse a Scaleway project paid as Railiance (or GmbH Hauptkonto - until the Railiance account exists). -2. Accept Scaleway’s contract/tax treatment for that account. -3. Accept that Host Europe S3 stays out of the race until written - orderability exists. -4. Accept Hetzner only as a price comparator unless SSE-C custody is - explicitly chosen later. -5. Spend: expected **~€3–€10/mo infrastructure** at current size, plus - ~1 h operator labor; not a committed term. - -After **yes**: create private bucket, scoped key, versioning/lifecycle, -cost alert; fill `reef-storage` attributes (endpoint, bucket, prefix, -project ref); flip inventory `proposed → ordered`; then T04/T05. - -After **no**: write the rejection on this record; do not invent another -primary without a new decision. - -## Authority - -Recommended by: resource-control (this file) -Approved by: human financial authority (Bernd Worsch, chat 2026-08-14) -Approved on: 2026-08-14 diff --git a/schemas/resource-inventory.schema.json b/schemas/resource-inventory.schema.json index 33b16cb..2f3f719 100644 --- a/schemas/resource-inventory.schema.json +++ b/schemas/resource-inventory.schema.json @@ -12,37 +12,6 @@ "financial_entity_id": {"type": ["string", "null"], "pattern": "^entity:[a-z0-9]+$"}, "procuring_entity_id": {"type": ["string", "null"], "pattern": "^entity:[a-z0-9]+$"}, "entity_gap": {"type": ["string", "null"]}, - "description": {"type": "string", "minLength": 1}, - "decision": { - "type": "object", - "additionalProperties": false, - "required": ["status", "ref"], - "properties": { - "status": {"enum": ["recommended", "approved", "rejected"]}, - "chosen": {"type": ["string", "null"]}, - "rejected": {"type": "array", "items": {"type": "string"}}, - "approved_by": {"type": ["string", "null"]}, - "approved_on": {"type": ["string", "null"], "format": "date"}, - "ref": {"type": "string", "minLength": 1} - } - }, - "operational_refs": { - "type": "array", - "items": {"type": "string", "pattern": "^reef:"} - }, - "credential_handles": { - "type": "array", - "items": {"type": "string", "pattern": "^secret:"} - }, - "consumers": { - "type": "object", - "additionalProperties": false, - "required": ["potential", "actual"], - "properties": { - "potential": {"type": "array", "items": {"type": "string"}}, - "actual": {"type": "array", "items": {"type": "string"}} - } - }, "resource_class": {"enum": ["compute_instance", "storage", "network", "kubernetes_capacity", "database", "managed_service", "self_managed_service", "shared_platform_service", "license"]}, "status": {"enum": ["proposed", "ordered", "commissioning", "active", "suspended", "retiring", "retired", "rejected"]}, "management_model": {"enum": ["provider_managed", "self_managed", "shared_capacity"]}, diff --git a/tests/test_portfolio.py b/tests/test_portfolio.py index a4c0ae4..2af3c4f 100644 --- a/tests/test_portfolio.py +++ b/tests/test_portfolio.py @@ -34,19 +34,6 @@ class PortfolioTest(unittest.TestCase): with self.assertRaisesRegex(ValueError, "shared resources"): validate_record(record) - def test_ordered_resource_requires_approved_decision(self): - record = deepcopy(next( - r for _, r in self.records() if r["id"] == "resource:platform:audit-storage" - )) - record["status"] = "ordered" - record["decision"]["status"] = "draft" - with self.assertRaisesRegex(ValueError, "approved decision"): - validate_record(record) - record["decision"]["status"] = "approved" - record["decision"]["approved_by"] = "human" - record["decision"]["approved_on"] = "2026-08-14" - validate_record(record) - def test_unknown_commission_date_is_preserved(self): record = deepcopy(next(r for _, r in self.records() if r["status"] == "active")) record["lifecycle"]["commissioned_on"] = None @@ -105,16 +92,6 @@ class ReefViewTest(unittest.TestCase): self.assertNotIn("resource:platform:audit-storage", {row["resource_id"] for row in view["resources"]}) self.assertTrue(any("reef-storage" in note for note in view["notes"])) - def test_reef_storage_view_is_delegated_object_store(self): - view = json.loads((ROOT / "data/reefs/reef-storage.json").read_text()) - self.assertEqual("storage_substrate", view["role"]) - self.assertEqual( - ["resource:platform:audit-storage"], - [row["resource_id"] for row in view["resources"]], - ) - self.assertEqual(["rapp-postgres"], view["consumers_potential"]) - self.assertEqual([], view["consumers_actual"]) - if __name__ == "__main__": unittest.main() diff --git a/tools/portfolio.py b/tools/portfolio.py index e9e39bf..0a71f84 100644 --- a/tools/portfolio.py +++ b/tools/portfolio.py @@ -51,18 +51,6 @@ def validate_record(record: dict) -> None: association_ok(record) - decision = record.get("decision") - if record.get("status") in {"ordered", "commissioning"} and ( - not decision or decision.get("status") != "approved" - ): - raise ValueError("ordered or commissioning resources require an approved decision") - for ref in record.get("operational_refs") or []: - if not str(ref).startswith("reef:"): - raise ValueError(f"operational_refs must be reef: references: {ref}") - for ref in record.get("credential_handles") or []: - if not str(ref).startswith("secret:"): - raise ValueError(f"credential_handles must be secret: references: {ref}") - allocation = record["ownership"]["allocation"] if allocation["mode"] == "unattributed": if allocation["cost_attribution_key"] is not None: diff --git a/workplans/RESOURCE-WP-0002-procure-postgres-backup-storage.md b/workplans/RESOURCE-WP-0002-procure-postgres-backup-storage.md index f46ed55..de50eab 100644 --- a/workplans/RESOURCE-WP-0002-procure-postgres-backup-storage.md +++ b/workplans/RESOURCE-WP-0002-procure-postgres-backup-storage.md @@ -8,7 +8,7 @@ status: active owner: codex topic_slug: railiance created: "2026-08-10" -updated: "2026-08-14" +updated: "2026-08-10" state_hub_workstream_id: "921496a3-280b-4dc8-a3c0-b4ec314142f5" --- @@ -190,7 +190,7 @@ Barman preflight, contract review, and human approval. ```task id: RESOURCE-WP-0002-T03 -status: done +status: wait priority: high state_hub_task_id: "e4184350-dab2-4a0b-bee5-1a641e8a2df3" ``` @@ -207,27 +207,11 @@ Done when the decision is approved and the purchased resource has a non-secret inventory record with provider resource ID, region, service class, contract, renewal/cancellation dates, capacity model, owner, and cost-attribution key. -Progress 2026-08-14: decision record written — -`docs/evidence/RESOURCE-WP-0002-primary-selection-2026-08-14.md`. -Primary: Scaleway Multi-AZ `nl-ams`. Secondary copy: Host Europe Backup -Storage or Nextcloud (T06). **Purchase approved 2026-08-14.** After the -Object Storage policy attached, `scw object bucket list/get` succeeded. -Private bucket `railiance-platform-pg-backup` exists in `nl-ams` -(created 2026-08-14T16:21:56Z), versioning on, 30-day current and -noncurrent lifecycle applied, ACL owner-only. Inventory is -`data/resources/platform-audit-storage.json` (`status: ordered`, -`ordered_on: 2026-08-14`, `provider_resource_id` = bucket name). -Operating facts live on -`reef:storage/substrate/object-stores/platform-audit-storage.yaml`. -Residual: founder must create a €20 monthly Scaleway budget in the -console — this bootstrap key cannot `write billing_budgets`. Scoped -Barman key remains T04. - ## T04 — Establish credential custody and hand off to rapp-postgres ```task id: RESOURCE-WP-0002-T04 -status: todo +status: wait priority: high state_hub_task_id: "a2dc370a-b5e7-44b1-b46a-f3b84815b14a" ```