From b847b334867fdaac07a71ce2eafeb6d69ee65c70 Mon Sep 17 00:00:00 2001 From: John Vandenberg Date: Mon, 26 Oct 2026 10:55:43 +0800 Subject: [PATCH] Stricter linting --- .deepsource.toml | 6 +- .mise/config.toml | 29 +++++--- CHECKS.md | 2 +- .../no_trailing_backslash.rego | 10 +-- config/lychee.toml | 4 +- config/ryl.toml | 72 +++++++++++++++---- .../src/deployment_types/docker_compose.rs | 8 +++ utilities/cli/tests/scenario_generation.rs | 1 + .../published/output/default/compose.yaml | 2 +- 9 files changed, 100 insertions(+), 34 deletions(-) diff --git a/.deepsource.toml b/.deepsource.toml index 1844ba3a..4cab10a5 100644 --- a/.deepsource.toml +++ b/.deepsource.toml @@ -6,11 +6,7 @@ version = 1 # if genuinely-generated glue under `pkg/` turns up as a finding, add a narrow per-file exclude then. # The runner's `src/bindings.rs` is the one int-gen output that cannot sit under `generated/`, so it is excluded # by path; refer to int-gen bindings.rs for details. -# `**/error.rs` is excluded outright: these files are thin error-type plumbing (thiserror `#[derive]` + `#[from]` -# conversions, plus the occasional unreachable defensive `From`), so their line coverage is noise. The exclude also -# suppresses issue detection there, because the Rust analyzer runs -- it is enabled in the DeepSource dashboard -# rather than in this file. (The matching Codecov exclusion is set in Codecov's web UI.) -exclude_patterns = ["**/error.rs", "**/ws-wasi-runner/src/bindings.rs", "generated/**", "verification/**"] +exclude_patterns = ["**/ws-wasi-runner/src/bindings.rs", "generated/**", "verification/**"] # Repo convention: tests live in a `tests/` directory or in source files prefixed `test_`. test_patterns = ["**/test_*.py", "**/tests/**"] diff --git a/.mise/config.toml b/.mise/config.toml index e4884a39..558ae9e0 100644 --- a/.mise/config.toml +++ b/.mise/config.toml @@ -1305,7 +1305,7 @@ description = "Run conftest OPA/Rego policies over the TOML config + lock files" # TOML rule namespaces are listed explicitly, so the per-file `gha` (YAML) namespace is never evaluated against this # combined TOML input. run = """ -ns_list="cross cargo mise mise_lock pyproject no_trailing_backslash checksums advisories" +ns_list="cross cargo mise mise_lock pyproject checksums advisories" named="Cargo.lock config/upstream-cache/data.toml config/deny.toml config/pyrefly.toml" # Both lists rely on word-splitting when they expand. # $ns expands to repeated `--namespace ` pairs; $named is a space-separated path list. @@ -1314,6 +1314,9 @@ ns=$(for n in $ns_list; do echo -n " --namespace $n"; done) # shellcheck disable=SC2086 git ls-files '*Cargo.toml' '.mise/config*.toml' '.mise/mise*.lock' '*pyproject.toml' $named | xargs conftest test --combine --parser toml $ns -p config/conftest/policy +# The trailing-backslash ban holds for every TOML file, not only the ones the namespaces above are written for. +{{ vars.toml_files }} | + xargs -0 conftest test --combine --parser toml --namespace no_trailing_backslash -p config/conftest/policy """ shell = "{{ vars.task_shell }}" @@ -1378,9 +1381,9 @@ conftest test --combine --namespace generated_trees -p config/conftest/policy $f shell = "{{ vars.task_shell }}" [tasks.conftest-check-yaml] -description = "Run conftest OPA/Rego policies over the GitHub Actions workflow + action YAML" -# Three passes over the workflow + action YAML. -# per-file rules (gha, no_trailing_backslash) over workflow YAML; --combine rules (gha_combined) over +description = "Run conftest OPA/Rego policies over the GitHub Actions YAML, and the backslash ban over all YAML" +# Five passes over the workflow + action YAML, then one over every YAML file. +# per-file rules (gha) over workflow YAML; --combine rules (gha_combined) over # workflow YAML for cross-cutting invariants where the rule has to compare each workflow's parsed body # against its own filename (the `paths:` self-reference check; conftest's per-file mode has no `input.path`); # gha_action over the composite actions under .github/actions/*/action.yaml (different schema, so its @@ -1388,16 +1391,16 @@ description = "Run conftest OPA/Rego policies over the GitHub Actions workflow + # cross-check a hard-coded action-input version against the matching mise [tools] pin (setup-dotnet's # dotnet-version vs the `dotnet` pin); and gha_uses over the workflow YAML combined with the composite actions so a # rule can resolve each `uses: ./.github/actions/` against that action's declared `inputs:` (GHA only warns on -# an undeclared input key, silently dropping the value). +# an undeclared input key, silently dropping the value). no_trailing_backslash reads `input` as a file list, so it needs +# --combine like the TOML pass in conftest-check-toml. run = """ -ns="--namespace gha --namespace no_trailing_backslash" -# $ns expands to repeated `--namespace ` pairs; word-splitting is intentional. -# shellcheck disable=SC2086 -conftest test --parser yaml $ns -p config/conftest/policy .github/workflows +conftest test --parser yaml --namespace gha -p config/conftest/policy .github/workflows conftest test --parser yaml --combine --namespace gha_combined -p config/conftest/policy .github/workflows conftest test --parser yaml --namespace gha_action -p config/conftest/policy .github/actions/*/action.yaml conftest test --combine --namespace gha_mise -p config/conftest/policy .github/workflows .mise conftest test --parser yaml --combine --namespace gha_uses -p config/conftest/policy .github/workflows .github/actions +git ls-files -z '*.yaml' '*.yml' | + xargs -0 conftest test --parser yaml --combine --namespace no_trailing_backslash -p config/conftest/policy """ shell = "{{ vars.task_shell }}" @@ -2254,12 +2257,18 @@ dashboard='^https://codecov\\.io/gh/edge-toolkit/core$' blob='^https://github\\.com/edge-toolkit/core/blob/main/(.*)$ '"file:///${PWD#/}/\\$1" set -- --exclude "$dashboard" --remap "$blob" lychee --config "$cfg" "$@" '**/*.md' 'libs/**/*.rs' 'services/**/*.rs' 'utilities/**/*.rs' +# The other languages' sources are named by git rather than globbed, which keeps their build output out as well. +src=$(git ls-files -- libs services utilities | rg '\\.(py|js|mjs|ts|dart|kt|cs|java|zig|c|cpp|h)$') +# $src is a newline-separated path list, split into one argument per path by design. +# shellcheck disable=SC2086 +lychee --config "$cfg" "$@" $src # Everything except the commit URLs is excluded on the second pass, by `--exclude .*` with `--include` winning. # Those files hold links this repo does not otherwise own -- release-asset bases joined to a filename at run # time, doc placeholders, a regex quoted inside a comment -- and none of them is a URL a reader could follow. # Checking the whole set reports 21 such fragments as broken. `--scheme https` drops the `file:` and # `docker-image:` values for the same reason, and every commit URL is https. conf=".mise/config*.toml .mise/*.sh config/*.toml Cargo.toml .deepsource.toml" +conf="$conf $(git ls-files 'config/*.yaml' 'config/*.rego' | coreutils tr '\\n' ' ')" gha=".github/workflows/*.yaml .github/actions/*/*.yaml" # k3s.yaml is skipped as a whole because one value in it cannot be filtered any other way. # It passes a BuildKit named-context reference to `docker build`, and the `:latest` tag on the end reads as @@ -2278,7 +2287,7 @@ shell = "{{ vars.task_shell }}" [tasks.ryl-check] description = "Lint YAML with ryl (a yamllint-compatible Rust linter)" -run = "ryl check -c config/ryl.toml .github config" +run = "ryl check -c config/ryl.toml ." [tasks._setup_all] description = "Private shared cross-platform preinstall" diff --git a/CHECKS.md b/CHECKS.md index 7320d962..fa33cc40 100644 --- a/CHECKS.md +++ b/CHECKS.md @@ -524,7 +524,7 @@ Rules, from `Cargo.toml`: config + lock files. Config: `config/conftest/policy`, `config/deny.toml`, `config/pyrefly.toml`, `config/upstream-cache/data.toml`. - `conftest-check-yaml` (`default` env; run by `conftest-check`) -- Run conftest OPA/Rego policies over the GitHub - Actions workflow + action YAML. Config: `config/conftest/policy`. + Actions YAML, and the backslash ban over all YAML. Config: `config/conftest/policy`. Rules, from `config/conftest/policy`: diff --git a/config/conftest/policy/no_trailing_backslash/no_trailing_backslash.rego b/config/conftest/policy/no_trailing_backslash/no_trailing_backslash.rego index 75e1646f..2ed4dc62 100644 --- a/config/conftest/policy/no_trailing_backslash/no_trailing_backslash.rego +++ b/config/conftest/policy/no_trailing_backslash/no_trailing_backslash.rego @@ -3,10 +3,12 @@ # anywhere in the combined input and flagging any literal `\` immediately followed by a newline. # # Scope: -# - TOML (`--namespace no_trailing_backslash` on conftest-check-toml): catches continuations embedded in multi-line -# `"""..."""` task `run` bodies, which the TOML parser preserves verbatim in the parsed string. -# - YAML (`--namespace no_trailing_backslash` on conftest-check-yaml): catches continuations inside `|`/`>` -# block-scalar values, similarly preserved by the YAML parser. +# - TOML (every tracked file, in conftest-check-toml): catches continuations inside multi-line `'''...'''` literal +# strings, which the TOML parser preserves verbatim. A `"""..."""` basic string is not covered: TOML itself folds a +# line-ending `\` and its newline away before Rego sees the value, so only the raw-text rule can reach those. +# - YAML (every tracked file, in conftest-check-yaml): catches continuations inside `|`/`>` block-scalar values, +# similarly preserved by the YAML parser. +# The rule walks `input` as a list of files, so it only ever sees anything when run with `--combine`. # - Dockerfile: NOT covered here -- conftest's dockerfile parser already consumes line-continuation backslashes when # it joins each instruction's value, so by the time Rego sees the parsed input the backslashes are gone. The # raw-text rule is the source of truth for Dockerfile coverage, and allowlists `Dockerfile*` there because diff --git a/config/lychee.toml b/config/lychee.toml index 43e92ed7..962a9e71 100644 --- a/config/lychee.toml +++ b/config/lychee.toml @@ -24,7 +24,9 @@ exclude_private = true # It is gitignored, but lychee's gitignore-respect doesn't cross the pnpm symlink farm (pnpm hangs each # package off a `.pnpm/` symlink, and `git check-ignore` itself refuses with "beyond a symbolic link" on # those paths). -exclude_path = ["data", "generated", "node_modules", "target"] +# Each entry is an unanchored regex over the path, so it is anchored to a whole directory name: a bare "data" also +# dropped every file under the `*-data1` modules. +exclude_path = ['(^|/)node_modules/', '(^|/)target/', '^(\./)?data/', '^(\./)?generated/'] # URL patterns (regex) to skip, none of which are real links. # doc/test placeholder hosts (e.g. http://host:8080/) and `{...}` format-string templates diff --git a/config/ryl.toml b/config/ryl.toml index 8666450f..dd4ca405 100644 --- a/config/ryl.toml +++ b/config/ryl.toml @@ -1,46 +1,94 @@ # Starts from yamllint's standard ruleset, every rule of it stated. -# Then aligns the cosmetic rules with the repo's other tools so they never disagree. +# Then aligns the cosmetic rules with the repo's other tools so they never disagree, and pins each rule's strictest +# setting the repo already meets so a regression fails rather than passing under a looser default. +# Package-manager lockfiles are skipped whole: their generator owns every byte of the layout. +exclude = ["**/*-lock.yaml"] + [files] +markdown = ["*.md"] yaml = ["*.yaml", "*.yml", ".yamllint"] +[lint] +strict = true + +# Each glob resolves from this file's directory, hence the `../` up to the repository root. +[lint.per-file-ignores] +# A GitHub Actions trigger taking no filters is written as a bare key (`workflow_dispatch:`), the idiomatic form. +"../.github/workflows/*.yaml" = ["empty-values"] +# The generated Kubernetes manifests carry the hub's module list as one env scalar, which has no way to wrap. +"../verification/**/k3s.yaml" = ["line-length"] + [lint.rules] -anchors = "enable" -braces = "enable" brackets = "enable" colons = "enable" commas = "enable" document-end = "disable" # The repo's workflows and configs don't use a `---` document-start marker. document-start = "disable" -empty-lines = "enable" -empty-values = "disable" -float-values = "disable" hyphens = "enable" -indentation = "enable" -key-duplicates = "enable" +# Keys are ordered by meaning (`name`, then `runs-on`, then `steps`), not alphabetically. key-ordering = "disable" new-line-at-end-of-file = "enable" -new-lines = "enable" -octal-values = "disable" +# Quoting is left to the author: a regex in a rule file is single-quoted so its backslashes need no doubling. quoted-strings = "disable" trailing-spaces = "enable" +[lint.rules.anchors] +forbid-ambiguous-anchor-alias-names = true +forbid-duplicated-anchors = true +forbid-undeclared-aliases = true +forbid-unused-anchors = true + +# An empty `{}` is the one flow mapping kept, for a value that must be present but has nothing to set. +[lint.rules.braces] +forbid = "non-empty" + # dprint (the YAML formatter) writes one space before an inline `#`. # yamllint defaults to two. Defer to dprint so the two can't fight. [lint.rules.comments] -level = "warning" min-spaces-from-content = 1 # A misindented comment reads as attached to the wrong block, so treat it as an error, not a warning. [lint.rules.comments-indentation] level = "error" +[lint.rules.empty-lines] +max = 1 +max-end = 0 +max-start = 0 + +[lint.rules.empty-values] +forbid-in-block-mappings = true +forbid-in-block-sequences = true +forbid-in-flow-mappings = true + +[lint.rules.float-values] +forbid-inf = true +forbid-nan = true +forbid-scientific-notation = true +require-numeral-before-decimal = true + +[lint.rules.indentation] +indent-sequences = true +spaces = 2 + +[lint.rules.key-duplicates] +forbid-duplicated-merge-keys = true + # Match the editorconfig line length (yamllint defaults to 80). [lint.rules.line-length] +allow-non-breakable-words = false max = 120 +[lint.rules.new-lines] +type = "unix" + +[lint.rules.octal-values] +forbid-explicit-octal = true +forbid-implicit-octal = true + [lint.rules.truthy] -level = "warning" +level = "error" [format] quote-style = "preserve" diff --git a/utilities/cli/src/deployment_types/docker_compose.rs b/utilities/cli/src/deployment_types/docker_compose.rs index 5b5a03c6..48ad5d35 100644 --- a/utilities/cli/src/deployment_types/docker_compose.rs +++ b/utilities/cli/src/deployment_types/docker_compose.rs @@ -764,7 +764,15 @@ impl ComposeRenderer { self.push_line(3, &format!("start_period: {}", healthcheck.start_period)); } + /// Write a service's `build:` key, as the shorthand `build: .` when nothing would sit beneath it. + /// + /// A bare `build:` parses as null rather than as compose's defaults of this directory and its Dockerfile. fn render_build(&mut self, build: &ComposeBuild) { + if matches!(build.source, BuildSource::Here) && build.additional_contexts.is_empty() && build.secrets.is_empty() + { + self.push_line(2, "build: ."); + return; + } self.push_line(2, "build:"); if let BuildSource::Within { context, dockerfile } = &build.source { self.push_line(3, &format!("context: {context}")); diff --git a/utilities/cli/tests/scenario_generation.rs b/utilities/cli/tests/scenario_generation.rs index 7ed78a06..33b978da 100644 --- a/utilities/cli/tests/scenario_generation.rs +++ b/utilities/cli/tests/scenario_generation.rs @@ -752,6 +752,7 @@ fn a_published_scenario_with_no_modules_of_its_own_needs_no_token() { assert!(!files["Dockerfile"].contains("AS released")); assert!(!files["compose.yaml"].contains("context:")); assert!(!files["compose.yaml"].contains("secrets:")); + assert!(files["compose.yaml"].contains(" build: .\n")); assert!(files["README.md"].contains("From this directory:\n\n```bash\nscenario=bare\n")); } diff --git a/verification/published/output/default/compose.yaml b/verification/published/output/default/compose.yaml index 6f2f900d..422b608b 100644 --- a/verification/published/output/default/compose.yaml +++ b/verification/published/output/default/compose.yaml @@ -40,7 +40,7 @@ services: start_period: 10s env_file: - secrets.env - build: + build: . network_mode: host environment: MODULES_PATHS: "/app/node_modules/onnxruntime-web,