From 80f58cd9f2fa54aaf5692773658092074bf098df Mon Sep 17 00:00:00 2001 From: DongHyeonka Date: Sun, 2 Aug 2026 02:27:20 +0900 Subject: [PATCH] =?UTF-8?q?docs:=20Phase=201=20=EA=B5=AC=ED=98=84=20?= =?UTF-8?q?=EA=B3=84=ED=9A=8D=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...project-infra-phase-1-automation-safety.md | 787 ++++++++++++++++++ ...-08-02-project-infra-phase-1-dev-gitops.md | 581 +++++++++++++ ...ject-infra-phase-1-validation-structure.md | 703 ++++++++++++++++ ...ect-infra-dev-structure-refactor-design.md | 2 +- 4 files changed, 2072 insertions(+), 1 deletion(-) create mode 100644 docs/superpowers/plans/2026-08-02-project-infra-phase-1-automation-safety.md create mode 100644 docs/superpowers/plans/2026-08-02-project-infra-phase-1-dev-gitops.md create mode 100644 docs/superpowers/plans/2026-08-02-project-infra-phase-1-validation-structure.md diff --git a/docs/superpowers/plans/2026-08-02-project-infra-phase-1-automation-safety.md b/docs/superpowers/plans/2026-08-02-project-infra-phase-1-automation-safety.md new file mode 100644 index 0000000..9ffec7c --- /dev/null +++ b/docs/superpowers/plans/2026-08-02-project-infra-phase-1-automation-safety.md @@ -0,0 +1,787 @@ +# Project Infra Phase 1 automation and Vault safety Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use `superpowers:subagent-driven-development` (recommended) or `superpowers:executing-plans` to execute this plan task by task. Use `superpowers:test-driven-development` for every script behavior change, `superpowers:systematic-debugging` for unexpected failures, and `superpowers:verification-before-completion` before reporting success. + +**Goal:** Make cluster targeting, Vault initialization/authentication, bootstrap, and teardown fail closed while keeping all tests disconnected from a real cluster. + +**Architecture:** Kubernetes clients are unusable until an explicit environment-to-context mapping is validated and locked. Vault credentials enter the Pod only through stdin, no CLI token cache is created, and repeatable tasks use a short-lived Kubernetes-auth token issued to a dedicated ServiceAccount. Bootstrap and teardown render through the shared Helm-aware Kustomize helper and expose narrow, additive safety gates. Legacy Helm ownership is detected read-only and never removed automatically. + +**Tech Stack:** Bash, fake-command contract tests, `kubectl`, Kustomize, Helm, Vault CLI 1.17.2, Vault HCL/JSON policies, `jq`, Kubernetes TokenRequest. + +## Global constraints + +- Complete the dev GitOps plan first; this plan depends on `tests/run.sh`, `tests/lib/assert.sh`, `scripts/lib/kustomize.sh`, and `gitops/clusters/dev/main/stages/05-operators`. +- Read `docs/standards/infra/STYLE.md`, `scripts.md`, `config-and-secrets.md`, `security-hardening.md`, `operations-runbook-upgrade-rollback.md`, `vault.md`, and relevant approved examples before editing. +- All tests must run against temporary fake `kubectl`, `helm`, and Vault responses. Never point a test at the current kubeconfig. +- Never print, trace, or place a root token, unseal key, Kubernetes JWT, Vault token, password, or MinIO secret in a process argument. +- Do not add an overwrite override for Vault initialization material. +- Do not run `kubectl apply`, `kubectl delete`, `helm upgrade`, or `helm uninstall` against a live cluster while implementing this plan. +- Do not edit workflow files or the sibling `cicd-platform` repository. + +## Dependency and public interfaces + +- `require_kube_context ENV` accepts only `KUBE_CONTEXT` or `KUBE_CONTEXT_`, verifies that context exists, locks it, and never reads the interactive current context as approval. +- `kubectl` and `helm` wrappers fail if `KUBE_CONTEXT_LOCKED` is empty; after locking, they always add the explicit context flag. +- `vault_exec ARGS` is for unauthenticated Vault operations only: status, init, and unseal. +- `vault_exec_with_token TOKEN ARGS` sends only the token through stdin, exports it only for the child Vault process, and is used by commands with no payload. +- `vault_exec_with_token_input TOKEN ARGS` prepends the token to the caller's stdin payload for commands that read HCL/JSON from stdin. +- `vault_root_session_begin KEYS_FILE` is called only by `vault-init.sh` when first-bootstrap access must be created or repaired. +- `vault_try_session_begin` attempts only Kubernetes auth and returns non-zero without exiting so `vault-init.sh` can decide whether first-bootstrap repair is possible. +- `vault_session_begin` wraps `vault_try_session_begin` and aborts with recovery guidance when the role is unavailable; repeatable seed/admin/provision tasks never fall back to root. +- `vault_exec_authenticated ARGS` and `vault_exec_authenticated_input ARGS` require an active in-memory session and delegate to their corresponding transport. +- `vault_session_end` revokes a Kubernetes-auth token with `vault token revoke -self`, then unsets it; a root bootstrap token is only unset. +- `apply_entrypoint LABEL PATH` uses render → server dry-run → diff → confirmation → server-side apply. +- Destructive flags are additive. No broad flag implies CRD deletion. + +--- + +## Task 1: Require an explicit Kubernetes context and lock every client call + +**Files:** + +- Create: `tests/contracts/kube-context-test.sh` +- Modify: `scripts/lib/common.sh` +- Modify: `scripts/README.md` + +**Step 1: Write the failing context contract** + +Use fake `kubectl` and `helm` programs that append NUL-safe argument records to a temporary log. Cover these cases in separate subprocesses: + +```text +require_kube_context dev fails when KUBE_CONTEXT and KUBE_CONTEXT_DEV are both empty +CONFIRM=yes does not bypass the missing mapping +KUBE_CONTEXT_DEV=dev-k3s succeeds when kubectl config get-contexts dev-k3s succeeds +an unknown mapped context fails +KUBE_CONTEXT takes precedence over KUBE_CONTEXT_DEV +after success every kubectl call contains --context dev-k3s +after success every helm call contains --kube-context dev-k3s +calling either wrapper before the lock fails and never invokes the fake binary +no call to kubectl config current-context occurs +``` + +Run: + +```bash +bash tests/run.sh kube-context +``` + +Expected: non-zero because the current wrapper falls back to an unlocked client and the current context. + +**Step 2: Make wrappers fail closed** + +Replace the wrapper behavior with this invariant: + +```bash +kubectl() { + [[ -n "${KUBE_CONTEXT_LOCKED:-}" ]] \ + || die "kubectl 호출 전에 require_kube_context로 context를 잠가야 합니다." + command kubectl --context "$KUBE_CONTEXT_LOCKED" "$@" +} + +helm() { + [[ -n "${KUBE_CONTEXT_LOCKED:-}" ]] \ + || die "helm 호출 전에 require_kube_context로 context를 잠가야 합니다." + command helm --kube-context "$KUBE_CONTEXT_LOCKED" "$@" +} +``` + +`require_kube_context` must use `command kubectl` while establishing the lock so it does not recurse into the guarded wrapper: + +```bash +expected="${KUBE_CONTEXT:-${!mapped_var:-}}" +[[ -n "$expected" ]] \ + || die "KUBE_CONTEXT 또는 ${mapped_var}가 필수입니다." +command kubectl config get-contexts "$expected" --no-headers >/dev/null 2>&1 \ + || die "kube-context를 찾을 수 없습니다: $expected" +_lock_kube_context "$expected" +``` + +Do not require the selected context to equal `current-context`; explicit wrapper flags make current context irrelevant. + +**Step 3: Verify behavior and static call discipline** + +Run: + +```bash +bash tests/run.sh kube-context +rg -n 'command (kubectl|helm)' scripts --glob '*.sh' +git diff --check +``` + +Expected: tests exit 0. Direct `command kubectl` appears only inside `require_kube_context` and the locked wrapper; direct `command helm` appears only inside the locked wrapper. + +**Step 4: Commit** + +```bash +git add scripts/lib/common.sh scripts/README.md tests/contracts/kube-context-test.sh +git commit -m "fix: Kubernetes context를 명시적으로 잠금" +``` + +--- + +## Task 2: Refuse unsafe Vault initialization material states + +**Files:** + +- Create: `tests/contracts/vault-key-material-test.sh` +- Modify: `scripts/tasks/vault-init.sh` +- Modify: `.gitignore` only if the existing Vault key pattern is incomplete + +**Step 1: Write the failing state-matrix test** + +Source `vault-init.sh` in subprocesses after overriding `vault_status_json` and `vault_exec`. Use a temporary parent directory and assert this matrix: + +| Vault state | File state | Result | +|---|---|---| +| uninitialized | absent | initialize once, atomically create mode `0600` | +| uninitialized | existing valid | abort; preserve checksum and mode | +| uninitialized | existing invalid | abort; preserve checksum and mode | +| initialized | absent | abort with recovery guidance | +| initialized | invalid JSON | abort; preserve file | +| initialized | wrong mode | abort; preserve file | +| initialized | valid keys, root token present | continue | +| initialized | valid keys, root token absent | continue to Kubernetes-auth path | +| any | symlink target | abort without reading or writing target | +| any | missing parent | abort without creating parent | + +Required JSON fields are: + +```jq +(.unseal_threshold | type == "number" and . > 0) and +(.unseal_threshold as $threshold | + .unseal_keys_b64 | type == "array" and length >= $threshold) and +([.unseal_keys_b64[] | type == "string" and length > 0] | all) and +((.root_token == null) or (.root_token | type == "string" and length > 0)) +``` + +Run: + +```bash +bash tests/run.sh vault-key-material +``` + +Expected: non-zero; at minimum the uninitialized/existing case overwrites under the current implementation. + +**Step 2: Split resolution, validation, and creation** + +Create private functions in `vault-init.sh`: + +```text +resolve_keys_file +validate_existing_keys_file +validate_vault_file_state +initialize_to_new_keys_file +``` + +`validate_existing_keys_file` must use `lstat` semantics through `[[ -L ]]`, `stat -c '%a'`, and `jq -e`. It must require exact mode `600`. + +`initialize_to_new_keys_file` must: + +1. verify the target is absent immediately before init; +2. create a temp file inside the existing parent with mode `0600` using `umask 077`; +3. write and validate init JSON in the temp file; +4. install without replacement by creating an atomic hard link from the same-directory temporary file to the absent target; +5. abort if another process created the target; +6. never expose an environment variable that allows replacement. + +Use `ln -- "$tmp_out" "$KEYS_FILE"`; because both paths are in the same directory, link creation is atomic and fails if the target exists. Remove the temporary link only after success. The test must exercise the race/collision branch with a fake creation between validation and installation. + +**Step 3: Verify the complete matrix** + +Run: + +```bash +bash tests/run.sh vault-key-material +bash -n scripts/tasks/vault-init.sh +git diff --check +``` + +Expected: all commands exit 0; test output confirms that every abort preserves the original checksum. + +**Step 4: Commit** + +```bash +git add scripts/tasks/vault-init.sh tests/contracts/vault-key-material-test.sh .gitignore +git commit -m "fix: Vault 초기화 자료 덮어쓰기 차단" +``` + +--- + +## Task 3: Add versioned Vault definitions and stdin-only authenticated execution + +**Files:** + +- Create: `bootstrap/foundation/vault/README.md` +- Create: `bootstrap/foundation/vault/policies/vault-bootstrap.hcl` +- Create: `bootstrap/foundation/vault/policies/vso-auth-platform.hcl` +- Create: `bootstrap/foundation/vault/policies/vso-storage.hcl` +- Create: `bootstrap/foundation/vault/policies/vault-admin.hcl` +- Create: `bootstrap/foundation/vault/roles/vault-bootstrap.json` +- Create: `bootstrap/foundation/vault/roles/vso-auth-platform.json` +- Create: `bootstrap/foundation/vault/roles/vso-storage.json` +- Create: `gitops/platform/vault/base/vault-bootstrap-serviceaccount.yaml` +- Modify: `gitops/platform/vault/base/kustomization.yaml` +- Create: `tests/contracts/vault-auth-session-test.sh` +- Create: `tests/contracts/vault-definition-test.sh` +- Modify: `scripts/lib/vault.sh` + +**Step 1: Write failing authentication and definition tests** + +`vault-auth-session-test.sh` must use a fake `kubectl` that records arguments and stdin. Prove: + +```text +vault_exec_with_token never includes the token in kubectl or sh argv +the remote shell reads the first stdin line as VAULT_TOKEN +JSON payload following the token line reaches `vault write PATH -` unchanged +no command contains `vault login` +no command or source contains `.vault-token` +TokenRequest uses serviceaccount/vault-bootstrap, namespace mnt, audience vault, duration 10m +successful Kubernetes login sets a session token and session end revokes self +vault_root_session_begin succeeds only when root_token exists +vault_try_session_begin and vault_session_begin never fall back to root +missing Kubernetes login aborts with recovery guidance +``` + +`vault-definition-test.sh` must assert that every HCL/JSON file exists, JSON parses, role audience is `vault`, role TTL is at most `15m` for bootstrap, and no policy body remains in a shell heredoc. + +Run: + +```bash +bash tests/run.sh vault-auth-session +bash tests/run.sh vault-definition +``` + +Expected: both tests fail because the definitions and wrapper do not exist. + +**Step 2: Add the dedicated ServiceAccount** + +Create a standard-labeled ServiceAccount named `vault-bootstrap` with `automountServiceAccountToken: false`; TokenRequest does not require an automatically mounted token. Add it to the Vault base Kustomization. Do not bind this ServiceAccount to `system:auth-delegator`; the Vault server's existing ServiceAccount performs token review. + +**Step 3: Add the checked-in policies** + +`vault-bootstrap.hcl` grants only the paths required by checked-in reconciliation tasks: + +```hcl +path "sys/mounts" { + capabilities = ["read", "list"] +} +path "sys/mounts/secret" { + capabilities = ["create", "read", "update"] +} +path "sys/auth" { + capabilities = ["read", "list"] +} +path "sys/auth/kubernetes" { + capabilities = ["create", "read", "update"] +} +path "sys/auth/userpass" { + capabilities = ["create", "read", "update"] +} +path "auth/kubernetes/config" { + capabilities = ["create", "read", "update"] +} +path "auth/kubernetes/role/*" { + capabilities = ["create", "read", "update", "delete", "list"] +} +path "auth/userpass/users/*" { + capabilities = ["create", "read", "update", "delete", "list"] +} +path "sys/policies/acl/*" { + capabilities = ["create", "read", "update", "delete", "list"] +} +path "secret/data/*" { + capabilities = ["create", "read", "update", "list"] +} +path "secret/metadata/*" { + capabilities = ["read", "list"] +} +path "auth/token/revoke-self" { + capabilities = ["update"] +} +``` + +Move the existing VSO and admin policy bodies byte-for-byte into their named HCL files, then delete the shell heredocs in the caller tasks during Task 4. + +**Step 4: Add role JSON** + +`vault-bootstrap.json` is: + +```json +{ + "bound_service_account_names": ["vault-bootstrap"], + "bound_service_account_namespaces": ["mnt"], + "audience": "vault", + "token_policies": ["vault-bootstrap"], + "token_ttl": "10m", + "token_max_ttl": "15m" +} +``` + +The VSO role files preserve the existing ServiceAccount, namespace, audience, policy, and one-hour TTL values. Keep one JSON file per role so drift is reviewable. + +`vso-auth-platform.json` is: + +```json +{ + "bound_service_account_names": ["vault-secrets-operator"], + "bound_service_account_namespaces": ["mnt"], + "audience": "vault", + "token_policies": ["vso-auth-platform"], + "token_ttl": "1h", + "token_max_ttl": "1h" +} +``` + +`vso-storage.json` has the same shape and replaces both occurrences of `vso-auth-platform` with `vso-storage`. + +**Step 5: Implement authenticated stdin transport** + +In `scripts/lib/vault.sh`, retain unauthenticated `vault_exec` for status/init/unseal and add: + +```bash +vault_exec_with_token() { + local token="${1:?token is required}" + shift + { + printf '%s\n' "$token" + } | kubectl exec -i -n "$VAULT_NAMESPACE" "$VAULT_POD" -- \ + sh -c 'IFS= read -r VAULT_TOKEN; export VAULT_TOKEN; exec vault "$@"' vault "$@" +} + +vault_exec_with_token_input() { + local token="${1:?token is required}" + shift + { + printf '%s\n' "$token" + cat + } | kubectl exec -i -n "$VAULT_NAMESPACE" "$VAULT_POD" -- \ + sh -c 'IFS= read -r VAULT_TOKEN; export VAULT_TOKEN; exec vault "$@"' vault "$@" +} + +vault_exec_authenticated() { + [[ -n "${VAULT_SESSION_TOKEN:-}" ]] || die "Vault 인증 session이 없습니다." + vault_exec_with_token "$VAULT_SESSION_TOKEN" "$@" +} + +vault_exec_authenticated_input() { + [[ -n "${VAULT_SESSION_TOKEN:-}" ]] || die "Vault 인증 session이 없습니다." + vault_exec_with_token_input "$VAULT_SESSION_TOKEN" "$@" +} +``` + +Build the Kubernetes login JSON without putting the JWT in argv: + +```bash +request_vault_bootstrap_token() { + local jwt response + jwt="$(kubectl create token vault-bootstrap \ + -n "$VAULT_NAMESPACE" --audience=vault --duration=10m)" + response="$(printf '%s' "$jwt" \ + | jq -Rs '{role:"vault-bootstrap", jwt:.}' \ + | vault_exec write -format=json auth/kubernetes/login -)" + unset jwt + jq -er '.auth.client_token' <<<"$response" +} +``` + +Register `vault_session_end` with `trap_cleanup_fn` only once per process. `vault_root_session_begin` reads the optional root token from the validated key file and marks the session kind as root; it is not called from `vault_session_begin`. Do not revoke the first-init root token; unset it after bootstrap access has been written. Never invoke `vault login`. + +**Step 6: Verify policy and transport contracts** + +Run: + +```bash +bash tests/run.sh vault-auth-session +bash tests/run.sh vault-definition +VALIDATION_PROFILE=local bash scripts/ci/validate.sh +git diff --check +``` + +Expected: all commands exit 0. + +**Step 7: Commit** + +```bash +git add bootstrap/foundation/vault gitops/platform/vault scripts/lib/vault.sh tests/contracts/vault-auth-session-test.sh tests/contracts/vault-definition-test.sh +git commit -m "feat: 단기 Vault bootstrap 인증 추가" +``` + +--- + +## Task 4: Migrate Vault tasks away from root login and shell heredocs + +**Files:** + +- Modify: `scripts/tasks/vault-init.sh` +- Modify: `scripts/tasks/vault-seed-apps.sh` +- Modify: `scripts/tasks/vault-setup-admin.sh` +- Modify: `scripts/tasks/minio-provision-registry.sh` +- Create: `tests/contracts/vault-task-auth-test.sh` +- Modify: `bootstrap/foundation/vault/README.md` + +**Step 1: Write the failing caller contract** + +The contract must statically and behaviorally prove: + +```text +vault_login_root_from_keyfile is absent +vault_exec_sh is absent +every authenticated read/write calls vault_exec_authenticated +all policy write commands read an HCL file from bootstrap/foundation/vault/policies +all Kubernetes role writes read a JSON file from bootstrap/foundation/vault/roles +seed JSON and admin password payloads remain on stdin after the token prefix +minio provisioning uses dev HTTP defaults and requires an explicit override for insecure non-dev TLS +root_token may be absent from a valid post-bootstrap key file +``` + +Run: + +```bash +bash tests/run.sh vault-task-auth +``` + +Expected: non-zero because current tasks call `vault_login_root_from_keyfile` and use cached authentication. + +**Step 2: Make `vault-init.sh` establish bootstrap access safely** + +Execution order must be: + +```text +resolve and validate state/file +initialize only when both Vault and file are absent +unseal with stdin keys +attempt `vault_try_session_begin` +if unavailable, require root_token and use stdin-authenticated root session +enable KV and Kubernetes auth +write vault-bootstrap policy and role from files +end root session without revoke +start Kubernetes-auth session +write VSO policies and roles from files +remove legacy definitions +revoke the short-lived session +``` + +When the root token was needed, pass it to `vault_exec_with_token`; do not run a login command. If the bootstrap login still fails after role reconciliation, abort before application policy changes. + +**Step 3: Convert all repeatable tasks to short-lived sessions** + +At the beginning of each of these task `main` functions, call `vault_session_begin` after unseal/readiness prerequisites: + +- `vault-seed-apps.sh` +- `vault-setup-admin.sh` +- `minio-provision-registry.sh` + +All Vault reads and writes then call `vault_exec_authenticated`. For a JSON payload, the caller pipeline remains: + +```bash +printf '%s\0%s\0' "$first" "$second" \ + | jq -Rs 'split("\u0000") | {first: .[0], second: .[1]}' \ + | vault_exec_authenticated_input kv put secret/example - +``` + +Because `vault_exec_with_token_input` prepends one token line and then copies stdin, the JSON body reaches the Vault CLI unchanged. Reads and argument-only writes use `vault_exec_authenticated`, so they never consume unrelated process stdin. + +**Step 4: Apply checked-in definitions** + +Replace all HCL heredocs and role key/value argv with: + +```bash +vault_exec_authenticated_input policy write vso-auth-platform - \ + <"$REPO_ROOT/bootstrap/foundation/vault/policies/vso-auth-platform.hcl" +vault_exec_authenticated_input write auth/kubernetes/role/vso-auth-platform - \ + <"$REPO_ROOT/bootstrap/foundation/vault/roles/vso-auth-platform.json" +``` + +Use the corresponding file for each policy and role. Keep runtime secrets out of these definition files. + +**Step 5: Preserve the dev-only MinIO transport semantics** + +Verify the environment rename from the dev GitOps plan: HTTP is the default only for dev. Staging/prod default to verified HTTPS, and `ALLOW_INSECURE_MINIO_TLS=yes` remains an additional explicit gate outside dev. Do not reintroduce a `lab` branch while migrating Vault calls. + +**Step 6: Verify callers** + +Run: + +```bash +bash tests/run.sh vault-key-material +bash tests/run.sh vault-auth-session +bash tests/run.sh vault-definition +bash tests/run.sh vault-task-auth +rg -n 'vault login|\.vault-token|vault_exec_sh|vault_login_root_from_keyfile' scripts bootstrap +git diff --check +``` + +Expected: tests exit 0 and the final `rg` returns no matches. + +**Step 7: Commit** + +```bash +git add scripts/tasks bootstrap/foundation/vault/README.md tests/contracts/vault-task-auth-test.sh +git commit -m "refactor: Vault 작업을 단기 인증으로 전환" +``` + +--- + +## Task 5: Rework bootstrap around ordered Git-rendered stages + +**Files:** + +- Modify: `scripts/bin/bootstrap.sh` +- Delete: `scripts/tasks/minio-operator-install.sh` +- Delete: `scripts/tasks/vso-install.sh` +- Create: `tests/contracts/bootstrap-flow-test.sh` +- Modify: `scripts/README.md`, `docs/operations.md`, `docs/vault-vso.md`, `gitops/clusters/dev/main/README.md` + +**Step 1: Write the failing bootstrap contract** + +Use fake clients and source/call individual functions. Prove: + +```text +only dev, staging, and prod are accepted environment names +the shipped environment documented by usage is dev +every render calls kustomize_render and therefore --enable-helm +each stage order is namespaces, 00-platform, 05-operators, 10-vault, 20-secrets, 30-data, 35-registry, 40-operations, 50-apps +each apply path is render, server dry-run, diff, confirm, SSA apply, readiness +SKIP_DIFF=yes succeeds only for dev and fails before render for staging/prod +legacy Helm releases make 05-operators skip by default +ADOPT_LEGACY_OPERATORS=yes allows 05-operators only after a dedicated confirmation +no legacy release is uninstalled +new clusters with no legacy release apply 05-operators normally +operator readiness waits for both required CRDs and both controller Deployments +all/ is never passed to apply_entrypoint +``` + +Run: + +```bash +bash tests/run.sh bootstrap-flow +``` + +Expected: non-zero because current bootstrap installs operators through task scripts and omits the operator stage. + +**Step 2: Use the shared renderer and validate the diff gate** + +Source `scripts/lib/kustomize.sh`, delete `render_entrypoint`, and redirect `kustomize_render` output into the temp file. + +Add: + +```bash +validate_diff_policy() { + if [[ "${SKIP_DIFF:-}" == "yes" && "$ENV_NAME" != "dev" ]]; then + die "SKIP_DIFF=yes 는 dev에서만 허용됩니다." + fi +} +``` + +Call it immediately after environment parsing and before any render or client mutation. + +**Step 3: Add read-only legacy ownership detection** + +Detect either known release without changing it: + +```bash +legacy_operator_release_exists() { + helm -n minio-operator status minio-operator >/dev/null 2>&1 \ + || helm -n vault-secrets-operator-system status vault-secrets-operator >/dev/null 2>&1 +} +``` + +Behavior: + +- no legacy release: apply `05-operators` normally; +- legacy release and no gate: log a warning, skip stage apply, then verify readiness; +- legacy release and `ADOPT_LEGACY_OPERATORS=yes`: require a dedicated confirmation naming both releases before `apply_entrypoint`; +- never run Helm install, upgrade, or uninstall. + +**Step 4: Add operator readiness boundaries** + +Wait for: + +```text +crd/tenants.minio.min.io Established +crd/vaultstaticsecrets.secrets.hashicorp.com Established +MinIO operator Deployment selected by app.kubernetes.io/name=operator Available +VSO Deployment selected by app.kubernetes.io/name=vault-secrets-operator Available +``` + +Use namespace-scoped rollout or condition waits and finite timeouts. Continue to stage `10-vault` only after all four checks pass. + +**Step 5: Remove imperative installers and update task flow** + +Delete `install_helm_operators` and both installer scripts. Keep Helm as a required local command because Kustomize Helm rendering needs it. Update usage to `dev|staging|prod`, mark dev as the currently shipped root, and document `ADOPT_LEGACY_OPERATORS=yes` as a Phase 2 transition-only gate. + +**Step 6: Verify bootstrap behavior** + +Run: + +```bash +bash tests/run.sh bootstrap-flow +bash -n scripts/bin/bootstrap.sh +rg -n 'helm (upgrade|install|uninstall)|minio-operator-install|vso-install' scripts +git diff --check +``` + +Expected: tests and syntax exit 0; `rg` returns no mutation/install-script matches. + +**Step 7: Commit** + +```bash +git add scripts/bin/bootstrap.sh scripts/tasks scripts/README.md docs/operations.md docs/vault-vso.md gitops/clusters/dev/main/README.md tests/contracts/bootstrap-flow-test.sh +git commit -m "refactor: bootstrap을 Git 렌더 단계로 전환" +``` + +--- + +## Task 6: Make teardown scope narrow and gates additive + +**Files:** + +- Modify: `scripts/bin/teardown.sh` +- Create: `tests/contracts/teardown-scope-test.sh` +- Modify: `scripts/README.md`, `docs/operations.md`, `gitops/clusters/dev/main/README.md` + +**Step 1: Write the failing teardown matrix** + +Fake `kustomize_render`, `kubectl`, and `helm`, then assert: + +| Flags | Permitted deletion | +|---|---| +| none | `50-apps` only | +| `DELETE_OPERATIONS=yes` | plus `40-operations` | +| `DELETE_DATA=yes` without operations | abort before deletion | +| operations + data | plus `35-registry`, `30-data`, `20-secrets` | +| `DELETE_VAULT=yes` without data | abort | +| operations + data + Vault | plus `10-vault` | +| `DELETE_NAMESPACE=yes` without Vault | abort | +| through namespace | plus namespace `mnt` | +| `TEARDOWN_PLATFORM=yes` without namespace | abort | +| through platform | plus `00-platform`, but not `05-operators` | +| `DELETE_OPERATORS=yes` without platform | abort | +| operators without `DELETE_CRDS=yes` | abort before operator deletion | +| all additive flags | plus `05-operators` and both operator namespaces | + +Also prove: + +```text +delete rendering uses kustomize_render, not kubectl delete -k +no helm uninstall command runs under any matrix row +FORCE_FINALIZERS=yes requires DELETE_NAMESPACE=yes +the confirmation summary names every enabled scope +``` + +Run: + +```bash +bash tests/run.sh teardown-scope +``` + +Expected: non-zero because current default deletes `40-operations` and platform teardown uninstalls Helm releases. + +**Step 2: Replace delete-by-kustomization with delete-by-rendered-file** + +Source `scripts/lib/kustomize.sh`. `delete_entrypoint` must render to a temporary file, show the exact resource count, require confirmation, and then call: + +```bash +kubectl delete -f "$rendered" \ + --ignore-not-found \ + --wait=true \ + --timeout=300s +``` + +This is required because the operator stage uses Helm rendering and `kubectl delete -k` does not honor the shared `--enable-helm` interface. + +**Step 3: Implement additive gate validation before any deletion** + +Add one `validate_teardown_gates` function that checks all dependencies before the initial confirmation: + +```text +DELETE_DATA requires DELETE_OPERATIONS +DELETE_VAULT requires DELETE_DATA +DELETE_NAMESPACE requires DELETE_VAULT +TEARDOWN_PLATFORM requires DELETE_NAMESPACE +DELETE_OPERATORS requires TEARDOWN_PLATFORM and DELETE_CRDS +DELETE_CRDS requires DELETE_OPERATORS +FORCE_FINALIZERS requires DELETE_NAMESPACE +``` + +Unknown non-empty flag values must fail; accepted values are empty or `yes`. + +**Step 4: Implement reverse-order scopes** + +Deletion order when every gate is enabled: + +```text +50-apps +40-operations +35-registry +30-data +20-secrets +10-vault +namespace/mnt +05-operators +namespace/vault-secrets-operator-system and namespace/minio-operator +00-platform +``` + +The operator stage deletion is allowed only when both `DELETE_OPERATORS=yes` and `DELETE_CRDS=yes`. Explain in usage that deleting the rendered chart stage deletes CRDs and can delete every custom resource. Do not call `helm uninstall`. + +**Step 5: Verify all scopes** + +Run: + +```bash +bash tests/run.sh teardown-scope +bash -n scripts/bin/teardown.sh +rg -n 'helm[[:space:]]+.*uninstall|kubectl[[:space:]]+delete[[:space:]]+-k' scripts/bin/teardown.sh +git diff --check +``` + +Expected: tests and syntax exit 0; `rg` returns no matches. + +**Step 6: Commit** + +```bash +git add scripts/bin/teardown.sh scripts/README.md docs/operations.md gitops/clusters/dev/main/README.md tests/contracts/teardown-scope-test.sh +git commit -m "fix: teardown 삭제 범위를 단계별로 제한" +``` + +--- + +## Task 7: Automation plan verification checkpoint + +**Files:** No source changes expected. + +**Step 1: Run focused contracts** + +```bash +bash tests/run.sh kube-context +bash tests/run.sh vault-key-material +bash tests/run.sh vault-auth-session +bash tests/run.sh vault-definition +bash tests/run.sh vault-task-auth +bash tests/run.sh bootstrap-flow +bash tests/run.sh teardown-scope +``` + +Expected: every command exits 0 without accessing a real kubeconfig. + +**Step 2: Run static secret and mutation checks** + +```bash +rg -n 'vault login|\.vault-token|vault_exec_sh|vault_login_root_from_keyfile' scripts bootstrap +rg -n 'helm[[:space:]]+(install|upgrade|uninstall)' scripts +rg -n 'Usage:.*lab|\blab default\b|ENV_NAME.*lab' scripts +git diff --check +``` + +Expected: all three `rg` commands return no matches and `git diff --check` emits nothing. + +**Step 3: Run repository validation available at this checkpoint** + +```bash +bash tests/run.sh +VALIDATION_PROFILE=full bash scripts/ci/validate.sh +git status --short +``` + +Expected: tests and validation exit 0; status contains no unrelated files. The validation-structure plan may add stricter file-mode and documentation-example gates later, but this checkpoint must pass all currently defined gates. diff --git a/docs/superpowers/plans/2026-08-02-project-infra-phase-1-dev-gitops.md b/docs/superpowers/plans/2026-08-02-project-infra-phase-1-dev-gitops.md new file mode 100644 index 0000000..e87e34a --- /dev/null +++ b/docs/superpowers/plans/2026-08-02-project-infra-phase-1-dev-gitops.md @@ -0,0 +1,581 @@ +# Project Infra Phase 1 dev GitOps Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use `superpowers:subagent-driven-development` (recommended) or `superpowers:executing-plans` to execute this plan task by task. Use `superpowers:test-driven-development` for behavior changes and `superpowers:verification-before-completion` before reporting success. + +**Goal:** Rename the active rollout from `lab` to `dev`, make operator desired state render from Git, and correct the ForwardAuth/Keycloak ownership defects without changing a live cluster. + +**Architecture:** The catalog remains ownership-first and environment-neutral in `base`, while `overlays/dev` contains dev-only values. `gitops/clusters/dev/main` is the only current deployable Kubernetes root. A shared render function always enables Kustomize Helm support. Operator charts live in independent platform units and are selected by a dedicated ordered stage. This plan only changes Git-owned source and local tests; it must not invoke `kubectl apply`, `kubectl delete`, or Helm mutation commands. + +**Tech Stack:** Bash, Kustomize 5.8.1, Helm 3.19.4, Kubernetes/K3s manifests, repository shell test harness, `rg`. + +## Global constraints + +- Read `/AGENTS.md`, the nearest nested `AGENTS.md`, `docs/standards/infra/STYLE.md`, and the directly relevant standards before each manifest task. +- Preserve `mnt`, `minio-operator`, and `vault-secrets-operator-system` namespace names in Phase 1. +- Preserve `.local.test` hostnames as dev-only values. +- Never apply `gitops/clusters/dev/main/all`; it is render/audit-only. +- Do not run a live-cluster mutation command while executing this plan. +- Do not edit CI workflow definitions or the sibling `cicd-platform` repository. +- Commit only the files listed by the current task and inspect `git status --short` before each commit. + +## Dependency and interface contract + +This plan is executed before the automation-safety and validation-structure plans. + +- `tests/run.sh [name-fragment]` discovers executable `tests/contracts/*-test.sh` files and optionally filters by basename. +- `tests/lib/assert.sh` provides `fail`, `assert_eq`, `assert_file_exists`, `assert_file_absent`, `assert_contains`, and `assert_not_contains`. +- `scripts/lib/kustomize.sh` exports `kustomize_render ENTRYPOINT`; rendered YAML is written only to stdout and diagnostics only to stderr. +- Every later render path must call `kustomize_render`; no later script may reimplement `kustomize build` or `kubectl kustomize`. +- The ordered dev stages are `00-platform`, `05-operators`, `10-vault`, `20-secrets`, `30-data`, `35-registry`, `40-operations`, and `50-apps`. + +--- + +## Task 1: Add the contract-test harness and rename the active environment + +**Files:** + +- Create: `tests/lib/assert.sh` +- Create: `tests/run.sh` +- Create: `tests/contracts/dev-layout-test.sh` +- Move: `gitops/clusters/lab/main` → `gitops/clusters/dev/main` +- Move: `gitops/apps/auth-migration/overlays/lab` → `gitops/apps/auth-migration/overlays/dev` +- Move: `gitops/apps/auth-server/overlays/lab` → `gitops/apps/auth-server/overlays/dev` +- Move: `gitops/apps/identity-postgres/overlays/lab` → `gitops/apps/identity-postgres/overlays/dev` +- Move: `gitops/apps/keycloak-realm-import/overlays/lab` → `gitops/apps/keycloak-realm-import/overlays/dev` +- Move: `gitops/platform/cert-manager/overlays/lab` → `gitops/platform/cert-manager/overlays/dev` +- Move: `gitops/platform/forward-auth/overlays/lab` → `gitops/platform/forward-auth/overlays/dev` +- Move: `gitops/platform/keycloak-operator/overlays/lab` → `gitops/platform/keycloak-operator/overlays/dev` +- Move: `gitops/platform/keycloak/overlays/lab` → `gitops/platform/keycloak/overlays/dev` +- Move: `gitops/platform/minio/overlays/lab` → `gitops/platform/minio/overlays/dev` +- Move: `gitops/platform/registry/overlays/lab` → `gitops/platform/registry/overlays/dev` +- Move: `gitops/platform/secret-delivery/overlays/lab` → `gitops/platform/secret-delivery/overlays/dev` +- Move: `gitops/platform/traefik/overlays/lab` → `gitops/platform/traefik/overlays/dev` +- Move: `gitops/platform/vault/overlays/lab` → `gitops/platform/vault/overlays/dev` +- Move: `gitops/policies/baseline/overlays/lab` → `gitops/policies/baseline/overlays/dev` +- Modify: moved Kustomization files, `tests/kustomize-entrypoints.txt` +- Modify: `AGENTS.md`, `README.md`, `guide.md`, `gitops/AGENTS.md`, `gitops/PROJECT.md`, `gitops/clusters/AGENTS.md`, `gitops/clusters/README.md`, `gitops/clusters/dev/main/README.md`, `docs/architecture.md`, `docs/architecture/repository-structure.md`, `docs/guides/getting-started.md`, `docs/ingress-traefik.md`, `docs/operations.md`, `docs/security-hardening.md`, `docs/troubleshooting.md`, `docs/vault-vso.md`, `scripts/bin/bootstrap.sh`, `scripts/bin/teardown.sh`, `scripts/tasks/minio-provision-registry.sh`, `scripts/ci/validate-docs.sh`, `scripts/README.md`, `tests/README.md` + +**Step 1: Write the failing layout contract** + +Use strict mode in every new script. The runner must execute each selected test in a fresh Bash process and return non-zero if any test fails: + +```bash +#!/usr/bin/env bash +set -Eeuo pipefail +IFS=$'\n\t' + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +filter="${1:-}" +failed=0 + +while IFS= read -r test_file; do + [[ -z "$filter" || "$(basename "$test_file")" == *"$filter"* ]] || continue + bash "$test_file" || failed=$((failed + 1)) +done < <(find "$ROOT_DIR/tests/contracts" -type f -name '*-test.sh' -print | sort) + +((failed == 0)) +``` + +`dev-layout-test.sh` must assert all of these conditions: + +```text +gitops/clusters/dev/main exists +gitops/clusters/lab does not exist +every selected unit has overlays/dev and no overlays/lab +tests/kustomize-entrypoints.txt contains gitops/clusters/dev/main +tests/kustomize-entrypoints.txt does not contain gitops/clusters/lab +no deployable kustomization contains example.com/environment: lab +the active docs listed above do not instruct users to deploy lab +``` + +**Step 2: Run the contract and verify that it fails for the current tree** + +Run: + +```bash +bash tests/run.sh dev-layout +``` + +Expected: non-zero, with the first failure reporting the missing `gitops/clusters/dev/main` path. + +**Step 3: Move the directories with Git-aware renames** + +Run one `git mv` per source directory. Do not use recursive copy/delete. After the moves, replace only active environment references: + +```bash +rg -l 'clusters/lab|overlays/lab|environment: lab|\blab\b' \ + AGENTS.md README.md guide.md gitops scripts docs tests \ + --glob '!docs/standards/**' \ + --glob '!docs/examples/**' \ + --glob '!docs/superpowers/**' +``` + +Review every hit and change current deployment semantics from `lab` to `dev`, including bootstrap/teardown accepted environment names and MinIO's dev-only HTTP default. Preserve historical text in approved design/spec documents and normative examples that intentionally compare environments. Change `gitops/AGENTS.md` base reuse wording to name `dev`, `staging`, and `prod`; it may still describe `lab` only as an optional disposable environment. + +**Step 4: Update the moved cluster root** + +All resource paths under `gitops/clusters/dev/main/stages/*/kustomization.yaml` must select `overlays/dev`. Every stage label must be: + +```yaml +labels: + - pairs: + example.com/environment: dev + example.com/owner-team: platform + includeSelectors: false + includeTemplates: true +``` + +Remove `../../../../../tenants/mnt/base` from `stages/00-platform`; namespace ownership belongs only to the separate `namespaces` entrypoint. + +Update `tests/kustomize-entrypoints.txt` to contain only dev cluster entrypoints. Keep `all/` in the render inventory, but mark it audit-only in the cluster README and tests README. + +**Step 5: Run the test and documentation gate** + +Run: + +```bash +bash tests/run.sh dev-layout +bash scripts/ci/validate-docs.sh +git diff --check +``` + +Expected: all commands exit 0. + +**Step 6: Commit** + +```bash +git add tests gitops AGENTS.md README.md guide.md docs scripts/bin/bootstrap.sh scripts/bin/teardown.sh scripts/tasks/minio-provision-registry.sh scripts/ci/validate-docs.sh scripts/README.md +git commit -m "refactor: dev 환경 경로로 전환" +``` + +--- + +## Task 2: Introduce one Helm-aware Kustomize render interface + +**Files:** + +- Create: `scripts/lib/kustomize.sh` +- Create: `tests/contracts/kustomize-render-test.sh` +- Modify: `scripts/ci/validate.sh` +- Modify: `scripts/lib/common.sh` only if it is needed to share `REPO_ROOT` without changing cluster behavior + +**Step 1: Write the failing fake-command test** + +The test creates a temporary `PATH` with fake `kustomize` and `kubectl` executables. It must prove: + +1. when `kustomize` exists, the helper calls exactly `kustomize build --enable-helm ENTRYPOINT`; +2. when only `kubectl` exists, it calls exactly `kubectl kustomize ENTRYPOINT --enable-helm`; +3. when neither exists, it exits non-zero; +4. rendered YAML remains on stdout and the command trace remains outside stdout. + +Run: + +```bash +bash tests/run.sh kustomize-render +``` + +Expected: non-zero because `scripts/lib/kustomize.sh` does not yet exist. + +**Step 2: Implement the shared helper** + +Use this public interface: + +```bash +#!/usr/bin/env bash +set -Eeuo pipefail +IFS=$'\n\t' + +kustomize_render() { + local entrypoint="${1:?entrypoint is required}" + if command -v kustomize >/dev/null 2>&1; then + kustomize build --enable-helm "$entrypoint" + elif command -v kubectl >/dev/null 2>&1; then + kubectl kustomize "$entrypoint" --enable-helm + else + printf 'ERROR: kustomize 또는 kubectl이 필요합니다.\n' >&2 + return 1 + fi +} +``` + +Source this file from `scripts/ci/validate.sh` and delete its local `render_kustomization` implementation. Change the validation call to: + +```bash +kustomize_render "$entrypoint" >"$rendered" +``` + +**Step 3: Verify the helper and current dev renders** + +Run: + +```bash +bash tests/run.sh kustomize-render +VALIDATION_PROFILE=local bash scripts/ci/validate.sh +git diff --check +``` + +Expected: all commands exit 0; render log lines identify dev entrypoints. + +**Step 4: Commit** + +```bash +git add scripts/lib/kustomize.sh scripts/ci/validate.sh tests/contracts/kustomize-render-test.sh +git commit -m "refactor: Helm 지원 렌더 경로 통합" +``` + +--- + +## Task 3: Add declarative operator namespaces and catalog units + +**Files:** + +- Create: `gitops/tenants/minio-operator/base/kustomization.yaml` +- Create: `gitops/tenants/minio-operator/base/namespace.yaml` +- Create: `gitops/tenants/vault-secrets-operator-system/base/kustomization.yaml` +- Create: `gitops/tenants/vault-secrets-operator-system/base/namespace.yaml` +- Create: `gitops/platform/minio-operator/base/kustomization.yaml` +- Create: `gitops/platform/minio-operator/overlays/dev/kustomization.yaml` +- Create: `gitops/platform/minio-operator/overlays/dev/values.yaml` +- Create: `gitops/platform/vault-secrets-operator/base/kustomization.yaml` +- Create: `gitops/platform/vault-secrets-operator/overlays/dev/kustomization.yaml` +- Move: `gitops/platform/secret-delivery/base/helm/values.yaml` → `gitops/platform/vault-secrets-operator/overlays/dev/values.yaml` +- Create: `gitops/clusters/dev/main/stages/05-operators/kustomization.yaml` +- Modify: `gitops/clusters/dev/main/namespaces/kustomization.yaml` +- Modify: `gitops/clusters/dev/main/all/kustomization.yaml` +- Modify: `tests/kustomize-entrypoints.txt` +- Create: `tests/contracts/operator-render-test.sh` +- Modify: `gitops/platform/README.md`, `gitops/tenants/README.md`, `gitops/clusters/dev/main/README.md` + +**Step 1: Write the failing operator render contract** + +Render `gitops/clusters/dev/main/stages/05-operators` through `kustomize_render`. Assert: + +```text +the render exits 0 +at least two Deployment documents exist +tenants.minio.min.io exists as a CustomResourceDefinition name +vaultstaticsecrets.secrets.hashicorp.com exists as a CustomResourceDefinition name +the namespaces entrypoint renders Namespace/minio-operator and Namespace/vault-secrets-operator-system +every namespaced resource in the operator stage targets minio-operator or vault-secrets-operator-system +no HelmRelease custom resource is present +chart versions 7.0.0 and 0.9.0 are pinned in source +includeCRDs: true is present in both dev overlay kustomizations +``` + +Run: + +```bash +bash tests/run.sh operator-render +``` + +Expected: non-zero because the operator stage does not exist. + +**Step 2: Add namespace ownership units** + +Each Namespace must use the standard labels from `gitops/tenants/mnt/base/namespace.yaml`, with `app.kubernetes.io/name` and `app.kubernetes.io/instance` set to its namespace. Apply the `restricted` Pod Security labels. Each base Kustomization contains only its `namespace.yaml`. + +Add both bases to `gitops/clusters/dev/main/namespaces/kustomization.yaml` after `mnt`. + +**Step 3: Add the MinIO Operator unit** + +The dev overlay Kustomization must be exactly shaped as follows: + +```yaml +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +resources: + - ../../base + +helmCharts: + - name: operator + repo: https://operator.min.io + version: 7.0.0 + releaseName: minio-operator + namespace: minio-operator + includeCRDs: true + valuesFile: values.yaml +``` + +Use only chart-supported keys from the pinned MinIO Operator 7.0.0 values schema. `values.yaml` must be: + +```yaml +operator: + replicaCount: 1 + securityContext: + runAsUser: 1000 + runAsGroup: 1000 + runAsNonRoot: true + fsGroup: 1000 + seccompProfile: + type: RuntimeDefault + containerSecurityContext: + runAsUser: 1000 + runAsGroup: 1000 + runAsNonRoot: true + allowPrivilegeEscalation: false + readOnlyRootFilesystem: true + capabilities: + drop: + - ALL + seccompProfile: + type: RuntimeDefault + resources: + requests: + cpu: 200m + memory: 256Mi + ephemeral-storage: 500Mi + limits: + memory: 384Mi + ephemeral-storage: 1Gi +``` + +The single replica is a dev overlay decision. A later staging/prod overlay must reassess replicas, PDB, and topology from measured capacity. + +**Step 4: Add the Vault Secrets Operator unit** + +The dev overlay Kustomization must pin: + +```yaml +helmCharts: + - name: vault-secrets-operator + repo: https://helm.releases.hashicorp.com + version: 0.9.0 + releaseName: vault-secrets-operator + namespace: vault-secrets-operator-system + includeCRDs: true + valuesFile: values.yaml +``` + +Move the existing hardened VSO values file unchanged first. Remove the now-empty `gitops/platform/secret-delivery/base/helm` directory from Git. Keep secret-delivery CRs in their existing unit; only the controller chart moves. + +**Step 5: Add the ordered stage and inventory** + +`gitops/clusters/dev/main/stages/05-operators/kustomization.yaml` contains exactly the two dev overlays and the normal dev/platform labels. Insert it between `00-platform` and `10-vault` in `all/kustomization.yaml` and in `tests/kustomize-entrypoints.txt`. + +Do not add these operator overlays to `00-platform`. + +**Step 6: Verify render, schema, and policy** + +Run: + +```bash +bash tests/run.sh operator-render +VALIDATION_PROFILE=full bash scripts/ci/validate.sh +git diff --check +``` + +Expected: operator test exits 0; full validation reports no invalid schema or policy result. If chart download is unavailable, stop and report the external availability failure rather than committing an unrendered chart definition. + +**Step 7: Commit** + +```bash +git add gitops/tenants gitops/platform/minio-operator gitops/platform/vault-secrets-operator gitops/platform/secret-delivery gitops/clusters/dev/main tests +git commit -m "feat: operator desired state를 GitOps로 선언" +``` + +--- + +## Task 4: Put ForwardAuth resources and patches under their real owners + +**Files:** + +- Move: `gitops/platform/forward-auth/component/oauth2-proxy-config.yaml` → `gitops/platform/forward-auth/overlays/dev/oauth2-proxy-config.yaml` +- Move: `gitops/platform/forward-auth/component/oauth2-proxy-ingress.yaml` → `gitops/platform/forward-auth/overlays/dev/oauth2-proxy-ingress.yaml` +- Move: `gitops/platform/forward-auth/component/oauth2-proxy-middleware.yaml` → `gitops/platform/forward-auth/overlays/dev/oauth2-proxy-middleware.yaml` +- Move: `gitops/platform/forward-auth/component/oauth2-proxy-networkpolicy.yaml` → `gitops/platform/forward-auth/overlays/dev/oauth2-proxy-networkpolicy.yaml` +- Delete: `gitops/platform/forward-auth/component/kustomization.yaml` +- Modify: `gitops/platform/forward-auth/overlays/dev/kustomization.yaml` +- Modify: `gitops/apps/auth-server/base/configmap.yaml` +- Modify: `gitops/apps/auth-server/overlays/dev/kustomization.yaml` +- Modify: `gitops/platform/keycloak/overlays/dev/kustomization.yaml` +- Delete: `gitops/platform/keycloak/overlays/dev/ingress-admin.yaml` +- Create: `tests/contracts/dev-ingress-render-test.sh` + +**Step 1: Write the failing rendered-output assertions** + +Render `gitops/clusters/dev/main/stages/50-apps` and `00-platform`. Extract resources by `kind` and `metadata.name` without assuming document order. Assert: + +```text +Ingress/auth-server annotation is exactly: +mnt-oauth2-proxy-errors@kubernetescrd,mnt-oauth2-proxy-auth@kubernetescrd,kube-system-security-headers@kubernetescrd +Ingress/keycloak exists +Ingress/keycloak-admin does not exist +ConfigMap/auth-server-config in the app stage contains SPRING_PROFILES_ACTIVE=dev +ConfigMap/auth-server-config rendered from gitops/apps/auth-server/base does not contain SPRING_PROFILES_ACTIVE +the 00-platform render contains oauth2-proxy ConfigMap, Ingress, Middleware resources, and NetworkPolicy +``` + +Run: + +```bash +bash tests/run.sh dev-ingress-render +``` + +Expected: non-zero because the current auth-server annotation has only the security middleware. + +**Step 2: Refactor the ForwardAuth overlay** + +Change the dev overlay from a Component reference to normal ownership: + +```yaml +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +namespace: mnt + +resources: + - ../../base + - oauth2-proxy-config.yaml + - oauth2-proxy-ingress.yaml + - oauth2-proxy-middleware.yaml + - oauth2-proxy-networkpolicy.yaml +``` + +Delete the component directory after all resources are moved. The platform stage continues to select only `forward-auth/overlays/dev`. + +**Step 3: Move the auth-server patch to the auth-server owner** + +Add this patch to `gitops/apps/auth-server/overlays/dev/kustomization.yaml`: + +```yaml + - target: + kind: Ingress + name: auth-server + patch: |- + - op: replace + path: /metadata/annotations/traefik.ingress.kubernetes.io~1router.middlewares + value: mnt-oauth2-proxy-errors@kubernetescrd,mnt-oauth2-proxy-auth@kubernetescrd,kube-system-security-headers@kubernetescrd +``` + +Remove `SPRING_PROFILES_ACTIVE` from the base ConfigMap and add it through the existing dev ConfigMap patch. + +**Step 4: Remove the dev admin Ingress from desired state** + +Remove `ingress-admin.yaml` from the Keycloak dev Kustomization and delete the file. Do not run `kubectl delete`; live cleanup belongs to Phase 2. + +**Step 5: Verify exact rendered behavior** + +Run: + +```bash +bash tests/run.sh dev-ingress-render +VALIDATION_PROFILE=full bash scripts/ci/validate.sh +git diff --check +``` + +Expected: all commands exit 0. + +**Step 6: Commit** + +```bash +git add gitops/platform/forward-auth gitops/apps/auth-server gitops/platform/keycloak tests/contracts/dev-ingress-render-test.sh +git commit -m "fix: dev ForwardAuth 소유 경계 수정" +``` + +--- + +## Task 5: Record the environment and CI ownership decisions + +**Files:** + +- Create: `docs/adr/0002-dev-environment-and-external-ci-ownership.md` +- Modify: `docs/architecture/repository-structure.md` +- Modify: `README.md` +- Modify: `docs/superpowers/specs/2026-08-02-project-infra-dev-structure-refactor-design.md` +- Modify: `scripts/ci/validate-docs.sh` + +**Step 1: Extend the docs contract so it fails first** + +Add assertions to `scripts/ci/validate-docs.sh` requiring the ADR to contain all of these literals: + +```text +Status: Accepted +gitops/clusters/dev/main +cicd-platform +delivery-platform.yaml +all/ is render/audit-only +``` + +Run: + +```bash +bash scripts/ci/validate-docs.sh +``` + +Expected: non-zero because ADR 0002 does not exist. + +**Step 2: Write the ADR** + +Use sections `Context`, `Decision`, `Consequences`, and `Phase 2 follow-up`. Record: + +- current rollout environment is `dev`, not `lab`; +- dev is controller-less and uses ordered bootstrap stages; +- `all/ is render/audit-only` and is never an apply target; +- project-infra owns local validation contracts; +- `cicd-platform` owns workflow execution, authoritative tools, evidence, and the future `delivery-platform.yaml` consumer onboarding; +- existing workflows remain transitional and are not changed in Phase 1. + +Verify that the approved design document remains marked `Status: approved`. + +**Step 3: Verify documentation** + +Run: + +```bash +bash scripts/ci/validate-docs.sh +git diff --check +``` + +Expected: both commands exit 0. + +**Step 4: Commit** + +```bash +git add docs/adr docs/architecture/repository-structure.md README.md docs/superpowers/specs scripts/ci/validate-docs.sh +git commit -m "docs: dev 및 CI 소유권 결정 기록" +``` + +--- + +## Task 6: Plan-level verification checkpoint + +**Files:** No source changes expected. + +**Step 1: Prove no live mutation was introduced by this plan** + +Run: + +```bash +rg -n 'kubectl[[:space:]]+(apply|delete|replace|patch)|helm[[:space:]]+(install|upgrade|uninstall)' tests scripts/ci scripts/lib +``` + +Expected: no new invocation in files created or modified by this plan. Existing task/bootstrap matches are handled by the next plan. + +**Step 2: Run the complete plan contract** + +Run: + +```bash +bash tests/run.sh +VALIDATION_PROFILE=full bash scripts/ci/validate.sh +bash scripts/ci/validate-docs.sh +git diff --check +git status --short +``` + +Expected: + +- tests and validation exit 0; +- every inventory entrypoint renders with Helm enabled; +- no active `lab` cluster or overlay path remains; +- `git diff --check` emits nothing; +- status contains no unrelated files. + +**Step 3: Inspect commits** + +```bash +git log --oneline --decorate -7 +``` + +Confirm that each task is independently reviewable. Do not squash before the user reviews the phase. diff --git a/docs/superpowers/plans/2026-08-02-project-infra-phase-1-validation-structure.md b/docs/superpowers/plans/2026-08-02-project-infra-phase-1-validation-structure.md new file mode 100644 index 0000000..3d98fee --- /dev/null +++ b/docs/superpowers/plans/2026-08-02-project-infra-phase-1-validation-structure.md @@ -0,0 +1,703 @@ +# Project Infra Phase 1 validation and repository structure Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use `superpowers:subagent-driven-development` (recommended) or `superpowers:executing-plans` to execute this plan task by task. Use `superpowers:test-driven-development` for validator changes and `superpowers:verification-before-completion` before reporting success. + +**Goal:** Make repository-local validation deterministic, put scripts and presentation assets in their declared directory classes, normalize source modes, and remove misleading empty implementation leaves. + +**Architecture:** `make validate` is the developer-friendly local contract and `make check` is the complete repository contract. Project tests and validators remain in this repository; workflow orchestration and authoritative tool provisioning remain owned by `cicd-platform`. Entrypoints live under `scripts/bin` or `scripts/ci`, shared code lives under `scripts/lib`, generated presentation artifacts are ignored, and empty IaC leaves are not represented as implemented environments. + +**Tech Stack:** Make, Bash, Kustomize/Helm, kubeconform, kube-linter, ShellCheck, shfmt, gitleaks, yq 4.47.2, Python 3, python-pptx, Pillow, Git file modes. + +## Global constraints + +- Complete the dev GitOps and automation-safety plans first. +- Read `docs/standards/infra/STYLE.md`, `scripts.md`, `kustomize.md`, and their approved examples before changing validation or examples. +- Do not edit `.github/**`, `.gitea/**`, or `/home/donghyeon/workspace/desktop-server-git/cicd-platform`. +- Treat `.mise.toml` only as a local developer convenience. Do not describe it as the authoritative CI lock. +- Do not create a Terraform/OpenTofu root; deleting placeholder leaves does not choose an IaC engine. +- Keep `docs/examples/infra/**` as the only approved manifest-example catalog. +- Keep `.github/CODEOWNERS.example` unchanged until real organization/team identifiers are supplied. +- Commit only task-scoped files and inspect the worktree before every commit. + +## Dependency and public interfaces + +- `scripts/bin/doctor.sh` is the user-facing tool diagnostic entrypoint. +- `scripts/ci/validate-structure.sh` validates filesystem shape, sensitive file rules, source modes, shell syntax, and activated IaC syntax. +- `scripts/ci/validate.sh` owns the project contract and sources `scripts/lib/kustomize.sh` for every render. +- `VALIDATION_PROFILE=local` may skip optional heavyweight validators with an explicit warning; core render, test, structure, docs-YAML syntax, and path contracts never skip. +- `VALIDATION_PROFILE=full` treats every declared validator as required. +- `make validate` runs the local profile once. `make check` runs the full profile once and fails if a required tool is absent. +- `validate_yaml_fences DOCS_ROOT` is sourceable from `scripts/ci/validate-docs.sh` for fixture tests. + +--- + +## Task 1: Move repository entrypoints into their declared script classes + +**Files:** + +- Move: `scripts/doctor.sh` → `scripts/bin/doctor.sh` +- Move: `scripts/validate.sh` → `scripts/ci/validate-structure.sh` +- Modify: `scripts/bin/doctor.sh` +- Modify: `scripts/ci/validate-structure.sh` +- Modify: `Makefile` +- Modify: `scripts/README.md` +- Modify: `README.md` +- Create: `tests/contracts/script-layout-test.sh` + +**Step 1: Write the failing layout and entrypoint contract** + +Assert: + +```text +scripts/doctor.sh is absent +scripts/validate.sh is absent +scripts/bin/doctor.sh exists and is executable +scripts/ci/validate-structure.sh exists and is executable +both moved files contain set -Eeuo pipefail and IFS=$'\n\t' +both moved files define main and end through a main "$@" guard +doctor defines usage and accepts -h/--help +Makefile references only the moved paths +make -n validate includes local project validation exactly once +make -n check includes VALIDATION_PROFILE=full exactly once +``` + +Run: + +```bash +bash tests/run.sh script-layout +``` + +Expected: non-zero because both scripts still live at the `scripts/` root. + +**Step 2: Move and refactor the scripts** + +Use `git mv`. Both scripts must calculate the repository root from their new location: + +```bash +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" +``` + +Wrap the current top-level validation flow in named functions and: + +```bash +main() { + validate_required_structure + validate_directory_names + collect_sources + validate_sensitive_sources + validate_secret_manifests + validate_replacement_tokens + validate_source_modes + validate_shell_syntax + validate_kustomization_layout + validate_local_helm_charts + validate_activated_iac + report_result +} + +if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then + main "$@" +fi +``` + +Use these exact function names so the entrypoint contract can call them independently. Preserve existing checks; do not silently drop the Secret/SOPS, replacement-token, Helm, or IaC checks while refactoring. + +Remove the Kustomize render loop from `validate-structure.sh`; deployable render ownership already lives in `scripts/ci/validate.sh` and rendering every catalog Kustomization is a duplicate network-heavy contract. Keep Kustomization path/type checks in structure validation, keep local `Chart.yaml` linting, and make `script-layout-test.sh` assert that `validate-structure.sh` contains neither `kustomize build` nor `kubectl kustomize`. + +**Step 3: Separate Make local and full profiles** + +The Make targets must have these exact dependencies and recipe commands: + +- `doctor` runs `@./scripts/bin/doctor.sh`. +- `validate-structure` runs `@./scripts/ci/validate-structure.sh`. +- `validate-project` runs `@VALIDATION_PROFILE=local bash ./scripts/ci/validate.sh`. +- `validate` depends on `validate-structure validate-project` and has no second recipe. +- `check` depends on `validate-structure` and runs `@VALIDATION_PROFILE=full bash ./scripts/ci/validate.sh`. + +Keep the existing help comments so `make help` remains useful. + +**Step 4: Verify and commit** + +Run: + +```bash +bash tests/run.sh script-layout +bash -n scripts/bin/doctor.sh scripts/ci/validate-structure.sh +make -n validate +make -n check +git diff --check +``` + +Expected: all commands exit 0. + +```bash +git add scripts Makefile README.md tests/contracts/script-layout-test.sh +git commit -m "refactor: 스크립트 entrypoint 위치 정규화" +``` + +--- + +## Task 2: Remove false implementation leaves, fix copy examples, and normalize modes + +**Files:** + +- Delete: `infrastructure/components/compute/README.md` +- Delete: `infrastructure/components/database/README.md` +- Delete: `infrastructure/components/networking/README.md` +- Delete: `infrastructure/components/storage/README.md` +- Delete: `infrastructure/live/dev/cluster/README.md` +- Delete: `infrastructure/live/staging/cluster/README.md` +- Delete: `infrastructure/live/prod/cluster/README.md` +- Modify: `infrastructure/components/README.md` +- Modify: `infrastructure/live/README.md` +- Modify: `README.md` +- Modify: `docs/guides/getting-started.md` +- Modify: `scripts/ci/validate-structure.sh` +- Create: `tests/contracts/repository-shape-test.sh` +- Change mode: every tracked `*.yaml` and `*.yml` source to `100644` + +**Step 1: Write the failing shape and mode contract** + +Assert: + +```text +the seven placeholder leaf directories listed above do not exist +the three parent roots and all `_template` roots still exist +tracked YAML/YML files have Git mode 100644 +tracked shell files under scripts have Git mode 100755 +no copy instruction uses `cp -R SOURCE/_template DEST` +copy instructions create a destination then copy `SOURCE/_template/.` into it +CODEOWNERS.example still exists +``` + +Run: + +```bash +bash tests/run.sh repository-shape +``` + +Expected: non-zero because placeholder leaves and thirteen executable YAML/value files exist. + +**Step 2: Remove only the misleading leaves** + +Delete the seven tracked README files and their now-empty directories. Update the parent READMEs to state: + +- `components/` exists only when a real reusable primitive is selected; +- `live//` exists only when an executable plan/apply root and backend decision exist; +- the IaC engine/provider/backend/state choice remains a Phase 2 decision. + +Do not remove `infrastructure/components/_template`, `infrastructure/live/_template`, parent README files, or `infrastructure/.iac-engine.example`. + +**Step 3: Correct template copy commands** + +For each README/getting-started example, use this pattern with its actual destination: + +```bash +mkdir -p infrastructure/live/dev/cluster +cp -R infrastructure/live/_template/. infrastructure/live/dev/cluster/ +``` + +Apply the same `mkdir -p` plus `SOURCE/. DEST/` form to component, stack, app, platform, and cluster examples. Use `gitops/clusters/staging/main` for the new-cluster example so instructions never copy a template over the active `gitops/clusters/dev/main`. This behaves the same whether a new destination was just created or already exists and never creates a nested `_template` directory. + +**Step 4: Normalize tracked source modes** + +Use `chmod 0644` on every tracked Kubernetes YAML, Kustomization YAML, and Helm values YAML. In particular verify the moved VSO values file, registry base, secret-delivery base, and Vault base files identified by the review. + +Add a structural check based on the Git index: + +```bash +while IFS= read -r -d '' record; do + mode="${record%% *}" + path="${record#*$'\t'}" + case "$path" in + *.yaml | *.yml) + [[ "$mode" == "100644" ]] || fail "YAML source must be mode 0644: $path" + ;; + scripts/*.sh) + [[ "$mode" == "100755" ]] || fail "script must be executable: $path" + ;; + esac +done < <(git ls-files -s -z) +``` + +Handle untracked files in the existing source collection separately with filesystem modes so the check also fails before a new file is added to Git. + +**Step 5: Verify and commit** + +Run: + +```bash +bash tests/run.sh repository-shape +./scripts/ci/validate-structure.sh +git ls-files -s '*.yaml' '*.yml' | awk '$1 != "100644" {print; bad=1} END {exit bad}' +git diff --check +``` + +Expected: all commands exit 0 and the mode query prints nothing. + +```bash +git add infrastructure README.md docs/guides/getting-started.md scripts/ci/validate-structure.sh tests/contracts/repository-shape-test.sh gitops +git commit -m "refactor: 저장소 shape과 YAML mode 정규화" +``` + +--- + +## Task 3: Enforce inventory, render, and source-of-truth contracts + +**Files:** + +- Modify: `scripts/ci/validate.sh` +- Modify: `tests/kustomize-entrypoints.txt` +- Create: `tests/contracts/entrypoint-inventory-test.sh` +- Modify: `tests/README.md` + +**Step 1: Write the failing inventory contract** + +Derive actual dev entrypoints from these paths: + +```text +gitops/clusters/dev/main/namespaces/kustomization.yaml +gitops/clusters/dev/main/all/kustomization.yaml +gitops/clusters/dev/main/stages/*/kustomization.yaml +``` + +Convert them to parent-directory paths, sort them, and compare byte-for-byte with the sorted non-comment inventory. Assert additionally: + +```text +no cluster Kustomization outside gitops/clusters/_template imports a `_template` path +no scripts/bin or scripts/tasks file references the cluster `all` entrypoint +no deployable rendered output contains example.com/environment: lab +every inventory entry renders only through kustomize_render +every render has no replace-in-overlay or __REPLACE_ME_* token +the existing ForwardAuth, Keycloak admin, and operator render contracts run from the full test runner +``` + +Run: + +```bash +bash tests/run.sh entrypoint-inventory +``` + +Expected: non-zero if the inventory is stale or if the current validator still has a second render implementation. + +**Step 2: Add deterministic inventory validation** + +Add `validate_entrypoint_inventory` before manifest rendering in `scripts/ci/validate.sh`. It must use temporary sorted files and `diff -u`; on mismatch, print the unified diff and fail the validation group. + +Reject template imports with `rg` over cluster Kustomization source and reject apply-code references to `/all` with `rg` over `scripts/bin` and `scripts/tasks`. Exclude comments only when the parser proves the line is a comment; do not broadly suppress files. + +**Step 3: Run contract tests from project validation** + +Add: + +```bash +validate_contract_tests() { + bash "$REPO_ROOT/tests/run.sh" +} +``` + +Call it once from `main`. Do not run tests a second time through Make dependencies. + +**Step 4: Make full-profile tool requirements explicit** + +In full mode require: + +```text +kustomize +helm +kubeconform +kube-linter +shellcheck +shfmt +gitleaks +yq +rg +``` + +In local mode, Kustomize/Helm rendering, Bash contract tests, `rg`, and `yq` remain core and cannot skip. kubeconform, kube-linter, ShellCheck, shfmt, and gitleaks may skip only with a warning that names the missing tool. + +**Step 5: Verify and commit** + +Run: + +```bash +bash tests/run.sh entrypoint-inventory +VALIDATION_PROFILE=local bash scripts/ci/validate.sh +git diff --check +``` + +Expected: all commands exit 0 and each contract test runs exactly once. + +```bash +git add scripts/ci/validate.sh tests/kustomize-entrypoints.txt tests/contracts/entrypoint-inventory-test.sh tests/README.md +git commit -m "test: GitOps entrypoint 계약 강화" +``` + +--- + +## Task 4: Validate every documentation YAML fence + +**Files:** + +- Modify: `.mise.toml` +- Modify: `scripts/bin/doctor.sh` +- Modify: `scripts/ci/validate-docs.sh` +- Create: `tests/contracts/docs-yaml-test.sh` +- Modify: Markdown files under `docs/**` only where a block is genuinely abbreviated, intentionally bad, or fails the approved example standards +- Modify: `docs/standards/infra/STYLE.md` to document the example marker contract + +**Step 1: Write fixture-driven failing tests** + +Refactor `validate-docs.sh` so sourcing it does not execute `main`. The test sources it and calls `validate_yaml_fences` on temporary Markdown fixtures. Cover: + +```text +valid YAML fence succeeds +syntactically invalid YAML fence fails even below a bad-example heading +unclosed YAML fence fails +normal complete Kubernetes resource invokes fake yq, kubeconform, and kube-linter +`나쁜 예시`, `❌`, and case-insensitive `bad example` headings invoke yq but skip schema/policy +an immediately preceding `` marker invokes yq but skips schema/policy +a normal complete resource that fails kubeconform fails the docs gate +a normal complete resource that fails kube-linter fails the docs gate +an abbreviated marker cannot suppress YAML syntax failure +``` + +Run: + +```bash +bash tests/run.sh docs-yaml +``` + +Expected: non-zero because current docs validation does not parse fenced YAML. + +**Step 2: Pin and diagnose the syntax parser** + +Add this local convenience pin: + +```toml +yq = "4.47.2" +``` + +Add `yq` to required doctor tools. In both local and full validation profiles, a missing yq is a hard error because syntax checking is a core docs contract. Document that `cicd-platform`, not `.mise.toml`, owns authoritative CI tool delivery. + +**Step 3: Implement fence extraction and classification** + +`validate_yaml_fences DOCS_ROOT` must read every tracked or present `*.md` file under the supplied root in sorted order and maintain: + +```text +current Markdown heading +whether a YAML/YML fence is open +source file and opening line +whether the immediately preceding non-blank line is the abbreviated marker +whether the current heading contains 나쁜 예시, ❌, or bad example +``` + +For every closed fence: + +1. write the exact block to a mode-`0600` temporary file; +2. run `yq eval-all '.' BLOCK` unconditionally; +3. classify it as a complete Kubernetes example only when it is not bad/abbreviated and every YAML document contains non-empty `apiVersion`, `kind`, and `metadata.name`; +4. run kubeconform with the repository CRD catalog configuration for complete examples; +5. run kube-linter with `.kube-linter.yaml` for complete examples; +6. report `file:opening-line` on every failure. + +Bad and abbreviated classifications skip only steps 4 and 5. They never skip syntax. + +Add this exact marker contract to `STYLE.md`: + +````markdown + +```yaml +apiVersion: apps/v1 +kind: Deployment +``` +```` + +When documenting the marker inside a Markdown fence, use a four-backtick outer fence so the standard itself remains well formed. + +**Step 4: Audit existing docs instead of blanket-suppressing them** + +Run the new gate. For each failure: + +- fix syntax in normal examples; +- fix security/resource/probe defects in examples presented as approved; +- add the abbreviated marker only when omitted fields are intentional prose; +- rely on a bad-example heading only for genuinely rejected examples. + +Do not mark all files or all standards as abbreviated. `docs/examples/infra/**` normal good examples must pass schema/policy when complete. + +**Step 5: Verify and commit** + +Run: + +```bash +bash tests/run.sh docs-yaml +VALIDATION_PROFILE=full bash scripts/ci/validate-docs.sh +git diff --check +``` + +Expected: all commands exit 0; validation output reports YAML fence count and complete Kubernetes example count. + +```bash +git add .mise.toml scripts/bin/doctor.sh scripts/ci/validate-docs.sh tests/contracts/docs-yaml-test.sh docs +git commit -m "test: 문서 YAML 예시 검증 추가" +``` + +--- + +## Task 5: Move and make the presentation source reproducible + +**Files:** + +- Move: `presentation/diagrams/**` → `docs/presentation/diagrams/**` +- Move: `presentation/exports/**` → `docs/presentation/exports/**` +- Move: `presentation/scripts/build_pptx.py` → `docs/presentation/scripts/build_pptx.py` +- Delete after move: `docs/presentation/build/deck.pptx` +- Create: `docs/presentation/README.md` +- Create: `docs/presentation/requirements.txt` +- Modify: `docs/presentation/scripts/build_pptx.py` +- Modify: `.gitignore` +- Create: `tests/contracts/presentation-layout-test.sh` + +**Step 1: Write the failing layout/reproducibility contract** + +Assert: + +```text +root presentation directory is absent +docs/presentation exists +docs/presentation/build is ignored +no PPTX file is tracked under docs/presentation +build_pptx.py contains no /home/ path +ROOT is derived from Path(__file__).resolve().parents[1] +requirements pin python-pptx and Pillow with == +every PNG path referenced by build_pptx.py exists under exports +the script compiles with python3 -m py_compile +``` + +Run: + +```bash +bash tests/run.sh presentation-layout +``` + +Expected: non-zero because the material is at repository root and the Python script hardcodes an absolute path. + +**Step 2: Move source assets and remove the generated deck** + +Use `git mv presentation docs/presentation`, then remove the tracked `docs/presentation/build/deck.pptx`. Add: + +```gitignore +docs/presentation/build/ +``` + +Keep every Draw.io source and every PNG referenced by the Python script. Do not delete source inputs merely to reduce repository size. + +**Step 3: Make paths dynamic and dependencies explicit** + +Replace the hardcoded root with: + +```python +ROOT = Path(__file__).resolve().parents[1] +EXPORTS = ROOT / "exports" +OUT = ROOT / "build" / "deck.pptx" +``` + +Use this `requirements.txt`: + +```text +python-pptx==1.0.2 +Pillow==11.3.0 +``` + +The README must contain commands that work from any checkout: + +```bash +python3 -m venv docs/presentation/.venv +docs/presentation/.venv/bin/pip install --requirement docs/presentation/requirements.txt +docs/presentation/.venv/bin/python docs/presentation/scripts/build_pptx.py +``` + +Explain that `build/deck.pptx` is generated and untracked. + +**Step 4: Rebuild once in an isolated environment** + +Create a temporary virtual environment outside the repository, install the pinned requirements, run the script, and validate the generated ZIP container: + +```bash +PRESENTATION_VENV="$(mktemp -d -t project-infra-presentation.XXXXXX)" +python3 -m venv "$PRESENTATION_VENV" +"$PRESENTATION_VENV/bin/pip" install --requirement docs/presentation/requirements.txt +"$PRESENTATION_VENV/bin/python" docs/presentation/scripts/build_pptx.py +"$PRESENTATION_VENV/bin/python" -m zipfile -t docs/presentation/build/deck.pptx +``` + +If dependency download is unavailable, request network approval or use an already-populated package cache; do not claim reproducibility without a successful build. The generated deck remains ignored and is not committed. + +**Step 5: Verify and commit** + +Run: + +```bash +bash tests/run.sh presentation-layout +git check-ignore docs/presentation/build/deck.pptx +git ls-files 'docs/presentation/*.pptx' 'docs/presentation/**/*.pptx' +git diff --check +``` + +Expected: tests and ignore check exit 0; `git ls-files` prints nothing. + +```bash +git add .gitignore docs/presentation tests/contracts/presentation-layout-test.sh +git commit -m "docs: presentation source를 문서 트리로 이동" +``` + +--- + +## Task 6: Finalize Make validation semantics and repository documentation + +**Files:** + +- Modify: `Makefile` +- Modify: `README.md` +- Modify: `docs/validation-report.md` +- Modify: `docs/architecture/repository-structure.md` +- Modify: `scripts/README.md` +- Modify: `tests/README.md` +- Modify: `.mise.toml` only if tool names changed during implementation +- Create: `tests/contracts/make-contract-test.sh` + +**Step 1: Write the failing Make contract** + +Use temporary fake validators to prove: + +```text +make validate uses VALIDATION_PROFILE=local +make validate emits a warning and succeeds when an optional validator is absent +make check uses VALIDATION_PROFILE=full +make check fails when any declared full validator is absent +make check runs structure, contracts, manifests, docs, shell, and secret groups once each +neither target invokes or references a workflow file +documentation states that cicd-platform owns authoritative CI execution and tool delivery +documentation does not claim delivery-platform.yaml is already active for project-infra +``` + +Run: + +```bash +bash tests/run.sh make-contract +``` + +Expected: non-zero until final Make and validation reporting are aligned. + +**Step 2: Update validation documentation with exact ownership** + +Document these commands: + +```bash +make doctor +make validate +make check +``` + +State explicitly: + +- `make validate` is the friendly local profile; +- `make check` is the complete repository contract and fails on missing tools; +- `.mise.toml` helps developers reproduce the expected tool set locally; +- the sibling `cicd-platform` repository owns triggers, runners, authoritative versions, evidence publication, and future consumer onboarding; +- current project workflow definitions are transitional and were not modified by Phase 1. + +Update `docs/validation-report.md` with the final gate list and the date of the latest local run. Do not record a passing result until Task 7 produces it. + +**Step 3: Verify and commit** + +Run: + +```bash +bash tests/run.sh make-contract +make validate +git diff --check +``` + +Expected: tests and local validation exit 0. + +```bash +git add Makefile README.md docs/validation-report.md docs/architecture/repository-structure.md scripts/README.md tests/README.md .mise.toml tests/contracts/make-contract-test.sh +git commit -m "docs: 로컬 검증과 중앙 CI 경계 명확화" +``` + +--- + +## Task 7: Full Phase 1 verification and review handoff + +**Files:** + +- Modify: `docs/validation-report.md` only with observed results + +**Step 1: Confirm no forbidden workflow or sibling-repository change** + +Run: + +```bash +git diff --name-only d0d90ea..HEAD -- .github .gitea +git -C /home/donghyeon/workspace/desktop-server-git/cicd-platform status --short +``` + +Expected: the first command prints nothing. The second is read-only evidence; do not modify or clean any pre-existing sibling worktree changes. + +**Step 2: Run the complete repository contract** + +Run: + +```bash +make check +``` + +Expected: exit 0 with no skipped validator. Capture the group summary, not secrets or rendered Secret data, in `docs/validation-report.md`. + +**Step 3: Run acceptance searches** + +```bash +find gitops/clusters gitops/apps gitops/platform gitops/policies -type d -name lab -print +rg -n 'example.com/environment:[[:space:]]*lab' gitops +rg -n 'vault login|\.vault-token|vault_exec_sh|vault_login_root_from_keyfile' scripts bootstrap +rg -n 'helm[[:space:]]+(install|upgrade|uninstall)' scripts +git ls-files -s '*.yaml' '*.yml' | awk '$1 != "100644" {print; bad=1} END {exit bad}' +git ls-files 'docs/presentation/*.pptx' 'docs/presentation/**/*.pptx' +git diff --check +``` + +Expected: every search prints nothing and every command exits 0. + +**Step 4: Review the final diff by ownership area** + +Run: + +```bash +git diff --stat d0d90ea..HEAD +git diff --name-status d0d90ea..HEAD +git status --short --branch +``` + +Review separately: + +1. dev path/labels and operator GitOps; +2. ForwardAuth/Keycloak rendered security; +3. context/Vault/bootstrap/teardown safety; +4. validation and docs; +5. repository moves, deletes, and file modes. + +Confirm that no live-cluster mutation was performed during implementation. + +**Step 5: Record evidence and commit** + +Update `docs/validation-report.md` only with the commands actually run, their observed status, and any environment prerequisites. Then: + +```bash +git add docs/validation-report.md +git commit -m "docs: Phase 1 검증 결과 기록" +``` + +Do not merge, push, or start Phase 2 without a separate user decision. diff --git a/docs/superpowers/specs/2026-08-02-project-infra-dev-structure-refactor-design.md b/docs/superpowers/specs/2026-08-02-project-infra-dev-structure-refactor-design.md index fcf3505..66c7ad8 100644 --- a/docs/superpowers/specs/2026-08-02-project-infra-dev-structure-refactor-design.md +++ b/docs/superpowers/specs/2026-08-02-project-infra-dev-structure-refactor-design.md @@ -2,7 +2,7 @@ Date: 2026-08-02 -Status: proposed for written-spec review +Status: approved Target repository: `project-infra`