# concordat Users' Guide For the internal module boundaries behind this CLI — the XDG layout, credential resolution order, cache/execution API split, and the rule-run and Parabellum sweep contracts — see [`docs/developers-guide.md`](developers-guide.md). ## Overview The `concordat` command line interface (CLI) helps maintainers enrol Git repositories with Concordat. Enrolling creates a `.concordat` file at the repository root. The file is a YAML 1.2 document with the key/value pair `enrolled: true`. Downstream tooling relies on this marker to detect participating repositories, and Concordat's continuous integration (CI) workflows read the same flag before applying changes. ## Installing the CLI 1. Create or update the virtual environment: ```shell uv sync --group dev ``` 2. Invoke the CLI with `uv run` to ensure the correct environment is used. ## Enrolling repositories - Enrol one or more repositories by passing their paths: ```shell uv run concordat enrol path/to/repo-one path/to/repo-two ``` - Ensure an estate with the correct `github_owner` is active before enrolling: run `concordat estate init --github-owner ` once and `concordat estate use ` to activate it. The CLI refuses repositories whose GitHub slug does not start with the recorded owner and fails fast when it cannot determine the slug from the repository or `origin` remote. - When the repository already contains a `.concordat` file with `enrolled: true`, the CLI prints `already enrolled` and makes no changes unless `--force` is supplied. With `--force`, concordat still opens (or updates) the platform-standards pull request for the active estate so the repository is present in the OpenTofu inventory. - The CLI commits the new file to the current branch. If the Git configuration does not define `user.name` and `user.email`, supply details explicitly: ```shell uv run concordat enrol path/to/repo --author-name "Jess Example" \ --author-email "jess@example.com" ``` - Pass `--push` to push the commit to the repository's `origin` remote after creation. - Remote repositories reachable over Secure Shell (SSH) can be enrolled directly. Provide the SSH URL and ensure an SSH agent exposes the required key: ```shell uv run concordat enrol git@github.com:example/project.git ``` The CLI clones the repository, creates the enrolment commit, and pushes it back to the remote. - When rehearsing or running tests without access to the platform-standards repository, set `CONCORDAT_SKIP_PLATFORM_PR=1` to disable the IaC pull request step while keeping the `github_owner` guard active for the GitHub owner. ## Disenrolling repositories - Mark repositories as no longer enrolled by setting the `.concordat` flag to `false`: ```shell uv run concordat disenrol path/to/repo-one path/to/repo-two ``` - The CLI commits the change to the current branch and accepts the same `--push`, `--author-name`, and `--author-email` options as the enrol command. - When an active estate is configured, `concordat disenrol` also opens a pull request against the platform-standards repository to remove the repository slug from the OpenTofu inventory (the default is `tofu/inventory/repositories.yaml`). Merge the pull request before the next OpenTofu apply. When `CONCORDAT_SKIP_PLATFORM_PR=1` is set, the inventory pull request step is skipped, but the `.concordat` flag is still updated. - After removing a repository from the inventory, subsequent `concordat apply` runs may fail with `prevent_destroy` because the repository module forbids deletions. Concordat offers to remove the affected resources from OpenTofu state (via `tofu state rm`) and retry the apply, which completes the disenrolment without deleting the repository. ## Listing repositories - List every repository within one or more GitHub namespaces: ```shell uv run concordat ls leynos df12 ``` Each line is an SSH URL that can be passed directly to `concordat enrol`. - Invoking `concordat ls` without namespaces defaults to the active estate's recorded `github_owner`, which keeps ad-hoc inventory dumps aligned with the estate configuration. - Provide a personal access token with `--token` or the `GITHUB_TOKEN` environment variable when listing private repositories: ```shell uv run concordat ls --token "$GITHUB_TOKEN" my-org ``` ## Configuration, credentials, cache, and state locations Concordat's local configuration, credentials, caches, and state live under the XDG base directories. A single global headline file names the active configured owner; per-owner configuration, credentials, estate caches, and state are namespaced beneath that owner, while the OpenTofu provider plugin cache is shared across owners, since provider binaries are identical regardless of owner: - `$XDG_CONFIG_HOME/concordat/config.yaml` — the **headline** config, global rather than owner-namespaced; its `github_owner` key names the active owner. Manage it with `concordat owner use ` and inspect it with `concordat owner show`. - `$XDG_CONFIG_HOME/concordat/owners//config.yaml` — that owner's estates and active estate. - `$XDG_CONFIG_HOME/concordat/owners//credentials.yaml` — optional credential fallbacks, mapping credential environment-variable names (`GITHUB_TOKEN`, `SCW_ACCESS_KEY`, `SCW_SECRET_KEY`, `AWS_*`, `SPACES_*`) to values. Environment variables always win. The file must be `chmod 600`: concordat refuses to read one carrying any group or world permission bit. Concordat never writes this file. - `$XDG_CACHE_HOME/concordat/owners//estates/` — estate repository caches. - `$XDG_CACHE_HOME/concordat/tofu/plugin-cache` — the shared OpenTofu provider plugin cache (exported as `TF_PLUGIN_CACHE_DIR` unless already set). - `$XDG_STATE_HOME/concordat/owners//runs/` — throwaway OpenTofu working trees; removed after each run unless `--keep-workdir` is given. Remote OpenTofu state stored in the configured S3 backend (for example Scaleway Object Storage) is unaffected by this layout. **Migration from the legacy flat format.** Older releases wrote estates directly into `$XDG_CONFIG_HOME/concordat/config.yaml` — the same path the headline config now occupies. A file found there carrying an `estate` section is therefore a legacy configuration: its estates are moved into `owners//config.yaml` automatically the first time the owner can be derived from those records, and the headline file is rewritten with only its `github_owner` key and any other non-estate settings. ## Managing estates Concordat tracks platform-standards repositories, referred to as *estates*, in the active owner's configuration file (see the locations section above). Each estate entry records an alias, the managed `github_owner`, the Git URL for the platform-standards repository, the OpenTofu inventory path, and the default branch. The CLI uses the **active estate** to determine where enrolment PRs should be opened. - Bootstrap a new estate from the bundled template: ```shell uv run concordat estate init core git@github.com:example/platform-standards.git \ --github-owner example \ --github-token "$GITHUB_TOKEN" ``` - The CLI copies the `platform-standards` directory into a temporary Git repo, commits the initial contents, and pushes to the provided remote. - `--github-owner` is required when the remote URL is not hosted on GitHub. When omitted, the CLI infers the owner from the repository slug, prompts for confirmation, and stores it so `concordat enrol` can enforce the namespace guard. - When the target repository does not exist, Concordat prompts before using the GitHub API (via `github3.py`) to create it. Pass `--yes` to skip all prompts in scripted environments. - Initialization aborts if the repository already contains commits. - List the configured estates and their remotes: ```shell uv run concordat estate ls ``` - Show the repositories that an estate currently manages. Without an argument, the CLI uses the active estate: ```shell uv run concordat estate show uv run concordat estate show sandbox ``` - Switch the active estate: ```shell uv run concordat estate use sandbox ``` ## Syncing canonical platform-standards artefacts Concordat ships a `platform-standards/` template tree. The canonical files under `platform-standards/canon/` are tracked in `platform-standards/canon/manifest.yaml` by a stable `id`, a `path`, and a sha256 digest (a content-derived version). Use `scripts/canon_artifacts.py` to compare a published platform-standards checkout against the template and optionally copy missing/outdated artefacts. - Show status for a checkout: ```shell uv run python -m scripts.canon_artifacts status path/to/platform-standards ``` - Filter to just lint configuration artefacts: ```shell uv run python -m scripts.canon_artifacts status path/to/platform-standards \ --types lint-config ``` - Copy all missing/outdated artefacts into the checkout: ```shell uv run python -m scripts.canon_artifacts sync path/to/platform-standards \ --all-outdated ``` - Launch an interactive menu for reviewing and syncing (requires `textual`, installed with dev dependencies via `uv sync --group dev`): ```shell uv run python -m scripts.canon_artifacts tui path/to/platform-standards ``` ## Auditing a checkout against a lint rule package `concordat artefact rule run` evaluates one canon lint rule package against a local checkout and reports structured findings. The first package, `rust-makefile-baseline`, audits a Rust repository's root `Makefile` for the canonical `build`, `test`, and `lint` targets (FP-003) and for a binding Whitaker lint gate (QG-001): ```shell concordat artefact rule run rust-makefile-baseline --repo /path/to/checkout ``` Options: - `--repo PATH` — the checkout to audit (defaults to the current directory). - `--format {table,json}` — output format (defaults to `table`). Verdicts are three-valued. `compliant` means the finding set is empty; `noncompliant` means the policy proved a violation; `indeterminate` means the policy could not prove compliance and fails closed (for example, the `Makefile` includes other files, or the parse had to recover from syntax errors). Exit codes: `0` compliant; `1` at least one finding, including indeterminate verdicts; `2` operational failure (for example, the pinned `makeutil` or `conftest` executable is missing), reported on standard error. The command requires two external tools on `PATH`: `conftest` and the pinned `makeutil` (see `platform-standards/canon/lint-rules/rust-makefile-baseline/README.md` for the pin and regeneration workflow). ### Sweeping the Rust estate `scripts/parabellum_sweep.py` audits every repository listed in `docs/parabellum/estate.yaml` and appends one record per repository to the append-only campaign ledger `docs/parabellum/ledger.jsonl`: ```shell uv run python -m scripts.parabellum_sweep [--only a,b] [--limit N] [--force] uv run python -m scripts.parabellum_sweep report ``` A repository already ledgered at its current head commit is skipped unless `--force` is given, so an interrupted sweep resumes by re-running the same command. The `report` subcommand regenerates `docs/parabellum/baseline-report.md` from the ledger. ## Previewing and applying estate changes Use the `plan` and `apply` commands to run OpenTofu against the active estate without leaving the CLI. Both commands require `GITHUB_TOKEN` and the estate's `github_owner` to be recorded. - Preview changes with `concordat plan`. Additional OpenTofu arguments can be appended directly to the command (for example, `-detailed-exitcode`). ```shell uv run concordat plan -- -detailed-exitcode ``` The CLI refreshes the cached estate under `$XDG_CACHE_HOME/concordat/owners//estates/`, clones it into a run directory under `$XDG_STATE_HOME/concordat/owners//runs/`, writes `terraform.tfvars` with the recorded owner, runs `tofu init -input=false`, and then `tofu plan`. Paths are echoed, so the workspace can be inspected; pass `--keep-workdir` to skip the cleanup step. Concordat preserves OpenTofu's standard CLI plan output (including the per-resource diff), so operators do not need to re-run `tofu plan` manually just to see what would change. When `backend/persistence.yaml` exists with `enabled: true`, the CLI adds `-backend-config=` to `tofu init`, maps `SCW_ACCESS_KEY`/ `SCW_SECRET_KEY` or `SPACES_ACCESS_KEY_ID`/`SPACES_SECRET_ACCESS_KEY` onto `AWS_ACCESS_KEY_ID`/`AWS_SECRET_ACCESS_KEY` when needed, and fails fast if none of the pairs is present. Standard error (stderr) logs echo the backend bucket, key, region, and config path—never credentials—for traceability. If the manifest is absent or `enabled: false`, `plan` and `apply` keep using the local state layout. - Reconcile the estate with `concordat apply`. The command requires an explicit `--auto-approve` to match OpenTofu's automation guard. ```shell uv run concordat apply --auto-approve ``` `concordat apply` uses the same workspace preparation as `plan`, adds `-auto-approve` for OpenTofu, and returns the exit code from the underlying `tofu` invocation, so pipelines can gate on it. Pass `--keep-workdir` to retain the apply workspace for inspection. ### Importing pre-existing GitHub repositories into state The GitHub provider rejects attempts to create a repository that already exists, even if the rest of the plan is valid. This typically shows up as a `422` response with a message such as "name already exists on this account". When Concordat detects this specific failure during `concordat apply`, it offers to import the affected repositories into state and retry the apply. The prompt defaults to "no" so automated runs cannot accidentally mutate state: ```plaintext One or more GitHub repositories already exist but are missing from state. Import into state and retry apply? (example/repo-one, example/repo-two) [y/N]: ``` If the operator answers `y`, Concordat runs `tofu import` for each missing repository and then re-runs `tofu apply`. Import IDs are attempted in a resilient order: 1. Repository name only (for example, `repo-one`). 2. Full `owner/name` slug (for example, `example/repo-one`). When running without a TTY (teletype; an interactive terminal session), for example in CI, Concordat does not prompt and does not attempt imports. Re-run with `--keep-workdir`, then import manually in the printed workspace directory. ### Persisting estate state in object storage Use `concordat estate persist` to move OpenTofu state into a shared, version-controlled backend for the active estate. The command: - prompts for bucket, region, endpoint, key prefix, and key suffix, seeding defaults from any existing `backend/persistence.yaml` - verifies the Scaleway bucket has versioning enabled and performs a zero-byte put/delete to confirm the supplied credentials can write to the prefix - writes `backend/.tfbackend` (no credentials) plus `backend/persistence.yaml` (`schema_version: 1`) describing the backend - pushes a branch named `estate/persist-` and opens a pull request when `GITHUB_TOKEN` resolves the estate remote to a GitHub repository Re-running the command refuses to replace existing backend files unless `--force` is supplied; use `--force` when rotating buckets or prefixes. Secrets such as `AWS_SECRET_ACCESS_KEY` are validated in memory only and are never written to disk. Non-interactive use for automation: - Provide backend values via flags (`--bucket`, `--region`, `--endpoint`, `--key-prefix`, `--key-suffix`) or the environment variables `CONCORDAT_PERSIST_BUCKET`, `CONCORDAT_PERSIST_REGION`, `CONCORDAT_PERSIST_ENDPOINT`, `CONCORDAT_PERSIST_KEY_PREFIX`, and `CONCORDAT_PERSIST_KEY_SUFFIX`. - When `--endpoint` (or `CONCORDAT_PERSIST_ENDPOINT`) omits a scheme, Concordat assumes `https://` (for example, `s3.fr-par.scw.cloud` becomes `https://s3.fr-par.scw.cloud`). - Pass `--no-input` to fail fast instead of prompting when any required value is missing. Defaults from an existing `backend/persistence.yaml` are still honoured in non-interactive mode. ### Configuring remote-state credentials Remote-state backends rely on environment variables; the CLI simply checks that they exist before shelling out to OpenTofu. Export the pair that matches the selected provider: Table 1: Backend environment variables required per storage provider. | Provider | Required variables | Optional variables | Notes | | ----------------------- | -------------------------------------------------- | ------------------------------------------------------------------------ | -------------------------------------------------------------------------- | | AWS S3 | `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY` | `AWS_SESSION_TOKEN` (when using temporary credentials such as STS) | Values are passed straight to OpenTofu's S3 backend. | | Scaleway Object Storage | `SCW_ACCESS_KEY`, `SCW_SECRET_KEY` | `AWS_SESSION_TOKEN` (only when Scaleway issues temporary AWS-style keys) | Concordat maps these onto the AWS variable names before invoking OpenTofu. | | DigitalOcean Spaces | `SPACES_ACCESS_KEY_ID`, `SPACES_SECRET_ACCESS_KEY` | `AWS_SESSION_TOKEN` (only when Spaces issues temporary AWS-style keys) | Concordat maps these onto the AWS variable names before invoking OpenTofu. | When `AWS_SESSION_TOKEN` is present, Concordat forwards it alongside whichever credential pair is selected so temporary AWS STS, Scaleway, or Spaces sessions work without additional flags. The stack declares an explicit `s3` backend in `platform-standards/tofu/backend.tf` and ships a Scaleway starter config at `platform-standards/tofu/backend/scaleway.tfbackend`. Initialize estates with OpenTofu 1.12 or newer using: ```bash GITHUB_TOKEN=placeholder \ tofu -chdir=platform-standards/tofu \ init -backend-config backend/scaleway.tfbackend ``` Example shell snippet: ```bash export AWS_ACCESS_KEY_ID=AKIA... # or SCW_ACCESS_KEY for Scaleway export AWS_SECRET_ACCESS_KEY=xxxx # or SCW_SECRET_KEY # export AWS_SESSION_TOKEN=... # optional for temporary sessions ``` When multiple estates exist, run `concordat estate persist` for each remote stack using the appropriate credentials. The roadmap and design doc (§2.8) describe lock troubleshooting steps and disaster-recovery procedures that build on this environment setup. ### Remote state FAQ (Scaleway-focused) - Versioning disabled error: In the Scaleway console, open Object Storage → your bucket → Settings → **Object versioning** and set it to **Enabled**. Re-run `concordat estate persist`. - Access denied when checking versioning: Confirm the access key/secret match the correct project, the bucket name is spelled correctly, and the endpoint matches the region (for example, `https://s3.fr-par.scw.cloud`). Retry after updating credentials; network glitches also show the same message. - Wrong bucket or region: Ensure `--bucket` and `--region` (or the `CONCORDAT_PERSIST_*` env vars) point to the intended bucket; the endpoint must be in the same region. - Need to retry without prompts: Pass `--no-input` plus the required flags to fail fast in automation; defaults from `backend/persistence.yaml` are still honoured. ### Lock troubleshooting OpenTofu's S3 backend supports native `.tflock` files when `use_lockfile = true` (the default for AWS and DigitalOcean backends generated by Concordat). Scaleway Object Storage does not implement Terraform locking, so Concordat omits the lockfile option for Scaleway backends and relies on single-writer discipline. - **Identifying lock contention:** When another process holds the lock, `tofu apply` blocks and echoes the `.tflock` object key and metadata (holder ID, timestamp) in its output. If the lock persists beyond 60 seconds and the persistence manifest defines a `notification_topic`, Concordat emits a structured JSON log line for downstream alerting pipelines. - **Stuck locks on AWS/DigitalOcean:** If a previous apply crashed without releasing the lock, manually delete the `.tflock` object from the bucket using the provider's CLI or console. Confirm no other apply is running before removal. - **Scaleway single-writer discipline:** Because Scaleway lacks `.tflock` support, coordinate applies externally (for example, via CI job serialization or manual team communication). Concurrent applies may corrupt state. - **Planned `--check-lock` mode:** A future `concordat apply --check-lock` flag will attempt to acquire the backend lock and report the blocking key when contention persists. This flag is not yet implemented; operators currently depend on the `.tflock` metadata echoed in apply logs. ### Disaster recovery Concordat requires bucket versioning on all remote backends, so every state update preserves earlier versions. Use versioning to recover from corrupted or accidentally overwritten state files. If versioning is not enabled, `concordat estate persist` fails with a blocking error and does not write any backend configuration files. **Locating version IDs:** Whenever `concordat apply` updates state, the CLI logs the bucket, key, and region to standard error (stderr). Combine this information with the provider's object-versioning API to list available versions. **Restoring a previous state version (Scaleway example):** 1. List versions of the state object: ```bash aws s3api list-object-versions \ --bucket df12-tfstate \ --prefix estates/example/main/terraform.tfstate \ --endpoint-url https://s3.fr-par.scw.cloud ``` 2. Identify the desired `VersionId` from the output and copy it over the current object: ```bash aws s3api copy-object \ --bucket df12-tfstate \ --copy-source "df12-tfstate/estates/example/main/terraform.tfstate?versionId=VERSION_ID" \ --key estates/example/main/terraform.tfstate \ --endpoint-url https://s3.fr-par.scw.cloud ``` 3. Run `concordat plan` to verify the restored state matches the expected infrastructure. Investigate any unexpected drift before applying. **Object Lock compliance mode (Scaleway):** Enable Object Lock with a retention window that matches regulatory requirements to prevent accidental or malicious deletion of state versions. Object Lock is orthogonal to `.tflock` mutexes; it protects historical data, not concurrent access. **Important:** Concordat does not automate rollbacks. Operators are responsible for identifying the correct version and restoring it manually. ### At-rest encryption State files often contain credentials and other sensitive data. Protect them with the following measures: - **Avoid storing secrets in state:** Mark sensitive attributes with `sensitive = true` in OpenTofu configurations. Split secrets into external vaults (for example, HashiCorp Vault, AWS Secrets Manager) or replace cleartext values with references. - **Enforce strict bucket policies:** Limit access via IAM (AWS) or Scaleway access policies. Grant the minimum permissions required for Concordat operations (read/write/delete on the state prefix). - **Server-side encryption (AWS):** Enable SSE-S3 or SSE-KMS on the bucket. OpenTofu's S3 backend automatically uses SSE when the bucket enforces it. - **Server-side encryption (Scaleway):** Scaleway Object Storage offers SSE-ONE and SSE-KMS alongside SSE-C, so state can be encrypted at rest. Configure it in the Scaleway provider or bucket configuration rather than through the backend. - **Do not set `encrypt = true` on Scaleway or DigitalOcean Spaces:** that flag makes the OpenTofu S3 backend send an AES256 (SSE-S3) header, which neither provider accepts. Concordat omits it for this reason; bucket encryption is configured provider-side instead. - **Server-side encryption with a customer-provided key (SSE-C):** OpenTofu's S3 backend supports SSE-C directly through the `sse_customer_key` backend argument, which can be sourced from the `AWS_SSE_CUSTOMER_KEY` environment variable (a 32-byte, base64-encoded key) instead of being written into committed backend configuration. **The key must never be persisted to the repository, the backend file, or logs:** losing it makes the state unreadable, and leaking it defeats the encryption. - **Client-side or envelope encryption (optional):** a genuinely separate, independent control from SSE-C, useful whichever provider is in use. Wrap `tofu state`/`tofu plan` calls with tooling that encrypts state before upload. - **Audit access logs:** Periodically review bucket access logs to detect unauthorized reads or unexpected access patterns. ### Estate configuration file Concordat stores estate metadata in `$XDG_CONFIG_HOME/concordat/owners//config.yaml`, where `` is the active owner configured in the headline configuration. The file is regular YAML 1.2 with an `estate` section: ```yaml estate: active_estate: core estates: core: github_owner: example repo_url: git@github.com:example/platform-standards.git branch: main inventory_path: tofu/inventory/repositories.yaml sandbox: github_owner: example repo_url: git@github.com:example/sandbox-standards.git branch: main inventory_path: tofu/inventory/repositories.yaml ``` - `active_estate` is optional; the first `estate init` call populates it automatically. - `github_owner` identifies the GitHub organization or user managed by the estate. `concordat enrol` and `concordat ls` rely on the stored owner to guard against cross-organization drift. - `branch` and `inventory_path` default to `main` and `tofu/inventory/repositories.yaml`. Override them when the remote uses another branch name or inventory layout. - Manual edits are allowed, but prefer the CLI to ensure validation is applied. ### Interaction with enrolment The `concordat enrol` command automatically targets the active estate and refuses to run unless that estate records `github_owner`. The `--platform-standards-url` flag still overrides the repository where the OpenTofu pull request is opened, but the namespace guard always uses the active estate owner. - Run `concordat estate use ` before invoking `concordat enrol` when switching estates (for example, when working on a fork). - Ensure repositories expose an `origin` remote pointing at GitHub (or pass the SSH URL directly) so the CLI can resolve the slug and enforce the owner guard. - If the estate inventory misses a repository, run `concordat estate show` to confirm the inventory contents before debugging the enrolment. ## Running the squash-only merge plan The `platform-standards/tofu` directory contains a runnable OpenTofu stack that enforces the squash-only merge strategy (RS-002). The stack consumes `platform-standards/tofu/inventory/repositories.yaml`, which now includes the non-production `test-case/squash-only-standard` record, so operators can rehearse changes without touching production. 1. Set a placeholder GitHub token so the provider schema loads without reaching the API: ```shell export GITHUB_TOKEN=placeholder ``` 2. Initialize the stack and preview the actions with the default `test-case` owner: ```shell cd platform-standards/tofu tofu init tofu plan -var github_owner=test-case -detailed-exitcode ``` Exit code `2` indicates that OpenTofu would make changes (expected for the sample repository), while exit code `0` confirms convergence. 3. Override `github_owner` and extend `inventory/repositories.yaml` when ready to target additional organizations. The `github_owner` guard blocks accidental cross-org drift by asserting that every slug shares the configured GitHub owner. ### Validating the test-case standard end to end Use the commands below when modifying the squash-only merge test case or demonstrating the guardrails to stakeholders: - Keep formatting and linting in sync: ```shell tofu fmt -recursive -check tflint --chdir=platform-standards/tofu ``` - Preview the drift that would enrol the sample repository: ```shell GITHUB_TOKEN=placeholder tofu -chdir=platform-standards/tofu \ plan -var github_owner=test-case -detailed-exitcode ``` Exit code `2` means changes are pending; exit code `0` shows convergence. Share the plan output when reviewers want to inspect the settings OpenTofu will apply. - Capture a plan file and run an ephemeral apply in a throwaway workspace: ```shell GITHUB_TOKEN=placeholder tofu -chdir=platform-standards/tofu plan \ -var github_owner=test-case -out=plan.tfplan tofu -chdir=platform-standards/tofu workspace new demo-squash || true GITHUB_TOKEN=placeholder tofu -chdir=platform-standards/tofu apply plan.tfplan ``` Always delete the workspace or discard the generated state file afterwards. - Exercise the module’s native unit tests (plan and apply): ```shell GITHUB_TOKEN=placeholder tofu -chdir=platform-standards/tofu/modules/repository \ test ``` - Drive the Terratest suite for happy and unhappy paths: ```shell GOCACHE=$PWD/platform-standards/tofu/terratest/.gocache \ go -C platform-standards/tofu/terratest test ./... ``` - Validate the Open Policy Agent (OPA) policy expectations: ```shell conftest test --policy platform-standards/tofu/policies \ platform-standards/tofu/policies/examples/*.json ``` Running the full sequence above mirrors the automation that CI performs, demonstrating that the test-case standard enforces RS-002 through static checks, unit-style tests, Terratest coverage, and policy validation before any real repository settings change. ## Auditor workflow - Scheduled audits run via `.github/workflows/auditor.yml` every day at 05:00 UTC. Results land in GitHub's Code Scanning dashboard because the workflow uploads the generated Static Analysis Results Interchange Format (SARIF) file using the GitHub `github/codeql-action/upload-sarif` action. - Trigger the workflow manually with **Run workflow** to inspect a specific revision. Provide `snapshot_path` (for example, `tests/fixtures/auditor/snapshot.json`) to replay a recorded API response and set `upload_sarif` to `false` when only a local artefact is required. - Run the same workflow locally with `act`: ```shell CONCORDAT_RUN_ACT_TESTS=1 pytest tests/workflows/test_auditor_workflow.py -k auditor ``` The test reads `tests/fixtures/workflows/auditor-workflow-dispatch.json`, downloads workflow artefacts under a temporary directory, and asserts that the SARIF log structure is valid. ## Troubleshooting - The CLI refuses to operate on bare repositories. Create a working tree or clone the repository locally first. - Ensure the repository has at least one existing commit. Enrolment commits require a parent revision. - When pushing fails for an SSH repository, verify that the SSH agent knows the key and that the remote accepts the configured credentials.