From 64ae5946d95c33f4186031940cd4dce3a2031bd6 Mon Sep 17 00:00:00 2001 From: codex Date: Fri, 9 Oct 2026 12:27:43 +0200 Subject: [PATCH] kv_duplicate_retire: block only on data-path grants The founder dry-run refused on mount-wide platform/metadata/* grants held by ops-mason-survey and platform-readonly. Those cannot read the value, so they are now recorded as metadata_only_policies and no longer block retirement. Co-Authored-By: Claude Opus 5.5 Assistant: claude-code Assistant-Model: opus Assistant-Process: 469992@bnt-lap001 Assistant-Session: e4b8e420-df0a-441d-83ae-7f19130594d9 --- scripts/kv_duplicate_retire.py | 17 +++++++++++++---- tests/test_kv_duplicate_retire.py | 6 ++++++ 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/scripts/kv_duplicate_retire.py b/scripts/kv_duplicate_retire.py index c5748c6..1dd56cd 100644 --- a/scripts/kv_duplicate_retire.py +++ b/scripts/kv_duplicate_retire.py @@ -10,7 +10,10 @@ no source references it. Before the metadata delete this process proves: * the canonical entry exists with a live current version; * the duplicate exists with exactly the expected version count; * no live ACL policy other than platform-admin grants a non-deny capability on - the duplicate's data or metadata path (exact, ``+`` and trailing ``*`` globs). + the duplicate's data path (exact, ``+`` and trailing ``*`` globs). Grants on + the metadata path only (e.g. mount-wide ``platform/metadata/*`` survey and + read-only inventory policies) cannot read the value, so they are recorded in + the receipt as ``metadata_only_policies`` but do not block. No value is read. Run from a railiance-platform checkout: @@ -103,18 +106,24 @@ def run(args, receipt): if duplicate.get('current_version') != EXPECTED_VERSIONS or receipt['duplicate_versions'] != EXPECTED_VERSIONS: raise Exit(2, 'duplicate_version_unexpected') - paths = ['platform/data/' + DUPLICATE, 'platform/metadata/' + DUPLICATE] - granting = {} + data_path, metadata_path = 'platform/data/' + DUPLICATE, 'platform/metadata/' + DUPLICATE + granting, metadata_only = {}, {} for name in policy_names(): if name in EXEMPT_POLICIES: continue result = bao(['policy', 'read', name]) if result.returncode != 0: raise Exit(2, 'policy_read_failed') - hits = granting_stanzas(result.stdout.decode(), paths) + text = result.stdout.decode() + hits = granting_stanzas(text, [data_path]) if hits: granting[name] = hits + else: + meta_hits = granting_stanzas(text, [metadata_path]) + if meta_hits: + metadata_only[name] = meta_hits receipt['granting_policies'] = granting + receipt['metadata_only_policies'] = metadata_only if granting: raise Exit(3, 'live_policy_grants_duplicate') if args.dry_run: diff --git a/tests/test_kv_duplicate_retire.py b/tests/test_kv_duplicate_retire.py index 270a642..85416ab 100644 --- a/tests/test_kv_duplicate_retire.py +++ b/tests/test_kv_duplicate_retire.py @@ -101,6 +101,12 @@ class Retire(unittest.TestCase): self.assertEqual(list(receipt['granting_policies']), ['coding-agent']) self.assertIn(DUP, self.state['kv']) + def test_mount_wide_metadata_survey_does_not_block(self): + self.state['policies']['platform-readonly'] = 'path "platform/metadata/*" { capabilities = ["read", "list"] }' + code, receipt = self.run_helper('--dry-run') + self.assertEqual((code, receipt['outcome']), (0, 'dry_run_would_retire')) + self.assertEqual(receipt['metadata_only_policies'], {'platform-readonly': ['platform/metadata/*']}) + def test_unexpected_versions_or_missing_canonical_refuse(self): self.state['kv'][DUP] = 2 code, receipt = self.run_helper()