diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index d7c25624f..e038c0b59 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,3 +1,5 @@ +# Pull Request + ## Summary _Please include a summary of the changes and the context of this PR._ diff --git a/.markdownlint.json b/.markdownlint.json new file mode 100644 index 000000000..75cd22066 --- /dev/null +++ b/.markdownlint.json @@ -0,0 +1,19 @@ +{ + "default": true, + "MD004": false, + "MD007": { + "indent": 2 + }, + "MD013": false, + "MD024": { + "siblings_only": true + }, + "MD026": { + "punctuation": ".,;:!。,;:" + }, + "MD029": false, + "MD033": false, + "MD036": false, + "MD041": false, + "blank_lines": false +} diff --git a/.mega-linter.yml b/.mega-linter.yml index 6b371be6b..e192e57bd 100644 --- a/.mega-linter.yml +++ b/.mega-linter.yml @@ -1,7 +1,8 @@ # https://megalinter.io/latest/supported-linters/ ADDITIONAL_EXCLUDED_DIRECTORIES: - vendor -MARKDOWN_MARKDOWNLINT_FILTER_REGEX_EXCLUDE: "(vendor/)" + - .opencode +MARKDOWN_MARKDOWNLINT_FILTER_REGEX_EXCLUDE: "(vendor/|\\.opencode/)" PROTOBUF_PROTOLINT_FILTER_REGEX_EXCLUDE: "(vendor/)" ENABLE_LINTERS: - ACTION_ZIZMOR diff --git a/.specify/memory/constitution.md b/.specify/memory/constitution.md index 8cf63f711..4665fa8ef 100644 --- a/.specify/memory/constitution.md +++ b/.specify/memory/constitution.md @@ -85,7 +85,7 @@ Decrease the number of decisions a developer or user needs to make. Provide defa Every repository under the ComplyTime organization MUST contain the following standard files in the root directory to ensure a consistent developer experience: | File | Description | Standard | -|:---|:----|:----| +| :--- | :---- | :---- | | `README.md` | Project overview, installation, and usage. | Markdown | | `LICENSE` | Legal terms of use. | **Apache License 2.0** | | `CONTRIBUTING.md` | Guidelines for contributors. | Link to org-wide guide or repo-specific details. | diff --git a/AGENTS.md b/AGENTS.md index 05289804a..0616a1b7c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -73,7 +73,7 @@ make crapload-check # check for CRAP regressions against baseline ### CI Workflow Structure | Workflow | File | Purpose | -|----------|------|---------| +| ---------- | ------ | --------- | | CI | `ci_checks.yml` | Standardized CI via org-infra reusable workflow | | Unit Test | `unit_test.yml` | Unit tests + buf lint | | E2E Test | `e2e_test.yml` | End-to-end tests with mock registry | @@ -206,19 +206,19 @@ These rules are non-negotiable. Violations are CRITICAL severity. ### PR Review Commands -| Command | When | Scope | -|---------|------|-------| -| `/review-council` | Pre-PR (local) | 5+ Divisor agents | -| `/review-pr [N]` | Post-PR (GitHub) | Single agent, CI analysis | +| Command | When | Scope | +|-------------------|------------------|---------------------------| +| `/review-council` | Pre-PR (local) | 5+ Divisor agents | +| `/review-pr [N]` | Post-PR (GitHub) | Single agent, CI analysis | ## Specification Workflow All non-trivial changes MUST be preceded by a spec workflow. -| Tier | Tool | When | Artifacts | -|------|------|------|-----------| -| Strategic | Speckit | >= 3 stories, cross-repo | `specs/NNN-*/` | -| Tactical | OpenSpec | < 3 stories, single-repo | `openspec/changes/*/` | +| Tier | Tool | When | Artifacts | +|------------|----------|---------------------------|------------------------| +| Strategic | Speckit | >= 3 stories, cross-repo | `specs/NNN-*/` | +| Tactical | OpenSpec | < 3 stories, single-repo | `openspec/changes/*/` | Pipeline: `constitution → specify → clarify → plan → tasks → analyze → checklist → implement` diff --git a/CHANGELOG.md b/CHANGELOG.md index c85321878..abc6081ee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,38 @@ the full log file lives. The flag description is updated to `"output debug logs to stderr and log file"`. (#614) +- `complyctl scan --format` reports (SARIF, OSCAL, Markdown) now written + to `.complytime/scan/` alongside the evaluation log, matching + documented behavior. Previously, format reports were written to the + workspace root. (#615) +- Generation freshness detection now tracks complypack digests alongside + policy digests. Previously, updating a complypack without changing the + policy would skip regeneration, causing providers to use stale + artifacts (#583). +- OCI reference parsing now supports standard `:tag` syntax (e.g., + `registry.com/org/image:v0.4.0`) in addition to the existing `@version` + notation for both policies and complypacks. Digest references + (`@sha256:...`) are also supported. Invalid OCI references in + `complytime.yaml` are now detected at config load time with clear + error messages. (#594) +- `complyctl doctor` now reports invalid policy references in + `CheckPolicyActivePeriod` and `CheckComplypacks` as explicit + failures instead of silently skipping them. `CheckVariables` + now surfaces per-policy resolution errors with specific messages + instead of a silent counter. (#600) +- Scan reports now resolve assessment plan IDs to requirement IDs, + ensuring output displays meaningful identifiers instead of internal + plan references. Affects EvaluationLog, OSCAL, SARIF, and Markdown + output formats. +- `complyctl get` now detects and rejects duplicate evaluator-ids across + complypack entries. Previously, multiple complypacks resolving to the + same evaluator-id caused non-deterministic digest selection during + scan, leading to unnecessary regeneration or stale artifacts (#647). +- `complyctl get` now re-fetches complypack artifacts when the cache + directory is missing, even if `state.json` records a matching digest. + Previously, a deleted cache directory caused the sync to be permanently + skipped until the user manually cleared state (#649). + ### Changed - Release workflow gains preflight validation gate: tag format and @@ -24,6 +56,10 @@ annotated tag creation. Concurrency group prevents parallel releases. (#560) +- All commands now accept `--workspace` flag to specify workspace directory +- `NewWorkspace()` function signature changed from `NewWorkspace()` to `NewWorkspace(baseDir string)` +- Log file and scan output paths are now relative to resolved workspace directory + ### Removed - **BREAKING**: Removed collector export infrastructure (`COMPLYTIME_EXPORT_ENABLED`, `collector:` config block, Export RPC). This was speculative infrastructure added before backend design was finalized. Export functionality will be redesigned and reintroduced when the backend shape is known. (#606) @@ -127,42 +163,4 @@ when `@version` syntax is detected. `@version` support will be removed in a future release. (#600) -### Changed - -- All commands now accept `--workspace` flag to specify workspace directory -- `NewWorkspace()` function signature changed from `NewWorkspace()` to `NewWorkspace(baseDir string)` -- Log file and scan output paths are now relative to resolved workspace directory - -### Fixed -- `complyctl scan --format` reports (SARIF, OSCAL, Markdown) now written - to `.complytime/scan/` alongside the evaluation log, matching - documented behavior. Previously, format reports were written to the - workspace root. (#615) -- Generation freshness detection now tracks complypack digests alongside - policy digests. Previously, updating a complypack without changing the - policy would skip regeneration, causing providers to use stale - artifacts (#583). -- OCI reference parsing now supports standard `:tag` syntax (e.g., - `registry.com/org/image:v0.4.0`) in addition to the existing `@version` - notation for both policies and complypacks. Digest references - (`@sha256:...`) are also supported. Invalid OCI references in - `complytime.yaml` are now detected at config load time with clear - error messages. (#594) -- `complyctl doctor` now reports invalid policy references in - `CheckPolicyActivePeriod` and `CheckComplypacks` as explicit - failures instead of silently skipping them. `CheckVariables` - now surfaces per-policy resolution errors with specific messages - instead of a silent counter. (#600) -- Scan reports now resolve assessment plan IDs to requirement IDs, - ensuring output displays meaningful identifiers instead of internal - plan references. Affects EvaluationLog, OSCAL, SARIF, and Markdown - output formats. -- `complyctl get` now detects and rejects duplicate evaluator-ids across - complypack entries. Previously, multiple complypacks resolving to the - same evaluator-id caused non-deterministic digest selection during - scan, leading to unnecessary regeneration or stale artifacts (#647). -- `complyctl get` now re-fetches complypack artifacts when the cache - directory is missing, even if `state.json` records a matching digest. - Previously, a deleted cache directory caused the sync to be permanently - skipped until the user manually cleared state (#649). diff --git a/README.md b/README.md index 821e13457..8a6bad24a 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ A lightweight compliance runtime that pulls [Gemara](https://gemara.openssf.org/ ## Architecture -``` +```text ┌──────────────────────────────────────────────────────────────────┐ │ Host │ │ │ @@ -51,7 +51,7 @@ A lightweight compliance runtime that pulls [Gemara](https://gemara.openssf.org/ **Components:** | Component | Description | -|:---|:---| +| :--- | :--- | | **OCI Registry** | Remote store for Gemara policies. Supports two OCI manifest layouts: split-layer (distinct media types per artifact) and Gemara bundle format (single artifact media type with annotation-based differentiation). Both formats are auto-detected and resolved transparently. | | **Workspace** | Resolved workspace directory containing `.complytime/complytime.yaml` (or legacy `complytime.yaml` at root). Configurable via `--workspace` flag or `COMPLYTIME_WORKSPACE` env var. Defines which registry, policies, and targets to use. Scan output lands in `.complytime/scan/`. | | **Cache** | Local OCI Layout stores under `~/.complytime/policies/`. One store per policy ID. `state.json` tracks digests for incremental sync. | @@ -69,7 +69,7 @@ A lightweight compliance runtime that pulls [Gemara](https://gemara.openssf.org/ ## CLI Commands | Command | Description | -|:---|:---| +| :--- | :--- | | `init` | Create a workspace configuration file | | `get` | Fetch new/modified policies from OCI registry and update cache | | `list` | List cached Gemara policies | @@ -79,7 +79,7 @@ A lightweight compliance runtime that pulls [Gemara](https://gemara.openssf.org/ | `providers` | List discovered scanning providers and their health status | | `version` | Print version | -Global flags: +Global flags: - `--debug` / `-d` — output debug logs - `--workspace` / `-w` — workspace directory (project root containing `.complytime/`, defaults to current directory) @@ -138,9 +138,9 @@ complyctl list complyctl list --policy-id nist-800-53-r5 ``` -| Flag | Description | -|:---|:---| -| `--policy-id` | Filter output to a single policy | +| Flag | Description | +|---------------|-----------------------------------| +| `--policy-id` | Filter output to a single policy | ### `generate` @@ -148,9 +148,9 @@ complyctl list --policy-id nist-800-53-r5 complyctl generate --policy-id nist-800-53-r5 ``` -| Flag | Short | Description | -|:---|:---|:---| -| `--policy-id` | `-p` | Policy ID to generate (required) | +| Flag | Short | Description | +|---------------|-------|-----------------------------------| +| `--policy-id` | `-p` | Policy ID to generate (required) | Resolves the policy dependency graph from cache, extracts assessment configurations, applies parameter overrides from `complytime.yaml`, and dispatches to the matching provider via Generate RPC. @@ -174,7 +174,7 @@ complyctl scan --policy-id nist-800-53-r5 --format sarif ``` | Argument / Flag | Short | Description | -|:---|:---|:---| +| :--- | :--- | :--- | | `[target]` | | Optional target ID to scope the scan (from `complytime.yaml`) | | `--policy-id` | `-p` | Policy ID to scan (required when no target is given, or target has multiple policies) | | `--format` | `-f` | Output format: `oscal`, `pretty`, `sarif` | @@ -187,7 +187,7 @@ Output written to `./.complytime/scan/`. #### Exit codes | Exit Code | Meaning | -|:---|:---| +| :--- | :--- | | `0` | Scan completed -- all targets evaluated (findings, if any, are in the report) | | non-zero | Operational error -- one or more targets could not be evaluated, or zero requirements assessed (partial results written before exit) | @@ -232,7 +232,7 @@ targets: ``` | Field | Description | -|:---|:---| +| :--- | :--- | | `policies[].url` | Full OCI reference (registry + repository + optional `:tag`) | | `policies[].id` | Optional shortname; if omitted, derived from last path segment of URL | | `variables` | Workspace-scoped constants passed to providers via Generate RPC | diff --git a/cmd/mock-oci-registry/testdata/RH-WORKSTATION-AUDIT.md b/cmd/mock-oci-registry/testdata/RH-WORKSTATION-AUDIT.md index 5a1cdf284..a6cc996a6 100644 --- a/cmd/mock-oci-registry/testdata/RH-WORKSTATION-AUDIT.md +++ b/cmd/mock-oci-registry/testdata/RH-WORKSTATION-AUDIT.md @@ -12,7 +12,7 @@ Automates the Red Hat external audit requirement to provide evidence of: ### Layer 1: Guidance (`rh-workstation-audit-guidance.yaml`) - **Framework**: CIS Controls v8 -- **Controls**: +- **Controls**: - Control 4: Secure Configuration of Enterprise Assets - Control 7: Continuous Vulnerability Management diff --git a/docs/AI_TOOLING.md b/docs/AI_TOOLING.md index d06fbce3f..c909443fc 100644 --- a/docs/AI_TOOLING.md +++ b/docs/AI_TOOLING.md @@ -29,7 +29,7 @@ Any AI agent that supports command loading can use this repository's AI tooling: Reviews a pull request for alignment, security, and compliance. Designed to be token-efficient and CI-aware. -``` +```none /review-pr 42 ``` @@ -107,7 +107,7 @@ Skills provide domain knowledge the agent loads as context when activated. Unlik ## Key Files | File | Purpose | -|------|---------| +| ------ | --------- | | `.specify/memory/constitution.md` | Organizational governance and coding standards | | `docs/AI_TOOLING.md` | This file — AI tooling documentation | | `.agents/skills/` | Directory for AI skills — agent-agnostic, auto-discovered by OpenCode | @@ -119,7 +119,7 @@ Skills provide domain knowledge the agent loads as context when activated. Unlik Features are managed via spec-driven development. SpecKit and OpenSpec use separate output directories with coordinated sequential numbering: -``` +```text specs/ # SpecKit output ├── 001-first-feature/ │ └── spec.md diff --git a/docs/CODE_OF_CONDUCT.md b/docs/CODE_OF_CONDUCT.md index 047856e14..f2a8e6bfa 100644 --- a/docs/CODE_OF_CONDUCT.md +++ b/docs/CODE_OF_CONDUCT.md @@ -42,6 +42,6 @@ All project maintainers are obligated to respect the privacy and security of the ## Attribution -This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.1, available at https://www.contributor-covenant.org/version/2/1/code_of_conduct.html +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.1, available at [homepage]: https://www.contributor-covenant.org diff --git a/docs/QUICK_START.md b/docs/QUICK_START.md index 0062a417c..8d530d5fc 100644 --- a/docs/QUICK_START.md +++ b/docs/QUICK_START.md @@ -13,7 +13,7 @@ installed before `complyctl scan` can run. ### Available providers | Provider | Binary | What it evaluates | Prerequisites | -|----------|--------|-------------------|---------------| +| ---------- | -------- | ------------------- | --------------- | | [openscap](https://github.com/complytime/complytime-providers/blob/main/cmd/openscap-provider/docs/configuration.md) | `complyctl-provider-openscap` | SCAP policies (CIS, STIG, HIPAA, OSPP, etc.) | `openscap-scanner`, `scap-security-guide` | | [ampel](https://github.com/complytime/complytime-providers/tree/main/cmd/ampel-provider) | `complyctl-provider-ampel` | GitHub / GitLab branch protection | `snappy`, `ampel`, `GITHUB_TOKEN` or `GITLAB_TOKEN` | | [opa](https://github.com/complytime/complytime-providers/tree/main/cmd/opa-provider) | `complyctl-provider-opa` | OPA/Rego policies via conftest | `conftest`, `git` | @@ -94,7 +94,7 @@ targets: ``` | Section | Purpose | -|---------|---------| +| --------- | --------- | | `policies` | OCI references to Gemara policy bundles. `id` is a short alias used by targets and for provider routing. | | `complypacks` | Optional OCI references to provider-specific content bundles (policies, data files, scripts). Fetched alongside policies during `complyctl get`. | | `variables` | Workspace-scoped constants passed to all providers (e.g., custom policy directories). | diff --git a/docs/RELEASE_PROCESS.md b/docs/RELEASE_PROCESS.md index 190f8e690..ac1545c71 100644 --- a/docs/RELEASE_PROCESS.md +++ b/docs/RELEASE_PROCESS.md @@ -13,7 +13,7 @@ This workflow needs to be associated with a [Tag](https://github.com/complytime/ ```bash git remote -v ... -upstream https://github.com/complytime/complyctl.git (push) +upstream https://github.com/complytime/complyctl.git (push) ``` ```bash @@ -85,7 +85,7 @@ fkinit -u #### Fork the repository -Create a fork from https://src.fedoraproject.org/rpms/complyctl +Create a fork from ```bash fedpkg clone --anonymous forks//rpms/complyctl @@ -148,7 +148,7 @@ fedpkg switch-branch rawhide fedpkg build ``` - Follow the builds status in the following links: - - [Builds Status](https://koji.fedoraproject.org/koji/packageinfo?packageID=42298) + - [Builds Status](https://koji.fedoraproject.org/koji/packageinfo?packageID=42298) ### Submit Fedora updates @@ -164,8 +164,8 @@ The new updates enter in `testing` state and are moved to stable after 7 days, o After moving to `stable` state, the update is signed and awaits to be pushed to the repositories by the Release Engineering Team. Check the package update status in the following links: - - [Updates Status](https://bodhi.fedoraproject.org/updates/?packages=complyctl) - - [Package Overview](https://src.fedoraproject.org/rpms/complyctl) +- [Updates Status](https://bodhi.fedoraproject.org/updates/?packages=complyctl) +- [Package Overview](https://src.fedoraproject.org/rpms/complyctl) #### Troubleshooting diff --git a/docs/TESTING_ENVIRONMENT.md b/docs/TESTING_ENVIRONMENT.md index f1fca987b..a26b4b9df 100644 --- a/docs/TESTING_ENVIRONMENT.md +++ b/docs/TESTING_ENVIRONMENT.md @@ -172,7 +172,7 @@ export GITHUB_TOKEN= ### Binaries | Binary | Location | -|--------|----------| +| -------- | ---------- | | `complyctl` | `./bin/` | | `mock-oci-registry` | `./bin/` | | `snappy` | `$GOPATH/bin` | @@ -266,7 +266,7 @@ Place raw Gemara YAML files in a directory and mount it into the devcontainer at `/bundles/` (or set `COMPLYCTL_BUNDLES_DIR` to a custom path): -``` +```text /bundles/ └── my-private-policy/ ├── catalog.yaml @@ -360,7 +360,7 @@ namespace remaps your host UID to a different UID inside the container. This can change file ownership on the host after the workspace stops, causing git to refuse operations: -``` +```text fatal: detected dubious ownership in repository at '/path/to/complyctl' ``` diff --git a/docs/TESTING_FARM.md b/docs/TESTING_FARM.md index af94ab2a0..d6c386ee3 100644 --- a/docs/TESTING_FARM.md +++ b/docs/TESTING_FARM.md @@ -1,7 +1,7 @@ -# Tests using Testing Farm +# Tests using Testing Farm [Testing Farm](https://packit.dev/docs/configuration/upstream/tests) is Packit's testing system. -Test execution is managed by tmt tool. +Test execution is managed by tmt tool. The entry of the testing farm tests is located at [.packit.yaml](../.packit.yaml), in the job named `tests`. diff --git a/docs/man/complyctl.md b/docs/man/complyctl.md index a10e2e4d7..6cf6f402d 100644 --- a/docs/man/complyctl.md +++ b/docs/man/complyctl.md @@ -18,9 +18,9 @@ Complyctl can be extended to support desired policy engines by the use of provid Providers communicate with complyctl via gRPC and can be authored using any preferred language. The provider acts as the gRPC server while the complyctl CLI acts as the client. When a complyctl command is run, it invokes the appropriate method served by the provider. -See more about authoring providers at https://github.com/complytime/complytime-providers/blob/main/docs/provider-guide.md. +See more about authoring providers at . -Also check the complytime-providers repository at https://github.com/complytime/complytime-providers for provider-specific documentation. +Also check the complytime-providers repository at for provider-specific documentation. # COMMANDS @@ -158,9 +158,9 @@ summary table. Default: **true** (show all controls). # SEE ALSO -See the upstream project at https://github.com/complytime/complyctl for more detailed documentation. +See the upstream project at for more detailed documentation. -See the complytime-providers repository at https://github.com/complytime/complytime-providers for provider-specific documentation. +See the complytime-providers repository at for provider-specific documentation. # COPYRIGHT diff --git a/openspec/.markdownlint.json b/openspec/.markdownlint.json new file mode 100644 index 000000000..cd07f0001 --- /dev/null +++ b/openspec/.markdownlint.json @@ -0,0 +1,4 @@ +{ + "MD013": false, + "MD041": false +} diff --git a/openspec/changes/align-policyref-oci-semantics/design.md b/openspec/changes/align-policyref-oci-semantics/design.md index 2f8fd2e76..6a72ea3a0 100644 --- a/openspec/changes/align-policyref-oci-semantics/design.md +++ b/openspec/changes/align-policyref-oci-semantics/design.md @@ -125,7 +125,7 @@ per-failure results are added for user visibility. ### D5: Deprecation warning message format **Decision**: Use the format: -``` +```text DEPRECATED: @version notation in policy URL "". Use ":tag" syntax instead (e.g., "registry.com/repo:v1.0"). @version support will be removed in a future release. diff --git a/openspec/changes/cross-repo-integration-tests/proposal.md b/openspec/changes/cross-repo-integration-tests/proposal.md index 477496f98..d3be31371 100644 --- a/openspec/changes/cross-repo-integration-tests/proposal.md +++ b/openspec/changes/cross-repo-integration-tests/proposal.md @@ -63,7 +63,7 @@ be implemented once this change is merged. ## Constitution Alignment | Principle | Assessment | -|:----------|:-----------| +| :---------- | :----------- | | I. Single Source of Truth | Test script, fixtures, and CI workflow live in one place (complyctl). No duplication with complytime-providers. | | II. Simplicity & Isolation | Minimal test content (1 control, 1 rule). Each test function is isolated and independently verifiable. | | III. Incremental Improvement | Scoped to complyctl side only. Providers side is a separate change. | diff --git a/openspec/changes/opa-devcontainer-content/design.md b/openspec/changes/opa-devcontainer-content/design.md index 26f0ee2ef..bd1f3895d 100644 --- a/openspec/changes/opa-devcontainer-content/design.md +++ b/openspec/changes/opa-devcontainer-content/design.md @@ -14,8 +14,8 @@ OCI artifact seeding in the mock registry delivers cached complypack content paths to providers. PR #538 extended the mock registry with `seedFromDirectory()` -for serving mounted Gemara YAML files. The OPA provider (PR -#31 in complytime-providers) implements Generate with +for serving mounted Gemara YAML files. The OPA provider (PR #31 +in complytime-providers) implements Generate with mapping-based Rego namespace filtering via `conftest`. A companion PR in complytime-providers will update the OPA diff --git a/openspec/changes/reject-duplicate-evaluator-id/design.md b/openspec/changes/reject-duplicate-evaluator-id/design.md index 7ec64a856..adc064585 100644 --- a/openspec/changes/reject-duplicate-evaluator-id/design.md +++ b/openspec/changes/reject-duplicate-evaluator-id/design.md @@ -25,7 +25,7 @@ Add a `validateUniqueEvaluatorIDs(state *cache.State, complypacks []complytime.P ### D3: Error format -``` +```text Error: duplicate evaluator-id "opa" found in complypack entries: - ghcr.io/org-a/complypack-opa@v1 - ghcr.io/org-b/complypack-opa@v2 diff --git a/openspec/changes/scan-error-exit-codes/design.md b/openspec/changes/scan-error-exit-codes/design.md index 2648f76f9..5fddb1051 100644 --- a/openspec/changes/scan-error-exit-codes/design.md +++ b/openspec/changes/scan-error-exit-codes/design.md @@ -4,7 +4,7 @@ The `bug/scan-proto` commit added `repeated string errors` to `ScanResponse` and The scan execution chain is: -``` +```text scanCmd.RunE → run() → scanPolicy() → executeScanPhase() → runScanAndReport() → executeScan() → scanAllTargets() → scanSingleTarget() → mgr.RouteScan() @@ -35,7 +35,7 @@ Errors must propagate from `scanSingleTarget` up to `runScanAndReport` where the Exit code semantics: | Condition | Exit code | Rationale | -|:--|:--|:--| +| :-- | :-- | :-- | | All targets scanned, all passed | 0 | Clean | | All targets scanned, some failed | 0 | Findings are data | | Some targets had operational errors | non-zero | Coverage gap | @@ -55,7 +55,7 @@ Operational errors are printed to stderr as `WARNING:` lines before `FormatScanS Format: -``` +```text WARNING: 2 operational error(s) during scan: - target 'staging': clone failed: auth denied - target 'dev': missing required tool: conftest diff --git a/openspec/changes/scan-target-arg/design.md b/openspec/changes/scan-target-arg/design.md index 70bab571a..d8aec8b21 100644 --- a/openspec/changes/scan-target-arg/design.md +++ b/openspec/changes/scan-target-arg/design.md @@ -61,7 +61,7 @@ Rationale: Clean separation of concerns. `run()` validates inputs and resolves r In `scanPolicy()`, the target filter must be applied after `ensureGenerated()` but before `executeScanPhase()`. Generation freshness is tracked per-policy (via `GenerationState.PolicyDigest`), not per-target. If we narrow targets before generation, a sequence like: -``` +```bash complyctl scan prod --policy-id nist → generates for prod only complyctl scan staging --policy-id nist → skips generation (digest fresh) — staging never generated ``` diff --git a/openspec/changes/workspace-configuration/design.md b/openspec/changes/workspace-configuration/design.md index 83738154a..737d188dd 100644 --- a/openspec/changes/workspace-configuration/design.md +++ b/openspec/changes/workspace-configuration/design.md @@ -59,7 +59,7 @@ Rationale: Smooth migration path. Users can adopt the new location at their own ### D4: Deprecation warning is informative, not disruptive When legacy location is detected, print to stderr: -``` +```text WARNING: complytime.yaml found at repository root (legacy location). Please move it to .complytime/complytime.yaml for better organization. Run: mkdir -p .complytime && mv complytime.yaml .complytime/complytime.yaml @@ -299,7 +299,7 @@ cmd.PersistentPreRun = func(_ *cobra.Command, _ []string) { ## Error Scenarios | Scenario | Error Message | Exit Code | -|----------|--------------|-----------| +| ---------- | -------------- | ----------- | | `--workspace /nonexistent` | `workspace directory does not exist: /nonexistent` | 1 | | `--workspace /etc/passwd` (file) | `workspace path is not a directory: /etc/passwd` | 1 | | Neither config location exists | `config file not found in /path (checked .complytime/complytime.yaml and complytime.yaml)` | 1 | diff --git a/openspec/changes/workspace-configuration/proposal.md b/openspec/changes/workspace-configuration/proposal.md index b53739d9d..963ccca3c 100644 --- a/openspec/changes/workspace-configuration/proposal.md +++ b/openspec/changes/workspace-configuration/proposal.md @@ -36,7 +36,7 @@ This aligns with how other tools work (git uses `.git/`, docker uses `.docker/`, ## Constitution Alignment | Principle | Status | Evidence | -|-----------|--------|----------| +| ----------- | -------- | ---------- | | I. Autonomous Collaboration | PASS | Workspace resolution happens early in command lifecycle; all subsystems receive resolved paths via dependency injection | | II. Composability First | PASS | Workspace resolution is a separate concern from config loading; components receive paths as parameters | | III. Observable Quality | PASS | Error messages clearly state which workspace directory failed validation; deprecation warnings guide users to new config location | @@ -57,7 +57,7 @@ This aligns with how other tools work (git uses `.git/`, docker uses `.docker/`, - **Tests**: Unit tests for workspace resolution, config detection, backward compatibility. Integration tests for flag, env var, and legacy config fallback. - **Documentation**: Update README.md with `--workspace` flag examples. Add migration guide to CHANGELOG.md. Update AGENTS.md "Recent Changes" section. - **Breaking changes**: None. The change is purely additive with backward compatibility. -- **Deprecation timeline**: +- **Deprecation timeline**: - Current release: Support both locations, print deprecation warning for root location - Future releases: Continue supporting both locations - No hard removal date set — will be determined based on user adoption metrics diff --git a/openspec/changes/workspace-configuration/release-notes.md b/openspec/changes/workspace-configuration/release-notes.md index 446776f9e..f81e4089c 100644 --- a/openspec/changes/workspace-configuration/release-notes.md +++ b/openspec/changes/workspace-configuration/release-notes.md @@ -38,7 +38,7 @@ git commit -m "chore: migrate complytime.yaml to .complytime/ directory" ``` Until you migrate, commands will continue to work but will show a deprecation warning: -``` +```text WARNING: complytime.yaml found at repository root (legacy location). Please move it to .complytime/complytime.yaml for better organization. Run: mkdir -p .complytime && mv complytime.yaml .complytime/complytime.yaml diff --git a/openspec/changes/workspace-configuration/specs/workspace-configuration.md b/openspec/changes/workspace-configuration/specs/workspace-configuration.md index d1901263d..65f23ecc4 100644 --- a/openspec/changes/workspace-configuration/specs/workspace-configuration.md +++ b/openspec/changes/workspace-configuration/specs/workspace-configuration.md @@ -202,28 +202,28 @@ const WorkspaceEnvVar = "COMPLYTIME_WORKSPACE" ### ER-001: Workspace Does Not Exist When workspace path does not exist: -``` +```text workspace directory does not exist: ``` ### ER-002: Workspace Not A Directory When workspace path is a file: -``` +```text workspace path is not a directory: ``` ### ER-003: Config Not Found When config file is not found in either location: -``` +```text config file not found in (checked .complytime/complytime.yaml and complytime.yaml) ``` ### ER-004: Invalid Path Resolution When path resolution fails: -``` +```text failed to resolve workspace path: ``` @@ -231,7 +231,7 @@ failed to resolve workspace path: ### DW-001: Legacy Config Location Warning -``` +```text WARNING: complytime.yaml found at repository root (legacy location). Please move it to .complytime/complytime.yaml for better organization. Run: mkdir -p .complytime && mv complytime.yaml .complytime/complytime.yaml diff --git a/openspec/changes/workspace-configuration/tasks.md b/openspec/changes/workspace-configuration/tasks.md index 41fa1810f..269e9d571 100644 --- a/openspec/changes/workspace-configuration/tasks.md +++ b/openspec/changes/workspace-configuration/tasks.md @@ -47,7 +47,7 @@ ### Task 2.3: Update scan output path construction - [ ] Update `processScanOutput()` in `scan.go` to use baseDir parameter -- [ ] Change `outDir := filepath.Join(".", complytime.WorkspaceDir, complytime.ScanOutputDir)` +- [ ] Change `outDir := filepath.Join(".", complytime.WorkspaceDir, complytime.ScanOutputDir)` to `outDir := filepath.Join(baseDir, complytime.WorkspaceDir, complytime.ScanOutputDir)` - [ ] Verify `writeScanReports()` receives correct output directory diff --git a/specs/.markdownlint.json b/specs/.markdownlint.json new file mode 100644 index 000000000..67d2ae55b --- /dev/null +++ b/specs/.markdownlint.json @@ -0,0 +1,3 @@ +{ + "MD013": false +} diff --git a/specs/001-gemara-native-workflow/contracts/README.md b/specs/001-gemara-native-workflow/contracts/README.md index c5a9f11b2..a95455e47 100644 --- a/specs/001-gemara-native-workflow/contracts/README.md +++ b/specs/001-gemara-native-workflow/contracts/README.md @@ -127,7 +127,7 @@ Mirrors `go-gemara` `ConfidenceLevel` type (R29). 1:1 mapping — no lossy conve The scanning interface protocol distinguishes three variable tiers: | Tier | Name | RPC | Proto field | Source | Owner | -|:---|:---|:---|:---|:---|:---| +| :--- | :--- | :--- | :--- | :--- | :--- | | 1 | Global variables | Generate | `GenerateRequest.global_variables` | Workspace config top-level `variables` | System admin | | 2 | Test variables | Generate | `AssessmentConfiguration.parameters` | Layer 3 Gemara policy | Policy author | | 3 | Target variables | Scan | `Target.variables` | Workspace config `targets[].variables` | System admin | @@ -164,13 +164,13 @@ buf generate - Generated code output: `pkg/plugin/api/` **Setup**: -1. Install buf: https://buf.build/docs/installation +1. Install buf: 2. Configure `buf.yaml` and `buf.gen.yaml` in `api/proto/` 3. Run `buf generate` to generate Go code **References**: -- buf documentation: https://buf.build/docs -- compliance-to-policy-go buf setup: https://github.com/oscal-compass/compliance-to-policy-go/blob/main/buf.yaml +- buf documentation: +- compliance-to-policy-go buf setup: ## Example Plugin Implementation diff --git a/specs/001-gemara-native-workflow/data-model.md b/specs/001-gemara-native-workflow/data-model.md index 1ce3bb7aa..acd918277 100644 --- a/specs/001-gemara-native-workflow/data-model.md +++ b/specs/001-gemara-native-workflow/data-model.md @@ -9,7 +9,7 @@ Workspace configuration file (YAML). Single mode using `PolicyEntry` objects (Session 2026-02-25d). No `pack` field, no separate `registry` section — each policy URL is a self-contained OCI reference including its own registry. | Field | Type | Required | Description | -|:---|:---|:---|:---| +| :--- | :--- | :--- | :--- | | `policies` | `[]PolicyEntry` | Yes (>=1) | Policies to fetch and evaluate. Each entry has a full OCI URL and optional shortname ID | | `variables` | `map[string]string` | No | Global variables — workspace-scoped config (e.g., `workspace: ./.complytime/scan`). Passed to providers during Generate RPC (R48, R49) | | `targets` | `[]TargetConfig` | No | Systems/environments to scan | @@ -24,7 +24,7 @@ Workspace configuration file (YAML). Single mode using `PolicyEntry` objects (Se ### PolicyEntry (Session 2026-02-25d) | Field | Type | Required | Description | -|:---|:---|:---|:---| +| :--- | :--- | :--- | :--- | | `url` | `string` | Yes | Full OCI reference including registry (e.g., `registry.com/policies/nist-800-53-r5@v1.0`) | | `id` | `string` | No | User-chosen shortname. If omitted, auto-derived from last URL path segment via `EffectiveID()` | @@ -51,7 +51,7 @@ Replaced by `PolicyEntry`. The separate `id` + `version` fields are now a single ### TargetConfig | Field | Type | Required | Description | -|:---|:---|:---|:---| +| :--- | :--- | :--- | :--- | | `id` | `string` | Yes | Target identifier | | `policies` | `[]string` | Yes (>=1) | Policy effective IDs applicable to this target (Session 2026-02-25d; was `policy_ids`) | | `variables` | `map[string]string` | No | Target variables — per-target runtime config (credentials, profile, kubeconfig) (R48) | @@ -99,14 +99,14 @@ Local OCI Layout store per policy ID using `oras-go/v2/content/oci`. Each policy ### CacheState (`state.json`) | Field | Type | Description | -|:---|:---|:---| +| :--- | :--- | :--- | | `last_sync` | `string` (ISO 8601) | Timestamp of last successful sync | | `policies` | `map[string]PolicyState` | Per-policy sync state | ### PolicyState | Field | Type | Description | -|:---|:---|:---| +| :--- | :--- | :--- | | `version` | `string` | Cached version tag | | `digest` | `string` | OCI manifest digest (SHA256) | | `synced_at` | `string` (ISO 8601) | When this policy was last synced | @@ -118,7 +118,7 @@ Local OCI Layout store per policy ID using `oras-go/v2/content/oci`. Each policy In-memory resolved graph of Gemara Layers 1-3 for a specific policy. Supports multi-evaluator routing — each assessment plan's `evaluation-methods[].executor.id` determines which plugin handles that plan's requirements (R32). | Field | Type | Description | -|:---|:---|:---| +| :--- | :--- | :--- | | `policy_id` | `string` | Root policy identifier | | `version` | `string` | Resolved version | | `controls` | `[]gemara.Control` | Layer 2 controls (from `go-gemara`). For OpenSCAP: each XCCDF rule = one Control containing one Assessment Requirement (the testable statement) + guideline-mappings to CIS items (`cis-*` pattern) | @@ -137,7 +137,7 @@ In-memory resolved graph of Gemara Layers 1-3 for a specific policy. Supports mu Configuration extracted from the policy graph and passed to plugins via Generate RPC. | Field | Type | Description | -|:---|:---|:---| +| :--- | :--- | :--- | | `plan_id` | `string` | Assessment plan identifier | | `requirement_id` | `string` | Requirement identifier | | `evaluator_id` | `string` | Target evaluator (maps to plugin) | @@ -162,7 +162,7 @@ Configuration extracted from the policy graph and passed to plugins via Generate Tracks the policy cache digest at generation time for freshness detection (R37). Persisted after `complyctl generate` (or auto-generate within `scan`). Read by `scan` to determine whether to reuse or regenerate. | Field | Type | Description | -|:---|:---|:---| +| :--- | :--- | :--- | | `policy_id` | `string` | Policy that was generated | | `policy_digest` | `string` | OCI manifest digest (SHA256) at generation time | | `generated_at` | `string` (ISO 8601) | When generation occurred | @@ -182,7 +182,7 @@ Structured plain-text execution plan output after graph resolution and plugin pr **ExecutionPlanRow** (one stanza per target-provider combination): | Field | Type | Description | -|:---|:---|:---| +| :--- | :--- | :--- | | `target_id` | `string` | Target identifier from workspace config | | `provider_id` | `string` | Evaluator/provider identifier from assessment plan | | `requirement_count` | `int` | Number of requirements routed to this provider for this target | @@ -201,7 +201,7 @@ Structured plain-text execution plan output after graph resolution and plugin pr Discovered gRPC scanning provider with lifecycle management. No sidecar manifest files — all metadata derived at runtime (R19). User-facing terminology: "scanning provider"; code-level: `plugin.Plugin` (R46). | Field | Type | Description | -|:---|:---|:---| +| :--- | :--- | :--- | | `plugin_id` | `string` | Derived from executable name | | `evaluator_id` | `string` | `plugin_id` minus `complyctl-provider-` prefix | | `path` | `string` | Filesystem path to executable | @@ -222,14 +222,14 @@ Discovered gRPC scanning provider with lifecycle management. No sidecar manifest Gemara Layer 4 output — always produced on scan. Uses `go-gemara` `EvaluationLog` type directly. | Field | Type | Description | -|:---|:---|:---| +| :--- | :--- | :--- | | `metadata` | `gemara.Metadata` | Log metadata (title, datetime, actors) | | `evaluations` | `[]gemara.ControlEvaluation` | Results grouped by control | **ControlEvaluation** (one per control): | Field | Type | Description | -|:---|:---|:---| +| :--- | :--- | :--- | | `name` | `string` | Control identifier | | `result` | `gemara.Result` | Aggregated result for this control | | `control` | `gemara.EntryMapping` | Reference to the control (`EntryId` + `ReferenceId` = policy ID) | @@ -250,7 +250,7 @@ Post-scan report-style output displayed in the terminal after every scan (FR-037 **ScanSummaryEntry** (one row per non-passing result): | Field | Type | Description | -|:---|:---|:---| +| :--- | :--- | :--- | | `requirement_id` | `string` | Requirement identifier from `AssessmentLog.RequirementId` | | `control_id` | `string` | Control identifier resolved via `reqToControl` map | | `result` | `Result` (enum) | Aggregated assessment outcome (failed, skipped, error only) | @@ -287,7 +287,7 @@ Pre-flight diagnostics output from `complyctl doctor` (FR-039, R44, R55). Not pe **CheckResult** (one per diagnostic check): | Field | Type | Description | -|:---|:---|:---| +| :--- | :--- | :--- | | `name` | `string` | Check identifier (e.g., `config`, `provider/{id}`, `policy/{id}`, `registry/{host}`, `variables/{id}`). Config check includes structural validation + target-policy cross-references (R50). Policy checks compare cached vs. remote version (R55). Variables checks validate Describe-declared `required_global_variables` against `config.variables` and `required_target_variables` against relevant `config.targets[].variables` using policy → evaluator → target mapping from cache (R51, R52) | | `status` | `CheckStatus` (enum) | `pass`, `fail`, `warn` | | `message` | `string` | Human-readable result (e.g., `complytime.yaml valid`, `v1.0.0 (latest)`, `cached v1.0.0, available v1.1.0`) | @@ -296,7 +296,7 @@ Pre-flight diagnostics output from `complyctl doctor` (FR-039, R44, R55). Not pe **CheckStatus** (enum): | Value | Emoji | Semantic | -|:---|:---|:---| +| :--- | :--- | :--- | | `pass` | ✅ | Check passed | | `fail` | ❌ | Check failed (blocking) | | `warn` | ⚠️ | Check produced a non-blocking warning | @@ -304,7 +304,7 @@ Pre-flight diagnostics output from `complyctl doctor` (FR-039, R44, R55). Not pe **Check categories (R55 updated)**: | Check | Name Pattern | Blocking | Pass | Warn | Fail | -|:---|:---|:---|:---|:---|:---| +| :--- | :--- | :--- | :--- | :--- | :--- | | Config | `config` | Yes | `complytime.yaml valid` | — | `config not found` / `validation failed` | | Provider health | `provider/{id}` | Yes | `healthy (v1.2.0)` | — | `unhealthy` / `Describe failed` | | Policy version | `policy/{id}` | No | `v1.0.0 (latest)` | `cached v1.0.0, available v1.1.0 — run complyctl get` | — | @@ -324,7 +324,7 @@ Pre-flight diagnostics output from `complyctl doctor` (FR-039, R44, R55). Not pe ### AssessmentLog (per requirement) | Field | Type | Description | -|:---|:---|:---| +| :--- | :--- | :--- | | `requirement_id` | `string` | Evaluated requirement | | `steps` | `[]Step` | Ordered execution steps | | `message` | `string` | Summary message | @@ -334,7 +334,7 @@ Pre-flight diagnostics output from `complyctl doctor` (FR-039, R44, R55). Not pe ### Step (per assessment step) | Field | Type | Description | -|:---|:---|:---| +| :--- | :--- | :--- | | `name` | `string` | Step name/identifier | | `result` | `Result` (enum) | Step outcome | | `message` | `string` | Step result message | @@ -344,7 +344,7 @@ Pre-flight diagnostics output from `complyctl doctor` (FR-039, R44, R55). Not pe ### Result (enum) | Value | Proto | Description | -|:---|:---|:---| +| :--- | :--- | :--- | | `unspecified` | `RESULT_UNSPECIFIED` | Default/zero value — result not yet determined | | `passed` | `RESULT_PASSED` | Requirement satisfied | | `failed` | `RESULT_FAILED` | Requirement not satisfied | @@ -422,7 +422,7 @@ PackManifest (types in 001 data model only; ALL CLI deferred to 002; R53) Pack manifest file (YAML). Declares what a comply-pack contains — developer-owned, immutable after build, ships in the pack. Separate from `complytime.yaml` (runtime config). All pack CLI commands (`pack init`, build, push, pull) deferred to 002. Pack builder is separate from `complyctl` runtime (Session 2026-02-25d). See R53, Session 2026-02-25b. | Field | Type | Required | Owner | Description | -|:---|:---|:---|:---|:---| +| :--- | :--- | :--- | :--- | :--- | | `id` | `string` | Yes | Developer | Pack identifier (e.g., `fedora-compliance`) | | `version` | `string` | Yes | Developer | Semantic version of the pack | | `description` | `string` | No | Developer | Human-readable description | @@ -438,14 +438,14 @@ Pack manifest file (YAML). Declares what a comply-pack contains — developer-ow ### PlatformConfig | Field | Type | Required | Description | -|:---|:---|:---|:---| +| :--- | :--- | :--- | :--- | | `os` | `string` | Yes | Target operating system (e.g., `fedora`) | | `datastream` | `string` | No | Absolute path to SCAP datastream XML | ### PackPolicyEntry | Field | Type | Required | Description | -|:---|:---|:---|:---| +| :--- | :--- | :--- | :--- | | `url` | `string` | Yes | Full OCI reference (registry + repo + version) | | `id` | `string` | Yes | Policy shortname | | `profile` | `string` | No | SSG profile name for OpenSCAP evaluator | @@ -457,7 +457,7 @@ Pack manifest file (YAML). Declares what a comply-pack contains — developer-ow ### PackProviderEntry | Field | Type | Required | Description | -|:---|:---|:---|:---| +| :--- | :--- | :--- | :--- | | `id` | `string` | Yes | Provider/evaluator identifier | | `binary` | `string` | Yes | Binary name (e.g., `complyctl-provider-openscap`) | | `source` | `string` | No | Build source (`build` for compiled, `bundled` for pre-built) | @@ -465,7 +465,7 @@ Pack manifest file (YAML). Declares what a comply-pack contains — developer-ow ### SystemDependency | Field | Type | Required | Description | -|:---|:---|:---|:---| +| :--- | :--- | :--- | :--- | | `name` | `string` | Yes | Package name | | `check` | `string` | Yes | Shell command to verify installation | | `install` | `string` | No | Installation command (for guidance only) | diff --git a/specs/001-gemara-native-workflow/plan.md b/specs/001-gemara-native-workflow/plan.md index 2280e4600..b3efe574b 100644 --- a/specs/001-gemara-native-workflow/plan.md +++ b/specs/001-gemara-native-workflow/plan.md @@ -220,7 +220,7 @@ flowchart TD *GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.* | Principle | Status | Evidence | -|:---|:---|:---| +| :--- | :--- | :--- | | I. Single Source of Truth | **PASS** | Constants centralized in `internal/complytime/consts.go`. Media type constants (R27). Config in `complytime.yaml`. Three variable tiers each have a single authoritative location (R48). | | II. Simplicity & Isolation | **PASS** | Small focused packages: `cache`, `config`, `plugin`, `policy`, `output`, `registry`, `doctor`. Targets-only Scan RPC simplifies proto contract (R47). | | III. Incremental Improvement | **PASS** | Scanning provider simplification (manifest/checksum removal) in separate PR scope from auth refactor. | diff --git a/specs/001-gemara-native-workflow/quickstart.md b/specs/001-gemara-native-workflow/quickstart.md index 925ce5cf9..d23b468a6 100644 --- a/specs/001-gemara-native-workflow/quickstart.md +++ b/specs/001-gemara-native-workflow/quickstart.md @@ -326,7 +326,7 @@ func main() { complyctl uses a two-tier output model. Only plugin-communicating commands create a log file (`.complytime/complyctl.log`, truncated per run). go-plugin output is filtered to WARN and above in the log file. | Tier | Commands | Terminal Output | Log File | Channel | -|:---|:---|:---|:---|:---| +| :--- | :--- | :--- | :--- | :--- | | Progress | `init`, `get` | Real-time per-step status | No | stderr | | Summary + log | `generate`, `scan`, `providers`, `doctor` | Tables, execution plans, scan summaries, diagnostics | Yes | stdout | | No logging | `version`, `list` | Output only | No | stdout | @@ -364,7 +364,7 @@ Pack design documentation will be produced in the `002-comply-packs` feature bra ## CLI Commands | Command | Description | -|:---|:---| +| :--- | :--- | | `init` | Create `complytime.yaml` — prompts for PolicyEntry URLs + optional IDs + targets (Session 2026-02-25d) | | `get` | Fetch/sync policies from OCI registry (per-registry clients from PolicyEntry URLs) | | `list` | List cached policies (effective IDs + versions, plain text) | diff --git a/specs/001-gemara-native-workflow/research.md b/specs/001-gemara-native-workflow/research.md index c4132fa90..62b863d4b 100644 --- a/specs/001-gemara-native-workflow/research.md +++ b/specs/001-gemara-native-workflow/research.md @@ -62,7 +62,7 @@ **Migration scope**: | Component | Before | After | -|:---|:---|:---| +| :--- | :--- | :--- | | `internal/registry/auth.go` | ~130 lines: `Authenticator` struct, `AuthConfig` interface, `OrasAuthConfig`, `loadDockerCredentials()`, `readDockerConfig()`, `queryCredHelper()`, `resolveInlineAuth()` | ~15 lines: `NewCredentialFunc() auth.CredentialFunc` backed by `credentials.NewStoreFromDocker()` | | `internal/registry/client.go` | `auth *Authenticator` field, `GetAuthConfig()` calls | `credFunc auth.CredentialFunc` field, direct `auth.Client{Credential: credFunc}` creation | | `get.go`, `generate.go`, etc. | `registry.NewAuthenticator()` | `registry.NewCredentialFunc()` (or inline in `NewClient`) | @@ -83,7 +83,7 @@ **Files/packages to remove or gut**: | Target | Action | -|:---|:---| +| :--- | :--- | | `cmd/complyctl/cli/plan.go` + test | Delete entirely | | `cmd/complyctl/cli/info.go` + test | Delete entirely | | `cmd/complyctl/option/common.go` ComplyTime struct | Remove C2P-specific `ToPluginOptions` and `FrameworkID`. Keep `Common` struct and `UserWorkspace` path helpers for directory resolution. Rename `ComplyTime` → `Options` if it no longer has C2P semantics. | @@ -163,7 +163,7 @@ **Migration scope**: | Component | Before | After | -|:---|:---|:---| +| :--- | :--- | :--- | | `internal/cache/cache.go` | Custom `EnsurePolicyDir`, `StoreManifest`, `StoreLayer` (97 lines) | OCI Layout store wrapper: `oci.New(path)`, tag/resolve (≈40 lines) | | `internal/cache/sync.go` | Custom temp-dir-rename, `downloadPolicyToDir` (147 lines) | `oras.Copy()` from remote to local store (≈60 lines) | | `internal/cache/state.go` | JSON state file (95 lines) | Unchanged — oras tracks blobs but not policy-level metadata | @@ -196,7 +196,7 @@ **What was in the manifest**: | Field | Replacement | -|:---|:---| +| :--- | :--- | | `evaluator_ids` | Derived from executable filename (`complyctl-provider-X` → evaluator `X`) | | `checksum` (SHA256) | Removed (see R20) | | `version` | Reported via HealthCheck RPC response | @@ -217,7 +217,7 @@ **Rationale — Threat model analysis**: | Threat | Mitigation by Checksum | Assessment | -|:---|:---|:---| +| :--- | :--- | :--- | | Binary tampering (attacker replaces plugin) | Checksum in manifest detects mismatch | **Ineffective** — manifest and binary share same directory/permissions. Attacker replaces both. | | Supply chain attack (compromised download) | Checksum verifies integrity | **Ineffective** — manifest is unsigned YAML, not from a trusted separate channel. | | Accidental corruption (bit rot) | Checksum detects corruption | **Marginal** — filesystem integrity already handles this. | @@ -346,7 +346,7 @@ func NewClient(executablePath string, logger hclog.Logger) (*Client, error) **Code impact**: | Component | Before | After | -|:---|:---|:---| +| :--- | :--- | :--- | | `contracts/plugin.proto` | `double confidence = 4` | `ConfidenceLevel confidence = 4` + new enum definition | | `pkg/plugin/client.go` | `Confidence float64` | `Confidence ConfidenceLevel` (new type mapping go-gemara enum) | | `pkg/plugin/server.go` | `Confidence: a.Confidence` (float64) | `Confidence: a.Confidence` (enum) | @@ -366,7 +366,7 @@ func NewClient(executablePath string, logger hclog.Logger) (*Client, error) **Mapping (code-level — policytype removal)**: | policytype.Policy field | AssessmentConfiguration equivalent | -|:---|:---| +| :--- | :--- | | `RuleSet.Rule.ID` | `RequirementID` (1:1 in OpenSCAP; requirement ID = XCCDF rule short name) | | `RuleSet.Rule.Parameters[].ID` | Key in `Parameters` map | | `RuleSet.Rule.Parameters[].Value` | Value in `Parameters` map | @@ -375,7 +375,7 @@ func NewClient(executablePath string, logger hclog.Logger) (*Client, error) **Conceptual mapping (OSCAP → Gemara, Session 2026-02-25)**: | OSCAP Concept | Gemara Artifact | Layer | -|:---|:---|:---| +| :--- | :--- | :--- | | XCCDF Rule | Control + Assessment Requirement | Layer 2 (Control Catalog) | | OVAL Check | Assessment Plan | Layer 3 (Policy) | | CIS ID (`cis-*`) | Guideline-mapping on Control | Layer 2 → Layer 1 | @@ -395,7 +395,7 @@ func NewClient(executablePath string, logger hclog.Logger) (*Client, error) **Data flow**: | Channel | Source | Lifecycle | RPC | Owner | -|:---|:---|:---|:---|:---| +| :--- | :--- | :--- | :--- | :--- | | Parameters | Layer 3 Gemara policy `assessment-plans.parameters` | `complyctl generate` | `GenerateRequest.configurations[].parameters` | Policy author | | Vars | Workspace config `targets[].variables` | `complyctl scan` | `ScanRequest.targets[].variables` | System admin | @@ -413,7 +413,7 @@ func NewClient(executablePath string, logger hclog.Logger) (*Client, error) **Code impact**: | Component | Before | After | -|:---|:---|:---| +| :--- | :--- | :--- | | `internal/policy/resolver.go` | Single `EvaluatorID` per policy | Per-assessment `EvaluatorID` from `plan.evaluation-methods[].executor.id` | | `internal/policy/assessment.go` | `GroupByEvaluator()` groups by policy-level evaluator | `GroupByEvaluator()` groups by per-assessment evaluator ID | | `internal/plugin/manager.go` | One `Generate()` call per policy | N `Generate()` calls per policy (one per evaluator group) | @@ -446,7 +446,7 @@ func NewClient(executablePath string, logger hclog.Logger) (*Client, error) **Code impact**: | Component | Before | After | -|:---|:---|:---| +| :--- | :--- | :--- | | `cmd/complyctl/cli/generate.go` | `generateCmd` (existing) | Stays as `generateCmd`; add digest persistence + execution plan output | | `internal/policy/generation_state.go` | (new) | Stores `GenerationState` (policy digest, timestamp, evaluator IDs) | | `cmd/complyctl/cli/scan.go` | Calls Scan RPC directly | Checks generation freshness first; auto-generates if needed | @@ -466,7 +466,7 @@ func NewClient(executablePath string, logger hclog.Logger) (*Client, error) **Code impact**: | Component | Before | After | -|:---|:---|:---| +| :--- | :--- | :--- | | `internal/complytime/config.go` | `PluginVariables map[string]string` | `EvaluatorConfig map[string]map[string]string` | | `api/plugin/plugin.proto` | `map plugin_variables = 1` | `map evaluator_config = 1` | | `cmd/complyctl/cli/generate.go` | Passes flat map | Looks up `evaluator_config[evaluatorID]` per group | @@ -509,7 +509,7 @@ Target Scope: **Decision**: `generate` persists a `GenerationState` (policy cache digest, timestamp, evaluator IDs used) alongside generated artifacts. `scan` checks this state before executing: | Condition | Behavior | -|:---|:---| +| :--- | :--- | | No `GenerationState` exists | Auto-generate (resolve graph, invoke Generate RPC, persist state) | | State exists, digest matches current cache | Reuse generated artifacts (skip Generate RPC) | | State exists, digest differs (policy updated via `get`) | Warn admin, auto-regenerate, then scan | @@ -534,7 +534,7 @@ Target Scope: **Code impact**: | Component | Change | -|:---|:---| +| :--- | :--- | | `internal/policy/generation_state.go` | New file: `GenerationState` struct, `SaveState()`, `LoadState()`, `IsFresh()`. Path: `{workspace}/.complytime/generation/{policy-id}.json` | | `cmd/complyctl/cli/generate.go` | After Generate RPC, save `GenerationState` with current policy digest | | `cmd/complyctl/cli/scan.go` | Before Scan RPC, check `IsFresh()`: if stale → warn + auto-generate; if missing → auto-generate; if fresh → reuse. `--dry-run` persists state then exits (no Scan RPC) | @@ -555,7 +555,7 @@ Target Scope: **Code impact**: | Component | Before | After | -|:---|:---|:---| +| :--- | :--- | :--- | | `cmd/complyctl/cli/plugins.go` | `text/tabwriter` + `fmt.Fprintf` | `charmbracelet/bubbles/table` + `lipgloss` via `internal/terminal`. Add `--plain` flag | | `internal/output/execution_plan.go` | `fmt.Fprintf` with manual column alignment | Two `bubbles/table` instances (Evaluator Routing + Target Scope) rendered via `lipgloss`. Returns styled string | | `cmd/complyctl/cli/generate.go` | `fmt.Print(output.FormatExecutionPlan(...))` | Same call, `FormatExecutionPlan` now returns charmbracelet-rendered output | @@ -578,7 +578,7 @@ Target Scope: **Code impact**: | Component | Before | After | -|:---|:---|:---| +| :--- | :--- | :--- | | `internal/policy/resolver.go` `parsePolicyLayer` | 3 format attempts (Gemara → structured → legacy), no error return | 1 format: `gemara.Policy` unmarshal + `extractFromGemaraPolicy`. Returns `(policyLayerResult, error)` | | `internal/policy/resolver.go` `ResolvePolicyGraph` | `policyLayer := parsePolicyLayer(...)` (ignores format failures) | `policyLayer, err := parsePolicyLayer(...)` — propagates error to caller | | `internal/policy/resolver.go` structured/legacy structs | ~35 lines of inline struct definitions + unmarshal logic | Deleted | @@ -601,7 +601,7 @@ Target Scope: **Code impact**: | Component | Change | -|:---|:---| +| :--- | :--- | | `cmd/complyctl/cli/get.go` | Add `fmt.Fprintf(os.Stderr, "Syncing policy %d/%d: %s...\n", i+1, total, policy.ID)` before each `sync.SyncPolicy()` call. Add `fmt.Fprintf(os.Stderr, " done\n")` after success. Replace final `fmt.Println("Synchronization completed.")` with `fmt.Fprintln(os.Stderr, "Synchronization completed.")` | | `cmd/complyctl/cli/init.go` | Show config creation status (if applicable), delegate to `get` for sync progress, then show doctor diagnostic results (R52, supersedes R50 ordering) | | `cmd/complyctl/cli/root.go` | No structural change — `logger` stays file-only. Log file created lazily on first write via `lazyLogWriter` (Session 2026-02-23f). No terminal notification | @@ -625,7 +625,7 @@ Target Scope: **Code impact**: | Component | Change | -|:---|:---| +| :--- | :--- | | `internal/policy/assessment.go` | New function: `ValidateEvaluatorConfig(groups map[string]EvaluatorGroup, configPath string) error`. Iterates groups; if `group.EvaluatorConfig == nil`, returns error naming the evaluator ID and config path | | `cmd/complyctl/cli/generate.go` | Add `if err := policy.ValidateEvaluatorConfig(groups, ws.Path()); err != nil { return err }` after `GroupByEvaluator()` | | `cmd/complyctl/cli/scan.go` | Same validation call after `GroupByEvaluator()` | @@ -647,7 +647,7 @@ Target Scope: **Code impact**: | Component | Before | After | -|:---|:---|:---| +| :--- | :--- | :--- | | `internal/complytime/consts.go` | (no scan output dir constant) | `const ScanOutputDir = ".complytime/scan"` | | `cmd/complyctl/cli/scan.go` | `filepath.Join(".", ".complytime/scan")` | `filepath.Join(".", consts.ScanOutputDir)` | | `internal/output/evaluator.go` | (receives `outDir` parameter — no change) | No change (caller passes the constant) | @@ -668,7 +668,7 @@ Target Scope: **Implementation**: | Component | Change | -|:---|:---| +| :--- | :--- | | `internal/complytime/consts.go` | Add emoji constants: `StatusPassed = "✅"`, `StatusFailed = "❌"`, `StatusSkipped = "⏭️"`, `StatusError = "⚠️"` | | `internal/output/scan_summary.go` | New file: `FormatScanSummary(assessments []plugin.AssessmentLog, reqTitles map[string]string) string` — builds charmbracelet table with 3 columns (Requirement ID, Title, Status emoji), sorts rows by status priority, appends aggregate totals line. Uses `internal/terminal` helpers for consistent styling | | `cmd/complyctl/cli/scan.go` | After scan completes (post-`eval.Write()`), call `output.FormatScanSummary()` and print to stdout. The summary table appears after the EvaluationLog is written, before any formatted report | @@ -676,7 +676,7 @@ Target Scope: **Status priority sort order** (highest priority first): | Priority | Status | Emoji | Semantic | -|:---|:---|:---|:---| +| :--- | :--- | :--- | :--- | | 1 | Failed | ❌ | Requirement not satisfied — immediate action needed | | 2 | Error | ⚠️ | Evaluation failed (plugin error) — investigation needed | | 3 | Skipped | ⏭️ | Excluded by tailoring — informational | @@ -700,7 +700,7 @@ Target Scope: **Implementation**: | Component | Change | -|:---|:---| +| :--- | :--- | | `cmd/complyctl/cli/doctor.go` | New file: `doctorCmd` registered in `root.go`. Calls `doctor.Run()` | | `internal/doctor/doctor.go` | New package: `Run(configPath, providerDir, registryURL string) []CheckResult`. Iterates checks, returns pass/fail/warn per check. Each check is an independent function | | `internal/doctor/checks.go` | Individual check functions: `CheckConfig()`, `CheckProviders()`, `CheckRegistry()`, `CheckEvaluatorConfig()` | @@ -709,7 +709,7 @@ Target Scope: **Check sequence**: | Check | Blocking | Pass | Fail | Warn | -|:---|:---|:---|:---|:---| +| :--- | :--- | :--- | :--- | :--- | | Config exists and valid | Yes | `✅ Workspace config valid` | `❌ Config not found at ./complytime.yaml` | — | | Providers discovered | Yes | `✅ 2 scanning providers found (openscap, kube-evaluator)` | `❌ No providers found in ~/.complytime/providers/` | — | | Provider HealthCheck | Yes | `✅ openscap: healthy (v1.2.0)` | `❌ openscap: unhealthy` | — | @@ -749,7 +749,7 @@ Target Scope: **Implementation**: | Component | Before (R43) | After (R45) | -|:---|:---|:---| +| :--- | :--- | :--- | | `internal/output/scan_summary.go` | `FormatScanSummary()` builds 3-column charmbracelet table (Req ID, Title, Status) | `FormatScanSummary()` builds emoji + message lines for non-passing results, then single-row charmbracelet totals table | | Table structure | Multi-row table with all results | Individual lines per non-passing result + one-row totals table | | Data source per line | `reqTitles[requirementID]` (catalog title) | `AssessmentLog.Steps[].Message` (provider-authored) | @@ -773,7 +773,7 @@ Target Scope: **Mapping**: | Before | After (user-facing) | Code-level | -|:---|:---|:---| +| :--- | :--- | :--- | | plugin (the executable) | scanning provider / provider | `plugin.Plugin` interface | | plugin system | scanning interface | `pkg/plugin/`, `internal/plugin/` packages | | `complyctl plugins` | `complyctl providers` | `cmd/complyctl/cli/providers.go` | @@ -796,21 +796,21 @@ Target Scope: **Proto impact**: -| Before | After | -|:---|:---| -| `message ScanRequest { repeated Target targets = 1; repeated string requirement_ids = 2; }` | `message ScanRequest { repeated Target targets = 1; }` | +| Before | After | +|-----------------------------------------------------------------------------------------------|--------------------------------------------------------| +| `message ScanRequest { repeated Target targets = 1; repeated string requirement_ids = 2; }` | `message ScanRequest { repeated Target targets = 1; }` | **Code impact**: -| Component | Change | -|:---|:---| -| `contracts/plugin.proto` | Remove `repeated string requirement_ids` from `ScanRequest` | -| `pkg/plugin/client.go` | Remove `RequirementIDs` from `ScanRequest` domain type | -| `pkg/plugin/server.go` | Update adapter — no requirement_ids mapping | -| `internal/plugin/manager.go` `RouteScan()` | Remove `reqIDs []string` parameter. Provider receives targets only | -| `cmd/complyctl/cli/scan.go` | Remove requirement ID collection and passing to `RouteScan()` | -| `cmd/openscap-plugin/server/server.go` | Remove requirement ID filtering in Scan handler — evaluate all requirements from Generate state | -| `cmd/test-plugin/main.go` | Update test plugin Scan handler | +| Component | Change | +|---------------------------------------------|-------------------------------------------------------------------------------------------------| +| `contracts/plugin.proto` | Remove `repeated string requirement_ids` from `ScanRequest` | +| `pkg/plugin/client.go` | Remove `RequirementIDs` from `ScanRequest` domain type | +| `pkg/plugin/server.go` | Update adapter — no requirement_ids mapping | +| `internal/plugin/manager.go` `RouteScan()` | Remove `reqIDs []string` parameter. Provider receives targets only | +| `cmd/complyctl/cli/scan.go` | Remove requirement ID collection and passing to `RouteScan()` | +| `cmd/openscap-plugin/server/server.go` | Remove requirement ID filtering in Scan handler — evaluate all requirements from Generate state | +| `cmd/test-plugin/main.go` | Update test plugin Scan handler | **Alternatives considered**: - Keep `requirement_ids` for selective re-scanning: The spec does not support selective scanning. If needed later, add an optional filter field — simpler to add than to remove. @@ -821,7 +821,7 @@ Target Scope: **Decision**: Replace `evaluator_config` (a nested map keyed by evaluator ID under PolicyConfig) with a three-tier variable model: | Tier | Name | Scope | Source | RPC | Owner | -|:---|:---|:---|:---|:---|:---| +| :--- | :--- | :--- | :--- | :--- | :--- | | 1 | Global variables | Workspace | Top-level `variables` in `complytime.yaml` | Generate RPC | System admin | | 2 | Target variables | Per-target | `targets[].variables` in `complytime.yaml` | Scan RPC | System admin | | 3 | Test variables | Per-requirement | Decomposed Gemara policy assessment plan | Generate RPC | Policy author | @@ -851,7 +851,7 @@ targets: **Code impact**: | Component | Before | After | -|:---|:---|:---| +| :--- | :--- | :--- | | `internal/config/config.go` `PolicyConfig` | `EvaluatorConfig map[string]map[string]string` | Remove `EvaluatorConfig` field | | `internal/config/config.go` `WorkspaceConfig` | No top-level variables | Add `Variables map[string]string` | | `internal/config/config.go` `TargetConfig` | `Variables map[string]string` (unchanged) | Unchanged — already correct | @@ -889,7 +889,7 @@ targets: **Command separation**: | Command | Analogy | Responsibility | -|:---|:---|:---| +| :--- | :--- | :--- | | `init` | `go mod init` | Create config → get → doctor (errors if config exists, R52) | | `get` | `go get -u` | Incremental policy sync from registry | | `doctor` | `flutter doctor` | Diagnose environment health (requires policy cache, R52) | @@ -897,7 +897,7 @@ targets: **Code impact**: | Component | Before | After | -|:---|:---|:---| +| :--- | :--- | :--- | | `cmd/complyctl/cli/init.go` | `--config` flag, `ValidateTargetPolicyVersions`, "Validating..." messages | No flags; errors if config exists; calls `getOpts.run()` then `runDoctor()` (R52) | | `internal/complytime/config.go` | `Load()` calls `Validate()` internally | `LoadFrom(path)` returns parsed config; callers validate explicitly | | `internal/doctor/doctor.go` `CheckConfig` | Calls `complytime.Load()` (basic parse) | Calls `LoadFrom()` + `Validate()` + `ValidateTargetPolicyVersions()` (full validation) | @@ -917,9 +917,9 @@ targets: **Proto impact**: -| Before | After | -|:---|:---| -| `message HealthCheckResponse { bool healthy = 1; string version = 2; string error_message = 3; }` | `message HealthCheckResponse { bool healthy = 1; string version = 2; string error_message = 3; repeated string required_global_variables = 4; repeated string required_target_variables = 5; }` | +| Before | After | +|----------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `message HealthCheckResponse { bool healthy = 1; string version = 2; string error_message = 3; }` | `message HealthCheckResponse { bool healthy = 1; string version = 2; string error_message = 3; repeated string required_global_variables = 4; repeated string required_target_variables = 5; }` | **Backward compatibility**: Proto3 defaults empty repeated fields to `[]`. Existing providers that don't populate these fields return empty lists — doctor treats them as "no variables required" and passes variable validation. Providers are updated incrementally. @@ -932,7 +932,7 @@ targets: **Code impact**: | Component | Change | -|:---|:---| +| :--- | :--- | | `contracts/plugin.proto` | Add two fields to `HealthCheckResponse` | | `pkg/plugin/client.go` | Add `RequiredGlobalVariables []string` and `RequiredTargetVariables []string` to domain `HealthCheckResponse` type | | `pkg/plugin/server.go` | Map new proto fields in adapter | @@ -962,7 +962,7 @@ targets: **Command separation (updated)**: | Command | Analogy | Responsibility | -|:---|:---|:---| +| :--- | :--- | :--- | | `init` | `go mod init` | Create config → get → doctor (errors if config exists) | | `get` | `go get -u` | Incremental policy sync from registry | | `doctor` | `flutter doctor` | Diagnose environment health (requires policy cache) | @@ -970,7 +970,7 @@ targets: **Code impact**: | Component | Before (R50) | After (R52) | -|:---|:---|:---| +| :--- | :--- | :--- | | `cmd/complyctl/cli/init.go` | Calls `runDoctor()` then `getOpts.run()` | Calls `getOpts.run()` then `runDoctor()` | | `cmd/complyctl/cli/doctor.go` | No cache dependency | Check policy cache exists; error if missing with "run `complyctl get` first" guidance | | `internal/doctor/doctor.go` | Variable check is existence-only | Variable check resolves policy → evaluator → target mapping from cache | @@ -989,7 +989,7 @@ targets: **`complypack.yaml` schema**: | Field | Type | Required | Owner | Description | -|:---|:---|:---|:---|:---| +| :--- | :--- | :--- | :--- | :--- | | `id` | `string` | Yes | Developer | Pack identifier (e.g., `fedora-compliance`) | | `version` | `string` | Yes | Developer | Semantic version | | `description` | `string` | No | Developer | Human-readable description | @@ -1013,7 +1013,7 @@ targets: **Fedora comply-pack inventory** (all Fedora content from ComplianceAsCode/oscal-content): | Policy ID | Catalog | SSG Profile | Source | -|:---|:---|:---|:---| +| :--- | :--- | :--- | :--- | | `policies/cis-fedora-l1-server` | `cis_fedora` | `cis_server_l1` | `component-definitions/fedora/fedora-cis_fedora-l1_server` | | `policies/cis-fedora-l1-workstation` | `cis_fedora` | `cis_workstation_l1` | `component-definitions/fedora/fedora-cis_fedora-l1_workstation` | | `policies/cis-fedora-l2-server` | `cis_fedora` | `cis_server_l2` | `component-definitions/fedora/fedora-cis_fedora-l2_server` | @@ -1027,7 +1027,7 @@ targets: **Code impact** (deferred to 002-comply-packs): | Component | Description | -|:---|:---| +| :--- | :--- | | `internal/complytime/pack.go` | `PackManifest` struct, `LoadPackManifest()`, `ValidatePackManifest()` | | `internal/doctor/doctor.go` | Extend `Run()` to detect and validate `complypack.yaml` alongside `complytime.yaml` | | `cmd/complyctl/cli/pack.go` | `pack doctor`, `pack build`, `pack push`, `pack pull` subcommands | @@ -1049,7 +1049,7 @@ targets: **Rationale**: | Change | Rationale | Constitution | -|:---|:---|:---| +| :--- | :--- | :--- | | Init config-only | Decouples config creation from network (get) and provider ecosystem (doctor). Matches `go mod init` which only creates `go.mod` without fetching dependencies. Each command does one thing. | II (Simplicity), VII (Convention) | | Pack init in 001 | Pack developers need to author manifests now. Only the creation command is needed — build/push/pull have their own dependency chain (tarball assembly, OCI push). Minimal scope increase. | III (Incremental) | | Dual-mode config | Pack consumers shouldn't duplicate registry URL and policy IDs already in the pack. Pack reference is a single OCI ref. Standalone mode remains for development, testing, and no-pack environments. Mutual exclusion prevents ambiguous config states. | I (Single Source), VII (Convention) | @@ -1094,7 +1094,7 @@ targets: **Code impact**: | Component | Before | After | -|:---|:---|:---| +| :--- | :--- | :--- | | `internal/complytime/config.go` `WorkspaceConfig` | `Registry`, `Policies`, `Targets`, `Variables` | Add `Pack string` field. `Registry`/`Policies` optional in pack mode | | `internal/complytime/config.go` `Validate()` | Requires `registry.url` and `policies` | Mutual exclusion check: `pack` XOR (`registry` + `policies`). In pack mode, `registry`/`policies` not required | | `cmd/complyctl/cli/init.go` | Composite orchestrator: config → get → doctor | Config-only: prompt for pack ref + targets → save → exit | @@ -1122,7 +1122,7 @@ targets: **Rationale**: | Change | Rationale | Constitution | -|:---|:---|:---| +| :--- | :--- | :--- | | Version comparison | Reachability probe provides near-zero actionable value — if `get` succeeded, the registry was reachable. Admins need to know *whether their cached policies are current*, not whether the registry responds to HTTP GET. Per-policy staleness is actionable: run `complyctl get` to update. | IV (Code for Humans — actionable output), VII (Convention — staleness check follows `get`-before-`doctor` ordering from R52) | | Per-provider config summary | Failures-only output tells admins what's broken but not what's working. A count summary (e.g., `3/3 global vars`) gives confidence that the provider is fully configured without verbose key dumps. `--verbose` provides drill-down for debugging without cluttering default output. | II (Simplicity — default output stays concise), IV (Humans First — admins see completeness at a glance) | | `--verbose` scope | Mixing version detail (digests, timestamps) with provider config detail into one flag makes output hard to reason about. Version comparison output is already clear per-policy. `--verbose` focuses on the one area where detail expansion matters most: provider config keys. | II (Simplicity — single-concern flag), III (Incremental — additional `--verbose` expansions can be added later) | @@ -1130,7 +1130,7 @@ targets: **Implementation**: | Component | Before | After | -|:---|:---|:---| +| :--- | :--- | :--- | | `internal/doctor/doctor.go` `CheckRegistries()` | HTTP GET to `/v2/` per unique registry — reachability probe | `CheckPolicyVersions()` — query latest version per policy from registry; compare against `PolicyState.digest`/`version` in `state.json` | | `internal/doctor/doctor.go` `CheckVariables()` | Reports only missing variables (failures) | Reports per-provider summary: `N/M global vars, N/M target vars`. `--verbose` expands to key-level detail | | `cmd/complyctl/cli/doctor.go` | No `--verbose` flag | Add `--verbose` bool flag to cobra command. Pass to `doctor.Run()` | @@ -1171,7 +1171,7 @@ targets: **Decision**: Five unit test coverage decisions for three critical packages (`internal/policy/`, `pkg/plugin/discovery.go`, `internal/cache/state.go`) that lacked direct unit tests. | Decision | Choice | Rationale | -|:---|:---|:---| +| :--- | :--- | :--- | | `internal/policy/` test scope | All exported functions with positive + negative cases | Dependency resolution core — `ResolvePolicyGraph`, `GroupByEvaluator`, `parsePolicyLayer`, `ExtractAssessmentConfigs`, generation state `Save/Load/IsFresh` all drive scan correctness | | Policy layer test fixtures | Minimal synthetic YAML stubs | Self-contained, no go-gemara fixture dependency. Stubs unmarshal into `gemara.Policy` — upstream shape changes break tests explicitly | | Plugin discovery test approach | Real temp directory with mock executables | Tests actual filesystem behavior (permission bits, prefix matching, evaluator ID extraction). Zero production code changes. Constitution II (Simplicity) | @@ -1181,7 +1181,7 @@ targets: **New test files**: | File | Package | Key Functions | Scenarios | -|:---|:---|:---|:---| +| :--- | :--- | :--- | :--- | | `internal/policy/resolver_test.go` | `policy_test` | `ResolvePolicyGraph`, `parsePolicyLayer`, `extractFromGemaraPolicy` | Empty/invalid inputs, missing layers, valid synthetic Gemara YAML, multi-evaluator extraction. Uses `PolicyLoader` interface mock | | `internal/policy/assessment_test.go` | `policy_test` | `ExtractAssessmentConfigs`, `GroupByEvaluator`, `ValidateGlobalVars` | Single-evaluator shortcut, multi-evaluator routing, empty graph, missing global vars | | `internal/policy/loader_test.go` | `policy_test` | `ResolveVersion`, `LoadLayerByMediaType`, `PolicyExists`, `ListCachedPolicies` | Cache miss, version resolution fallback, media type not found | @@ -1207,7 +1207,7 @@ targets: **Decision**: Five UX decisions reshaping terminal output and log file handling. | Decision | Choice | Rationale | -|:---|:---|:---| +| :--- | :--- | :--- | | Log file location | `.complytime/complyctl.log` | Logs are diagnostic artifacts — same category as scan output. Keeps workspace root clean. Supersedes `./complyctl.log` (Session 2026-02-23f) | | Default table rendering | Plain aligned text (podman-style) + emoji | Pipeable, works in all terminals, no ANSI capability assumptions. Supersedes charmbracelet default (R38) | | Scan summary totals | Compact inline: `44 ✅ 3 ❌ 2 ⏭️ 1 ⚠️` | Single line, emoji counts, no labels. Matches podman/docker density | @@ -1223,7 +1223,7 @@ targets: **Code impact**: | Component | Before | After | -|:---|:---|:---| +| :--- | :--- | :--- | | `internal/terminal/table.go` | `RenderTable` (lipgloss default), `ShowPlainTable` (--plain) | `ShowPlainTable` becomes primary. `RenderTable` retained for `--pretty`. Default function is `ShowPlainTable` | | `cmd/complyctl/cli/providers.go` | `--plain` flag, `RenderTable` default | `--pretty` flag, `ShowPlainTable` default | | `cmd/complyctl/cli/list.go` | `RenderTable` default | `ShowPlainTable` default, two columns: POLICY ID + VERSION | @@ -1244,7 +1244,7 @@ targets: **Decision**: Three UX decisions refining output location and discovery command flags. | Decision | Choice | Rationale | -|:---|:---|:---| +| :--- | :--- | :--- | | `--pretty` on discovery commands | Remove from `list` and `providers` | Discovery commands are informational — plain text is the right default. No persona needs styled output for `list` or `providers`. `--pretty` reserved for reporting/summary commands (`scan`, `generate --dry-run`). Reduces flag surface area | | `--format` output location | CWD (current working directory) | Formatted reports (OSCAL, SARIF, Markdown) are user-facing deliverables — users expect them in a visible location, not buried in `.complytime/scan/`. EvaluationLog (diagnostic artifact) stays in hidden dir. Terminal summary stays on stdout | | EvaluationLog path printing | Always print to terminal | EvaluationLog is always produced. Users need to know where it is for debugging. One line of output, low noise. Deterministic path but worth surfacing | @@ -1252,7 +1252,7 @@ targets: **Output path split**: | Artifact | Location | Rationale | -|:---|:---|:---| +| :--- | :--- | :--- | | EvaluationLog (always) | `{workspace}/.complytime/scan/` | Diagnostic artifact — hidden dir is appropriate | | Formatted report (`--format`) | CWD | User-facing deliverable — visible location expected | | Log file | `{workspace}/.complytime/complyctl.log` | Diagnostic — hidden dir (unchanged from R57) | @@ -1263,7 +1263,7 @@ targets: **Code impact**: | Component | Before | After | -|:---|:---|:---| +| :--- | :--- | :--- | | `cmd/complyctl/cli/list.go` | `--pretty` flag, `ShowPlainTable` default | `--pretty` flag removed. `ShowPlainTable` only | | `cmd/complyctl/cli/providers.go` | `--pretty` flag, `ShowPlainTable` default | `--pretty` flag removed. `ShowPlainTable` only | | `cmd/complyctl/cli/scan.go` | `outDir = ".complytime/scan"` for all output | `outDir` for EvaluationLog stays `.complytime/scan/`. Formatted reports (`--format`) written to `"."` (CWD) | @@ -1287,7 +1287,7 @@ targets: **Decision**: Five interconnected UX decisions replacing the plain-default/`--pretty`-opt-in model with lipgloss-as-universal-default and restructuring scan/generate output. | Decision | Choice | Rationale | -|:---|:---|:---| +| :--- | :--- | :--- | | Scan results table columns | Requirement ID, Control ID, Status (emoji), Message | Admins need enough context to act on failures without cross-referencing the EvaluationLog. Supersedes FR-037 "no table / ActionError-style" | | Default table rendering | Lipgloss-rendered tables with subtle borders as universal default | Current `--pretty` look becomes the only look. Terminal-width-adaptive, cleaner than raw whitespace padding. `--pretty` flag removed from all commands | | Report-style layout | Intro text → subtle lipgloss table → conclusion text | Wrapping tables with contextual intro/conclusion provides scannable, professional output. All tabular commands follow this pattern | @@ -1336,7 +1336,7 @@ Generation completed. **Code impact**: | Component | Before | After | -|:---|:---|:---| +| :--- | :--- | :--- | | `internal/terminal/table.go` | `ShowPlainTable` (default), `RenderTable` (--pretty) | `RenderTable` becomes primary (TTY). `ShowPlainTable` fallback (non-TTY). New `RenderReport(intro, headers, rows, conclusion)` function wraps the pattern. TTY detection via `term.IsTerminal(os.Stdout.Fd())` | | `internal/output/scan_summary.go` | `FormatScanSummary` returns emoji+message lines + inline totals | `FormatScanSummary` returns report-style: intro text, 4-column table rows (reqID, ctrlID, status, message), conclusion (totals + file paths). Accepts `reqToControl` map for control ID lookup | | `internal/output/execution_plan.go` | `FormatExecutionPlan` with two tables + `pretty bool` param | `FormatExecutionPlan` with single table (Target, Provider, Requirements, Status). No `pretty` param — always lipgloss for TTY. `ProviderRoute` and `TargetScope` structs merged into single `ExecutionPlanRow` | diff --git a/specs/001-gemara-native-workflow/spec.md b/specs/001-gemara-native-workflow/spec.md index b00aa388d..65785dbc5 100644 --- a/specs/001-gemara-native-workflow/spec.md +++ b/specs/001-gemara-native-workflow/spec.md @@ -145,7 +145,7 @@ - Q: Refined OSCAL/OpenSCAP to Gemara mapping — what does each OSCAP concept produce in the Gemara model? → A: Refines Session 2026-02-24c. No complyctl code changes — content authoring and plugin interpretation only. | OSCAP Concept | Gemara Artifact | Layer | Notes | -|:---|:---|:---|:---| +| :--- | :--- | :--- | :--- | | XCCDF Rule (e.g., `dconf_db_up_to_date`) | **Control** + **Assessment Requirement** | Layer 2 (Control Catalog) | One rule = one Control containing one Assessment Requirement. The Control is the grouping entity; the Assessment Requirement is the testable statement derived from the rule | | OVAL Check (technical evaluation plan) | **Assessment Plan** | Layer 3 (Policy) | Describes how to evaluate the Control's requirement | | CIS ID (`cis-*` pattern, e.g., `cis-1.1.1.1`) | **Guideline-mapping** on the Control | Layer 2 → Layer 1 reference | Maps the Control back to the originating CIS benchmark item (Layer 1 Guideline) | @@ -360,7 +360,7 @@ A system administrator needs to discover available policies and view scan result ### Edge Cases (Defined Behaviors) | Condition | Expected Behavior | -|:--------------------------------------------------------------------------------------|:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +|---------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | Policy ID in `complyctl scan` not in cache or registry | Return `ErrPolicyNotFound` with policy ID. Do not fall back to partial data. | | Policy updated via `get` since last generate | `scan` detects digest mismatch between cached policy and generate-time digest. Warns admin and auto-regenerates before scanning. No silent staleness — admin is always informed when generated artifacts are refreshed (Session 2026-02-23b). | | Conflicting policy versions for same target | Use the version explicitly pinned in workspace config. If both are unpinned, fail with conflict error listing both policy IDs. | @@ -376,7 +376,7 @@ A system administrator needs to discover available policies and view scan result | `complyctl init` receives duplicate policy URL | Warn (log duplicate), skip the entry, continue execution | | `complyctl init` with no policies provided | Write empty `policies:` list with commented-out example showing `url` + `id` structure | | Scanning provider configuration error (e.g., missing target variable) | Provider error propagates back; complyctl enhances the error message with evaluator ID and workspace config path for actionable troubleshooting (Session 2026-02-23d, updated 2026-02-24). | -| OpenSCAP scan exceeds deadline (e.g., `oscap xccdf eval` hangs) | Timeout error MUST include the full shell-ready `oscap` command string so the admin can copy-paste and run it manually to reproduce. Provider MUST log the command at Info level before execution so it appears in `.complytime/complyctl.log` (Session 2026-03-02). | +| OpenSCAP scan exceeds deadline (e.g., `oscap xccdf eval` hangs) | Timeout error MUST include the full shell-ready `oscap` command string so the admin can copy-paste and run it manually to reproduce. Provider MUST log the command at Info level before execution so it appears in `.complytime/complyctl.log` (Session 2026-03-02). | ## Requirements *(mandatory)* @@ -451,7 +451,7 @@ A system administrator needs to discover available policies and view scan result All exported functions in the following critical packages MUST have unit tests with both positive and negative cases (Session 2026-02-26): | Package | Test File(s) | Key Functions | Required Scenarios | -|:---|:---|:---|:---| +| :--- | :--- | :--- | :--- | | `internal/policy/` | resolver_test.go | `ResolvePolicyGraph`, `parsePolicyLayer`, `extractFromGemaraPolicy` | Empty/invalid inputs, missing layers, valid Gemara YAML parsing (synthetic stubs, not upstream fixtures), multi-evaluator extraction. Resolver uses `PolicyLoader` interface mock — no OCI store dependency | | `internal/policy/` | assessment_test.go | `ExtractAssessmentConfigs`, `GroupByEvaluator` | Single-evaluator shortcut, multi-evaluator routing, empty graph. *(`ValidateGlobalVars` removed — Phase 12, Session 2026-02-26b)* | | `internal/policy/` | loader_test.go | `ResolveVersion`, `LoadLayerByMediaType`, `PolicyExists`, `ListCachedPolicies` | Cache miss, version resolution fallback, media type not found | diff --git a/specs/001-gemara-native-workflow/tasks.md b/specs/001-gemara-native-workflow/tasks.md index 0925fa867..37190feda 100644 --- a/specs/001-gemara-native-workflow/tasks.md +++ b/specs/001-gemara-native-workflow/tasks.md @@ -6,7 +6,7 @@ ## Status Summary | Phase | Scope | Status | -|:---|:---|:---| +| :--- | :--- | :--- | | 1 | Setup — Legacy removal, dependency updates, proto codegen | **COMPLETE** | | 2 | Foundational — Cache, registry, policy resolution, plugin SDK | **COMPLETE** | | 3 | US1 — Init, Get, Doctor, List | **COMPLETE** | @@ -270,7 +270,7 @@ - [x] T094 Run `go build ./...` — full build green - [x] T095 Run `go test ./...` — all tests pass - [x] T096 Run `go vet ./...` and `gofmt -l .` — clean -- [x] T097 Verify no stale references — search for `Pack `, `RegistryConfig`, `policy_ids` in Go source (except `pack.go` data model); zero matches expected +- [x] T097 Verify no stale references — search for `Pack ` (deliberate space), `RegistryConfig`, `policy_ids` in Go source (except `pack.go` data model); zero matches expected **Checkpoint**: Behavioral tests, pack data model, PolicyEntry propagated. Full build green. All 001 implementation complete. @@ -311,7 +311,7 @@ ### 8b: Per-Provider Configuration Summary (replaces failures-only output) -- [x] T106 [US1] Refactor `CheckVariables()` in `internal/doctor/doctor.go` — add `verbose bool` parameter. Default mode: per-provider summary line with resolved count + missing count (e.g., `✅ provider/openscap: 3/3 global vars, 2/2 target vars` or `❌ provider/kube-eval: 1/2 global vars — missing workspace`). Verbose mode: append per-key status lines below each provider summary (e.g., ` global: workspace ✅, output_dir ✅`). Keep existing global + target variable validation logic (R51). Returns `[]CheckResult` — one per provider in default mode, additional detail results in verbose mode +- [x] T106 [US1] Refactor `CheckVariables()` in `internal/doctor/doctor.go` — add `verbose bool` parameter. Default mode: per-provider summary line with resolved count + missing count (e.g., `✅ provider/openscap: 3/3 global vars, 2/2 target vars` or `❌ provider/kube-eval: 1/2 global vars — missing workspace`). Verbose mode: append per-key status lines below each provider summary (e.g., `global: workspace ✅, output_dir ✅`). Keep existing global + target variable validation logic (R51). Returns `[]CheckResult` — one per provider in default mode, additional detail results in verbose mode - [x] T107 [US1] Update `Run()` signature in `internal/doctor/doctor.go` — add `verbose bool` parameter. Pass through to `CheckVariables()`. All other checks unaffected by verbose flag ### 8c: `--verbose` CLI Flag @@ -476,7 +476,7 @@ Lipgloss universal default, scan results 4-column table, execution plan collapse Error message consistency ("complytime" → "complyctl"/file name), `get --timeout`, resolver error surfacing. 16 tasks (T226-T241). -### Comply-Packs (Phase 9) — DEFERRED to 002 +### Comply-Packs (Phase 9) — DEFERRED to 002 (detail) Full pack CLI lifecycle. Separate feature branch. diff --git a/specs/002-ampel-branch-scan/data-model.md b/specs/002-ampel-branch-scan/data-model.md index d9c28a8e5..63f38cbb7 100644 --- a/specs/002-ampel-branch-scan/data-model.md +++ b/specs/002-ampel-branch-scan/data-model.md @@ -15,22 +15,22 @@ during generate. **Fields**: | Field | Type | Description | -|-------|------|-------------| +| ------- | ------ | ------------- | | ID | string | Policy identifier matching assessment requirement ID (e.g., "BP-1.01") | | Meta | AmpelMeta | Policy metadata including description and control references | | Tenets | []AmpelTenet | CEL-based verification checks | **AmpelMeta fields**: -| Field | Type | Description | -|-------|------|-------------| -| Description | string | Human-readable policy description | -| Controls | []Control | Control references (framework, class, id) | +| Field | Type | Description | +|-------------|------------|-------------------------------------------| +| Description | string | Human-readable policy description | +| Controls | []Control | Control references (framework, class, id) | **AmpelTenet fields**: | Field | Type | Description | -|-------|------|-------------| +| ------- | ------ | ------------- | | ID | string | Unique tenet identifier within the policy | | Code | string | CEL expression for verification | | Predicates | PredicateSpec | Attestation types to evaluate | @@ -55,15 +55,15 @@ Written to `{workspace}/ampel/policy/complytime-ampel-policy.json`. **Fields**: | Field | Type | Description | -|-------|------|-------------| +| ------- | ------ | ------------- | | ID | string | Always "complytime-ampel-policy" | | Meta | BundleMeta | Bundle metadata with framework reference | | Policies | []AmpelPolicy | Array of matched granular policies | **BundleMeta fields**: -| Field | Type | Description | -|-------|------|-------------| +| Field | Type | Description | +|------------|-------------|---------------------------------------| | Frameworks | []Framework | Single entry: ComplyTime-AMPEL-Policy | ### 2. TargetRepository @@ -74,8 +74,8 @@ the workspace configuration file. **Fields**: | Field | Type | Description | -|-------|------|-------------| -| URL | string | Repository URL (https://github.com/org/repo) | +| ------- | ------ | ------------- | +| URL | string | Repository URL () | | Branches | []string | Branch names to evaluate protection rules on | | Specs | []string | Snappy spec file references (e.g., "builtin:github/branch-rules.yaml") | @@ -101,7 +101,7 @@ a JSON file in the workspace. **Fields**: | Field | Type | Description | -|-------|------|-------------| +| ------- | ------ | ------------- | | Repository | string | Repository URL | | Branch | string | Branch name evaluated | | ScannedAt | time.Time | Timestamp of scan | @@ -112,7 +112,7 @@ a JSON file in the workspace. **Finding fields**: | Field | Type | Description | -|-------|------|-------------| +| ------- | ------ | ------------- | | TenetID | string | AMPEL tenet that was evaluated | | Title | string | Human-readable rule name | | Result | string | "pass" or "fail" | @@ -133,7 +133,7 @@ override is the `ampel_policy_dir` global variable in **Package constants**: | Constant | Value | Description | -|----------|-------|-------------| +| ---------- | ------- | ------------- | | PluginDir | "ampel" | Plugin subdirectory under workspace | | DefaultGranularPolicyDir | "granular-policies" | Default granular policy source directory | | GeneratedPolicyDir | "policy" | Generated policy bundle output directory | @@ -177,13 +177,13 @@ plugin.ScanResponse (returned to complyctl) ## State Transitions ### Generate Flow -``` +```text No policy → Generate() → Policy file exists in PolicyDir Policy exists → Generate() → Policy overwritten with new scope ``` ### Scan Flow -``` +```text No results → Scan() → Per-repo result files created + ScanResponse returned Results exist → Scan() → Results overwritten + ScanResponse returned ``` diff --git a/specs/002-ampel-branch-scan/plan.md b/specs/002-ampel-branch-scan/plan.md index 04ec24029..4f58027fb 100644 --- a/specs/002-ampel-branch-scan/plan.md +++ b/specs/002-ampel-branch-scan/plan.md @@ -40,7 +40,7 @@ already in go.mod *GATE: Must pass before Phase 0 research. Re-checked post-design.* | Principle | Status | Evidence | -|-----------|--------|----------| +| ----------- | -------- | ---------- | | I. Single Source of Truth | PASS | Plugin configuration defaults (PluginDir, DefaultGranularPolicyDir, GeneratedPolicyDir, DefaultResultsDir) defined as package constants in `config/config.go`. Policy bundle ID centralized. No magic strings. | | II. Simplicity & Isolation | PASS | Eight packages with single responsibilities. Each independently testable. Convert package isolated for future Gemara migration — justified by explicit migration plan, not speculative. | | III. Incremental Improvement | PASS | Plugin is a self-contained addition under `cmd/ampel-plugin/`. No unrelated changes to existing packages. Superseded tasks (T013, T014) tracked separately. | @@ -129,7 +129,7 @@ defined responsibility. ### Package Responsibilities | Package | Responsibility | Key Dependency | -|---------|---------------|----------------| +| --------- | --------------- | ---------------- | | `main` | Plugin registration via `plugin.Serve()` | pkg/plugin | | `config` | Workspace path helpers, directory creation | stdlib + hclog | | `server` | Implement `plugin.Plugin` (Generate, Scan, Describe), orchestrate other packages | pkg/plugin | diff --git a/specs/002-ampel-branch-scan/research.md b/specs/002-ampel-branch-scan/research.md index fbcbaafbc..59d733465 100644 --- a/specs/002-ampel-branch-scan/research.md +++ b/specs/002-ampel-branch-scan/research.md @@ -137,7 +137,7 @@ and reuse of existing libraries. All needed libraries are already in `go.mod`: | Need | Library | Already in go.mod | -|------|---------|-------------------| +| ------ | --------- | ------------------- | | Plugin framework | pkg/plugin (in-tree) | Yes | | Plugin hosting | hashicorp/go-plugin v1.7.0 | Yes | | Structured logging | hashicorp/go-hclog v1.6.3 | Yes | diff --git a/specs/003-complybeacon-export/plan.md b/specs/003-complybeacon-export/plan.md index 063153348..56ce268b4 100644 --- a/specs/003-complybeacon-export/plan.md +++ b/specs/003-complybeacon-export/plan.md @@ -54,7 +54,7 @@ ProofWatch/OTEL only in plugins that opt in. *GATE: Must pass before implementation. Re-checked post-design.* | Principle | Status | Evidence | -|-----------|--------|----------| +| ----------- | -------- | ---------- | | I. Single Source of Truth | PASS | Collector config centralized in `complytime.yaml` `collector` section. No magic strings — endpoint and auth read from config and passed via gRPC. | | II. Simplicity & Isolation | PASS | Export logic is a well-isolated post-scan step within `scan.go`, triggered only by `--format otel`. Plugin SDK extension follows existing RouteScan pattern. No OTEL/ProofWatch in complyctl core — isolation between orchestrator and emitter. | | III. Incremental Improvement | PASS | Self-contained addition: new proto RPC, SDK extension, format option, config field. No changes to existing scan/generate/doctor logic beyond additive doctor check and the new format path. | @@ -116,7 +116,7 @@ internal/doctor/ ### Package Responsibilities | Package | Change | Responsibility | -|---------|--------|----------------| +| --------- | -------- | ---------------- | | `api/plugin` | Modified | Proto contract — new `Export` RPC, messages, `supports_export` field | | `pkg/plugin` | Modified | Plugin SDK — `RouteExport`, domain types, client/server gRPC mapping | | `cmd/complyctl/cli` | Modified | `scan.go` — add `otel` format path: validate collector config, resolve OIDC token, call `RouteExport` on capable plugins after scan, display export summary | @@ -126,7 +126,7 @@ internal/doctor/ ### What complyctl Does NOT Own | Concern | Owner | -|---------|-------| +| --------- | ------- | | OTLP emission | Plugin (via ProofWatch) | | OTEL SDK setup | Plugin | | Evidence format | Plugin (GemaraEvidence) | diff --git a/specs/003-crapload-monitoring/contracts/reusable-workflow.md b/specs/003-crapload-monitoring/contracts/reusable-workflow.md index ad36d761e..1916f6803 100644 --- a/specs/003-crapload-monitoring/contracts/reusable-workflow.md +++ b/specs/003-crapload-monitoring/contracts/reusable-workflow.md @@ -9,7 +9,7 @@ GitHub Actions reusable workflow (`workflow_call`) ### Inputs | Input | Type | Required | Default | Description | -|-------|------|----------|---------|-------------| +| ------- | ------ | ---------- | --------- | ------------- | | `go-version-file` | string | no | `'./go.mod'` | Path to go.mod for Go version detection | | `gaze-version` | string | no | `'latest'` | Gaze version tag to install via `go install` | | `baseline-file` | string | no | `'.gaze/baseline.json'` | Path to committed baseline thresholds file | @@ -24,16 +24,16 @@ GitHub Actions reusable workflow (`workflow_call`) ### Required Permissions -| Permission | Level | Purpose | -|------------|-------|---------| -| `contents` | `read` | Checkout code and read baseline file | +| Permission | Level | Purpose | +|--------------|----------|--------------------------------------| +| `contents` | `read` | Checkout code and read baseline file | The reusable workflow requires only `contents: read`. PR comment posting is the caller's responsibility, keeping the reusable workflow minimal and avoiding the need to pass `pull-requests: write` tokens across repository boundaries. ### Outputs | Output | Type | Description | -|--------|------|-------------| +| -------- | ------ | ------------- | | `status` | string | `pass` or `fail` | | `crapload-count` | number | Number of functions at or above CRAP threshold | | `gaze-crapload-count` | number | Number of functions at or above GazeCRAP threshold | @@ -42,10 +42,10 @@ The reusable workflow requires only `contents: read`. PR comment posting is the ### Artifacts -| Artifact | Contents | Always uploaded | -|----------|----------|-----------------| -| `crapload-analysis` | `/tmp/crapload-comment-body.md` (markdown comment body) | Yes | -| `crapload-analysis-detailed` | `/tmp/gaze-report.json`, `/tmp/crapload-current.json` | Only when analysis runs (skipped if no Go changes) | +| Artifact | Contents | Always uploaded | +|-------------------------------|---------------------------------------------------------|------------------------------------------------------| +| `crapload-analysis` | `/tmp/crapload-comment-body.md` (markdown comment body) | Yes | +| `crapload-analysis-detailed` | `/tmp/gaze-report.json`, `/tmp/crapload-current.json` | Only when analysis runs (skipped if no Go changes) | ## Behaviour Contract diff --git a/specs/003-crapload-monitoring/plan.md b/specs/003-crapload-monitoring/plan.md index 72b3291c8..ff389d5f9 100644 --- a/specs/003-crapload-monitoring/plan.md +++ b/specs/003-crapload-monitoring/plan.md @@ -24,7 +24,7 @@ Implement continuous CRAP (Change Risk Anti-Pattern) and GazeCRAP monitoring for *GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.* | Principle | Status | Notes | -|-----------|--------|-------| +| ----------- | -------- | ------- | | I. Single Source of Truth | PASS | Thresholds stored in single baseline file; CRAP threshold defaults defined once in reusable workflow inputs | | II. Simplicity & Isolation | PASS | Separate workflows for PR analysis and event-based metrics publishing; reusable workflow is a single-purpose component | | III. Incremental Improvement | PASS | Feature is self-contained; no unrelated changes | @@ -74,7 +74,7 @@ Makefile # New targets: crapload, crapload-bas ### Inputs (with defaults) | Input | Type | Default | Description | -|-------|------|---------|-------------| +| ------- | ------ | --------- | ------------- | | `go-version-file` | string | `'./go.mod'` | Path to go.mod for Go version detection | | `gaze-version` | string | `'latest'` | Gaze version tag to install | | `baseline-file` | string | `'.gaze/baseline.json'` | Path to baseline thresholds file | diff --git a/specs/003-crapload-monitoring/quickstart.md b/specs/003-crapload-monitoring/quickstart.md index 727ce515a..251fb18db 100644 --- a/specs/003-crapload-monitoring/quickstart.md +++ b/specs/003-crapload-monitoring/quickstart.md @@ -57,11 +57,11 @@ This compares current scores against the committed baseline and reports regressi ## Interpreting Results -| Score Range | Risk Level | Action | -|-------------|------------|--------| -| 0-14 | Safe | No action needed | -| 15-29 | Warning | Consider simplifying or adding tests | -| 30+ | Dangerous | Must reduce complexity or improve coverage | +| Score Range | Risk Level | Action | +|-------------|------------|---------------------------------------------| +| 0-14 | Safe | No action needed | +| 15-29 | Warning | Consider simplifying or adding tests | +| 30+ | Dangerous | Must reduce complexity or improve coverage | ## Common Workflows diff --git a/specs/004-providers-repository-split/contracts/provider-sdk.md b/specs/004-providers-repository-split/contracts/provider-sdk.md index 247026211..0631f3e06 100644 --- a/specs/004-providers-repository-split/contracts/provider-sdk.md +++ b/specs/004-providers-repository-split/contracts/provider-sdk.md @@ -16,7 +16,7 @@ package (renamed from `pkg/plugin/`) published by the complyctl module. After the terminology rename (`FR-013`, `FR-020`), the central interface lives at: -``` +```go import "github.com/complytime/complyctl/pkg/provider" ``` @@ -63,7 +63,7 @@ These values are stable and MUST NOT change. They are checked during the go-plug before any RPC is dispatched. Existing installed provider binaries rely on these values. | Field | Value | Changeability | -|---|---|---| +| --- | --- | --- | | `MagicCookieKey` | `"COMPLYCTL_PLUGIN"` | **FROZEN** | | `MagicCookieValue` | `"ddff478d-578e-4d9d-8253-35e8ebf548d2"` | **FROZEN** | | `ProtocolVersion` | `1` | **FROZEN** | @@ -82,7 +82,7 @@ before any RPC is dispatched. Existing installed provider binaries rely on these Provider executables MUST follow the naming convention: -``` +```text complyctl-provider- ``` @@ -109,7 +109,7 @@ changes as part of `FR-014`. complyctl discovers provider binaries at runtime by scanning two directories in priority order: | Priority | Path | Description | -|---|---|---| +| --- | --- | --- | | 1 (highest) | `~/.complytime/providers/` | User-installed providers | | 2 | `/usr/libexec/complytime/providers/` | System-installed providers | @@ -322,7 +322,7 @@ branch (`FR-003`). ## 8. Contract Stability Guarantees | Surface | Stability | Notes | -|---|---|---| +| --- | --- | --- | | `Provider` interface (Describe, Generate, Scan) | **Stable** | Changes require a new major proto version | | `Exporter` interface (Export) | **Stable** | Optional; detected via runtime type assertion | | Wire protocol (handshake, gRPC service, proto package) | **Frozen** | `complyctl.plugin.v1` — rename deferred | diff --git a/specs/004-providers-repository-split/data-model.md b/specs/004-providers-repository-split/data-model.md index 01dc907c8..42e72db3a 100644 --- a/specs/004-providers-repository-split/data-model.md +++ b/specs/004-providers-repository-split/data-model.md @@ -14,7 +14,7 @@ This feature does not introduce a new persistent data model. It is a source code The central entity in the provider SDK is the `Provider` interface (currently named `Plugin`). After migration it lives at `github.com/complytime/complyctl/pkg/provider`. -``` +```text Provider (interface) ├── Describe(ctx, *DescribeRequest) (*DescribeResponse, error) ├── Generate(ctx, *GenerateRequest) (*GenerateResponse, error) @@ -33,7 +33,7 @@ Provider (interface) Holds the identity and filesystem path of a discovered provider. -``` +```text ProviderInfo ├── PluginID string → renamed: ProviderID string ├── EvaluatorID string (unchanged — routes Generate/Scan requests) @@ -48,7 +48,7 @@ ProviderInfo The `Manager` orchestrates provider lifecycle and request routing. Stays in `pkg/provider/` after rename. -``` +```text Manager ├── discovery *Discovery ├── providers map[string]*LoadedProvider ← renamed from plugins map[string]*LoadedPlugin @@ -71,7 +71,7 @@ LoadedProvider (formerly LoadedPlugin) Scans filesystem directories for provider executables. -``` +```text Discovery └── providerDir string ← renamed from pluginDir ``` @@ -85,7 +85,7 @@ Discovery ## Entity: ProviderExecutablePrefix (constant) -``` +```go // internal/complytime/consts.go const ProviderExecutablePrefix = "complyctl-provider-" // formerly PluginExecutablePrefix ``` @@ -98,7 +98,7 @@ const ProviderExecutablePrefix = "complyctl-provider-" // formerly PluginExecu The migration must not change any workspace paths — operators must experience no disruption. -``` +```text ~/.complytime/providers/ # user provider directory (discovery) /usr/libexec/complytime/providers/ # system provider directory (discovery fallback) .complytime/openscap/ # openscap workspace artifacts (unchanged) @@ -112,7 +112,7 @@ The migration must not change any workspace paths — operators must experience The gRPC wire protocol is frozen. No changes to message types, field numbers, or service definition. -``` +```text Package: complyctl.plugin.v1 (UNCHANGED — proto package rename deferred) Service: Plugin RPCs: Generate, Scan, Describe, Export (Export added by PR #463) @@ -125,7 +125,7 @@ Protocol version: 1 ## Module Boundaries After Migration -``` +```text github.com/complytime/complyctl └── pkg/provider/ # SDK: Provider interface, Manager, Discovery, gRPC harness └── api/plugin/ # Protobuf generated code (package name unchanged) diff --git a/specs/004-providers-repository-split/plan.md b/specs/004-providers-repository-split/plan.md index 3b5a599b2..8bd09d578 100644 --- a/specs/004-providers-repository-split/plan.md +++ b/specs/004-providers-repository-split/plan.md @@ -21,7 +21,7 @@ _Impact_: - The `cmd/openscap-plugin/server/` and `cmd/ampel-plugin/server/` both gain `Export` stubs — these must be carried into `complytime-providers`. - The `otel` output format constant is added to `internal/complytime/consts.go` — this stays in complyctl core, no conflict. - The `CollectorConfig` in `internal/complytime/config.go` and the `CheckCollector` in `internal/doctor/doctor.go` are core-only changes — no conflict. -- **Action**: Ensure the complyctl SDK tagged release used by `complytime-providers` is taken *after* PR #463 merges, so the Export RPC is part of the published contract. +- **Action**: Ensure the complyctl SDK tagged release used by `complytime-providers` is taken _after_ PR #463 merges, so the Export RPC is part of the published contract. **PR #479 — `fix: get resolves pinned version instead of defaulting to latest tag`** @@ -52,10 +52,10 @@ _Impact_: No impact on the providers migration scope. The `VersionResolver` inte ## Constitution Check -*GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.* +_GATE: Must pass before Phase 0 research. Re-check after Phase 1 design._ | Principle | Status | Notes | -|---|---|---| +| --- | --- | --- | | I. Single Source of Truth | **PASS** | `PluginExecutablePrefix` value preserved unchanged; SDK published once and referenced by version | | II. Simplicity & Isolation | **PASS** | Split reduces coupling; each repo has a single concern | | III. Incremental Improvement | **PASS** | Scope is tightly bounded to migration + terminology rename; RPM/testing-farm deferred | diff --git a/specs/004-providers-repository-split/research.md b/specs/004-providers-repository-split/research.md index b799401e6..6e958ec87 100644 --- a/specs/004-providers-repository-split/research.md +++ b/specs/004-providers-repository-split/research.md @@ -27,7 +27,7 @@ **Affected identifiers (confirmed by code inspection)**: | Current | Renamed To | File | -|---|---|---| +| --- | --- | --- | | `type Plugin interface` | `type Provider interface` | `manager.go` | | `type LoadedPlugin struct` | `type LoadedPlugin struct` | `manager.go` _(kept — "Loaded" qualifies a runtime state, not the concept)_ | | `func (m *Manager) GetPlugin` | `func (m *Manager) GetProvider` | `manager.go` | @@ -44,7 +44,7 @@ Note on `GRPCEvaluatorPlugin`: this type name is referenced as the value type inside `SupportedPlugins`/`SupportedProviders` map and in the `Serve()` function. The embedded `goplugin.Plugin` field is dictated by the `hashicorp/go-plugin` interface and cannot be renamed. The struct name itself (`GRPCEvaluatorPlugin`) may be renamed to `GRPCEvaluatorProvider` since it is our own type, but this is low value. **Wire values — FROZEN (must not change)**: -``` +```text MagicCookieKey: "COMPLYCTL_PLUGIN" MagicCookieValue: "ddff478d-578e-4d9d-8253-35e8ebf548d2" ProtocolVersion: 1 @@ -73,7 +73,7 @@ Binary prefix: "complyctl-provider-" **Rationale**: Enforcing a hard sequential gate (tag first, then start) adds unnecessary friction and delays development. Using a `replace` directive locally is idiomatic Go for cross-module development. The merge gate ensures the published repository is never in a broken state. -**Implementation note**: The complyctl SDK release must be taken *after* PR #463 merges (so `Export` RPC is included in the contract) and *after* PR #479 merges (so `ResolveVersion` is included). The tag should be the first release that includes both. +**Implementation note**: The complyctl SDK release must be taken _after_ PR #463 merges (so `Export` RPC is included in the contract) and _after_ PR #479 merges (so `ResolveVersion` is included). The tag should be the first release that includes both. **Alternatives considered**: - Hard sequential gate (tag first): rejected — adds a synchronization barrier with no safety benefit if `replace` usage is gated at merge time. diff --git a/specs/004-providers-repository-split/tasks.md b/specs/004-providers-repository-split/tasks.md index ca613c37f..bbdfe24a1 100644 --- a/specs/004-providers-repository-split/tasks.md +++ b/specs/004-providers-repository-split/tasks.md @@ -172,3 +172,8 @@ The complyctl SDK tagged release (end of Phase 1) is the critical coordination p - `api/plugin/` directory and `complyctl.plugin.v1` proto package are NOT renamed (FR-019) - `LoadedPlugin` → `LoadedProvider` rename (T003) is optional per data-model.md but included for consistency; confirm with reviewers before implementing - Commit after each phase checkpoint; open complyctl PR and complytime-providers PR separately + +[US1]: spec.md +[US2]: spec.md +[US3]: spec.md +[US4]: spec.md diff --git a/specs/005-rpm-packaging-ci/data-model.md b/specs/005-rpm-packaging-ci/data-model.md index e6172baaf..783553a72 100644 --- a/specs/005-rpm-packaging-ci/data-model.md +++ b/specs/005-rpm-packaging-ci/data-model.md @@ -71,12 +71,12 @@ complytime-providers (source RPM) ## Directory Ownership -| Path | Owned By | Notes | -|---------------------------------------|----------------|-----------------------------------| -| `/usr/bin/complyctl` | complyctl | CLI entry point | -| `/usr/share/man/man1/complyctl.1.gz` | complyctl | Man page | -| `/usr/libexec/complytime/` | complyctl | Parent directory | -| `/usr/libexec/complytime/providers/` | complyctl | Provider discovery directory | +| Path | Owned By | Notes | +|---------------------------------------------|-------------------------------|------------------------------------| +| `/usr/bin/complyctl` | complyctl | CLI entry point | +| `/usr/share/man/man1/complyctl.1.gz` | complyctl | Man page | +| `/usr/libexec/complytime/` | complyctl | Parent directory | +| `/usr/libexec/complytime/providers/` | complyctl | Provider discovery directory | | `.../providers/complyctl-provider-openscap` | complytime-providers-openscap | Installed into complyctl-owned dir | | `.../providers/complyctl-provider-ampel` | complytime-providers-ampel | Installed into complyctl-owned dir | @@ -105,9 +105,9 @@ sub-package RPMs. ## CI Target Matrix | Target | COPR Build | Testing Farm | Propose Downstream | Koji Build | Bodhi Update | -|---------------------------|:---:|:---:|:---:|:---:|:---:| -| fedora-rawhide-x86_64 | PR | PR | release | commit | -- | -| fedora-43-x86_64 | PR | PR | release | commit | commit | -| fedora-42-x86_64 | PR | PR | release | commit | commit | -| centos-stream-10-x86_64 | PR | PR | -- | -- | -- | -| centos-stream-9-x86_64 | PR | PR | -- | -- | -- | +|---------------------------|:----------:|:------------:|:------------------:|:----------:|:------------:| +| fedora-rawhide-x86_64 | PR | PR | release | commit | -- | +| fedora-43-x86_64 | PR | PR | release | commit | commit | +| fedora-42-x86_64 | PR | PR | release | commit | commit | +| centos-stream-10-x86_64 | PR | PR | -- | -- | -- | +| centos-stream-9-x86_64 | PR | PR | -- | -- | -- | diff --git a/specs/005-rpm-packaging-ci/tasks.md b/specs/005-rpm-packaging-ci/tasks.md index 8619a9616..92c46b4c0 100644 --- a/specs/005-rpm-packaging-ci/tasks.md +++ b/specs/005-rpm-packaging-ci/tasks.md @@ -83,7 +83,7 @@ executed in a clone of that repository. - [x] T007 [P] [US2] [providers-repo] Create FMF metadata root at .fmf/version in the complytime-providers repository. Create the directory `.fmf/` and write a single file `version` containing just `1` (no trailing newline or extra content). This enables testing-farm to discover TMT plans. Reference: FR-022. - [x] T008 [P] [US2] [providers-repo] Create TMT test plan at plans/test-RPM-providers.fmf in the complytime-providers repository. Create the `plans/` directory. The plan validates that both provider binaries are installed at the expected path after RPM installation. Content: - ``` + ```yaml summary: Validate complytime-providers RPM sub-packages deliver provider binaries execute: diff --git a/tests/e2e/README.md b/tests/e2e/README.md index 8a54cbe40..02bf0c585 100644 --- a/tests/e2e/README.md +++ b/tests/e2e/README.md @@ -11,7 +11,7 @@ Builds `complyctl` + `complyctl-provider-test`, then runs all e2e tests with an Build tag: `e2e`. | Test | Validates | -|:---|:---| +| :--- | :--- | | `FullWorkflow` | get → list → generate → scan (oscal, pretty, sarif) | | `PolicyCache` | OCI layout structure, state.json tracking | | `MultiplePolicies` | Multi-policy fetch + list | @@ -36,7 +36,7 @@ The in-process mock registry (`helpers_test.go`) implements OCI Distribution Spe **Policies:** | Repository | Layers | Tags | -|:---|:---|:---| +| :--- | :--- | :--- | | `nist-800-53-r5` | catalog + policy | v1.0.0, latest | | `policies/nist-800-53-r5` | catalog + policy | v1.0.0, latest | | `cis-benchmark` | catalog | v2.0.0, latest | @@ -46,7 +46,7 @@ The policy layer uses evaluator ID `test`, which routes to the `complyctl-provid **Complypacks:** | Repository | Evaluator ID | Tags | -|:---|:---|:---| +| :--- | :--- | :--- | | `complypacks/opa-a` | `opa` | v1.0.0, latest | | `complypacks/opa-b` | `opa` | v1.0.0, latest |