* ci: harden CI and supply-chain pipeline
* Add frontend Vitest step to ci.yml so the 241 existing frontend tests run on
every PR (mirrors the backend build/test/lint/audit order).
* Pin Node 26 as a single source of truth: new .node-version, node-version-file
on all setup-node calls, engines.node ">=26.0.0" in all three package.json
files. Matches the Dockerfile's node:26-alpine.
* SHA-pin remaining mutable actions in the start-app composite
(actions/setup-node v6, actions/cache v4.3.0).
* Pin Dockerfile supply-chain inputs: golang:1.26.3-alpine by sha256 digest in
both builder stages; replace mutable-tag git clone with commit-SHA fetch for
docker/cli (v29.4.1) and docker/compose (v5.1.3). LDFLAGS version strings
and otel patch preserved unchanged.
* Ref-scope docker-publish concurrency so two different release tags cannot
cancel each other; same-ref reruns still cancel as before.
* Harden CLA workflow: drop actions:write from permissions; tighten the
issue_comment trigger to PRs only (github.event.issue.pull_request != null)
matching the two documented CLA phrases. No PR code is checked out.
* Drop trivy-version: latest from both Trivy scans so the SHA-pinned
aquasecurity/trivy-action governs the bundled binary version. The
HIGH/CRITICAL gate, severity filter, and trivy.yaml (OpenVEX) are unchanged.
* Restructure Dependabot: add applies-to: security-updates groups for npm
(root/backend/frontend), docker, and github-actions; switch github-actions
to directories so the local composite action is monitored alongside the
top-level workflows.
* Add a daily scheduled SARIF security scan (security-scan.yml): two parallel
jobs scanning saelix/sencho:latest and a fresh main HEAD build, uploading to
GitHub code scanning. Least-privilege (contents: read, security-events:
write). Visibility only; existing PR-blocking and release-blocking Trivy
gates are not weakened.
Validation: backend tsc clean; frontend tsc clean; frontend npm test 27 files
/ 241 tests pass; npm audit --audit-level=high passes at root, backend, and
frontend; docker buildx build --check passes with no warnings (all pinned
digests resolve from the registry).
* ci(frontend): set explicit jsdom URL so localStorage initializes in CI
jsdom does not instantiate window.localStorage / sessionStorage when the
document has the opaque about:blank origin. Five frontend test files that
call localStorage.clear() in beforeEach started failing once the new
frontend Vitest step in this PR began running them on Linux CI runners.
Configuring environmentOptions.jsdom.url with a real same-origin URL is
the documented Vitest 4.x workaround and is a config-only change. All 27
test files (241 tests) pass locally with the fix applied.
The release-only `update-screenshots` job opened a `chore/refresh-screenshots`
PR and immediately tried to squash-merge it. Branch protection (1 review,
6 status checks) rejected the merge on every release, leaving an open PR
behind. Screenshots will instead be refreshed manually after UI changes.
Removed:
- The `update-screenshots` job from ci.yml (~57 lines).
- The `paths-ignore: docs/images/**` push trigger filter; its sole purpose
was to break the auto-merge re-trigger cascade. Its absence also fixes a
latent bug where docs-only pushes to main would have skipped sync-docs.
- Four `head_ref != 'chore/refresh-screenshots'` guards in other jobs.
- The "doc screenshots" mention in the skip-bot-PRs comment.
Reworked the screenshot capture spec to be opt-in:
- playwright.config.ts now defines two projects. The default `chromium`
project ignores screenshots.spec.ts; a separate `screenshots` project
matches it and is invoked manually.
- The e2e CI job runs `--project=chromium` so the screenshots project
cannot accidentally run in CI.
- Updated the spec's module comment with the new manual invocation.
Net: 86 lines removed, 27 added.
Updates the backend listen port, Vite dev proxy target, Docker EXPOSE,
compose port mapping, .env.example default, GitHub Actions smoke-test
default, healthcheck URLs, and every doc/example reference. Test fixtures
that include example URLs were updated for consistency, though their
assertions are port-agnostic.
The rate-limit value of 3000 in middleware/rateLimiters.ts and the
3000 entry in WEB_UI_PORTS (which detects user containers like Grafana)
are intentionally untouched.
The Playwright browser download (Chrome for Testing + Chrome Headless
Shell + FFmpeg, ~170MB total) is the single largest cost in the
start-app composite action. Observed in a recent E2E run: 4m15s to
download cold vs ~10s on a warm cache. Across weekly cache evictions
and the ~30% of runs that hit a cold cache, that is multiple action
minutes per PR averaged over time, and several minutes of wall-time
regression on any individual cold run.
Split the existing `playwright install --with-deps chromium` step
into two halves:
1. `playwright install-deps chromium` runs unconditionally because
apt packages live under /var and are not preserved by actions/cache.
This is fast (~10s).
2. `playwright install chromium` runs only when the browser cache
under ~/.cache/ms-playwright misses. The cache key is pinned to
the @playwright/test version read from the root package.json, so
a future bump invalidates the cache automatically and the old
browsers are replaced cleanly.
Sharding (the other half of the original optimization plan) was
evaluated and rejected. With ~80s of per-job setup and only ~60s of
test time, 2-way sharding would roughly double action minutes while
saving only ~5s of wall time on warm runs. The math only pencils out
when test time significantly exceeds setup time, which is not the
case for Sencho today. Revisit if the suite grows past ~5 minutes
of pure test time.
The backend CI job already runs `tsc` against backend/src/ and only
marks the job green after build + tests + lint + audit all pass. The
E2E job then checked out the same source and ran the same tsc again
inside the start-app composite.
Upload backend/dist/ as a 1-day retention artifact at the end of the
backend job, download it in the E2E job before invoking the composite,
and gate the composite's "Build backend" step on a new
`skip-backend-build` input. The update-screenshots job (which also
uses the composite but has no upstream backend job) leaves the input
at its default of 'false' and continues to build from source.
Savings are modest (~10-15s per E2E run, on top of what setup-node's
~/.npm cache already buys us) but the real win is the contract:
the dist/ that E2E exercises is now byte-for-byte the same artifact
that the backend job verified, eliminating a whole class of "works
in backend job, breaks in E2E" drift.
When an E2E test flakes in CI, the backend and frontend stdout is gone
the moment the step exits. That makes a failed run extremely hard to
triage: you get the Playwright report but nothing from the services
underneath it. Redirect both dev servers to files under the workspace's
ci-logs/ directory and include that directory in the failure-case
artifact upload, so the report and the service logs travel together.
Log files live under $GITHUB_WORKSPACE rather than /tmp because
actions/upload-artifact@v4+ computes a common parent directory across
all path entries. Mixing repo-relative paths with /tmp paths makes '/'
the common root, which v4+ then strips from the artifact, silently
dropping the absolute entries. Keeping everything under the workspace
avoids that footgun.
`stdbuf -oL -eL` forces line-buffered output on both services. Node
stdout is block-buffered when redirected to a regular file, so a crash
or abort can lose the final few hundred lines - exactly the output you
need when debugging a CI failure.
Also remove the duplicated `E2E_USERNAME: admin` / `E2E_PASSWORD:
password123` env blocks from the e2e and update-screenshots jobs.
Those values were already the fallback defaults in e2e/helpers.ts
(`process.env.E2E_USERNAME ?? 'admin'`), so the env blocks were doing
nothing on the default path and created two sources of truth for the
same literals. e2e/helpers.ts remains the canonical source; callers
that want to override the creds still set E2E_USERNAME / E2E_PASSWORD
as job-level env.
* ci: optimize workflows to cut redundant runs and add timeout protection
Skip validation jobs (build, test, lint, Docker, E2E) on push-to-main
since code was already validated on the PR. Skip CI entirely for
release-please PRs (version bump only). Add timeout-minutes to all jobs
to prevent hung runners from burning 360 min (GitHub default). Add npm
caching to the start-app composite action. Add concurrency control to
docker-publish. Group Dependabot updates to batch PRs per ecosystem.
Estimated savings: ~10 min per release cycle, ~30-40 min/day.
* chore: add tsbuildinfo to gitignore
Adds the ability to schedule recurring Docker operations (stack restarts,
fleet snapshots, system prunes) via cron expressions with full execution
history logging. Includes Run Now for on-demand execution.