diff --git a/docs/evidence/2026-10-21-eso-adoption-prep/prep-receipt.json b/docs/evidence/2026-10-21-eso-adoption-prep/prep-receipt.json new file mode 100644 index 0000000..be92c29 --- /dev/null +++ b/docs/evidence/2026-10-21-eso-adoption-prep/prep-receipt.json @@ -0,0 +1,67 @@ +{ + "schema": "railiance-platform.eso-adoption-prep/v1", + "task": "RPF-WP-0044-T06", + "captured_at": "2026-10-21", + "approval_ref": "89f1058f-33bc-47a8-8a49-0b44d426c67e", + "readiness_gate": { + "receipt": "readiness-r1.json", + "source_ready": true, + "missing_or_invalid": [ + "RPF-WP-0044-T04", + "RPF-WP-0044-T05" + ], + "T04_reason": "docs/evidence/2026-10-20-issue-core-verify-live.json lacks authenticated_ingest_status=201 and authenticated_close_status=200 (verify-live ran without --smoke) and is older than the 300 s freshness window", + "T05_reason": "gate requires a fresh (<=300 s) argocd-workload-adoption-proof/v1 receipt; the 2026-10-11 receipt is stale. A fresh read-only run (scripts/workload_adoption_proof.py --live) failed because ssh railiance01 (user tegwick) can no longer read /etc/rancher/k3s/k3s.yaml after the CUST-WP-0073 cutover", + "smoke_not_run": "verify-live --smoke creates and closes a Forgejo issue; not read-only, so not run under the preparation-only boundary" + }, + "offline_render": { + "chart": "external-secrets 0.16.1", + "values_source": "argocd/railiance01/drafts/external-secrets.application.yaml", + "render_sha256": "55236645afc9f2d9d376c28e73aed70bd31727d0e6f166664b3ebd568d5f136f", + "objects": 39, + "kinds": { + "ServiceAccount": 3, + "Secret": 1, + "CustomResourceDefinition": 20, + "ClusterRole": 5, + "ClusterRoleBinding": 2, + "Role": 1, + "RoleBinding": 1, + "Service": 1, + "Deployment": 3, + "ValidatingWebhookConfiguration": 2 + }, + "crds": 20, + "crds_prune_delete_protected": 20, + "store_or_externalsecret_objects_rendered": [], + "kinds_with_cabundle": [ + "CustomResourceDefinition/clustergenerators.generators.external-secrets.io", + "CustomResourceDefinition/clustersecretstores.external-secrets.io", + "CustomResourceDefinition/secretstores.external-secrets.io", + "CustomResourceDefinition/vaultdynamicsecrets.generators.external-secrets.io", + "CustomResourceDefinition/webhooks.generators.external-secrets.io" + ], + "credential_kind_objects_rendered": [ + "external-secrets-webhook" + ], + "notes": [ + "20/20 CRDs carry argocd.argoproj.io/sync-options Prune=false,Delete=false", + "no ClusterSecretStore/SecretStore/ExternalSecret objects rendered, so adoption cannot delete or replace any store or ExternalSecret", + "the one rendered credential-kind object is the empty webhook certificate holder (metadata only, no data key); server-side apply does not take ownership of the cert-controller-injected data", + "ValidatingWebhookConfigurations declare no caBundle and no CRD declares a conversion webhook (caBundle hits are schema text); no ignoreDifferences added because no diff has shown drift yet" + ] + }, + "draft_policy": { + "automated": false, + "finalizers": false, + "ServerSideApply": true, + "Force_or_Replace": false, + "prune_in_sync_command": false + }, + "not_done_live": { + "fresh_native_client_side_diff": "blocked: no agent kube read path on railiance01 (tegwick kubeconfig removed; custodian-agent observer role has no CRD/RBAC/webhook/ESO read)", + "store_baseline": "blocked: same reason; expected ValidationFailed: openbao-activity-core, openbao-email-connect" + }, + "native_mutation": false, + "secret_values_observed_by_agent": false +} diff --git a/docs/evidence/2026-10-21-eso-adoption-prep/readiness-r1.json b/docs/evidence/2026-10-21-eso-adoption-prep/readiness-r1.json new file mode 100644 index 0000000..b6a4789 --- /dev/null +++ b/docs/evidence/2026-10-21-eso-adoption-prep/readiness-r1.json @@ -0,0 +1 @@ +{"approval_ref": "89f1058f-33bc-47a8-8a49-0b44d426c67e", "checks": {"source": {"chart_pin": "0.16.1", "crd_prune_delete_protected": true, "manual_sync_only": true, "server_side_apply": true}}, "founder_approval_required": false, "live_or_credential_calls": false, "missing_or_invalid": ["RPF-WP-0044-T04", "RPF-WP-0044-T05"], "native_mutation": false, "next_action": "wait_for_predecessor_automated_proof", "ready_for_fresh_native_render_diff": false, "schema": "eso-adoption-readiness/v1", "source_ready": true, "task": "RPF-WP-0044-T06"} diff --git a/docs/runbooks/rpf-wp-0044-t06-eso-adoption.md b/docs/runbooks/rpf-wp-0044-t06-eso-adoption.md new file mode 100644 index 0000000..13c4441 --- /dev/null +++ b/docs/runbooks/rpf-wp-0044-t06-eso-adoption.md @@ -0,0 +1,93 @@ +# RPF-WP-0044-T06 — adopt external-secrets into Argo (founder runbook) + +Authority: D2 (`89f1058f-33bc-47a8-8a49-0b44d426c67e`). No further founder approval is needed. +Highest-risk adoption: ESO serves every ExternalSecret on railiance01. Prep receipt: +`docs/evidence/2026-10-21-eso-adoption-prep/prep-receipt.json`. + +## State at preparation (2026-10-21, agent, offline only) + +- Draft `argocd/railiance01/drafts/external-secrets.application.yaml`: chart `0.16.1`, + `ServerSideApply=true`, no `automated`, no finalizer, no Force/Replace, and CRD values + `crds.annotations: argocd.argoproj.io/sync-options: Prune=false,Delete=false`. The readiness gate + passes the source check. +- Offline render (render sha256 `55236645…f136f`): 39 objects, made up of 20 CRDs (all 20 + prune/delete protected), 3 Deployments, 3 ServiceAccounts, 5 ClusterRoles, 2 ClusterRoleBindings, + 1 Role, 1 RoleBinding, 1 Service, 2 ValidatingWebhookConfigurations and the empty webhook-cert + holder (metadata only). **No ClusterSecretStore or ExternalSecret is rendered**, so adoption cannot + delete or replace a store. The webhooks declare no `caBundle`, so server-side apply does not take + it from the cert-controller. `ignoreDifferences` is added only if the diff below shows drift. +- **Not done by the agent:** the fresh live diff, the store baseline and the fresh predecessor proofs. + Since the CUST-WP-0073 cutover (2026-10-20) the agent's `ssh railiance01` login (`tegwick`) cannot + read the k3s kubeconfig, and the custodian-agent observer role has no CRD, RBAC, webhook or ESO read. + Run every `ssh railiance01 "kubectl …"` below through a login that still has cluster-admin read. + +## 1. Pre-check (founder, or agent once read access exists) + +```bash +cd ~/railiance-platform && git pull --ff-only +D=docs/evidence/2026-10-21-eso-adoption-prep +# a) fresh predecessor proofs (gate window is 300 s, so run all three back to back) +( cd ~/rapp-issue-core && make -s verify-live VERIFY_ARGS="--expected-revision 186c817b67d3919a64acdddbd3889e539841f946 --expected-source-url ssh://git@forgejo.coulomb.social:30022/coulomb/rapp-issue-core.git --smoke" ) > $D/issue-core-proof.json +python3 scripts/workload_adoption_proof.py --live --source-root ~/target-revenue --observation $D/target-revenue-observation.json --receipt $D/target-revenue-proof.json +python3 scripts/eso_adoption_readiness.py --draft argocd/railiance01/drafts/external-secrets.application.yaml --issue-core-proof $D/issue-core-proof.json --target-revenue-proof $D/target-revenue-proof.json | tee $D/readiness-r2.json +# must print "ready_for_fresh_native_render_diff": true +# b) fresh render + client-side diff (T05 method); credential data in diff output is masked by kubectl +python3 -c "import yaml;print(yaml.safe_load(open('argocd/railiance01/drafts/external-secrets.application.yaml'))['spec']['source']['helm']['values'])" > /tmp/eso-values.yaml +helm template external-secrets external-secrets --repo https://charts.external-secrets.io --version 0.16.1 -n external-secrets -f /tmp/eso-values.yaml > /tmp/eso-render.yaml +ssh railiance01 "kubectl diff -f -" < /tmp/eso-render.yaml > $D/client-diff.txt; echo rc=$? +# c) store baseline +ssh railiance01 "kubectl get clustersecretstores -o custom-columns=NAME:.metadata.name,STATUS:.status.conditions[0].reason --no-headers" | tee $D/store-baseline-before.txt +``` + +Go only if all of these hold: +- the readiness gate is true; +- the diff touches CRDs in metadata only (the protective annotation) and changes no CRD `spec`; +- nothing is deleted or replaced; +- the baseline shows every store `Valid` except `openbao-activity-core` and `openbao-email-connect` (`ValidationFailed`). + +If the diff shows webhook `caBundle` drift, an agent adds `ignoreDifferences` for +`ValidatingWebhookConfiguration` `/webhooks/0/clientConfig/caBundle` and `/webhooks/1/clientConfig/caBundle` and re-runs b. + +## 2. Create the Application (an agent, after the gate is green) + +The agent moves the draft to `argocd/railiance01/applications/external-secrets.application.yaml`, +commits and pushes, runs `make argocd-pin-drift`, then creates the child only with +`ssh railiance01 "kubectl apply --dry-run=server -f -" < ` followed by the same command without `--dry-run`. +The root also carries other OutOfSync children. Creating the Application syncs nothing. + +## 3. The founder's sync command (one command) + +```bash +ssh railiance01 "kubectl -n argocd patch application external-secrets --type merge -p '{\"operation\":{\"initiatedBy\":{\"username\":\"rpf-wp-0044-t06\"},\"sync\":{\"revision\":\"0.16.1\",\"prune\":false,\"syncOptions\":[\"ServerSideApply=true\"],\"syncStrategy\":{\"apply\":{\"force\":false}}}}}'" +``` + +(Local double quotes; the remote shell receives the JSON inside single quotes. Prune off, force off, SSA.) + +## 4. Post-sync checks + +```bash +ssh railiance01 "kubectl -n argocd get application external-secrets -o custom-columns=SYNC:.status.sync.status,HEALTH:.status.health.status,PHASE:.status.operationState.phase,REV:.status.sync.revision" +# expect Synced Healthy Succeeded 0.16.1 +ssh railiance01 "kubectl -n external-secrets get deploy" # 3/3 available, no restarts beyond rollout +ssh railiance01 "kubectl get crd -o name | grep -c external-secrets.io" # still 20 +ssh railiance01 "kubectl get clustersecretstores -o custom-columns=NAME:.metadata.name,STATUS:.status.conditions[0].reason --no-headers" > $D/store-baseline-after.txt +diff $D/store-baseline-before.txt $D/store-baseline-after.txt && echo baseline-unchanged +# ExternalSecret refresh sample (issue-core): force a refresh and confirm Ready +ssh railiance01 "kubectl -n issue-core annotate externalsecret issue-core-runtime force-sync=\$(date +%s) --overwrite && sleep 20 && kubectl -n issue-core get externalsecret issue-core-runtime -o custom-columns=READY:.status.conditions[0].status,REASON:.status.conditions[0].reason" +# expect True SecretSynced +``` + +The agent records these as `docs/evidence/-eso-argocd-adoption.json`, re-runs issue-core +`verify-live`, and marks T06 done after the proving period. + +## Rollback (abort owner: railiance-platform) + +Never prune and never delete the Application with cascade. Revert the Application to the previously +reviewed chart `0.16.1` values in Git (`git revert `), re-apply the child, then +run the same sync command as above. To stop Argo management without touching workloads, delete only +the Application object. It has no finalizer, so no cascade happens, and the CRDs are protected anyway. + +## Separate later step (not part of adoption) + +Retire the stale `pending-upgrade` Helm release record (`sh.helm.release.v1.external-secrets.v*`) +under its own reviewed cleanup. ArgoCD does not track it, and it must not be replaced silently. diff --git a/workplans/RPF-WP-0044-argocd-phase-b-adopt-existing-applications.md b/workplans/RPF-WP-0044-argocd-phase-b-adopt-existing-applications.md index 5a7ac81..fb13240 100644 --- a/workplans/RPF-WP-0044-argocd-phase-b-adopt-existing-applications.md +++ b/workplans/RPF-WP-0044-argocd-phase-b-adopt-existing-applications.md @@ -344,7 +344,7 @@ decision_id: 89f1058f-33bc-47a8-8a49-0b44d426c67e priority: medium depends_on: [RPF-WP-0044-T04, RPF-WP-0044-T05] needs_human: false -blocking_reason: "Ordered D2 adoption: issue-core T04 must provide automated Argo health/diff and private/authenticated smoke proof; target-revenue T05 is already proven. No further founder approval is required." +blocking_reason: "Source ready (gate source check passes). Blocked on fresh live evidence: readiness gate needs <=300 s issue-core proof WITH --smoke (authenticated 201/200) and a fresh target-revenue proof; fresh client-side diff and ClusterSecretStore baseline need cluster read the agent no longer has (tegwick kubeconfig removed by CUST-WP-0073; observer role lacks CRD/ESO read). Runbook docs/runbooks/rpf-wp-0044-t06-eso-adoption.md step 1, then create child + one founder sync." state_hub_task_id: "4ca54868-c31f-5e3d-b7d3-a15b84c15f80" ``` @@ -372,6 +372,8 @@ CRDs or silently replace the Helm release record as part of adoption. 2026-10-03: human gate routed to hub decision 89f1058f-33bc-47a8-8a49-0b44d426c67e (D2: ArgoCD owns production: go-ahead sequence for railiance-apps-root, issue-core, policy-nexus and activity-core automation) by CUST-WP-0075; resolving that decision raises C-40 here. +2026-10-21 (preparation, agent, no live mutation): `scripts/eso_adoption_readiness.py` passes the source check (chart 0.16.1, 20/20 CRDs Prune=false,Delete=false, ServerSideApply, manual, no finalizer, no Force/Replace) but reports T04 and T05 missing. The 2026-10-20 issue-core verify-live receipt has no authenticated smoke statuses (it ran without `--smoke`, and `--smoke` creates and closes a Forgejo issue, so it is not read-only) and is outside the 300 s window. A fresh read-only target-revenue proof failed because `ssh railiance01` (tegwick) can no longer read the k3s kubeconfig after the CUST-WP-0073 cutover. Offline render of 0.16.1 with the draft values: 39 objects, no ClusterSecretStore or ExternalSecret rendered, webhooks declare no caBundle, the webhook-cert holder is metadata-only; no ignoreDifferences added yet. The fresh live client-side diff and the store baseline were not run (no agent cluster read path). Receipts: `docs/evidence/2026-10-21-eso-adoption-prep/`. Runbook with the exact founder sync command: `docs/runbooks/rpf-wp-0044-t06-eso-adoption.md`. + ## Turn the root back to the production lane ```task