Skip to content

ci: audit every blueprint on a schedule - #1049

Open
DPS0340 wants to merge 4 commits into
Dokploy:canaryfrom
DPS0340:ci/audit-all-blueprints
Open

ci: audit every blueprint on a schedule#1049
DPS0340 wants to merge 4 commits into
Dokploy:canaryfrom
DPS0340:ci/audit-all-blueprints

Conversation

@DPS0340

@DPS0340 DPS0340 commented Jul 26, 2026

Copy link
Copy Markdown

What

Adds a scheduled workflow that runs validate-docker-compose.ts and validate-template.ts over every blueprint, so rule drift gets reported instead of accumulating silently.

Implements the CI suggestion from #1047.

Why

The per-PR validators only look at blueprints a PR touches — which is the right call for CI cost, but it means a template is checked when it lands and then never again. When a rule is added or tightened, every existing blueprint that breaks it stays broken with nothing surfacing it.

That is not hypothetical. Running the validators across all 500 today:

check failing of
validate-docker-compose.ts 30 500
validate-template.ts 80 500

And #1048 demonstrated the mechanism live: editing vault/docker-compose.yml made CI look at vault/template.toml for the first time since a rule tightened, and it failed on a problem that had nothing to do with that PR.

Design decisions

Scheduled + manual only, never on pull_request. A contributor must not be blocked by a pre-existing failure they did not introduce. That is exactly what happened on #1048, and it should not be the default experience.

Reports rather than fails. The default run emits a ::warning:: and writes a full breakdown to the job summary. workflow_dispatch takes a fail_on_findings boolean for when you want it to gate — so you can flip it on once the backlog is cleared, without editing the workflow.

Reuses the existing validators unchanged. No new checking logic to keep in sync; it runs the same two scripts the PR workflows do, with the same pnpm exec tsx invocation and pinned Node 20.16.0 / pnpm 8 setup as validate-docker-compose.yml.

permissions: contents: read — it only reads.

The job summary it produces

Ran the workflow's audit step verbatim against the current tree. It renders a summary table plus one section per finding, with the validator's real output:

## Blueprint audit

| check | failing | of |
| --- | --- | --- |
| `validate-docker-compose.ts` | 30 | 500 |
| `validate-template.ts` | 80 | 500 |

### `adguardhome` — docker-compose.yml
❌ Service 'adguardhome': ports[0] uses port mapping format '53:53/tcp'. …

### `vault` — docker-compose.yml
❌ Service 'vault': Found 'container_name' field. …

110 finding sections in total.

Testing

I extracted the run: block from the workflow and executed it as a shell script against the real repo, rather than eyeballing the YAML:

$ bash /tmp/audit.sh
::warning::30 compose and 80 template.toml findings across 500 blueprints

compose_failed=30
toml_failed=80

Those numbers match what I counted by hand when filing #1047, which is the check that matters — the script isn't just exiting cleanly, it is finding the right things. The YAML also parses (triggers schedule + workflow_dispatch, 7 steps).

One caveat I want to be straight about. My local pnpm is broken (corepack tries to reinstall on every pnpm exec and errors), so my local run substituted npx tsx for pnpm exec tsx. The committed workflow keeps pnpm exec to match validate-docker-compose.yml, and the surrounding setup steps are copied from that workflow verbatim — but the pnpm exec path specifically is the one line I verified by consistency with the existing working workflow rather than by executing it myself. Worth a look on the first scheduled run.

Note on the current findings

To be clear, I am not proposing to fix the 110 in this PR. Some are almost certainly intentional — the port-mapping findings are adguardhome (53/udp DNS), poste.io (SMTP/POP/IMAP), wg-easy (51820/udp), enshrouded (game server), all of which genuinely need host ports. Those may argue for an allowance in the validator rather than a change to the templates. This PR just makes the number visible.

Copilot AI review requested due to automatic review settings July 26, 2026 03:49
@dosubot dosubot Bot added the size:XS This PR changes 0-9 lines, ignoring generated files. label Jul 26, 2026
@github-actions

github-actions Bot commented Jul 26, 2026

Copy link
Copy Markdown
built with Refined Cloudflare Pages Action

⚡ Cloudflare Pages Deployment

Name Status Preview Last Commit
templates ✅ Ready (View Log) Visit Preview 82adc99

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new GitHub Actions workflow to periodically audit all blueprints with the existing validate-docker-compose.ts and validate-template.ts scripts, surfacing rule drift without impacting PR CI.

