diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 1bd01a5..c706c2e 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -33,6 +33,8 @@ dotnet format AgenticDevOps.sln --verify-no-changes dotnet test AgenticDevOps.sln -c Release --settings coverlet.runsettings terraform -chdir=infra fmt -check -recursive terraform -chdir=infra validate +terraform -chdir=infra/bootstrap fmt -check -recursive +terraform -chdir=infra/bootstrap validate gh aw compile ``` diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 773e171..2a0b184 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -71,27 +71,36 @@ jobs: path: ./TestResults retention-days: 7 + markdown-lint: + name: Markdown lint + uses: DevOpsDerek/workflows/.github/workflows/lint-markdown.yml@30f86d7d8536364b0a31de4bc66214b39e8785bb + with: + node-version: 22.15.0 + markdownlint-cli2-version: 0.17.2 + working-directory: . + markdown-paths: '{README.md,AGENTS.md,SECURITY.md,docs/SETUP.md,.github/copilot-instructions.md,.github/workflows/ci-doctor.md,.github/workflows/doc-updater.md,.github/workflows/test-improver.md,.github/workflows/issue-triage.md}' + terraform: - name: Terraform lint & validate + name: Terraform lint & validate (infra) + uses: DevOpsDerek/workflows/.github/workflows/lint-terraform.yml@30f86d7d8536364b0a31de4bc66214b39e8785bb + with: + terraform-version: 1.15.6 + working-directory: infra + + terraform-bootstrap: + name: Terraform lint & validate (bootstrap) + uses: DevOpsDerek/workflows/.github/workflows/lint-terraform.yml@30f86d7d8536364b0a31de4bc66214b39e8785bb + with: + terraform-version: 1.15.6 + working-directory: infra/bootstrap + + tflint: + name: TFLint runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v4 - - name: Setup Terraform - uses: hashicorp/setup-terraform@v3 - with: - terraform_version: 1.15.6 - - - name: Terraform format check - run: terraform -chdir=infra fmt -recursive -check - - - name: Terraform init (no backend) - run: terraform -chdir=infra init -backend=false -input=false - - - name: Terraform validate - run: terraform -chdir=infra validate -no-color - - name: Setup TFLint uses: terraform-linters/setup-tflint@v4 with: @@ -99,8 +108,10 @@ jobs: - name: TFLint run: | - tflint --chdir=infra --init - tflint --chdir=infra -f compact + tflint --chdir=infra --config="${GITHUB_WORKSPACE}/infra/.tflint.hcl" --init + tflint --chdir=infra --config="${GITHUB_WORKSPACE}/infra/.tflint.hcl" -f compact + tflint --chdir=infra/bootstrap --config="${GITHUB_WORKSPACE}/infra/.tflint.hcl" --init + tflint --chdir=infra/bootstrap --config="${GITHUB_WORKSPACE}/infra/.tflint.hcl" -f compact security-scan: name: Trivy IaC & filesystem scan @@ -169,15 +180,6 @@ jobs: # generated and validated by gh-aw's own compiler. files: ".github/workflows/cd.yml, .github/workflows/ci.yml, .github/workflows/codeql.yml, .github/workflows/deploy.yml" - - name: markdownlint - uses: DavidAnson/markdownlint-cli2-action@v19 - with: - globs: | - **/*.md - !.github/workflows/** - !.github/aw/imports/** - !**/node_modules/** - - name: Validate agentic sources and locks uses: DevOpsDerek/workflows/.github/actions/validate-agentic-workflows@dac4b81c298cb3ea6821ea312efa5375f42d5ccb with: diff --git a/.github/workflows/doc-updater.lock.yml b/.github/workflows/doc-updater.lock.yml index ef130a1..a5491cb 100644 --- a/.github/workflows/doc-updater.lock.yml +++ b/.github/workflows/doc-updater.lock.yml @@ -1,4 +1,4 @@ -# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"9cd31b97f6d617fecea0b875ff44a0b634d1ff37f64e5ad652e7701a20c7664e","body_hash":"9c6b93eb17e34b30844e3fb818d5fac28380d3db6053c4f5d702b7f80e8bd20b","compiler_version":"v0.89.21","strict":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.87"}} +# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"c9a678aacfcbbab572794726ba23187dcb8d2780a2793c163bc0c3902d286885","body_hash":"c8ee58fdfda9f83a5311fcee503314413cd5d0aa9d0c17ee8d9073af63fb7e8f","compiler_version":"v0.89.21","strict":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.87"}} # gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_CI_TRIGGER_TOKEN","GH_AW_DEFAULT_OTLP_ENDPOINT","GH_AW_DEFAULT_OTLP_HEADERS","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"3d3c42e5aac5ba805825da76410c181273ba90b1","version":"v7.0.1"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"820762786026740c76f36085b0efc47a31fe5020","version":"v7.0.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"924af5fdc64061cfbf66fb584c8b07e2ac230c60","version":"v0.89.21"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.28.23","digest":"sha256:2c78aaba1c108e130e2d6d01e4f2cca334ea04c53e6f258913ac34173fe7e3b2","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.28.23@sha256:2c78aaba1c108e130e2d6d01e4f2cca334ea04c53e6f258913ac34173fe7e3b2"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.28.23","digest":"sha256:c15c3d1208df10c5b588a3657be53742aa982ae0909d1eb1812525268794ca64","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.28.23@sha256:c15c3d1208df10c5b588a3657be53742aa982ae0909d1eb1812525268794ca64"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.28.23","digest":"sha256:02ffc56dd40158223064ef03a78c2d9717473c93723b4e403d455ea6f0b09ae0","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.28.23@sha256:02ffc56dd40158223064ef03a78c2d9717473c93723b4e403d455ea6f0b09ae0"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.4.25","digest":"sha256:9be0a86220e807a0ecc89e53d7453468f7a53fbc6b3d1efd2299025ffe01d086","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.4.25@sha256:9be0a86220e807a0ecc89e53d7453468f7a53fbc6b3d1efd2299025ffe01d086"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:11c2c544876cdbaffefe0f7f1283b74838c209654b4934a78e23ed11f750490f","pinned_image":"ghcr.io/github/gh-aw-node@sha256:11c2c544876cdbaffefe0f7f1283b74838c209654b4934a78e23ed11f750490f"},{"image":"ghcr.io/github/github-mcp-server:v1.12.2","digest":"sha256:508a0857ec762b1ab1cece29193345b501fab1dd9d1228a7b617062954cecac6","pinned_image":"ghcr.io/github/github-mcp-server:v1.12.2@sha256:508a0857ec762b1ab1cece29193345b501fab1dd9d1228a7b617062954cecac6"}],"mcp_servers":[{"name":"github","tools":["get_commit","get_file_contents","get_latest_release","get_me","get_pull_request","get_pull_request_comments","get_pull_request_diff","get_pull_request_files","get_pull_request_review_comments","get_pull_request_reviews","get_pull_request_status","get_release_by_tag","get_tag","issue_read","list_branches","list_commits","list_issue_types","list_issues","list_pull_requests","list_releases","list_starred_repositories","list_tags","pull_request_read","search_code","search_issues","search_pull_requests","search_repositories"]},{"name":"safeoutputs","tools":["create_pull_request","missing_data","missing_tool","noop"]}]} # This file was automatically generated by gh-aw (v0.89.21). DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md # @@ -289,7 +289,7 @@ jobs: GH_AW_PROMPT_CONTENT_0003: "\nThe following GitHub context information is available for this workflow:\n{{#if github.actor}}\n- **actor**: __GH_AW_GITHUB_ACTOR__\n{{/if}}\n{{#if github.repository}}\n- **repository**: __GH_AW_GITHUB_REPOSITORY__\n{{/if}}\n{{#if github.workspace}}\n- **workspace**: __GH_AW_GITHUB_WORKSPACE__\n{{/if}}\n{{#if github.event.issue.number || (github.aw.context.item_type == 'issue' && github.aw.context.item_number)}}\n- **issue-number**: #__GH_AW_EXPR_802A9F6A__\n{{/if}}\n{{#if github.event.discussion.number || (github.aw.context.item_type == 'discussion' && github.aw.context.item_number)}}\n- **discussion-number**: #__GH_AW_EXPR_1A3A194A__\n{{/if}}\n{{#if github.event.pull_request.number || (github.aw.context.item_type == 'pull_request' && github.aw.context.item_number)}}\n- **pull-request-number**: #__GH_AW_EXPR_463A214A__\n{{/if}}\n{{#if github.event.comment.id || github.aw.context.comment_id}}\n- **comment-id**: __GH_AW_EXPR_FF1D34CE__\n{{/if}}\n{{#if github.run_id}}\n- **workflow-run-id**: __GH_AW_GITHUB_RUN_ID__\n{{/if}}\n\n\n" GH_AW_PROMPT_CONTENT_0004: "\n" GH_AW_PROMPT_CONTENT_0005: "Compare the repository's public documentation with recent, directly relevant\ncode changes. Propose at most one small documentation-only correction where\nthe mismatch is clear. Do not infer undocumented product behavior, edit source\ncode, update generated API references without running their documented\ngenerator, or include secrets, credentials, or private user data.\n\nRun the repository's relevant documentation checks and report their exact\nresults in the draft pull request. A draft pull request is a proposal for human\nreview and must never be merged automatically.\n" - GH_AW_PROMPT_CONTENT_0006: "# Documentation Updater Agent\n\nFollow the imported documentation-upkeep policy after changes land on `main`\nunder `src/`, `infra/`, or `.github/workflows/`.\n\n## Repository context\n\nThe .NET 10 API lives in `src/Api`, tests in `tests/Api.Tests`, Azure Container\nApps Terraform in `infra`, and workflows in `.github/workflows`. Check `README.md`,\n`docs/`, `.github/copilot-instructions.md`, and `AGENTS.md` against the triggering\ncommit. Preserve the existing tone and structure.\n\nOnly edit documentation Markdown, not `.github/workflows/*.md` sources,\napplication code, Terraform, Dockerfiles, or generated references. Preserve the\ndocumented OIDC-only authentication, environment-specific state, and production\nreviewer requirements; never deploy, promote, apply infrastructure, or publish.\n\nUse a title like `docs: sync documentation with recent changes`. CI checks\ndocumentation with markdownlint-cli2, excluding `.github/workflows/**`. Report\nthe exact check result or explicitly state if the tool is unavailable; do not\nclaim a check passed without executing it.\n\n" + GH_AW_PROMPT_CONTENT_0006: "# Documentation Updater Agent\n\nFollow the imported documentation-upkeep policy after changes land on `main`\nunder `src/`, `infra/`, or `.github/workflows/`.\n\n## Repository context\n\nThe .NET 10 API lives in `src/Api`, tests in `tests/Api.Tests`, Azure Container\nApps Terraform in `infra`, and workflows in `.github/workflows`. Check `README.md`,\n`docs/`, `.github/copilot-instructions.md`, and `AGENTS.md` against the triggering\ncommit. Preserve the existing tone and structure.\n\nOnly edit documentation Markdown, not `.github/workflows/*.md` sources,\napplication code, Terraform, Dockerfiles, or generated references. Preserve the\ndocumented OIDC-only authentication, environment-specific state, and production\nreviewer requirements; never deploy, promote, apply infrastructure, or publish.\n\nUse a title like `docs: sync documentation with recent changes`. CI checks\nauthored Markdown, including these workflow sources, with the pinned central\nmarkdownlint-cli2 workflow. Cached `.github/aw/imports/` files are excluded;\ngenerated `*.lock.yml` files are not Markdown inputs and are validated by\n`gh aw compile`. Report the exact check result or explicitly state if the tool\nis unavailable; do not claim a check passed without executing it.\n\n" with: script: | const { setupGlobals } = require(process.env.GH_AW_ACTIONS_DIR + '/setup_globals.cjs'); diff --git a/.github/workflows/doc-updater.md b/.github/workflows/doc-updater.md index 5635b67..143cd75 100644 --- a/.github/workflows/doc-updater.md +++ b/.github/workflows/doc-updater.md @@ -57,6 +57,8 @@ documented OIDC-only authentication, environment-specific state, and production reviewer requirements; never deploy, promote, apply infrastructure, or publish. Use a title like `docs: sync documentation with recent changes`. CI checks -documentation with markdownlint-cli2, excluding `.github/workflows/**`. Report -the exact check result or explicitly state if the tool is unavailable; do not -claim a check passed without executing it. +authored Markdown, including these workflow sources, with the pinned central +markdownlint-cli2 workflow. Cached `.github/aw/imports/` files are excluded; +generated `*.lock.yml` files are not Markdown inputs and are validated by +`gh aw compile`. Report the exact check result or explicitly state if the tool +is unavailable; do not claim a check passed without executing it. diff --git a/AGENTS.md b/AGENTS.md index 77f6fa6..8267f5e 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -15,7 +15,8 @@ via Terraform, with GitHub Actions CI/CD and four agentic workflows. | Format check | `dotnet format AgenticDevOps.sln --verify-no-changes` | | Build | `dotnet build AgenticDevOps.sln -c Release` | | Test + coverage | `dotnet test AgenticDevOps.sln -c Release --settings coverlet.runsettings` | -| Terraform validate | `terraform -chdir=infra validate` | +| Terraform format check | `terraform -chdir=infra fmt -recursive -check` and `terraform -chdir=infra/bootstrap fmt -recursive -check` | +| Terraform validate | `terraform -chdir=infra validate` and `terraform -chdir=infra/bootstrap validate` | | Compile agentic workflows | `gh aw compile` | ## Directory map @@ -23,6 +24,7 @@ via Terraform, with GitHub Actions CI/CD and four agentic workflows. - `src/Api/` — application code (edit here for feature/bug work). - `tests/Api.Tests/` — tests (the **Test Improver** agent only edits here). - `infra/` — Terraform; `infra/envs/` holds per-environment vars and backends. +- `infra/bootstrap/` — separate Terraform root for remote state and OIDC setup. - `.github/workflows/` — CI/CD YAML and agentic `*.md` + generated `*.lock.yml`. - `docs/` — setup and supporting documentation (the **Doc Updater** agent edits Markdown only). diff --git a/README.md b/README.md index 22e5049..f544a4c 100644 --- a/README.md +++ b/README.md @@ -50,7 +50,7 @@ catalog at runtime. Local sources retain repository-specific triggers, paths, commands, read permissions, and bounded safe-output configuration; do not copy shared implementations into this repository. -The adopted catalog revision is +The agentic imports and validator retain catalog revision `dac4b81c298cb3ea6821ea312efa5375f42d5ccb`. Issue triage now proposes an existing label and issue type in one comment rather @@ -64,12 +64,23 @@ The existing `meta-lint` CI job also calls the central `.github/actions/validate-agentic-workflows` action using the same immutable catalog revision. It validates and recompiles gh-aw sources and fails on stale locks with only `contents: read`. A caller-side check also rejects changes to -`.github/aw/actions-lock.json` produced by compilation. Existing .NET build/format/test and 60% coverage -gate, test artifacts, Terraform/TFLint, Trivy, CodeQL, workflow/Markdown linting, -check names, and triggers remain unchanged. The catalog's narrow checked-script -helper is not a replacement for this multi-step CI. CD and environment-bound -OIDC promotion remain unchanged; configure the protected production environment -and required reviewers as described in [setup](docs/SETUP.md) before deployment. +`.github/aw/actions-lock.json` produced by compilation. CI also calls the +catalog's top-level `.github/workflows/lint-terraform.yml` and +`.github/workflows/lint-markdown.yml` workflows at immutable revision +`30f86d7d8536364b0a31de4bc66214b39e8785bb`, independently of the retained +agentic catalog revision. Terraform formatting and validation +run separately for `infra/` and `infra/bootstrap/` using Terraform 1.15.6; the +TFLint checks explicitly pass the shared `infra/.tflint.hcl` configuration +to both initialization and lint commands for both roots. Markdown lint uses pinned Node.js +22.15.0 and markdownlint-cli2 0.17.2 for authored Markdown, including gh-aw +sources, through an explicit glob listing the nine authored files, not cached +imports (extend that list when adding Markdown); generated `*.lock.yml` files are +validated by gh-aw compilation instead. The .NET build/format/test and 60% +coverage gate, test artifacts, Trivy, CodeQL, actionlint, workflow triggers, +and CD and environment-bound OIDC promotion remain in place. The catalog's +narrow checked-script helper is not a replacement for this multi-step CI. +Configure the protected production environment and required reviewers as +described in [setup](docs/SETUP.md) before deployment. Use gh-aw **v0.89.21**, matching the explicit CI validator input: diff --git a/infra/.terraform.lock.hcl b/infra/.terraform.lock.hcl index 7ec273e..e439cb8 100644 --- a/infra/.terraform.lock.hcl +++ b/infra/.terraform.lock.hcl @@ -5,6 +5,7 @@ provider "registry.terraform.io/hashicorp/azurerm" { version = "4.76.0" constraints = "~> 4.40" hashes = [ + "h1:bOzo55MQgsvQ+KRwrWM5q+waapuMZ3AGIkK5ZIOe+JU=", "h1:s6/CZvCarsCPchXCgyY6s9kMngomo7nYQyzm9HaqyjY=", "zh:07bb3a087abca8bd14cc28e3d3d4abb71e0ed711ecec65e7c0a2f1a97b948cf4", "zh:0fa6640b5d6c0b0fe0b7fa25cebc0091b7dda2efd83ec0347c5a50dfce0957bc", @@ -25,6 +26,7 @@ provider "registry.terraform.io/hashicorp/random" { version = "3.9.0" constraints = "~> 3.6" hashes = [ + "h1:UlBuNVuCGJ39tTv2c5gz2NRZnQbXfbIWbTzWcth5o74=", "h1:q/uaUTBdKgAmZESrwsoeDQff9uUA/cI/N5ZKNgVwa9c=", "zh:161ad0bd9a75768c82f53fb6e7172a9d8be2d4889b012645a34795031aaf1bf1", "zh:19dc9a5b17729725ccfc4f45b0500af0ee5bc6b6b160c7adb8f2bf617d2c80ea", diff --git a/infra/bootstrap/.terraform.lock.hcl b/infra/bootstrap/.terraform.lock.hcl index 7ec273e..e439cb8 100644 --- a/infra/bootstrap/.terraform.lock.hcl +++ b/infra/bootstrap/.terraform.lock.hcl @@ -5,6 +5,7 @@ provider "registry.terraform.io/hashicorp/azurerm" { version = "4.76.0" constraints = "~> 4.40" hashes = [ + "h1:bOzo55MQgsvQ+KRwrWM5q+waapuMZ3AGIkK5ZIOe+JU=", "h1:s6/CZvCarsCPchXCgyY6s9kMngomo7nYQyzm9HaqyjY=", "zh:07bb3a087abca8bd14cc28e3d3d4abb71e0ed711ecec65e7c0a2f1a97b948cf4", "zh:0fa6640b5d6c0b0fe0b7fa25cebc0091b7dda2efd83ec0347c5a50dfce0957bc", @@ -25,6 +26,7 @@ provider "registry.terraform.io/hashicorp/random" { version = "3.9.0" constraints = "~> 3.6" hashes = [ + "h1:UlBuNVuCGJ39tTv2c5gz2NRZnQbXfbIWbTzWcth5o74=", "h1:q/uaUTBdKgAmZESrwsoeDQff9uUA/cI/N5ZKNgVwa9c=", "zh:161ad0bd9a75768c82f53fb6e7172a9d8be2d4889b012645a34795031aaf1bf1", "zh:19dc9a5b17729725ccfc4f45b0500af0ee5bc6b6b160c7adb8f2bf617d2c80ea",