diff --git a/Makefile b/Makefile index cc7e265..18b2496 100644 --- a/Makefile +++ b/Makefile @@ -451,6 +451,9 @@ argocd-repo-apply: ## Apply a SOPS-encrypted ArgoCD repository Secret (set ARGOC (echo "ERROR: set ARGOCD_REPOSITORY_SECRET=argocd/repositories/.repository.sops.yaml"; exit 1) sops -d $(ARGOCD_REPOSITORY_SECRET) | $(KUBECTL) apply -f - +argocd-pin-drift: ## Read-only: compare railiance01 child Application pins in Git with live (run before any root sync) + python3 scripts/argocd_child_pin_drift.py + argocd-status: ## Show Railiance ArgoCD projects, root app, and registered repos $(KUBECTL) get appprojects.argoproj.io -n $(ARGOCD_NAMESPACE) \ railiance-bootstrap railiance-tenants railiance-platform-addons diff --git a/WORK-RECORDS.md b/WORK-RECORDS.md index dd329b6..c6dde0a 100644 --- a/WORK-RECORDS.md +++ b/WORK-RECORDS.md @@ -10,6 +10,7 @@ | --- | --- | --- | --- | --- | | workplan | RPF-WP-ADHOC-2026-10-10 | finished | — | workplans/ADHOC-2026-10-10.md | | workplan | RPF-WP-ADHOC-2026-10-11 | finished | — | workplans/ADHOC-2026-10-11.md | +| workplan | RPF-WP-ADHOC-2026-10-19 | active | — | workplans/ADHOC-2026-10-19.md | | workplan | RPF-WP-0015 | active | — | workplans/RPF-WP-0015-audit-core-custody-and-recovery-coordination.md | | workplan | RPF-WP-0025 | finished | — | workplans/RPF-WP-0025-openbao-operator-only-access.md | | workplan | RPF-WP-0027 | blocked | — | workplans/RPF-WP-0027-keycape-live-secret-exposure-recovery.md | @@ -44,6 +45,9 @@ | task | RPF-WP-ADHOC-2026-10-11-T01 | done | — | workplans/ADHOC-2026-10-11.md | | task | RPF-WP-ADHOC-2026-10-11-T02 | done | — | workplans/ADHOC-2026-10-11.md | | task | RPF-WP-ADHOC-2026-10-11-T03 | done | — | workplans/ADHOC-2026-10-11.md | +| task | RPF-WP-ADHOC-2026-10-19-T01 | done | — | workplans/ADHOC-2026-10-19.md | +| task | RPF-WP-ADHOC-2026-10-19-T02 | done | — | workplans/ADHOC-2026-10-19.md | +| task | RPF-WP-ADHOC-2026-10-19-T03 | wait | — | workplans/ADHOC-2026-10-19.md | | task | RPF-WP-0015-T01 | done | — | workplans/RPF-WP-0015-audit-core-custody-and-recovery-coordination.md | | task | RPF-WP-0015-T02 | done | — | workplans/RPF-WP-0015-audit-core-custody-and-recovery-coordination.md | | task | RPF-WP-0015-T03 | wait | — | workplans/RPF-WP-0015-audit-core-custody-and-recovery-coordination.md | diff --git a/argocd/railiance01/applications/README.md b/argocd/railiance01/applications/README.md index 903e353..87be28a 100644 --- a/argocd/railiance01/applications/README.md +++ b/argocd/railiance01/applications/README.md @@ -12,3 +12,8 @@ with the founder's go-ahead: no `automated` block, no finalizer, and a pinned `targetRevision`. A merge here plus a hand sync of the root is a live production change on railiance01. Drafts wait in `../drafts/`, which no root syncs. + +A release that changes a child's `targetRevision` commits that pin here in the +same change window, whether or not the live Application was patched directly. +Before any hand sync of the root, run `make argocd-pin-drift`; any `DRIFT` +line means the sync would roll that child back, so stop and reconcile first. diff --git a/argocd/railiance01/applications/activity-core.application.yaml b/argocd/railiance01/applications/activity-core.application.yaml index 9ae85cb..c65ffb8 100644 --- a/argocd/railiance01/applications/activity-core.application.yaml +++ b/argocd/railiance01/applications/activity-core.application.yaml @@ -1,4 +1,7 @@ # ACTIVITY-WP-0041 / RPF-WP-0048: founder-authorized adoption, 2026-09-27. +# Pin = live revision of the 2026-10-19 founder-approved release +# (activity-core docs/evidence/2026-10-19-grant-v2-lease-expiry-release.json). +# Every activity-core release commits its pin here; see RPF-WP-ADHOC-2026-10-19. apiVersion: argoproj.io/v1alpha1 kind: Application metadata: @@ -10,7 +13,7 @@ spec: project: activity-core source: repoURL: https://forgejo.coulomb.social/coulomb/activity-core.git - targetRevision: ce9a8f8edf4ec162bae3aa087803bd57a1b33a66 + targetRevision: 8884b123c54877bec44fb8355792244a8353a8ff path: k8s/gitops destination: server: https://kubernetes.default.svc diff --git a/scripts/argocd_child_pin_drift.py b/scripts/argocd_child_pin_drift.py new file mode 100644 index 0000000..dc10ab2 --- /dev/null +++ b/scripts/argocd_child_pin_drift.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python3 +"""Read-only drift check between child Application pins in Git and on railiance01. + +`railiance-apps-root` syncs by hand from `argocd/railiance01/applications`. If a +release patches a child's live targetRevision without committing the same pin +here, the next root sync silently rolls that child back. Run this before any +root sync and after every release. Exit 1 on drift, 2 on a read failure. +""" +from __future__ import annotations +import json +from pathlib import Path +import subprocess +import sys + +import yaml + +APPS_DIR = Path(__file__).resolve().parent.parent / "argocd/railiance01/applications" + + +def declared() -> dict[str, str]: + pins = {} + for path in sorted(APPS_DIR.glob("*.y*ml")): + for doc in yaml.safe_load_all(path.read_text()): + if doc and doc.get("kind") == "Application": + pins[doc["metadata"]["name"]] = doc["spec"]["source"]["targetRevision"] + return pins + + +def live() -> dict[str, str]: + out = subprocess.run( + ["kubectl", "-n", "argocd", "get", "applications.argoproj.io", "-o", "json"], + capture_output=True, text=True, check=True, + ).stdout + return { + item["metadata"]["name"]: item["spec"].get("source", {}).get("targetRevision", "") + for item in json.loads(out)["items"] + } + + +def main() -> int: + try: + running = live() + except (subprocess.CalledProcessError, FileNotFoundError) as exc: + print(f"ERROR: cannot read live Applications: {exc}", file=sys.stderr) + return 2 + drift = 0 + for name, pin in declared().items(): + current = running.get(name) + if current == pin: + print(f"ok {name} {pin[:12]}") + else: + drift += 1 + print(f"DRIFT {name} git={pin[:12]} live={(current or 'absent')[:12]}") + if drift: + print(f"{drift} child pin(s) differ: commit the live pin here (or revert live) " + "before syncing railiance-apps-root.", file=sys.stderr) + return 1 if drift else 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/workplans/ADHOC-2026-10-19.md b/workplans/ADHOC-2026-10-19.md new file mode 100644 index 0000000..cdc8af9 --- /dev/null +++ b/workplans/ADHOC-2026-10-19.md @@ -0,0 +1,60 @@ +--- +id: RPF-WP-ADHOC-2026-10-19 +type: workplan +title: "Reconcile railiance01 child Application pins with live and guard root syncs" +domain: financials +repo: railiance-platform +status: active +flavor: implementation +owner: claude-code +topic_slug: railiance +created: "2026-10-19" +updated: "2026-10-19" +--- + +## Align the activity-core pin with the founder-approved live release + +```task +id: RPF-WP-ADHOC-2026-10-19-T01 +status: done +priority: high +``` + +The activity-core releases on 2026-10-04 (`a86a143`) and 2026-10-19 (`8884b12`) +patched the live child Application without committing its pin here. Git still +pinned `ce9a8f8` (2026-09-28), and `railiance-apps-root` was OutOfSync, so the +next hand root sync would have rolled activity-core back by two releases. The +pin is now `8884b123c54877bec44fb8355792244a8353a8ff`, which equals live. This +is a Git-only change: live state is already at this revision, and no sync was +run. + +## Add a read-only pin drift check before root syncs + +```task +id: RPF-WP-ADHOC-2026-10-19-T02 +status: done +priority: medium +``` + +`make argocd-pin-drift` (`scripts/argocd_child_pin_drift.py`) compares every +child Application's `targetRevision` in `argocd/railiance01/applications` with +live, and exits 1 on drift. The applications README now requires it before any +hand sync of the root. activity-core `docs/gitops-release.md` now requires +releases to commit their pin here and pass this check. + +## informed-decision pin drift (owner confirmation) + +```task +id: RPF-WP-ADHOC-2026-10-19-T03 +status: wait +needs_human: true +priority: medium +``` + +Live informed-decision runs `312ab97e876e…` ("Prepare owner-reviewed zlib-only +native release decision", 2026-10-11). Git pins `9207a513` (2026-10-05). Live is +a descendant of the pin, so this is likely the same skipped-pin pattern. It is +not changed here, because this session cannot verify that `312ab97` was an +admitted release. Once the informed-decision owner confirms it, commit +that pin. Until then, do not hand sync `railiance-apps-root`, because the sync +would revert informed-decision.