Changes:

  • Introduces a scheduled (weekly) + manual (workflow_dispatch) workflow to run both validators across every blueprints/*/ directory.
  • Generates a GitHub Actions job summary with failing counts and per-blueprint failure snippets.
  • Adds an optional manual “gate” mode (fail_on_findings) to fail the run when findings exist.

fi

- name: Fail if requested
if: inputs.fail_on_findings && (steps.audit.outputs.compose_failed != '0' || steps.audit.outputs.toml_failed != '0')
Comment on lines +53 to +55
COMPOSE_FAILED=""
TOML_FAILED=""

Copilot AI review requested due to automatic review settings July 26, 2026 04:10
@DPS0340

DPS0340 commented Jul 26, 2026

Copy link
Copy Markdown
Author

Both correct and both fixed in 0d3aeb6. The first one would have broken the scheduled run — which is the only way this workflow normally executes — so thank you for catching it.

1. inputs on a schedule run. You're right: inputs is only populated for workflow_dispatch, so referencing it from a cron run is exactly the wrong place to have a fragile expression. Now:

if: >-
  github.event_name == 'workflow_dispatch' &&
  github.event.inputs.fail_on_findings == 'true' &&
  (steps.audit.outputs.compose_failed != '0' || steps.audit.outputs.toml_failed != '0')

Gated on the event first, and the value read through github.event.inputs, which is an empty string rather than undefined when absent. Also compared against the string 'true'workflow_dispatch boolean inputs arrive as strings, so a truthiness test would have fired on 'false' too.

2. Fixed /tmp paths. Also right, and this one I should have caught myself: I hit precisely this while testing locally, where a leftover /tmp/report.md from an earlier run polluted the summary. I cleaned it up by hand and moved on instead of treating it as a bug in the workflow.

Now uses $RUNNER_TEMP with an explicit rm -f at the top of the step:

WORK="${RUNNER_TEMP:-/tmp}"
OUT="$WORK/validator-out.txt"
REPORT="$WORK/audit-report.md"
SUMMARY="$WORK/audit-summary.md"
rm -f "$OUT" "$REPORT" "$SUMMARY"

Verified rather than assumed. Re-extracted the run: block and executed it against the real tree, after deliberately planting a stale audit-report.md in $RUNNER_TEMP first:

::warning::30 compose and 80 template.toml findings across 500 blueprints
compose_failed=30
toml_failed=80

stale content present in the summary: 0 occurrences

Same counts as before (and still matching the hand-derived numbers in #1047), summary still renders 110 finding sections, and the planted file is gone rather than appended. I also confirmed the previous version would have leaked it — the old script cats the report unconditionally.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated 1 comment.

Comment on lines +108 to +114
if [ -f "$REPORT" ]; then
cat "$REPORT" >> "$SUMMARY"
else
echo "No findings." >> "$SUMMARY"
fi

cat "$SUMMARY" >> "$GITHUB_STEP_SUMMARY"
Copilot AI review requested due to automatic review settings July 26, 2026 04:38
@DPS0340

DPS0340 commented Jul 26, 2026

Copy link
Copy Markdown
Author

Good catch — fixed in 57c7cce.

I measured it before deciding how to respond, because the answer changes the fix: the summary is currently 35 KB against a ~1 MB cap, so there's roughly 30× headroom and it would take about 3,200 finding sections to hit it today. Not urgent — but the failure mode you describe is the bad kind, because it gets worse as the backlog grows and it truncates silently, so the run would look healthy while hiding exactly the findings the job exists to report.

Two changes:

  1. The appended summary is bounded at 512 KB. Past that it appends a truncation notice with the real byte count and points at the artifact, so a reader can always tell they're looking at a partial view.
  2. The full report is uploaded as a blueprint-audit artifact on every run (if: always(), 30-day retention), so the complete output survives regardless of size — including the untruncated case.

Verified both branches by executing the step, not by reading it.

Truncation path, forced with a 2,000-byte limit:

summary bytes: 2126          (2000 of report + the notice)
_Report truncated at 2000 bytes (full size: 35272)._
_Download the `blueprint-audit` artifact for the complete output._
artifact: 35272 bytes        (complete)

Normal path, real 512 KB limit against a representative sample:

| `validate-docker-compose.ts` | 3 | 5 |
| `validate-template.ts`       | 1 | 5 |
truncation notice: 0
artifact == summary   (byte-identical, nothing lost)

One note on scope: I verified the untruncated branch against a 5-blueprint sample (3 known-failing, 2 known-passing) rather than the full 500, because my local runs of the complete sweep kept getting killed. The full sweep was verified in the previous round and produced the same 30 / 80 counts that match #1047; this change only touches what happens after the counts are computed, so the sample exercises the same code path.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated 2 comments.

Comment on lines +67 to +91
if [ -f "$dir/docker-compose.yml" ]; then
if ! (cd build-scripts && pnpm exec tsx validate-docker-compose.ts \
--file "../$dir/docker-compose.yml") > "$OUT" 2>&1; then
COMPOSE_FAILED="$COMPOSE_FAILED $id"
{
echo "### \`$id\` — docker-compose.yml"
echo '```'
grep '❌' "$OUT" || cat "$OUT"
echo '```'
} >> "$REPORT"
fi
fi

if [ -f "$dir/template.toml" ]; then
if ! (cd build-scripts && pnpm exec tsx validate-template.ts \
--dir "../$dir") > "$OUT" 2>&1; then
TOML_FAILED="$TOML_FAILED $id"
{
echo "### \`$id\` — template.toml"
echo '```'
grep '❌' "$OUT" || cat "$OUT"
echo '```'
} >> "$REPORT"
fi
fi
Comment on lines +120 to +131
if [ "$SUMMARY_BYTES" -gt "$SUMMARY_LIMIT" ]; then
head -c "$SUMMARY_LIMIT" "$SUMMARY" >> "$GITHUB_STEP_SUMMARY"
{
echo ""
echo "---"
echo ""
echo "_Report truncated at $SUMMARY_LIMIT bytes (full size: $SUMMARY_BYTES)._"
echo "_Download the \`blueprint-audit\` artifact for the complete output._"
} >> "$GITHUB_STEP_SUMMARY"
else
cat "$SUMMARY" >> "$GITHUB_STEP_SUMMARY"
fi
Copilot AI review requested due to automatic review settings July 26, 2026 04:46
@DPS0340

DPS0340 commented Jul 26, 2026

Copy link
Copy Markdown
Author

Both right, both fixed in 82adc99. The second one I could reproduce as a live defect in what I'd just pushed, which is a good argument for the change.

1. Skipping validators when a file is missing. Agreed, and the reasoning is stronger than "consistency" — the skip hides exactly the blueprints most likely to be broken. Confirmed the validators do the right thing on their own:

$ validate-docker-compose.ts --file .../broken-no-compose/docker-compose.yml
❌ docker-compose.yml not found at .../docker-compose.yml
exit=1

$ validate-template.ts --dir .../broken-no-toml
exit=1

Both if [ -f ... ] guards removed. Verified against a sandbox with two deliberately broken blueprints (one missing each file):

::warning::3 compose and 3 template.toml findings across 5 blueprints

### `broken-no-compose` — docker-compose.yml
❌ docker-compose.yml not found at ...
### `broken-no-toml` — template.toml
❌ template.toml not found at ...

Previously both would have been silently clean. Re-ran the same 5 real blueprints afterwards and got the unchanged compose:3 toml:1, so no regression on well-formed ones.

2. head -c cutting mid-fence. This is a real defect and it was already happening. My earlier truncation test produced a summary with 7 fence lines — an odd number, so the last fence was unclosed and the notice ended up inside the code block:

❌ Service 'any-sync-bundle': ports[0] uses port mapping format '33010:33010'...
---
_Report truncated at 2000 bytes (full size: 35272)._

That renders as literal text inside an open fence — the truncation would have been invisible, which defeats the point of the warning.

Took your suggestion: on overflow, emit only the header table plus the notice, and rely on the artifact. Verified the header is exactly 7 lines (line 8 is the first ### finding), and the result now has 0 fence lines — balanced:

## Blueprint audit

| check | failing | of |
| --- | --- | --- |
| `validate-docker-compose.ts` | 30 | 500 |
| `validate-template.ts` | 80 | 500 |

_Report omitted: 35272 bytes exceeds the 2000 byte budget for a job summary._
_Download the `blueprint-audit` artifact for the complete output._

Also reworded from "truncated" to "omitted", since the report is no longer partially included — it is entirely in the artifact.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated no new comments.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XS This PR changes 0-9 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants