diff --git a/log.md b/log.md index d7953bf..abc895d 100644 --- a/log.md +++ b/log.md @@ -85,3 +85,4 @@ 2026-08-29T06:03:06-05:00 | PMO | 3 turns verified+archived (quota fc518c4, deploy b7799ea, pdf 10e930e); Redmine 490/491/494/499 -> Done 100%; briefing RUNNING; dispatcher gated 2026-08-29T06:19:13-05:00 | PMO | 497 amended: per-account BW scale (100+ refs), one bootstrap seed per account, batch warm + memory cache 2026-08-29T06:19:29-05:00 | PMO | 497 corrected: BW machine accounts map to CLOUDRON accounts (per-vertical API identities), not raw Linux accounts +2026-08-29T06:20:17-05:00 | PMO | identity model ticket filed (linux runtime + cloudron acting, N per account); 497 corrected diff --git a/scripts/amend-517.py b/scripts/amend-517.py new file mode 100644 index 0000000..d4b2a3b --- /dev/null +++ b/scripts/amend-517.py @@ -0,0 +1,25 @@ +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/517.json", headers={"X-Redmine-API-Key": KEY}), timeout=15))["issue"] + +marker = "## Scope" +extra = """## Scope + +**Per-task, per-turn awareness (Charles, 2026-08-29 ~09:20 — hard requirement):** the acting Cloudron account is selected PER TASK and applies to every action of that task's turns: +- Task-level source: Redmine custom field (configurable name, e.g. "cloudron-account") with fallback to a per-instance default acting identity. +- Turn-level: the conductor resolves the acting identity at turn start; ALL outbound actions in that turn (Redmine writeback, Discourse posts, Gitea calls, integrations) authenticate as that identity via its keyproxy refs from the per-Linux-account BW vault (#497 final model). +- Attribution: REPORT header + loop JSONL record acting identity per turn; usage accounting (from #490) aggregates per acting identity AND per Linux account. + +Original scope follows: +""" +desc = i["description"].replace(marker, extra, 1) +body = json.dumps({"issue": {"description": desc, + "notes": "Hard requirement added: acting cloudron identity is chosen per task (Redmine custom field + fallback) and stamped on every action of its turns; attribution + usage roll up per acting identity."}}).encode() +req = urllib.request.Request(URL + "/issues/517.json", data=body, + headers={"X-Redmine-API-Key": KEY, "Content-Type": "application/json"}, method="PUT") +urllib.request.urlopen(req, timeout=15) +print("517 per-task/per-turn requirement recorded") diff --git a/scripts/finalize-497.py b/scripts/finalize-497.py new file mode 100644 index 0000000..effcbef --- /dev/null +++ b/scripts/finalize-497.py @@ -0,0 +1,40 @@ +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")