docs: Phase 1 구현 계획 추가

This commit is contained in:
DongHyeonka
2026-08-28 17:04:21 +09:00
parent da159dcd22
commit 80f58cd9f2
4 changed files with 2072 additions and 1 deletions
@@ -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/<unit>` exists only when a real reusable primitive is selected;
- `live/<env>/<root>` 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 `<!-- infra-example: abbreviated -->` 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
<!-- infra-example: abbreviated -->
```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.