diff --git a/docs/state-hub-railiance01-cutover-phase4-2026-07-06.md b/docs/state-hub-railiance01-cutover-phase4-2026-07-06.md new file mode 100644 index 0000000..00a5c33 --- /dev/null +++ b/docs/state-hub-railiance01-cutover-phase4-2026-07-06.md @@ -0,0 +1,83 @@ +# T05 Phase 4 Evidence — Sweep Checkout Migration (2026-07-06) + +Workplan: `CUST-WP-0054-T05` +Parent runbook: `docs/state-hub-railiance01-cutover-plan.md` +`no_secret_material_recorded: true` + +## Checklist + +| Step | Action | Result | Evidence | +| --- | --- | --- | --- | +| 4.1 | Clone root `/home/tegwick/` | **pass** | 74 host_paths registered; 73 new clones + 4 retried | +| 4.2 | Bulk clone from git remotes | **pass** | `railiance01-bulk-clone-repos.sh` on railiance01 | +| 4.3 | Register `host_paths` | **pass** | host=`239.62.205.92.host.secureserver.net` × 74 | +| 4.4 | Sweep on railiance01 | **pass** | `POST /consistency/sweep/remote-all` exit_code=0 | +| 4.5 | Writeback on railiance01 | **pass** | `the-custodian` fix: `push: pushed` from `/home/tegwick/` | + +**Phase 4 status: complete.** + +## Infrastructure changes + +### state-hub image (`main-phase4-sweep`) + +Imported to railiance01 k3s (local build; promote via Forgejo CI next): + +- Dockerfile: `git`, `openssh-client` +- `STATE_HUB_SWEEP_HOSTNAME` env (Kubernetes pod hostname cannot contain dots) +- `api/classification.py`: railiance01 path fallback for allowed-values YAML +- `scripts/consistency_check.py`: `_sweep_hostname()` for `host_paths` resolution + +### Helm (`deploy/railiance/apps/helm/state-hub-values.yaml`) + +```yaml +sweep: + enabled: true + hostname: 239.62.205.92.host.secureserver.net + hostPath: /home/tegwick + sshHostPath: /home/tegwick/.ssh +``` + +Deployment mounts host clone tree + SSH config; `postStart` sets +`git config --global safe.directory '*'` (root in pod, tegwick-owned repos). + +### railiance01 host + +- SSH: `~/.ssh/config` with `forgejo-remote` + `gitea-remote` (workstation `id_gitea` copied) +- Clone tree: `/home/tegwick/` (77 entries incl. tooling dirs) +- Failed-then-fixed slugs: `ihp-railiance-probe`, `llm-connect`, `markitect-project` (from `markitect_project`), `vergabe_teilnahme` (from `vergabe-teilnahme`) + +## Tooling added (the-custodian) + +- `tools/railiance01-bulk-clone-repos.sh` +- `tools/railiance01-register-host-paths.sh` + +## Verification + +```bash +# Single-repo fix inside pod — railiance01 path, push succeeds +kubectl -n state-hub exec deploy/state-hub -- \ + python3 /app/scripts/consistency_check.py --repo the-custodian --fix \ + --api-base http://127.0.0.1:8000 +# → repo_path: /home/tegwick/the-custodian, push: pushed + +# Full sweep canary +curl -s -X POST http://127.0.0.1:8000/consistency/sweep/remote-all \ + -H "Content-Type: application/json" \ + -d '{"max_seconds":300,"source":"phase4-canary"}' +# → exit_code: 0, 8 repos processed (budget-limited first pass), skipped_missing: 0 +``` + +`.custodian-brief.md` on railiance01 (`17:08 UTC`) is newer than workstation (`15:27 UTC`). + +Workstation `host_paths[bnt-lap001]` retained for dev; production sweep resolves +`host_paths[239.62.205.92.host.secureserver.net]`. + +## Follow-ups (Phase 5) + +- Promote `main-phase4-sweep` image via Forgejo CI (replace hand-imported tag) +- Allow longer first-pass sweep budget or multiple cron cycles to reconcile all 74 repos +- activity-core scheduled sweeps should recover (were failing with exit_code=1 pre-Phase-4) + +## Next: Phase 5 + +48–72h stabilization before coulombcore hub teardown. \ No newline at end of file diff --git a/tools/railiance01-bulk-clone-repos.sh b/tools/railiance01-bulk-clone-repos.sh new file mode 100755 index 0000000..56fe350 --- /dev/null +++ b/tools/railiance01-bulk-clone-repos.sh @@ -0,0 +1,73 @@ +#!/usr/bin/env bash +# Bulk-clone State Hub registered repos onto railiance01 under /home/tegwick/. +# Usage: railiance01-bulk-clone-repos.sh [--dry-run] [--slug SLUG] +set -euo pipefail + +API_BASE="${API_BASE:-http://127.0.0.1:8000}" +CLONE_ROOT="${CLONE_ROOT:-/home/tegwick}" +DRY_RUN=0 + +if [[ "${1:-}" == "--dry-run" ]]; then + DRY_RUN=1 + shift +fi + +SLUG_FILTER="${1:-}" + +clone_url_for() { + local slug="$1" remote="$2" + case "${remote}" in + forgejo-remote:*|gitea-remote:*) echo "${remote}" ;; + ssh://git@92.205.130.254:30022/coulomb/*) echo "gitea-remote:coulomb/${slug}.git" ;; + http://92.205.130.254:32166/coulomb/*) echo "gitea-remote:coulomb/${slug}.git" ;; + http://gitea.local/worsch/*) echo "gitea-remote:coulomb/${slug}.git" ;; + "") echo "gitea-remote:coulomb/${slug}.git" ;; + *) echo "gitea-remote:coulomb/${slug}.git" ;; + esac +} + +repos_json=$(curl -fsS "${API_BASE}/repos/") +mapfile -t slugs < <(python3 -c " +import json, sys +repos = json.load(sys.stdin) +filt = sys.argv[1] if len(sys.argv) > 1 else '' +for r in sorted(repos, key=lambda x: x['slug']): + if filt and r['slug'] != filt: + continue + print(r['slug']) +" <<<"${repos_json}" "${SLUG_FILTER}") + +cloned=0 +skipped=0 +failed=0 + +for slug in "${slugs[@]}"; do + dest="${CLONE_ROOT}/${slug}" + if [[ -d "${dest}/.git" ]]; then + echo "SKIP ${slug} (exists)" + skipped=$((skipped + 1)) + continue + fi + remote=$(python3 -c " +import json, sys +repos = json.load(sys.stdin) +for r in repos: + if r['slug'] == sys.argv[1]: + print(r.get('remote_url') or '') + break +" <<<"${repos_json}" "${slug}") + url=$(clone_url_for "${slug}" "${remote}") + if [[ "${DRY_RUN}" -eq 1 ]]; then + echo "DRY ${slug} <- ${url}" + continue + fi + echo "CLONE ${slug} <- ${url}" + if git clone "${url}" "${dest}"; then + cloned=$((cloned + 1)) + else + echo "FAIL ${slug}" >&2 + failed=$((failed + 1)) + fi +done + +echo "done: cloned=${cloned} skipped=${skipped} failed=${failed}" \ No newline at end of file diff --git a/tools/railiance01-register-host-paths.sh b/tools/railiance01-register-host-paths.sh new file mode 100755 index 0000000..f016619 --- /dev/null +++ b/tools/railiance01-register-host-paths.sh @@ -0,0 +1,49 @@ +#!/usr/bin/env bash +# Register railiance01 host_paths for all State Hub repos. +# Usage: railiance01-register-host-paths.sh [--dry-run] +set -euo pipefail + +API_BASE="${API_BASE:-http://127.0.0.1:8000}" +HOST="${RAILIANCE_HOST:-239.62.205.92.host.secureserver.net}" +CLONE_ROOT="${CLONE_ROOT:-/home/tegwick}" +DRY_RUN=0 + +if [[ "${1:-}" == "--dry-run" ]]; then + DRY_RUN=1 +fi + +repos=$(curl -fsS "${API_BASE}/repos/") +registered=0 +skipped=0 + +while IFS= read -r slug; do + path="${CLONE_ROOT}/${slug}" + current=$(python3 -c " +import json, sys +repos = json.load(sys.stdin) +for r in repos: + if r['slug'] == sys.argv[1]: + print((r.get('host_paths') or {}).get(sys.argv[2], '')) + break +" <<<"${repos}" "${slug}" "${HOST}") + if [[ "${current}" == "${path}" ]]; then + echo "OK ${slug}" + skipped=$((skipped + 1)) + continue + fi + if [[ "${DRY_RUN}" -eq 1 ]]; then + echo "DRY ${slug} host_paths[${HOST}]=${path} (was: ${current:-})" + continue + fi + curl -fsS -X POST "${API_BASE}/repos/${slug}/paths" \ + -H "Content-Type: application/json" \ + -d "{\"host\": \"${HOST}\", \"path\": \"${path}\"}" >/dev/null + echo "POST ${slug} -> ${path}" + registered=$((registered + 1)) +done < <(python3 -c " +import json, sys +for r in sorted(json.load(sys.stdin), key=lambda x: x['slug']): + print(r['slug']) +" <<<"${repos}") + +echo "done: registered=${registered} unchanged=${skipped}" \ No newline at end of file diff --git a/workplans/CUST-WP-0054-workstation-independence-and-fleet-realignment.md b/workplans/CUST-WP-0054-workstation-independence-and-fleet-realignment.md index ed49878..9fa12eb 100644 --- a/workplans/CUST-WP-0054-workstation-independence-and-fleet-realignment.md +++ b/workplans/CUST-WP-0054-workstation-independence-and-fleet-realignment.md @@ -226,7 +226,11 @@ baseline counts recorded — `docs/state-hub-railiance01-cutover-preflight-2026- **Phase 3 complete** (2026-07-06): access rewired — `state-hub-primary` → railiance01 `10.43.68.154`; activity-core in-cluster URL; `fleet-state-hub-coulombcore` stopped — `docs/state-hub-railiance01-cutover-phase3-2026-07-06.md`. coulombcore -writer still frozen (rollback). Phase 4 sweep paths next. +writer still frozen (rollback). +**Phase 4 complete** (2026-07-06): 74 repos cloned under `/home/tegwick/`, +`host_paths[239.62.205.92.host.secureserver.net]` registered, state-hub pod mounts +clone tree + sweep hostname override, remote-all sweep exit_code=0 — +`docs/state-hub-railiance01-cutover-phase4-2026-07-06.md`. Phase 5 stabilization next. ## Task: Working-memory and sink path decoupling