diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 1c90b632..d75665fc 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -4,6 +4,8 @@ before submitting. Keep PRs focused on one concern — split when the diff outgrows the concern, not at an arbitrary line count. Prerequisite cleanup the change itself surfaces (e.g., a tidy-drift fix needed for a new gate to land green) can ride along when called out in the PR body. + +For pattern OTTL stanzas / recipe content, see STYLE.md §"Commits". --> ## What this PR does diff --git a/.github/workflows/pr-lint.yml b/.github/workflows/pr-lint.yml index 1d02829b..6ea667ae 100644 --- a/.github/workflows/pr-lint.yml +++ b/.github/workflows/pr-lint.yml @@ -93,6 +93,13 @@ jobs: exit 1 fi + - name: title doesn't reference recipes/pattern-N path + # Issue #427 convention gate. See STYLE.md §"Commits". + if: github.actor != 'dependabot[bot]' + env: + TITLE: ${{ github.event.pull_request.title }} + run: bash scripts/recipes-path-check.sh "${TITLE:-}" + # DCO sign-off is gated on first external contributor (NORTHSTARS O5 # target: ≥5 external contributors by M6). Pre-alpha solo-maintainer # commits don't need the legal trail — re-enable by uncommenting when diff --git a/Makefile b/Makefile index 00a9ca06..5aeec0f6 100644 --- a/Makefile +++ b/Makefile @@ -14,7 +14,7 @@ .PHONY: coverage coverage-check # Policy gates (each enforces a specific RFC-bound invariant) -.PHONY: license-check license-fix govulncheck dco-check ci-fuzz-nccl-fr nccl-fr-rce-gate register-lint actionlint zizmor doc-check doc-check-release no-autoupdate-check anonymize-pod-evicted-fixture-check base-digest-check build-tags attribute-namespace-check deprecation-check rfc-status-check cut-criteria-status cut-criteria-status-all cut-criteria-render cut-criteria-check slo-rules-check test-flake-audit +.PHONY: license-check license-fix govulncheck dco-check ci-fuzz-nccl-fr nccl-fr-rce-gate register-lint actionlint zizmor doc-check doc-check-release no-autoupdate-check recipes-path-check anonymize-pod-evicted-fixture-check base-digest-check build-tags attribute-namespace-check deprecation-check rfc-status-check cut-criteria-status cut-criteria-status-all cut-criteria-render cut-criteria-check slo-rules-check test-flake-audit # Aggregate gates: pre-commit / pre-push / fast-CI / full-CI .PHONY: check verify ci ci-fast ci-full @@ -403,6 +403,9 @@ no-autoupdate-check: ## Enforce RFC-0008: cmd/, components/, internal/, pkg/ co @scripts/no-autoupdate-check.sh @scripts/no-autoupdate-check_test.sh +recipes-path-check: ## Issue #427 convention gate: assert PR titles / commit subjects don't reference an aspirational `recipes/pattern-N/` directory. Runs the gate's own regression test against a fixture suite of accept/reject subject shapes. + @scripts/recipes-path-check_test.sh + anonymize-pod-evicted-fixture-check: ## M19 carry-forward #1: verify every operator-contributed pod_evicted replay fixture under _real_world/ carries no PII shapes (IPv4, email, cloud-instance node names, image refs). Also runs the anonymizer's own mutation regression tests. @set -e; for d in module/pkg/replay/pod_evicted/_real_world/*/; do \ if [ -f "$$d/manifest.json" ]; then \ @@ -414,7 +417,7 @@ anonymize-pod-evicted-fixture-check: ## M19 carry-forward #1: verify every oper zizmor: ## Security-lint GitHub Actions workflows (template injection, untrusted-input-in-script, over-broad permissions, cache poisoning). Gates at --min-severity=high. @scripts/zizmor.sh -ci-fast: lint vet mod-verify attribute-namespace-check doc-check test-flake-audit ## Fast-feedback CI subset. <60s on a dev laptop; the gates that catch the most defects per second. Run on every save / pre-commit; not a substitute for `ci-full`. See PRINCIPLES.md §10. +ci-fast: lint vet mod-verify attribute-namespace-check doc-check recipes-path-check test-flake-audit ## Fast-feedback CI subset. <60s on a dev laptop; the gates that catch the most defects per second. Run on every save / pre-commit; not a substitute for `ci-full`. See PRINCIPLES.md §10. # `ci-full` is the full superset CI enforces on every PR. `ci` is kept as a # back-compat alias so existing scripts, docs, and hooks invoking `make ci` @@ -428,7 +431,7 @@ ci-fast: lint vet mod-verify attribute-namespace-check doc-check test-flake-audi # the ratchet path. We accept the wall-time hit because local `ci-full` # divergence from CI surfaces ceiling breaches only post-PR-open, which # defeats the per-PR enforcement intent of #302. -ci-full: license-check generate-fixtures-check verdict-fixtures-check vet build-tags tidy-check mod-verify lint nccl-fr-rce-gate register-lint actionlint zizmor coverage-check ci-fuzz-nccl-fr govulncheck doc-check deprecation-check no-autoupdate-check anonymize-pod-evicted-fixture-check test-flake-audit pre-push-test build smoke smoke-quickstart bench-allocs-check ## Everything CI runs. Run before opening a PR. +ci-full: license-check generate-fixtures-check verdict-fixtures-check vet build-tags tidy-check mod-verify lint nccl-fr-rce-gate register-lint actionlint zizmor coverage-check ci-fuzz-nccl-fr govulncheck doc-check deprecation-check no-autoupdate-check recipes-path-check anonymize-pod-evicted-fixture-check test-flake-audit pre-push-test build smoke smoke-quickstart bench-allocs-check ## Everything CI runs. Run before opening a PR. pre-push-test: ## Regression harness for .githooks/pre-push path-filter routing. Cheap (<1s); runs the hook in dry-run mode against synthetic diff ranges and asserts each gate fires only when its source paths change. @bash scripts/pre-push-test.sh diff --git a/STYLE.md b/STYLE.md index 51314f10..add4eead 100644 --- a/STYLE.md +++ b/STYLE.md @@ -181,6 +181,10 @@ That's it. No copyright block, no Apache header text. The `LICENSE` file at the - **Imperative present tense**: "Add NCCL receiver", not "Added NCCL receiver". - **Subject ≤ 72 chars**, body wraps at 72. - **DCO sign-off required**: `git commit -s`. +- **Reference real paths, not aspirational ones.** Pattern OTTL stanzas and recipe content live under [`docs/integrations/examples/`](docs/integrations/examples/) (per-target YAML files) with prose in [`docs/integrations/`](docs/integrations/). See issue [#427](https://github.com/TraceCoreAI/tracecore/issues/427). + - **Do not** write `feat(recipes): pattern-N ...` implying a top-level `recipes/pattern-N/{ottl.yaml,README.md}` directory; that layout does not exist and migrating into it would rot cross-links across six-plus docs. + - **Acceptable subject shapes:** `feat(integrations/examples): pattern-N OTTL stanza`, `feat(filelog-container): pattern-N ...`, or `feat(pattern-N): OTTL stanza in docs/integrations/examples/`. + - **Enforced** by `scripts/recipes-path-check.sh` (run in `.github/workflows/pr-lint.yml`). ## Changelog diff --git a/scripts/recipes-path-check.sh b/scripts/recipes-path-check.sh new file mode 100755 index 00000000..e37f9935 --- /dev/null +++ b/scripts/recipes-path-check.sh @@ -0,0 +1,53 @@ +#!/usr/bin/env bash +# recipes-path-check.sh — issue #427 convention gate. See STYLE.md §"Commits". + +set -euo pipefail + +subject="${1:-}" + +if [ -z "$subject" ]; then + exit 0 +fi + +# Rule 1: literal `recipes/pattern-N` path (filesystem reference). +if printf '%s' "$subject" | grep -qE '(^|[^/])recipes/pattern-[0-9]+'; then + cat >&2 <): pattern-N ... + feat(pattern-N): OTTL stanza in docs/integrations/examples/ +EOF + exit 1 +fi + +# Rule 2: `feat(recipes?):` scope paired with pattern-N context (audit #421). +if printf '%s' "$subject" \ + | grep -qiE '^[a-z]+\(recipes?\):.*pattern[^a-zA-Z0-9]*[0-9]+'; then + cat >&2 <): pattern-N ... + feat(pattern-N): OTTL stanza in docs/integrations/examples/ +EOF + exit 1 +fi + +exit 0 diff --git a/scripts/recipes-path-check_test.sh b/scripts/recipes-path-check_test.sh new file mode 100755 index 00000000..8b4a5a9e --- /dev/null +++ b/scripts/recipes-path-check_test.sh @@ -0,0 +1,51 @@ +#!/usr/bin/env bash +# recipes-path-check_test.sh — regression test for the issue #427 gate. + +set -euo pipefail + +gate="$(cd "$(dirname "$0")" && pwd)/recipes-path-check.sh" + +if [ ! -x "$gate" ]; then + echo "FAIL: gate script not found or not executable: $gate" + exit 1 +fi + +# Run the gate against one input string; return its exit code. +run_gate() { bash "$gate" "$1" >/dev/null 2>&1; } + +assert_fails() { + if run_gate "$2"; then + echo "FAIL [$1]: gate exited 0 on '$2' (expected reject)" + return 1 + fi + echo "ok: $1" +} + +assert_passes() { + if ! run_gate "$2"; then + echo "FAIL [$1]: gate exited 1 on '$2' (expected accept)" + return 1 + fi + echo "ok: $1" +} + +# --- Reject cases: aspirational recipes/pattern-N/ layout -------------- + +assert_fails "rejects bare recipes/pattern-N path" "feat(recipes): recipes/pattern-7/ottl.yaml" +assert_fails "rejects scope recipes/pattern-N" "chore(recipes/pattern-2): bump" +assert_fails "rejects two-digit pattern" "docs: see recipes/pattern-13/README.md" +assert_fails "rejects feat(recipes) without explicit path" "feat(recipes): OTTL stanzas + bridge for pattern #7" +assert_fails "rejects feat(recipe) singular" "feat(recipe): pattern-2 IB link flap OTTL stanza" +assert_fails "rejects colon-prefixed recipes/pattern-N" "chore(foo):recipes/pattern-7" + +# --- Accept cases: real placement and unrelated subjects --------------- + +assert_passes "accepts docs/integrations/examples path" "feat(integrations/examples): pattern-7 OTTL stanza" +assert_passes "accepts per-target scope" "feat(filelog-container): pattern-7 dataloader OTTL" +assert_passes "accepts pattern-N scope" "feat(pattern-7): OTTL stanza in docs/integrations/examples/" +assert_passes "accepts unrelated subject" "feat(processor): add silent_data_corruption detector" +assert_passes "accepts internal/recipes Go path" "test: cover ./internal/recipes/... against live exporter" +assert_passes "accepts internal/recipes/pattern-N path" "test: refactor internal/recipes/pattern-7 detector" +assert_passes "accepts empty input" "" + +echo "all recipes-path-check tests passed"