railiance-platform/scripts/fluid_telegram_custody_move.py
codex bed91ceaf2
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
RPF-WP-0035-T03: fluid-telegram operator matrix lane and custody helpers
- New CCR request type workload-kv-operator-matrix (scripts/operator_matrix_lane.py):
  exact per-entry create/read/update, OIDC role on the existing netkingdom mount,
  and exact agent denies appended to the live agent-high-risk-boundary (live
  content preserved, partial presence refused). credential-change.py now
  dispatches lane request types through one module table.
- CCR-2026-0039 (proposed): hall-of-helix four-entry matrix at tenant coulomb
  per the founder ruling of 2026-10-09; policy, role and overlay sources.
- scripts/fluid_telegram_custody_move.py: silent CAS-0 move of the four
  undocumented infotech entries, deleting sources only after all verify.
- scripts/kv_duplicate_retire.py: silent retirement of
  activity-core/default/llm-connect-provider-secrets once no live non-admin
  policy grants it.
- Founder session commands recorded in the T03 note.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 469992@bnt-lap001
Assistant-Session: e4b8e420-df0a-441d-83ae-7f19130594d9
2026-10-09 12:10:21 +02:00

192 lines
7.6 KiB
Python

#!/usr/bin/env python3
"""RPF-WP-0035-T03: attended, silent move of fluid-telegram custody to tenant coulomb.
Founder ruling 2026-10-09 (ops-mason message b72ff77d): the hall-of-helix entries
found under the undocumented infotech prefix move to the approved coulomb prefix
that CCR-2026-0039 grants. For each of bot-token, operator-app, operator-session
and redaction-salt this process reads the current version at
``platform/workloads/infotech/fluid-telegram/hall-of-helix/<entry>``, writes it to
``platform/workloads/coulomb/fluid-telegram/hall-of-helix/<entry>`` with CAS 0
and verifies the readback. Only after all four are verified, and no source
changed during the run, are the infotech entries metadata-deleted. Values only
travel through this process's memory and the ``bao`` child's stdin.
Run from a railiance-platform checkout at the reviewed commit:
BAO_ADDR=http://127.0.0.1:18200 VAULT_ADDR=http://127.0.0.1:18200 \\
warden access openbao-platform-admin-login --exec -- \\
python3 scripts/fluid_telegram_custody_move.py \\
--confirm 'MOVE fluid-telegram hall-of-helix to coulomb' \\
--receipt "$HOME/ft-custody-move-receipt.json"
``--dry-run`` touches only KV metadata. ``--keep-source`` copies and verifies
but does not delete. A rerun after a completed move reports ``already_moved``.
The process prints nothing (stdout/stderr go to /dev/null). The optional
receipt (mode 0600, refused if it exists) holds versions, booleans and an
outcome code only.
Exit codes:
0 moved (or already moved / already identical); dry-run: a move would run
1 unexpected internal error (contained)
2 precondition refused before any write (confirmation, envelope, identity,
an entry absent at both prefixes, absence not provable, receipt exists)
3 a coulomb entry exists and differs, or a current version is not live;
never overwritten
4 write or delete failed
5 verify failed: readback differs, or a source changed during the run
6 dry-run only: some coulomb entries already exist
"""
from __future__ import annotations
import argparse
import json
import os
from pathlib import Path
import sys
sys.path.insert(0, str(Path(__file__).resolve().parent))
from tenant_engine_audit_core_sender_seed import Exit, bao, live_version, read_json # noqa: E402
SOURCE = 'workloads/infotech/fluid-telegram/hall-of-helix'
TARGET = 'workloads/coulomb/fluid-telegram/hall-of-helix'
ENTRIES = ('bot-token', 'operator-app', 'operator-session', 'redaction-salt')
CONFIRM = 'MOVE fluid-telegram hall-of-helix to coulomb'
SCHEMA = 'platform.fluid-telegram-custody-move.v1'
def meta(prefix, entry):
return read_json(f'platform/metadata/{prefix}/{entry}', absent_ok=True)
def data(prefix, entry, code=2):
raw = read_json(f'platform/data/{prefix}/{entry}', absent_ok=False, code=code)
try:
return raw['data'], raw['metadata']['version']
except (KeyError, TypeError):
raise Exit(code, 'unparseable_read') from None
def run(args, receipt):
if args.confirm != CONFIRM:
raise Exit(2, 'exact_confirmation_required')
if (Path.home().parent.name != '.warden-attended-login'
or os.environ.get('BAO_TOKEN') or os.environ.get('VAULT_TOKEN')):
raise Exit(2, 'attended_warden_envelope_required')
ident = read_json('auth/token/lookup-self', absent_ok=False)
policies = ident.get('policies') or []
if 'platform-admin' not in policies or 'root' in policies:
raise Exit(2, 'attended_platform_admin_required')
rows = receipt['entries']
plan = []
for entry in ENTRIES:
src, dst = meta(SOURCE, entry), meta(TARGET, entry)
row = rows[entry] = {'source_present': src is not None, 'target_present': dst is not None}
if src is None and dst is None:
raise Exit(2, 'entry_absent_at_both_prefixes')
if src is not None:
if live_version(src) is None:
raise Exit(3, 'source_current_version_not_live')
row['source_version'] = src['current_version']
if dst is not None:
if live_version(dst) is None:
raise Exit(3, 'target_current_version_not_live')
row['target_version'] = dst['current_version']
plan.append((entry, src, dst))
if args.dry_run:
receipt['value_reads'] = 0
if any(dst is not None for _, src, dst in plan if src is not None):
raise Exit(6, 'dry_run_target_present')
return 'dry_run_would_move' if any(src for _, src, _ in plan) else 'dry_run_already_moved'
if all(src is None for _, src, _ in plan):
return 'already_moved'
# Phase 1: copy and verify every entry before anything is deleted.
for entry, src, dst in plan:
if src is None:
continue
value, version = data(SOURCE, entry)
if version != src['current_version']:
raise Exit(5, 'source_changed_during_run')
row = rows[entry]
if dst is not None:
current, _ = data(TARGET, entry, code=3)
if current != value:
raise Exit(3, 'target_differs')
row['outcome'] = 'already_identical'
continue
result = bao(['write', '-format=json', f'platform/data/{TARGET}/{entry}', '-'],
payload={'options': {'cas': 0}, 'data': value})
if result.returncode != 0:
raise Exit(4, 'write_failed')
back, back_version = data(TARGET, entry, code=5)
if back != value or back_version != 1:
raise Exit(5, 'readback_mismatch')
row['target_version'] = 1
row['outcome'] = 'copied'
for entry, src, _ in plan:
if src is None:
continue
again = meta(SOURCE, entry)
if again is None or again.get('current_version') != src['current_version']:
raise Exit(5, 'source_changed_during_run')
if args.keep_source:
return 'copied_source_kept'
# Phase 2: remove the undocumented infotech entries (all versions).
for entry, src, _ in plan:
if src is None:
continue
if bao(['delete', f'platform/metadata/{SOURCE}/{entry}']).returncode != 0:
raise Exit(4, 'source_delete_failed')
if meta(SOURCE, entry) is not None:
raise Exit(5, 'source_still_present')
rows[entry]['source_deleted'] = True
return 'moved'
def main(argv=None):
devnull = os.open(os.devnull, os.O_WRONLY)
os.dup2(devnull, 1)
os.dup2(devnull, 2)
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--confirm', default='')
parser.add_argument('--receipt', type=Path)
parser.add_argument('--dry-run', action='store_true')
parser.add_argument('--keep-source', action='store_true')
try:
args = parser.parse_args(argv)
except SystemExit:
return 2
receipt = {'schema': SCHEMA, 'mode': 'dry-run' if args.dry_run else 'move',
'source': 'platform/' + SOURCE, 'target': 'platform/' + TARGET,
'entries': {}, 'credential_values_emitted': False}
fd = None
if args.receipt is not None:
try:
fd = os.open(args.receipt, os.O_WRONLY | os.O_CREAT | os.O_EXCL | os.O_NOFOLLOW, 0o600)
except OSError:
return 2
try:
receipt['outcome'] = run(args, receipt)
code = 0
except Exit as exc:
receipt['outcome'] = exc.reason
code = exc.code
except Exception:
receipt['outcome'] = 'contained_internal_error'
code = 1
receipt['exit_code'] = code
if fd is not None:
os.write(fd, (json.dumps(receipt, indent=2, sort_keys=True) + '\n').encode())
os.close(fd)
return code
if __name__ == '__main__':
sys.exit(main())