diff --git a/docs/bootstrap-recovery-ceremony.md b/docs/bootstrap-recovery-ceremony.md
index d571c8c..6b89814 100644
--- a/docs/bootstrap-recovery-ceremony.md
+++ b/docs/bootstrap-recovery-ceremony.md
@@ -41,7 +41,11 @@ owner custody and pass the decryption key through attended temporary unlock.
| Current consumer source | Reconcile the deployed revisions with the repaired caller/provider source. The October 14 reserved platform-group fix must be deployed and independently checked before claiming live enforcement. |
| Legacy credential disposition | IAM owner identifies the exact historical personal-token/trigger principals, actual consumers and revocation method before any deletion. Deleting a Secret does not revoke an issued provider credential. |
-The current independent key availability and exact artifact/manifest are pending.
+The operator subsequently reported holding the `railiance-backup` age key.
+Key material has not been received or read. Exact artifact/manifest selection
+and verification that this independently held key matches the selected
+ciphertext remain pending. The backup lane recipient and bootstrap
+`keys/age.pub` must not be assumed to be the same.
Do not draft a blanket restore or grant approval while these remain unknown.
Once fixed, bind the source revisions, target identities, allowed operations,
recovery/cleanup sequence and maintenance window to the reviewed live packet.
@@ -116,3 +120,25 @@ Emergent Security 0.2.6, package digest
start/root cards withheld, orientation only. Current SECURITY-COMPANION,
Platform backup/custody procedures, owner runtime contracts and the workplan
were reviewed as sources. No execution authority is derived from the cards.
+
+
+## Off-host inventory return — 2026-10-15
+
+Both bounded metadata inventories completed. Storage Share's inspected
+`Backup/railiance-backups` scope includes the current privacyIDEA database
+ciphertext and the October14 OpenBao snapshot. It did not reveal a matching
+bootstrap/identity-file bundle in the inspected folders. Scaleway's inspected
+`railiance-platform-pg-backup/platform-pg/` tree contains database backup trees
+and an application archive subtree, with no identity-file bundle in this bounded
+scope. Other accounts/buckets/prefixes are unassessed. See
+[the discovery summary](evidence/2026-10-15-bootstrap-recovery/offsite-discovery-summary.json).
+No object contents or age keys were read; the operator envelope exited0 and
+self-revoked. Existing scoped backup credentials were reused without changes;
+the Scaleway bootstrap API key was not used.
+
+The next human act is to run the existing custody helper in their own terminal
+with `--expected-recipient` set to the public recipient in the discovery summary,
+`--prompt --check-only`. Paste private material only into the hidden prompt.
+This verifies the independently held key against the backup owner's declared
+recipient and removes its temporary copy. It does not by itself prove that the
+selected backup decrypts or includes the full matching identity recovery set.
diff --git a/docs/evidence/2026-10-15-bootstrap-recovery/caller-source-inventory.json b/docs/evidence/2026-10-15-bootstrap-recovery/caller-source-inventory.json
index 636ce45..825a132 100644
--- a/docs/evidence/2026-10-15-bootstrap-recovery/caller-source-inventory.json
+++ b/docs/evidence/2026-10-15-bootstrap-recovery/caller-source-inventory.json
@@ -1,8 +1,8 @@
{
"schema": "netkingdom.bootstrap-caller-inventory.v1",
"observed_at": "2026-10-15T20:54:09.856047+00:00",
- "scope": "all current .sh/.py non-test files under sso-mfa/bootstrap and sso-mfa/k8s, Makefile, console, delivery copier and recovery transport tool",
- "entry_count": 87,
+ "scope": "all current .sh/.py non-test files under sso-mfa/bootstrap and sso-mfa/k8s, Makefile, console, delivery copier and recovery transport tool plus two bounded provider metadata inventory helpers",
+ "entry_count": 89,
"entries": [
{
"path": "Makefile",
@@ -424,6 +424,16 @@
"sha256": "7efe14c0d71f2c846434d19f8d05702d69d3c40a617a700b08fe13b3764d0650",
"classification": "diagnostic; structural observations do not prove recovery or revoke credentials"
},
+ {
+ "path": "tools/bootstrap-recovery/inventory-scaleway.py",
+ "sha256": "a219df205708fa4ed44b763a418585d63c820c25434bc0fd14e805eb5c2f79d3",
+ "classification": "read-only bounded owner backup metadata; existing caller/custody only; no age key or backup content read"
+ },
+ {
+ "path": "tools/bootstrap-recovery/inventory-storage-share.py",
+ "sha256": "fe56454e5ca740395e6d6a2a0f8185abdac1d8ca6d0eedb31273db409dee4954",
+ "classification": "read-only bounded owner backup metadata; existing caller/custody only; no age key or backup content read"
+ },
{
"path": "tools/bootstrap-recovery/verify-bundle.py",
"sha256": "885b05a2011e711228e42e316bda70bbe0d97d481383d61aa8469d0f10452370",
diff --git a/docs/evidence/2026-10-15-bootstrap-recovery/offsite-discovery-summary.json b/docs/evidence/2026-10-15-bootstrap-recovery/offsite-discovery-summary.json
new file mode 100644
index 0000000..3a4e8fd
--- /dev/null
+++ b/docs/evidence/2026-10-15-bootstrap-recovery/offsite-discovery-summary.json
@@ -0,0 +1,37 @@
+{
+ "observed_at": "2026-10-15T21:58:20.278564+00:00",
+ "operator_reports_holding_railiance_backup_age_key": true,
+ "private_key_received_or_read": false,
+ "candidate_key_match_verified": false,
+ "storage_share": {
+ "inventory": "storage-share-inventory-r1.json",
+ "status": "metadata_inventory_complete",
+ "attended_envelope_exit_code": 0,
+ "session_self_revocation_and_cleanup_confirmed": true,
+ "selected_candidate": "r01-net-kingdom-pg/net-kingdom-pg-privacyidea_db-20261015T023000Z.dump.age",
+ "candidate_bytes": 89129309,
+ "candidate_contents_downloaded": false,
+ "bootstrap_identity_file_bundle_found_in_inspected_scope": false
+ },
+ "scaleway": {
+ "inventory": "scaleway-inventory-r1.json",
+ "status": "metadata_inventory_complete",
+ "bucket": "railiance-platform-pg-backup",
+ "scope": "platform-pg/ and at most three child levels; WAL/base object trees and other buckets/prefixes not enumerated",
+ "bootstrap_identity_file_bundle_found_in_inspected_scope": false,
+ "bootstrap_api_key_used": false
+ },
+ "key_check": {
+ "source": "railiance-platform/lib/railiance-backup-common.sh",
+ "source_sha256": "09f349ae815339732db505d6896fb1ca384be7c66b40574750a3ba30d4d922e0",
+ "declared_default_public_recipient": "age1zvryunvjhvpkmasskauga2heeg0ztnte9ymgppvjge36ekumk50syr3tsz",
+ "operator_hidden_terminal_check_requested": true,
+ "scope": "A successful key match checks the declared public recipient, not actual ciphertext decryption or whole-stack recovery."
+ },
+ "source_tests": {
+ "passed": 13,
+ "command": "python3 -m pytest -q tests/test_scaleway_backup_inventory.py tests/test_storage_share_inventory.py"
+ },
+ "native_recovery_proven": false,
+ "production_mutations_performed": false
+}
diff --git a/docs/evidence/2026-10-15-bootstrap-recovery/readiness.json b/docs/evidence/2026-10-15-bootstrap-recovery/readiness.json
index fc5495b..0bca3fb 100644
--- a/docs/evidence/2026-10-15-bootstrap-recovery/readiness.json
+++ b/docs/evidence/2026-10-15-bootstrap-recovery/readiness.json
@@ -18,9 +18,16 @@
"verification": "source-verification.json",
"ceremony": "../../bootstrap-recovery-ceremony.md",
"outstanding": [
- "Exact off-host artifact/version/digest and complete matching data/key/config coverage",
+ "Matching off-host identity data/key/config set is not located in inspected provider scopes; a recent PI database ciphertext is identified, not downloaded/decrypted",
"Independent age key availability/custody confirmation",
"Owner admitted isolated recovery target/procedure and native service continuity/cleanup evidence",
"T02 current caller/provider source admission and exact legacy provider credential disposition; retain existing deferred-rotation boundary"
- ]
+ ],
+ "operator_reports_holding_railiance_backup_age_key": true,
+ "operator_report_recorded_at": "2026-10-15T21:52:22.549430+00:00",
+ "key_material_received": false,
+ "key_matches_selected_ciphertext_verified": false,
+ "offsite_discovery": "offsite-discovery-summary.json",
+ "privacyidea_database_ciphertext_identified": true,
+ "required_full_asset_coverage_confirmed": false
}
diff --git a/docs/evidence/2026-10-15-bootstrap-recovery/scaleway-inventory-r1.json b/docs/evidence/2026-10-15-bootstrap-recovery/scaleway-inventory-r1.json
new file mode 100644
index 0000000..7f89f4e
--- /dev/null
+++ b/docs/evidence/2026-10-15-bootstrap-recovery/scaleway-inventory-r1.json
@@ -0,0 +1,93 @@
+{
+ "schema": "netkingdom.scaleway-backup-inventory.v1",
+ "status": "metadata_inventory_complete",
+ "started_at": "2026-10-15T21:54:28.925951+00:00",
+ "read_only": true,
+ "provider": "Scaleway",
+ "bucket": "railiance-platform-pg-backup",
+ "prefix": "platform-pg/",
+ "source_sha256": "a219df205708fa4ed44b763a418585d63c820c25434bc0fd14e805eb5c2f79d3",
+ "bootstrap_api_key_used": false,
+ "age_keys_read": false,
+ "object_contents_downloaded": false,
+ "secret_values_exported": false,
+ "scope": "Bounded prefix metadata, at most three child levels; WAL/base trees not enumerated; other buckets/prefixes unassessed",
+ "prefixes": {
+ "platform-pg/": {
+ "directories": [
+ "platform-pg/application-archives/",
+ "platform-pg/apps-pg/",
+ "platform-pg/forgejo-db/",
+ "platform-pg/platform-pg-2/",
+ "platform-pg/platform-pg/"
+ ],
+ "files": []
+ },
+ "platform-pg/application-archives/": {
+ "directories": [
+ "platform-pg/application-archives/email-connect/"
+ ],
+ "files": []
+ },
+ "platform-pg/apps-pg/": {
+ "directories": [
+ "platform-pg/apps-pg/apps-pg/"
+ ],
+ "files": []
+ },
+ "platform-pg/forgejo-db/": {
+ "directories": [
+ "platform-pg/forgejo-db/forgejo-db/"
+ ],
+ "files": []
+ },
+ "platform-pg/platform-pg-2/": {
+ "directories": [
+ "platform-pg/platform-pg-2/platform-pg-2/"
+ ],
+ "files": []
+ },
+ "platform-pg/platform-pg/": {
+ "directories": [
+ "platform-pg/platform-pg/base/",
+ "platform-pg/platform-pg/wals/"
+ ],
+ "files": []
+ },
+ "platform-pg/application-archives/email-connect/": {
+ "directories": [
+ "platform-pg/application-archives/email-connect/2026/"
+ ],
+ "files": []
+ },
+ "platform-pg/apps-pg/apps-pg/": {
+ "directories": [
+ "platform-pg/apps-pg/apps-pg/base/",
+ "platform-pg/apps-pg/apps-pg/wals/"
+ ],
+ "files": []
+ },
+ "platform-pg/forgejo-db/forgejo-db/": {
+ "directories": [
+ "platform-pg/forgejo-db/forgejo-db/base/",
+ "platform-pg/forgejo-db/forgejo-db/wals/"
+ ],
+ "files": []
+ },
+ "platform-pg/platform-pg-2/platform-pg-2/": {
+ "directories": [
+ "platform-pg/platform-pg-2/platform-pg-2/base/",
+ "platform-pg/platform-pg-2/platform-pg-2/wals/"
+ ],
+ "files": []
+ },
+ "platform-pg/application-archives/email-connect/2026/": {
+ "directories": [
+ "platform-pg/application-archives/email-connect/2026/10/"
+ ],
+ "files": []
+ }
+ },
+ "runtime_delivery_uid": "30c4f6fc-8eb7-4cdc-b692-0f74c4a5b3ce",
+ "finished_at": "2026-10-15T21:54:39.109262+00:00"
+}
diff --git a/docs/evidence/2026-10-15-bootstrap-recovery/storage-share-inventory-r1.json b/docs/evidence/2026-10-15-bootstrap-recovery/storage-share-inventory-r1.json
new file mode 100644
index 0000000..7600a34
--- /dev/null
+++ b/docs/evidence/2026-10-15-bootstrap-recovery/storage-share-inventory-r1.json
@@ -0,0 +1,562 @@
+{
+ "schema": "netkingdom.storage-share-inventory.v1",
+ "status": "metadata_inventory_complete",
+ "started_at": "2026-10-15T21:52:20.452797+00:00",
+ "read_only": true,
+ "age_keys_read": false,
+ "backup_contents_downloaded": false,
+ "secret_values_exported": false,
+ "source_sha256": "fe56454e5ca740395e6d6a2a0f8185abdac1d8ca6d0eedb31273db409dee4954",
+ "owner_sources": [
+ {
+ "path": "/home/worsch/railiance-platform/scripts/state_hub_preflight_lane.py",
+ "sha256": "8bf63beda028de7c2375084afb7c22fb00a731c54ebf0b3ba369a699d920e0f5"
+ },
+ {
+ "path": "/home/worsch/railiance-platform/scripts/migrate_nextcloud_backup_account.py",
+ "sha256": "d3285f403f3c947f75c3288d989152e59ab9a8f5542bcf901bd0a06b0f3e050c"
+ }
+ ],
+ "root": [
+ {
+ "name": "core-apps-pg",
+ "directory": true,
+ "bytes": null
+ },
+ {
+ "name": "core-gitea-db",
+ "directory": true,
+ "bytes": null
+ },
+ {
+ "name": "core-net-kingdom-pg",
+ "directory": true,
+ "bytes": null
+ },
+ {
+ "name": "core-state-hub-db",
+ "directory": true,
+ "bytes": null
+ },
+ {
+ "name": "forgejo",
+ "directory": true,
+ "bytes": null
+ },
+ {
+ "name": "openbao",
+ "directory": true,
+ "bytes": null
+ },
+ {
+ "name": "r01-forgejo-db",
+ "directory": true,
+ "bytes": null
+ },
+ {
+ "name": "r01-net-kingdom-pg",
+ "directory": true,
+ "bytes": null
+ },
+ {
+ "name": "r01-platform-pg",
+ "directory": true,
+ "bytes": null
+ },
+ {
+ "name": "r01-state-hub-db",
+ "directory": true,
+ "bytes": null
+ },
+ {
+ "name": "r01-user-engine-pg",
+ "directory": true,
+ "bytes": null
+ }
+ ],
+ "folders": {
+ "core-apps-pg": [],
+ "core-gitea-db": [],
+ "core-net-kingdom-pg": [],
+ "core-state-hub-db": [],
+ "openbao": [
+ {
+ "name": "openbao-raft-snapshot-RPF-WP-0015-T03-REBOOT-20261014-01-20261014T181053Z.snap.age",
+ "directory": false,
+ "bytes": 375618
+ }
+ ],
+ "r01-forgejo-db": [
+ {
+ "name": "forgejo-db-forgejo-20260906T023000Z.dump.age",
+ "directory": false,
+ "bytes": 7515593
+ },
+ {
+ "name": "forgejo-db-forgejo-20260907T023000Z.dump.age",
+ "directory": false,
+ "bytes": 7320543
+ },
+ {
+ "name": "forgejo-db-forgejo-20260908T023000Z.dump.age",
+ "directory": false,
+ "bytes": 7413040
+ },
+ {
+ "name": "forgejo-db-forgejo-20260909T023000Z.dump.age",
+ "directory": false,
+ "bytes": 7663338
+ },
+ {
+ "name": "forgejo-db-forgejo-20260910T023000Z.dump.age",
+ "directory": false,
+ "bytes": 8079045
+ },
+ {
+ "name": "forgejo-db-forgejo-20260911T023000Z.dump.age",
+ "directory": false,
+ "bytes": 8404583
+ },
+ {
+ "name": "forgejo-db-forgejo-20260912T023000Z.dump.age",
+ "directory": false,
+ "bytes": 8631188
+ },
+ {
+ "name": "forgejo-db-forgejo-20260913T023000Z.dump.age",
+ "directory": false,
+ "bytes": 8472109
+ },
+ {
+ "name": "forgejo-db-forgejo-20260914T023000Z.dump.age",
+ "directory": false,
+ "bytes": 8523610
+ },
+ {
+ "name": "forgejo-db-forgejo-20261005T023000Z.dump.age",
+ "directory": false,
+ "bytes": 11745885
+ },
+ {
+ "name": "forgejo-db-forgejo-20261006T023000Z.dump.age",
+ "directory": false,
+ "bytes": 11481436
+ },
+ {
+ "name": "forgejo-db-forgejo-20261007T023000Z.dump.age",
+ "directory": false,
+ "bytes": 11693800
+ },
+ {
+ "name": "forgejo-db-forgejo-20261008T023000Z.dump.age",
+ "directory": false,
+ "bytes": 11784032
+ },
+ {
+ "name": "forgejo-db-forgejo-20261009T023003Z.dump.age",
+ "directory": false,
+ "bytes": 12210534
+ },
+ {
+ "name": "forgejo-db-forgejo-20261010T023000Z.dump.age",
+ "directory": false,
+ "bytes": 12378726
+ },
+ {
+ "name": "forgejo-db-forgejo-20261011T023000Z.dump.age",
+ "directory": false,
+ "bytes": 12369466
+ },
+ {
+ "name": "forgejo-db-forgejo-20261012T023000Z.dump.age",
+ "directory": false,
+ "bytes": 12701177
+ },
+ {
+ "name": "forgejo-db-forgejo-20261013T023000Z.dump.age",
+ "directory": false,
+ "bytes": 12761857
+ },
+ {
+ "name": "forgejo-db-forgejo-20261014T023000Z.dump.age",
+ "directory": false,
+ "bytes": 13110850
+ },
+ {
+ "name": "forgejo-db-forgejo-20261015T023000Z.dump.age",
+ "directory": false,
+ "bytes": 13349242
+ },
+ {
+ "name": "forgejo-db-forgejo-weekly-20260906T023000Z.dump.age",
+ "directory": false,
+ "bytes": 7515593
+ },
+ {
+ "name": "forgejo-db-forgejo-weekly-20260913T023000Z.dump.age",
+ "directory": false,
+ "bytes": 8472109
+ },
+ {
+ "name": "forgejo-db-forgejo-weekly-20261011T023000Z.dump.age",
+ "directory": false,
+ "bytes": 12369466
+ }
+ ],
+ "r01-net-kingdom-pg": [
+ {
+ "name": "net-kingdom-pg-interhub-20260906T023000Z.dump.age",
+ "directory": false,
+ "bytes": 215541
+ },
+ {
+ "name": "net-kingdom-pg-interhub-20260907T023000Z.dump.age",
+ "directory": false,
+ "bytes": 215541
+ },
+ {
+ "name": "net-kingdom-pg-interhub-20260908T023000Z.dump.age",
+ "directory": false,
+ "bytes": 215541
+ },
+ {
+ "name": "net-kingdom-pg-interhub-20260909T023000Z.dump.age",
+ "directory": false,
+ "bytes": 215541
+ },
+ {
+ "name": "net-kingdom-pg-interhub-20260910T023000Z.dump.age",
+ "directory": false,
+ "bytes": 215541
+ },
+ {
+ "name": "net-kingdom-pg-interhub-20260911T023000Z.dump.age",
+ "directory": false,
+ "bytes": 215541
+ },
+ {
+ "name": "net-kingdom-pg-interhub-20260912T023000Z.dump.age",
+ "directory": false,
+ "bytes": 215541
+ },
+ {
+ "name": "net-kingdom-pg-interhub-20260913T023000Z.dump.age",
+ "directory": false,
+ "bytes": 215541
+ },
+ {
+ "name": "net-kingdom-pg-interhub-20260914T023000Z.dump.age",
+ "directory": false,
+ "bytes": 215541
+ },
+ {
+ "name": "net-kingdom-pg-interhub-20261005T023000Z.dump.age",
+ "directory": false,
+ "bytes": 215541
+ },
+ {
+ "name": "net-kingdom-pg-interhub-20261006T023000Z.dump.age",
+ "directory": false,
+ "bytes": 215541
+ },
+ {
+ "name": "net-kingdom-pg-interhub-20261007T023000Z.dump.age",
+ "directory": false,
+ "bytes": 215541
+ },
+ {
+ "name": "net-kingdom-pg-interhub-20261008T023000Z.dump.age",
+ "directory": false,
+ "bytes": 215541
+ },
+ {
+ "name": "net-kingdom-pg-interhub-20261009T023003Z.dump.age",
+ "directory": false,
+ "bytes": 215541
+ },
+ {
+ "name": "net-kingdom-pg-interhub-20261010T023000Z.dump.age",
+ "directory": false,
+ "bytes": 215541
+ },
+ {
+ "name": "net-kingdom-pg-interhub-20261011T023000Z.dump.age",
+ "directory": false,
+ "bytes": 215541
+ },
+ {
+ "name": "net-kingdom-pg-interhub-20261012T023000Z.dump.age",
+ "directory": false,
+ "bytes": 215541
+ },
+ {
+ "name": "net-kingdom-pg-interhub-20261013T023000Z.dump.age",
+ "directory": false,
+ "bytes": 215541
+ },
+ {
+ "name": "net-kingdom-pg-interhub-20261014T023000Z.dump.age",
+ "directory": false,
+ "bytes": 215541
+ },
+ {
+ "name": "net-kingdom-pg-interhub-20261015T023000Z.dump.age",
+ "directory": false,
+ "bytes": 215541
+ },
+ {
+ "name": "net-kingdom-pg-interhub-weekly-20260906T023000Z.dump.age",
+ "directory": false,
+ "bytes": 215541
+ },
+ {
+ "name": "net-kingdom-pg-interhub-weekly-20260913T023000Z.dump.age",
+ "directory": false,
+ "bytes": 215541
+ },
+ {
+ "name": "net-kingdom-pg-interhub-weekly-20261011T023000Z.dump.age",
+ "directory": false,
+ "bytes": 215541
+ },
+ {
+ "name": "net-kingdom-pg-privacyidea_db-20260906T023000Z.dump.age",
+ "directory": false,
+ "bytes": 271941
+ },
+ {
+ "name": "net-kingdom-pg-privacyidea_db-20260907T023000Z.dump.age",
+ "directory": false,
+ "bytes": 272271
+ },
+ {
+ "name": "net-kingdom-pg-privacyidea_db-20260908T023000Z.dump.age",
+ "directory": false,
+ "bytes": 272599
+ },
+ {
+ "name": "net-kingdom-pg-privacyidea_db-20260909T023000Z.dump.age",
+ "directory": false,
+ "bytes": 274660
+ },
+ {
+ "name": "net-kingdom-pg-privacyidea_db-20260910T023000Z.dump.age",
+ "directory": false,
+ "bytes": 274660
+ },
+ {
+ "name": "net-kingdom-pg-privacyidea_db-20260911T023000Z.dump.age",
+ "directory": false,
+ "bytes": 274996
+ },
+ {
+ "name": "net-kingdom-pg-privacyidea_db-20260912T023000Z.dump.age",
+ "directory": false,
+ "bytes": 276353
+ },
+ {
+ "name": "net-kingdom-pg-privacyidea_db-20260913T023000Z.dump.age",
+ "directory": false,
+ "bytes": 277468
+ },
+ {
+ "name": "net-kingdom-pg-privacyidea_db-20260914T023000Z.dump.age",
+ "directory": false,
+ "bytes": 1008342
+ },
+ {
+ "name": "net-kingdom-pg-privacyidea_db-20261005T023000Z.dump.age",
+ "directory": false,
+ "bytes": 58987550
+ },
+ {
+ "name": "net-kingdom-pg-privacyidea_db-20261006T023000Z.dump.age",
+ "directory": false,
+ "bytes": 61979707
+ },
+ {
+ "name": "net-kingdom-pg-privacyidea_db-20261007T023000Z.dump.age",
+ "directory": false,
+ "bytes": 64976206
+ },
+ {
+ "name": "net-kingdom-pg-privacyidea_db-20261008T023000Z.dump.age",
+ "directory": false,
+ "bytes": 68001818
+ },
+ {
+ "name": "net-kingdom-pg-privacyidea_db-20261009T023003Z.dump.age",
+ "directory": false,
+ "bytes": 71016444
+ },
+ {
+ "name": "net-kingdom-pg-privacyidea_db-20261010T023000Z.dump.age",
+ "directory": false,
+ "bytes": 74006618
+ },
+ {
+ "name": "net-kingdom-pg-privacyidea_db-20261011T023000Z.dump.age",
+ "directory": false,
+ "bytes": 77020445
+ },
+ {
+ "name": "net-kingdom-pg-privacyidea_db-20261012T023000Z.dump.age",
+ "directory": false,
+ "bytes": 80037505
+ },
+ {
+ "name": "net-kingdom-pg-privacyidea_db-20261013T023000Z.dump.age",
+ "directory": false,
+ "bytes": 83066939
+ },
+ {
+ "name": "net-kingdom-pg-privacyidea_db-20261014T023000Z.dump.age",
+ "directory": false,
+ "bytes": 86103429
+ },
+ {
+ "name": "net-kingdom-pg-privacyidea_db-20261015T023000Z.dump.age",
+ "directory": false,
+ "bytes": 89129309
+ },
+ {
+ "name": "net-kingdom-pg-privacyidea_db-weekly-20260906T023000Z.dump.age",
+ "directory": false,
+ "bytes": 271941
+ },
+ {
+ "name": "net-kingdom-pg-privacyidea_db-weekly-20260913T023000Z.dump.age",
+ "directory": false,
+ "bytes": 277468
+ },
+ {
+ "name": "net-kingdom-pg-privacyidea_db-weekly-20261011T023000Z.dump.age",
+ "directory": false,
+ "bytes": 77020445
+ }
+ ],
+ "r01-platform-pg": [],
+ "r01-state-hub-db": [
+ {
+ "name": "state-hub-db-state_hub-20260906T023000Z.dump.age",
+ "directory": false,
+ "bytes": 6924847
+ },
+ {
+ "name": "state-hub-db-state_hub-20260907T023000Z.dump.age",
+ "directory": false,
+ "bytes": 7141118
+ },
+ {
+ "name": "state-hub-db-state_hub-20260908T023000Z.dump.age",
+ "directory": false,
+ "bytes": 7180575
+ },
+ {
+ "name": "state-hub-db-state_hub-20260909T023000Z.dump.age",
+ "directory": false,
+ "bytes": 7390311
+ },
+ {
+ "name": "state-hub-db-state_hub-20260910T023000Z.dump.age",
+ "directory": false,
+ "bytes": 7542258
+ },
+ {
+ "name": "state-hub-db-state_hub-20260911T023000Z.dump.age",
+ "directory": false,
+ "bytes": 7687798
+ },
+ {
+ "name": "state-hub-db-state_hub-20260912T023000Z.dump.age",
+ "directory": false,
+ "bytes": 7748393
+ },
+ {
+ "name": "state-hub-db-state_hub-20260913T023000Z.dump.age",
+ "directory": false,
+ "bytes": 7765176
+ },
+ {
+ "name": "state-hub-db-state_hub-20260914T023000Z.dump.age",
+ "directory": false,
+ "bytes": 7839953
+ },
+ {
+ "name": "state-hub-db-state_hub-20261005T023000Z.dump.age",
+ "directory": false,
+ "bytes": 9407746
+ },
+ {
+ "name": "state-hub-db-state_hub-20261006T023000Z.dump.age",
+ "directory": false,
+ "bytes": 9464763
+ },
+ {
+ "name": "state-hub-db-state_hub-20261007T023000Z.dump.age",
+ "directory": false,
+ "bytes": 9549640
+ },
+ {
+ "name": "state-hub-db-state_hub-20261008T023000Z.dump.age",
+ "directory": false,
+ "bytes": 9602097
+ },
+ {
+ "name": "state-hub-db-state_hub-20261009T023003Z.dump.age",
+ "directory": false,
+ "bytes": 9693941
+ },
+ {
+ "name": "state-hub-db-state_hub-20261010T023000Z.dump.age",
+ "directory": false,
+ "bytes": 9766236
+ },
+ {
+ "name": "state-hub-db-state_hub-20261011T023000Z.dump.age",
+ "directory": false,
+ "bytes": 9864346
+ },
+ {
+ "name": "state-hub-db-state_hub-20261012T023000Z.dump.age",
+ "directory": false,
+ "bytes": 9973489
+ },
+ {
+ "name": "state-hub-db-state_hub-20261013T023000Z.dump.age",
+ "directory": false,
+ "bytes": 10036580
+ },
+ {
+ "name": "state-hub-db-state_hub-20261014T023000Z.dump.age",
+ "directory": false,
+ "bytes": 10265536
+ },
+ {
+ "name": "state-hub-db-state_hub-20261015T023000Z.dump.age",
+ "directory": false,
+ "bytes": 10388491
+ },
+ {
+ "name": "state-hub-db-state_hub-weekly-20260906T023000Z.dump.age",
+ "directory": false,
+ "bytes": 6924847
+ },
+ {
+ "name": "state-hub-db-state_hub-weekly-20260913T023000Z.dump.age",
+ "directory": false,
+ "bytes": 7765176
+ },
+ {
+ "name": "state-hub-db-state_hub-weekly-20261011T023000Z.dump.age",
+ "directory": false,
+ "bytes": 9864346
+ }
+ ],
+ "r01-user-engine-pg": []
+ },
+ "caller_auth_path": "auth/netkingdom/oidc/callback",
+ "caller_entity_id": "4a1f663c-0621-0483-edd0-f852274f4b6b",
+ "scope": "Backup/railiance-backups root and bounded identity/database subfolders; other accounts/folders unassessed",
+ "finished_at": "2026-10-15T21:52:28.155972+00:00"
+}
diff --git a/tests/test_scaleway_backup_inventory.py b/tests/test_scaleway_backup_inventory.py
new file mode 100644
index 0000000..15e2c1a
--- /dev/null
+++ b/tests/test_scaleway_backup_inventory.py
@@ -0,0 +1,37 @@
+"""Scoped S3 list-only fixtures; no credentials, live cluster or cloud."""
+import importlib.util
+from pathlib import Path
+import pytest
+
+ROOT=Path(__file__).resolve().parents[1]
+spec=importlib.util.spec_from_file_location('s3_inventory',ROOT/'tools/bootstrap-recovery/inventory-scaleway.py')
+module=importlib.util.module_from_spec(spec);spec.loader.exec_module(module)
+
+
+class Client:
+ def __init__(self,pages):self.pages=iter(pages);self.calls=[]
+ def list_objects_v2(self,**kw):self.calls.append(kw);return next(self.pages)
+
+
+def test_scoped_list_metadata_follows_continuation_without_reading_contents():
+ client=Client([{'IsTruncated':True,'NextContinuationToken':'next'},
+ {'Contents':[{'Key':'platform-pg/identity/backup.tar.age','Size':256}]}])
+ r=module.list_prefix(client,'platform-pg/identity/')
+ assert r['files']==[{'key':'platform-pg/identity/backup.tar.age','bytes':256}]
+ assert all(x['Bucket']==module.BUCKET and x['Prefix']=='platform-pg/identity/' for x in client.calls)
+ assert client.calls[1]['ContinuationToken']=='next'
+
+
+def test_wal_and_base_presence_is_recorded_without_traversal():
+ client=Client([{'CommonPrefixes':[{'Prefix':'platform-pg/base/'},{'Prefix':'platform-pg/wals/'}]}])
+ r=module.inventory(client)
+ assert len(client.calls)==1 and len(r['platform-pg/']['directories'])==2
+
+
+@pytest.mark.parametrize('key',['elsewhere/backup.age','platform-pg/../backup.age','platform-pg/private\nmarker.age'])
+def test_foreign_or_unsafe_metadata_refuses(key):
+ with pytest.raises(module.Refused):module.list_prefix(Client([{'Contents':[{'Key':key,'Size':1}]}]),module.PREFIX)
+
+
+def test_missing_pagination_state_refuses_incomplete_inventory():
+ with pytest.raises(module.Refused):module.list_prefix(Client([{'IsTruncated':True}]),module.PREFIX)
diff --git a/tests/test_storage_share_inventory.py b/tests/test_storage_share_inventory.py
new file mode 100644
index 0000000..7cd0367
--- /dev/null
+++ b/tests/test_storage_share_inventory.py
@@ -0,0 +1,43 @@
+"""Read-only DAV metadata parser tests; no provider or credential access."""
+import importlib.util
+from pathlib import Path
+import pytest
+
+ROOT=Path(__file__).resolve().parents[1]
+spec=importlib.util.spec_from_file_location('storage_inventory',ROOT/'tools/bootstrap-recovery/inventory-storage-share.py')
+module=importlib.util.module_from_spec(spec);spec.loader.exec_module(module)
+
+
+def body(path, kind='file', size='16', origin=''):
+ collection='' if kind=='dir' else ''
+ return f'''{origin}{path}
+ HTTP/1.1 200 OK{collection}
+ {size}'''.encode()
+
+
+def test_only_closed_safe_metadata_is_returned():
+ raw=body(module.BASE+'bootstrap.tar.age')
+ assert module.parse_listing(raw,'')==[{'name':'bootstrap.tar.age','directory':False,'bytes':16}]
+
+
+@pytest.mark.parametrize('path,origin',[(module.BASE+'%2e%2e/else.age',''),
+ ('/other/account/backup.age',''),(module.BASE+'backup.age','https://unapproved.invalid')])
+def test_foreign_and_escaping_references_refuse(path,origin):
+ with pytest.raises(module.Refused):module.parse_listing(body(path,origin=origin),'')
+
+
+def test_non_numeric_size_and_oversized_response_refuse():
+ with pytest.raises(module.Refused):module.parse_listing(body(module.BASE+'backup.age',size='not-a-size'),'')
+ with pytest.raises(module.Refused):module.parse_listing(b'X'*(module.LIMIT+1),'')
+
+
+def test_unrecognized_plaintext_files_are_not_exported():
+ assert module.parse_listing(body(module.BASE+'private-password.txt'),'')==[]
+
+
+def test_unrelated_authenticated_role_cannot_read_retrieval_credential(monkeypatch):
+ calls=[]
+ monkeypatch.setattr(module,'bao',lambda args:calls.append(args) or object())
+ monkeypatch.setattr(module,'data',lambda result:{'data':{'policies':['default'],'path':'auth/netkingdom/oidc/callback'}})
+ with pytest.raises(module.Refused):module.inventory()
+ assert calls==[['token','lookup','-format=json']]
diff --git a/tools/bootstrap-recovery/README.md b/tools/bootstrap-recovery/README.md
index dc4a010..0f86602 100644
--- a/tools/bootstrap-recovery/README.md
+++ b/tools/bootstrap-recovery/README.md
@@ -21,3 +21,17 @@ archive fixtures, never production credentials:
```bash
python3 -m pytest -q tests/test_bootstrap_recovery_transport.py
```
+
+
+`inventory-storage-share.py` is a silent read-only child of the existing
+attended Platform Backup-account procedure. It checks the caller's OIDC/admin
+scope, reuses the owner custody accessor, and exports only bounded DAV names and
+sizes. Warden revokes and cleans up the session. It never reads an age key or
+object content and contains provider errors/redirects/proxies.
+
+`inventory-scaleway.py` lists bounded metadata in the fixed backup bucket/prefix
+using the existing scoped runtime delivery and pinned primary cluster. It uses
+neither the bootstrap API key nor object GET/write/delete operations. Run through
+the already admitted caller with cached boto3 1.40.18; it emits only its safe
+receipt. Both inventories explicitly state their bounds and cannot prove asset
+absence outside them, ciphertext integrity, decryption or native recovery.
diff --git a/tools/bootstrap-recovery/inventory-scaleway.py b/tools/bootstrap-recovery/inventory-scaleway.py
new file mode 100644
index 0000000..4f64cee
--- /dev/null
+++ b/tools/bootstrap-recovery/inventory-scaleway.py
@@ -0,0 +1,104 @@
+#!/usr/bin/env python3
+"""Read-only bounded S3 backup metadata through existing scoped runtime delivery.
+
+No bootstrap API key, new grant, object content read, age key, provider write or
+retention action. The caller must already have admitted cluster read authority.
+"""
+import argparse
+import base64
+from datetime import datetime, timezone
+import hashlib
+import json
+import os
+from pathlib import Path
+import re
+import subprocess
+
+BUCKET='railiance-platform-pg-backup'
+ENDPOINT='https://s3.nl-ams.scw.cloud'
+PREFIX='platform-pg/'
+CLUSTER='a553c742-0115-43d4-99a4-a5ca56fe0786'
+
+
+class Refused(Exception):pass
+
+
+def kube(args):
+ p=subprocess.run(['kubectl',*args,'-o','json'],stdout=subprocess.PIPE,stderr=subprocess.DEVNULL,timeout=30)
+ if p.returncode:raise Refused()
+ return json.loads(p.stdout)
+
+
+def list_prefix(client,prefix):
+ if not prefix.startswith(PREFIX) or not re.fullmatch(r'[A-Za-z0-9._/-]+',prefix) or '..' in prefix.split('/'):raise Refused()
+ directories=[];files=[];continuation=None
+ for _ in range(20):
+ args={'Bucket':BUCKET,'Prefix':prefix,'Delimiter':'/','MaxKeys':1000}
+ if continuation:args['ContinuationToken']=continuation
+ page=client.list_objects_v2(**args)
+ for row in page.get('CommonPrefixes',[]):
+ value=row['Prefix']
+ if not value.startswith(prefix) or not value.endswith('/') or not re.fullmatch(r'[A-Za-z0-9._/-]+',value) or '..' in value.split('/'):raise Refused()
+ directories.append(value)
+ for row in page.get('Contents',[]):
+ name=row['Key']
+ if not name.startswith(prefix) or not re.fullmatch(r'[A-Za-z0-9._/-]+',name) or '..' in name.split('/'):raise Refused()
+ if name.endswith(('.age','.json','.yaml')):
+ size=row['Size']
+ if type(size) is not int or size<0:raise Refused()
+ files.append({'key':name,'bytes':size})
+ if not page.get('IsTruncated'):break
+ continuation=page.get('NextContinuationToken')
+ if not continuation:raise Refused()
+ else:raise Refused()
+ return {'directories':sorted(set(directories)),'files':sorted(files,key=lambda x:x['key'])}
+
+
+def inventory(client):
+ pending=[(PREFIX,0)];result={}
+ while pending:
+ prefix,depth=pending.pop(0)
+ if prefix in result:raise Refused()
+ if len(result)>=64:raise Refused()
+ row=list_prefix(client,prefix);result[prefix]=row
+ if depth<3:
+ for child in row['directories']:
+ # Native database WAL/base trees are not application/key-file
+ # archives. Record their presence without enumerating every WAL.
+ if any(x in child.split('/') for x in ['wals','base']):continue
+ pending.append((child,depth+1))
+ return result
+
+
+def main():
+ p=argparse.ArgumentParser(description=__doc__);p.add_argument('--receipt',required=True)
+ a=p.parse_args();fd=os.open(a.receipt,os.O_WRONLY|os.O_CREAT|os.O_EXCL|os.O_NOFOLLOW,0o600)
+ r={'schema':'netkingdom.scaleway-backup-inventory.v1','status':'failed',
+ 'started_at':datetime.now(timezone.utc).isoformat(),'read_only':True,
+ 'provider':'Scaleway','bucket':BUCKET,'prefix':PREFIX,'source_sha256':hashlib.sha256(Path(__file__).read_bytes()).hexdigest(),
+ 'bootstrap_api_key_used':False,'age_keys_read':False,'object_contents_downloaded':False,
+ 'secret_values_exported':False,'scope':'Bounded prefix metadata, at most three child levels; WAL/base trees not enumerated; other buckets/prefixes unassessed'}
+ try:
+ if kube(['get','namespace','kube-system'])['metadata']['uid']!=CLUSTER:raise Refused()
+ delivered=kube(['-n','databases','get','secret','platform-pg-backup-s3'])
+ if delivered['metadata']['namespace']!='databases' or delivered['metadata']['name']!='platform-pg-backup-s3':raise Refused()
+ values=delivered['data']
+ # Same governed runtime delivery used by Platform's archive verifier;
+ # explicit credentials avoid SDK ambient/profile/metadata discovery.
+ import boto3
+ from botocore.config import Config
+ client=boto3.client('s3',endpoint_url=ENDPOINT,region_name='nl-ams',
+ aws_access_key_id=base64.b64decode(values['ACCESS_KEY_ID'],validate=True).decode(),
+ aws_secret_access_key=base64.b64decode(values['ACCESS_SECRET_KEY'],validate=True).decode(),
+ config=Config(signature_version='s3v4',proxies={},connect_timeout=15,read_timeout=30,
+ retries={'max_attempts':1},s3={'addressing_style':'path'}))
+ r['prefixes']=inventory(client);r['status']='metadata_inventory_complete'
+ r['runtime_delivery_uid']=delivered['metadata']['uid']
+ except Exception:r['error']='inventory_failed_diagnostics_suppressed'
+ finally:
+ r['finished_at']=datetime.now(timezone.utc).isoformat()
+ with os.fdopen(fd,'w') as out:json.dump(r,out,indent=2);out.write('\n')
+ return int(r['status']=='failed')
+
+
+if __name__=='__main__':raise SystemExit(main())
diff --git a/tools/bootstrap-recovery/inventory-storage-share.py b/tools/bootstrap-recovery/inventory-storage-share.py
new file mode 100644
index 0000000..adde0df
--- /dev/null
+++ b/tools/bootstrap-recovery/inventory-storage-share.py
@@ -0,0 +1,109 @@
+#!/usr/bin/env python3
+"""Silent read-only inventory in the existing Platform Backup-account procedure.
+
+Runs only inside the attended platform-admin envelope. Reuses Platform's owner
+custody accessor and fixed provider/account contract; no age key is read and no
+backup is downloaded, uploaded, deleted, rotated or granted.
+"""
+import argparse
+import base64
+from datetime import datetime, timezone
+import hashlib
+import json
+import os
+from pathlib import Path
+import re
+import sys
+import urllib.parse
+import urllib.request
+import xml.etree.ElementTree as ET
+
+ROOT=Path(__file__).resolve().parents[2]
+OWNER=ROOT.parent/'railiance-platform/scripts'
+sys.path.insert(0,str(OWNER))
+from state_hub_preflight_lane import bao, data
+from migrate_nextcloud_backup_account import HOST, OPERATOR_PATH
+BASE='/remote.php/dav/files/Backup/railiance-backups/'
+LIMIT=4*1024*1024
+NAME=re.compile(r'[A-Za-z0-9._-]{1,160}\Z')
+
+
+class Refused(Exception):pass
+
+
+class NoRedirect(urllib.request.HTTPRedirectHandler):
+ def redirect_request(self,*args,**kwargs):raise Refused()
+
+
+def parse_listing(raw, relative):
+ if len(raw)>LIMIT:raise Refused()
+ rows=[]
+ for item in ET.fromstring(raw).findall('{DAV:}response'):
+ href=urllib.parse.urlsplit(item.findtext('{DAV:}href') or '')
+ if href.netloc and href.netloc!=urllib.parse.urlsplit(HOST).netloc:raise Refused()
+ path=urllib.parse.unquote(href.path)
+ if not path.startswith(BASE+relative):raise Refused()
+ name=path[len(BASE+relative):].rstrip('/')
+ if not name:continue
+ if not NAME.fullmatch(name) or name in {'.','..'}:raise Refused()
+ for row in item.findall('{DAV:}propstat'):
+ if ' 200 ' not in (row.findtext('{DAV:}status') or ''):continue
+ prop=row.find('{DAV:}prop')
+ if prop is None:continue
+ folder=prop.find('{DAV:}resourcetype/{DAV:}collection') is not None
+ size=prop.findtext('{DAV:}getcontentlength')
+ if size is not None and not size.isdigit():raise Refused()
+ # Export only known-safe operational names/size, not arbitrary DAV
+ # properties, URLs, account metadata or raw provider responses.
+ if folder or name.endswith(('.age','.json','.yaml')):
+ rows.append({'name':name,'directory':folder,'bytes':int(size) if size else None})
+ if len(rows)>10000 or len({r['name'] for r in rows})!=len(rows):raise Refused()
+ return sorted(rows,key=lambda x:x['name'])
+
+
+def listing(relative,credentials):
+ if relative and (not relative.endswith('/') or not NAME.fullmatch(relative[:-1])):raise Refused()
+ auth=base64.b64encode((credentials['BACKUP_USERNAME']+':'+credentials['BACKUP_PASSWORD']).encode()).decode()
+ req=urllib.request.Request(HOST+BASE+relative,
+ data=b'',
+ method='PROPFIND',headers={'Authorization':'Basic '+auth,'Depth':'1','Content-Type':'application/xml'})
+ opener=urllib.request.build_opener(urllib.request.ProxyHandler({}),NoRedirect())
+ with opener.open(req,timeout=30) as response:
+ if response.status!=207 or response.url!=HOST+BASE+relative:raise Refused()
+ raw=response.read(LIMIT+1)
+ return parse_listing(raw,relative)
+
+
+def inventory():
+ # Lookup payload contains a token: retain only safe caller/role facts.
+ identity=data(bao(['token','lookup','-format=json']))['data']
+ if 'platform-admin' not in identity.get('policies',[]) or 'root' in identity.get('policies',[]):raise Refused()
+ if not str(identity.get('path','')).startswith('auth/netkingdom/'):raise Refused()
+ credentials=data(bao(['read','-format=json',OPERATOR_PATH]))['data']['data']
+ if credentials.get('BACKUP_USERNAME')!='Backup' or not isinstance(credentials.get('BACKUP_PASSWORD'),str) or not credentials['BACKUP_PASSWORD']:raise Refused()
+ root=listing('',credentials)
+ folders={}
+ for row in root:
+ if row['directory'] and re.match(r'^(?:r01-|core-|identity|sso|bootstrap|net-kingdom|openbao)',row['name']):
+ if len(folders)>=32:raise Refused()
+ folders[row['name']]=listing(row['name']+'/',credentials)
+ return {'root':root,'folders':folders,'caller_auth_path':identity['path'],
+ 'caller_entity_id':identity.get('entity_id'),'scope':'Backup/railiance-backups root and bounded identity/database subfolders; other accounts/folders unassessed'}
+
+
+def main():
+ p=argparse.ArgumentParser(description=__doc__);p.add_argument('--receipt',required=True)
+ a=p.parse_args();fd=os.open(a.receipt,os.O_WRONLY|os.O_CREAT|os.O_EXCL|os.O_NOFOLLOW,0o600)
+ r={'schema':'netkingdom.storage-share-inventory.v1','status':'failed','started_at':datetime.now(timezone.utc).isoformat(),
+ 'read_only':True,'age_keys_read':False,'backup_contents_downloaded':False,'secret_values_exported':False,
+ 'source_sha256':hashlib.sha256(Path(__file__).read_bytes()).hexdigest(),
+ 'owner_sources':[{'path':str(OWNER/name),'sha256':hashlib.sha256((OWNER/name).read_bytes()).hexdigest()} for name in ['state_hub_preflight_lane.py','migrate_nextcloud_backup_account.py']]}
+ try:r.update(inventory());r['status']='metadata_inventory_complete'
+ except Exception:r['error']='inventory_failed_diagnostics_suppressed'
+ finally:
+ r['finished_at']=datetime.now(timezone.utc).isoformat()
+ with os.fdopen(fd,'w') as out:json.dump(r,out,indent=2);out.write('\n')
+ return int(r['status']=='failed')
+
+
+if __name__=='__main__':raise SystemExit(main())
diff --git a/workplans/NK-WP-0049-bootstrap-secret-free-recovery-handoff.md b/workplans/NK-WP-0049-bootstrap-secret-free-recovery-handoff.md
index 7b3903f..46e8df4 100644
--- a/workplans/NK-WP-0049-bootstrap-secret-free-recovery-handoff.md
+++ b/workplans/NK-WP-0049-bootstrap-secret-free-recovery-handoff.md
@@ -438,6 +438,27 @@ reviewed isolated target/procedure and actual owner/native acceptance. Evidence:
`docs/evidence/2026-10-15-bootstrap-recovery/`; full verification results are
recorded there. No workplan completion is asserted.
+2026-10-15 off-host discovery follow-through: operator reports holding the
+`railiance-backup` age key independently; no private key was received/read.
+The read-only Storage Share inventory completed through the existing attended
+Platform Backup-account custody procedure; Warden exited 0, confirming session
+self-revocation/cleanup. The inspected account has a current privacyIDEA database
+ciphertext (`r01-net-kingdom-pg/net-kingdom-pg-privacyidea_db-20261015T023000Z.dump.age`,
+89,129,309 bytes) and the October14 OpenBao snapshot. The inspected folders do
+not contain a bootstrap/identity-file bundle. Bounded Scaleway metadata inventory
+under the existing scoped backup delivery also completed; it found database
+base/WAL trees and an application archive subtree, with no identity-file bundle
+in the inspected scope. No bootstrap API key, age key or object content was read
+by these inventories; other accounts/buckets/prefixes remain unassessed.
+
+Thirteen fixture checks pass for contained caller scope, metadata/path bounds,
+pagination and malformed/foreign responses. Requested the human's hidden-terminal
+key check against the backup owner's declared public recipient. Matching that
+recipient remains distinct from actual decryption and full recovery; the
+bootstrap `keys/age.pub` is not assumed to be the same recipient. T02/T03 remain
+open. Evidence: `offsite-discovery-summary.json` and the two exact inventory
+receipts in the existing October15 evidence folder.
+
## Verify owner-approved recovery and current native callers
```task
@@ -445,7 +466,7 @@ id: NK-WP-0049-T03
status: wait
priority: high
depends_on: [NK-WP-0049-T02]
-blocking_reason: "Exact off-host artifact/coverage and independently held matching key remain unconfirmed after Scaleway/Storage Share destination identification. Requires completed T02 owner/runtime and legacy-credential disposition, then the exact reviewed isolated recovery ceremony and native consumer acceptance. No live bootstrap/root retrieval or deferred exposure rotation is authorized by source tests."
+blocking_reason: "A recent off-host PI database ciphertext is identified, but matching full identity data/key/config coverage and actual independent-key decryption remain unverified after bounded Storage Share/Scaleway inventory. Requires completed T02 owner/runtime and legacy-credential disposition, then the exact reviewed isolated recovery ceremony and native consumer acceptance. No live bootstrap/root retrieval or deferred exposure rotation is authorized by source tests."
state_hub_task_id: "d68cd4d3-1ba3-530e-88f9-26ebb8dd6eac"
```