import os, json, urllib.request URL = os.environ["REDMINE_URL"] KEY = os.environ["REDMINE_API_KEY"] i = json.load(urllib.request.urlopen(urllib.request.Request( URL + "/issues/497.json", headers={"X-Redmine-API-Key": KEY}), timeout=15))["issue"] desc = i["description"] start = desc.find("## Scale requirement") if start != -1: desc = desc[:start] desc += """## Scale + identity model (Charles, FINAL 2026-08-29 ~09:15) - **1:1 Linux account to Bitwarden account.** Each of the 9 Linux accounts has exactly ONE Bitwarden account (machine account). That single vault contains ALL credentials for the vertical: its Cloudron account details AND all third-party accounts (Google, Signal, Discord, IMAP, Linkwarden, ...) — potentially a dozen or more secrets per account, 100+ fleet-wide. - Bootstrap = exactly one 0600 seed env-file per Linux account provisioned by the deploy installer (install-account.sh); everything else resolves from that account's BW vault at runtime. One secret per Linux account ever touches disk, and only that one. - Ref namespace is instance-local: mpk- refs resolve against THAT account's BW projects/secrets; same ref names across accounts are a feature. - Batch + cache: startup warm = list projects + secrets once (bitwarden-go list support); memory-only TTL cache; refresh on miss. No per-ref cold calls in the dispatch path. - Fleet inventory: BW project/secret listing doubles as credential inventory for audits (never materializing values). - Acting identities (#517) remain Cloudron accounts / 3rd-party accounts; they are ENTRIES in the per-Linux-account vault, selected by ref. Acceptance additions: fixture test with 2 fake Linux accounts x 15 secrets each (mixed cloudron + 3rd-party); warm-cache latency documented; bootstrap-seed flow in deploy runbook. """ body = json.dumps({"issue": {"description": desc, "notes": "FINAL model: 1:1 Linux-to-Bitwarden; the single per-account vault holds cloudron + all third-party credentials for the vertical. Prior per-cloudron-BW framing superseded."}}).encode() req = urllib.request.Request(URL + "/issues/497.json", data=body, headers={"X-Redmine-API-Key": KEY, "Content-Type": "application/json"}, method="PUT") urllib.request.urlopen(req, timeout=15) print("497 final model set") # Align 517 wording i5 = json.load(urllib.request.urlopen(urllib.request.Request( URL + "/issues/517.json", headers={"X-Redmine-API-Key": KEY}), timeout=15))["issue"] body5 = json.dumps({"issue": {"notes": "Clarification per #497 final: credential STORAGE is one BW vault per Linux account (cloudron + 3rd-party creds as entries). Acting identity = which entry the action uses. The two levels remain: runtime (linux) + acting (cloudron/3rd-party account)."}}).encode() req5 = urllib.request.Request(URL + "/issues/517.json", data=body5, headers={"X-Redmine-API-Key": KEY, "Content-Type": "application/json"}, method="PUT") urllib.request.urlopen(req5, timeout=15) print("517 aligned")