mirror of
https://github.com/J3vb/OwnCord.git
synced 2026-09-03 03:50:00 +03:00
* feat(b3-6): contract drift — generated route, table and config-key indexes B3-6 item 9 (workstream 10). `check:server` already diffs the two generators; this adds a third for the three server contracts that only prose described until now. `Server/cmd/gendocs` rewrites one marked block per document: - `docs/api.md` "Route index (generated)" — 111 rows from `chi.Walk` over the production router built with uploads, voice and the GIF proxy on, the same scaffolding `api/absence_contract_test.go` uses. Carries that test's vacuity guards: fewer than 100 routes, or no `/admin/` route, fails the run. - `docs/schema.md` "Table index (generated)" — 34 rows from `sqlite_master` and `pragma_table_info` on an in-memory database with the migrations applied. sqlc exposes no catalog, so the migrated schema is the catalog. - `docs/server-configuration.md` "Key index (generated)" — 56 keys from the koanf struct tags, each mapped to the `###` section of the hand-written reference that names it. A key documented nowhere fails the run by name. Output is padded exactly the way Prettier formats a table, so the drift check and the hygiene gate agree instead of undoing each other. Wiring, copied from protocol-verify: `make docs-generate` / `make docs-verify`, a `DOCS_VERIFY` step in `check:server` and the generator in `generate` (`scripts/run.mjs`), a CI step on the ubuntu leg of `server-build-test`, and a `.githooks/pre-commit` block on router, handler, migration, config and generator paths. Everything hand-written in the three documents is untouched. The new `cmd/gendocs` file imports `db` for the catalog, so it takes a boundary row in the B3-0 inventory and `server-boundaries.md` is regenerated with it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KmiqjgTuov1stBTB6uGkvo * docs(b3-6): evidence block for item 9 (machine-readable contract drift) Records the three RED controls and their restore, the counts (111 routes, 34 tables, 56 config keys, 0 undocumented), and two corrections to the item's spec: the configuration reference table lives in docs/server-configuration.md, not docs/deployment.md, and sqlc exposes no catalog — the migrated in-memory schema is the catalog. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KmiqjgTuov1stBTB6uGkvo * fix(b3-6): gendocs — exclude ANALYZE artifacts, honest hook message, admin routes trigger the hook, generate order, width ceiling Review findings on item 9. 1. The table index dropped `sqlite_stat1` / `sqlite_stat4`. `db.Migrate` runs ANALYZE after applying migrations, so those hold planner statistics, not schema — and `sqlite_stat4` exists only because the current modernc.org/sqlite build has STAT4, so a driver bump would have failed the docs drift check on an unrelated dependency PR. Filtered with GLOB (LIKE's `_` is a wildcard), block regenerated, header line's justification corrected: 34 -> 32 tables. 2. The pre-commit message now covers both failure modes — stale blocks are regenerated and staged, a key the tool named as undocumented is documented in docs/server-configuration.md. 3. `Server/admin/.*\.go` added to the hook's trigger: the 34 `/admin/api/*` routes are registered there, not in api/router.go, so a new admin route could commit stale docs locally. 4. `run.mjs` `generate` runs gendocs after `sqlc generate` — gendocs compiles the api package, which imports db/dbgen. 5. The vacuity guard now requires a traversed `/admin/api/` subroute rather than any `/admin/` path, which the per-method mount catch-alls satisfied on their own, so its message is true. `writeTable` gained a comment naming its ceiling: padding counts runes, Prettier counts display width, so a full-width cell would diverge — none exists in the generated content. Evidence block updated for the new table count. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KmiqjgTuov1stBTB6uGkvo * fix(b3-6): gendocs — generate the route index from the full-tag build with telemetry on; the hook triggers on every api/ and admin/ Go file (Codex P2s on #1456) 1. `/metrics` was missing from the route index. It mounts only when `telemetry.PrometheusHandler()` returns non-nil (api/router.go:431-437), which needs the otel build tag AND telemetry enabled at runtime; the generator ran in the default build with telemetry unset, so the index omitted a production route. The route index is now the superset build. The scaffold config enables telemetry with the Prometheus exporter and the tool calls telemetry.Init the way main.go does, and every invocation passes -tags otel,wazero: Makefile docs-generate/docs-verify, scripts/run.mjs (DOCS_VERIFY and generate), .githooks/pre-commit, the regenCmd quoted into all three block header lines, and the CLAUDE.md row. ci.yml inherits it through `make docs-verify`. The route block's header line now says which build it came from and what is enabled. Rather than a build-tag constant, the tool checks the condition that actually gates the route: if telemetry.Init leaves no Prometheus handler it exits non-zero naming the tags, so the default build cannot quietly generate a short index. Nothing under Server/api or Server/admin carries a build constraint, so wazero adds and removes no route; it rides along so one build serves the whole repository. Route count 111 -> 121 (ten per-method rows for the /metrics mount, the same shape chi gives /admin and /livekit). 2. The pre-commit trigger named individual api/ files and missed client_update.go, whose MountClientUpdateRoute registers a route directly. It is now the whole of Server/api/ and Server/admin/ — naming files individually is how a trigger goes stale — plus the existing migrations/, config/config.go and cmd/gendocs/ patterns. Evidence block updated: route count and the tagged-build decision. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KmiqjgTuov1stBTB6uGkvo --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
653 lines
26 KiB
YAML
653 lines
26 KiB
YAML
name: CI
|
|
|
|
on:
|
|
# dev is deliberately not a push trigger: while a dev -> main PR is open every
|
|
# push to dev already fires pull_request(synchronize), so listing it here ran
|
|
# the whole suite twice for one push. Use workflow_dispatch for a dev branch
|
|
# with no PR open yet.
|
|
push:
|
|
branches: [main]
|
|
pull_request:
|
|
branches: [main, dev]
|
|
workflow_dispatch:
|
|
|
|
# Cancel in-progress runs for the same branch/PR
|
|
concurrency:
|
|
group: ci-${{ github.ref }}
|
|
cancel-in-progress: true
|
|
|
|
permissions:
|
|
contents: read
|
|
|
|
jobs:
|
|
server-build-test:
|
|
name: Server Build & Test (${{ matrix.os }})
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
include:
|
|
- os: windows-latest
|
|
binary: chatserver.exe
|
|
- os: ubuntu-latest
|
|
binary: chatserver
|
|
runs-on: ${{ matrix.os }}
|
|
defaults:
|
|
run:
|
|
working-directory: Server/
|
|
steps:
|
|
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
|
|
|
|
- uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5.6.0
|
|
with:
|
|
go-version: "1.26"
|
|
cache-dependency-path: Server/go.sum
|
|
|
|
- name: Build server
|
|
run: go build -o ${{ matrix.binary }} -ldflags "-s -w" .
|
|
|
|
# Phase B + C build-tag matrix. Each tag variant must compile so the
|
|
# tag boundaries don't drift.
|
|
- name: Build with -tags otel (Phase B Step 8)
|
|
run: go build -tags otel ./...
|
|
- name: Build with -tags wazero (Phase C Step 9)
|
|
run: go build -tags wazero ./...
|
|
- name: Build with -tags otel,wazero (full community-hub build)
|
|
run: go build -tags otel,wazero ./...
|
|
|
|
- name: Go vulnerability check
|
|
run: go install golang.org/x/vuln/cmd/govulncheck@v1.1.4 && govulncheck ./...
|
|
|
|
# Generated sqlc output must never drift from db/queries/. One leg of
|
|
# the matrix is enough; make is not guaranteed on the Windows runner.
|
|
- name: Verify generated sqlc output (make sqlc-verify)
|
|
if: matrix.os == 'ubuntu-latest'
|
|
run: make sqlc-install sqlc-verify
|
|
|
|
# Protocol message-type constants (Go + TS) must never drift from
|
|
# protocol/schema.json — the single source of truth.
|
|
- name: Verify generated protocol constants (make protocol-verify)
|
|
if: matrix.os == 'ubuntu-latest'
|
|
run: make protocol-verify
|
|
|
|
# The route, table and config-key index blocks in docs/ must never drift
|
|
# from the mounted router, the migrated schema and config.Config's koanf
|
|
# tags. Same one-leg rule as the two checks above.
|
|
- name: Verify generated docs (make docs-verify)
|
|
if: matrix.os == 'ubuntu-latest'
|
|
run: make docs-verify
|
|
|
|
- name: Run tests with race detection and coverage
|
|
run: go test -race -timeout 20m ./... -coverprofile=coverage.out -cover
|
|
|
|
- name: Run tests with deadlock detection
|
|
run: go test -tags deadlock -count=1 ./...
|
|
|
|
# Tag-gated tests (DC-06 / T-2026-07-25-16). The build-tag matrix above
|
|
# only COMPILES the otel/wazero variants; the tests behind those tags
|
|
# (plugin/sandbox_wazero_test.go, telemetry/telemetry_otel_test.go) ran
|
|
# nowhere until this step. Scoped to the two packages that carry tagged
|
|
# files — every other package is tag-invariant and already covered by the
|
|
# race run above. One leg is enough; no -race (the runtime under the tag
|
|
# is the concern, not new concurrency).
|
|
- name: Run tag-gated tests (-tags wazero, -tags otel)
|
|
if: matrix.os == 'ubuntu-latest'
|
|
run: |
|
|
go test -tags wazero -count=1 ./plugin/...
|
|
go test -tags otel -count=1 ./telemetry/...
|
|
|
|
# Coverage ratchet (B3-6 item 1). Reads the profile the race step wrote,
|
|
# but placed after the other test steps so a floor miss does not hide
|
|
# their results. Linux leg only: the profile is not the same on both legs
|
|
# — OS-tagged files swap in and out and several tests skip on Windows —
|
|
# so the floors are pinned to one leg and the figure stays deterministic.
|
|
# Ratchet rule in Server/CLAUDE.md.
|
|
- name: Check coverage floor
|
|
if: matrix.os == 'ubuntu-latest'
|
|
run: bash scripts/coverage-floor.sh coverage.out
|
|
|
|
- name: Upload Go coverage
|
|
if: always()
|
|
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
|
with:
|
|
name: go-coverage-${{ matrix.os }}
|
|
path: Server/coverage.out
|
|
retention-days: 7
|
|
|
|
# verify: false — the action's default `config verify` pass fetches
|
|
# golangci-lint.run's JSONSchema over HTTPS before linting anything, so a
|
|
# timeout on that host fails a required job having run zero linters (it
|
|
# took main red on d352696). `golangci-lint run` rejects a bad config on
|
|
# its own; the schema pass only bought a prettier error message, priced
|
|
# at a third-party site inside the gate.
|
|
- name: Lint
|
|
uses: golangci/golangci-lint-action@ba0d7d2ec06a0ea1cb5fa41b2e4a3ab91d21278a # v9.3.0
|
|
with:
|
|
version: v2.11.3
|
|
working-directory: Server/
|
|
verify: false
|
|
|
|
# ubuntu-latest deliberately: the client TS code has zero win32-conditional
|
|
# paths (no process.platform / path.sep branches in src or the unit suites),
|
|
# prettier pins endOfLine: lf and .gitattributes forces eol=lf, so a Windows
|
|
# runner adds queue time without adding coverage. Windows-specific behavior
|
|
# is covered where it exists: rust-tests and the tauri-build matrix.
|
|
client-check:
|
|
name: Client Static Checks
|
|
runs-on: ubuntu-latest
|
|
defaults:
|
|
run:
|
|
working-directory: Client/
|
|
steps:
|
|
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
|
|
|
|
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
|
with:
|
|
node-version: 24
|
|
cache: npm
|
|
cache-dependency-path: Client/package-lock.json
|
|
|
|
- name: Install npm dependencies
|
|
run: npm ci
|
|
|
|
# Scoped to shipped dependencies. The remaining high findings are all one
|
|
# advisory, brace-expansion <=5.0.7, reaching us only through dev tooling
|
|
# (eslint, @vitest/coverage-v8, stryker). Those are already on their
|
|
# latest versions, so no bump reaches the fix, and there is no patched
|
|
# release in the 1.x/2.x lines they pin. Forcing every copy to 5.0.9 via
|
|
# overrides was tried and broke the build: minimatch requires
|
|
# brace-expansion as CJS and v5 is not callable that way, which took out
|
|
# vitest's coverage provider. Nothing here ships to users; revisit when
|
|
# eslint and @vitest/coverage-v8 widen their minimatch ranges.
|
|
- name: Security audit (npm, shipped deps)
|
|
run: npm audit --omit=dev --audit-level=high
|
|
|
|
- name: Oxlint (fast correctness checks)
|
|
run: npx oxlint src/
|
|
|
|
- name: TypeScript check
|
|
run: npx tsc --noEmit
|
|
|
|
- name: TypeScript check (Playwright specs)
|
|
# The main tsconfig excludes tests/e2e from the app graph; this
|
|
# project typechecks every tests/e2e spec + fixtures + the
|
|
# playwright configs so type rot cannot hide there.
|
|
run: npx tsc -p tsconfig.e2e.json --noEmit
|
|
|
|
- name: ESLint (type-aware rules)
|
|
run: npx eslint src/
|
|
|
|
- name: Knip (unused code & deps)
|
|
# Blocking since the 2026-08-04 remediation: the '|| true' era let a
|
|
# real unused-export finding sit invisible in every green run.
|
|
run: npx knip
|
|
|
|
# Unit tests live in their own job so a suite failure is visible as exactly one
|
|
# failing check instead of masking the static gates above. The suite is GREEN
|
|
# and must stay green — never "fix" a failing test by editing its assertions.
|
|
# ubuntu-latest for the same reason as client-check above: jsdom-only vitest
|
|
# with no platform-conditional code under test.
|
|
# The automated half of G-04: a planning document that states a finding count
|
|
# the ledger contradicts fails here instead of quietly misleading a reader.
|
|
# Deliberately tiny — no npm ci, because the script imports nothing outside
|
|
# node:. It also runs its own selftest, since the whole check rests on
|
|
# patterns narrow enough not to cry wolf.
|
|
docs-consistency:
|
|
name: Docs & Ledger Consistency
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
|
|
|
|
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
|
with:
|
|
node-version: 24
|
|
|
|
- name: Self-test the count matcher
|
|
run: node scripts/check-doc-counts.mjs --selftest
|
|
|
|
- name: Documents must agree with the findings ledger
|
|
run: node scripts/check-doc-counts.mjs
|
|
|
|
- name: Ledger schema is valid
|
|
run: node .superpowers/render-ledger.mjs --check
|
|
|
|
# release.yml writes protocol_epoch into the signed server-update
|
|
# manifest with exactly this command, and release.yml only runs at tag
|
|
# time — so the read is proven here, on every pull request.
|
|
- name: protocol_epoch is readable the way release.yml reads it
|
|
run: test "$(jq -e '.protocol_epoch' protocol/schema.json)" -ge 1
|
|
|
|
# R-09 / RL-16. The release gate itself is only invoked for real at tag
|
|
# time, which is the wrong place to find a bug in it — so its decision
|
|
# logic is exercised here, on every pull request, against fixtures. Same
|
|
# reason Server/scripts/docker-smoke.sh is called from both workflows.
|
|
# This also parses the required-check list out of
|
|
# b0-dev-branch-protection.sh, so a change to that list's shape fails here
|
|
# rather than silently weakening the gate.
|
|
- name: Self-test the release gate
|
|
run: node scripts/verify-gate-evidence.mjs --selftest
|
|
|
|
# RL-07. FINDINGS.md is not tracked, so it cannot drift -- but L-07 also
|
|
# asks that the rendering be reproducible and that CI reject a generation
|
|
# failure. Rendering twice and comparing tests both: the render must
|
|
# succeed (it validates and exits 1 before writing), and it must be a pure
|
|
# function of the ledger. The severity rule in validate() is what makes
|
|
# the second half true -- an unranked severity would make render()'s sort
|
|
# implementation-defined.
|
|
- name: FINDINGS.md renders, and renders identically twice
|
|
run: |
|
|
node .superpowers/render-ledger.mjs
|
|
cp .superpowers/FINDINGS.md "$RUNNER_TEMP/FINDINGS.first.md"
|
|
node .superpowers/render-ledger.mjs
|
|
cmp "$RUNNER_TEMP/FINDINGS.first.md" .superpowers/FINDINGS.md || {
|
|
echo "ERROR: rendering the ledger twice produced different output."
|
|
echo "render() must be a pure function of findings-ledger.json."
|
|
exit 1
|
|
}
|
|
|
|
# The rendering is the human-readable view and is deliberately untracked,
|
|
# so this artifact is how a reviewer reads it without a Node run.
|
|
# if: always() -- you want it downloadable precisely when the job failed.
|
|
- name: Upload the rendered findings ledger
|
|
if: always()
|
|
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
|
with:
|
|
name: findings-ledger-rendering
|
|
path: .superpowers/FINDINGS.md
|
|
retention-days: 7
|
|
|
|
# Repository-wide formatting, script lint and workflow lint (RL-19 / L-13, S-05).
|
|
#
|
|
# Root-scoped and ubuntu-only for the same reason as docs-consistency above:
|
|
# every gate here is platform-independent text analysis, and .gitattributes
|
|
# pins eol=lf so a second OS would only re-prove line endings.
|
|
#
|
|
# Prettier lives here rather than in client-check because it is no longer a
|
|
# client gate -- one config at the repository root covers Markdown, YAML,
|
|
# JSON, CSS and the root scripts as well as client TypeScript.
|
|
hygiene:
|
|
name: Repository Hygiene
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
|
|
|
|
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
|
with:
|
|
node-version: 24
|
|
|
|
# Root install only -- prettier is the sole dependency this job needs, and
|
|
# the client's install is client-check's job.
|
|
- name: Install root dependencies
|
|
run: npm ci
|
|
|
|
# shellcheck ships in the ubuntu runner image. actionlint does not, so it
|
|
# is pinned by version and checked by digest: an unpinned installer script
|
|
# would be the one unverified download in a workflow file that pins every
|
|
# action by commit SHA.
|
|
- name: Install actionlint
|
|
env:
|
|
ACTIONLINT_VERSION: 1.7.7
|
|
ACTIONLINT_SHA256: 023070a287cd8cccd71515fedc843f1985bf96c436b7effaecce67290e7e0757
|
|
run: |
|
|
set -euo pipefail
|
|
url="https://github.com/rhysd/actionlint/releases/download/v${ACTIONLINT_VERSION}/actionlint_${ACTIONLINT_VERSION}_linux_amd64.tar.gz"
|
|
curl -sSfL --retry 3 -o "$RUNNER_TEMP/actionlint.tar.gz" "$url"
|
|
echo "$ACTIONLINT_SHA256 $RUNNER_TEMP/actionlint.tar.gz" | sha256sum -c -
|
|
tar -xzf "$RUNNER_TEMP/actionlint.tar.gz" -C "$RUNNER_TEMP" actionlint
|
|
echo "$RUNNER_TEMP" >> "$GITHUB_PATH"
|
|
|
|
- name: Report tool versions
|
|
run: shellcheck --version && actionlint --version
|
|
|
|
# The same entry point a contributor runs. run.mjs takes its shellcheck and
|
|
# actionlint file lists from `git ls-files`, never a filesystem glob.
|
|
- name: Formatting, shell and workflow gates
|
|
run: npm run check:hygiene
|
|
|
|
client-tests:
|
|
name: Client Unit Tests
|
|
runs-on: ubuntu-latest
|
|
defaults:
|
|
run:
|
|
working-directory: Client/
|
|
steps:
|
|
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
|
|
|
|
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
|
with:
|
|
node-version: 24
|
|
cache: npm
|
|
cache-dependency-path: Client/package-lock.json
|
|
|
|
- name: Install npm dependencies
|
|
run: npm ci
|
|
|
|
- name: Run unit tests with coverage
|
|
run: npx vitest run --coverage --reporter=default
|
|
|
|
- name: Upload client coverage
|
|
if: always()
|
|
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
|
with:
|
|
name: client-coverage
|
|
path: Client/coverage/
|
|
retention-days: 7
|
|
|
|
# Rust unit tests used to live inside tauri-build, which only runs on PRs to
|
|
# main — so #[cfg(test)] code never ran on pushes or on PRs to dev, and could
|
|
# rot for a whole release cycle. This job runs them on every event. Clippy is
|
|
# run with --all-targets here (tauri-build's lib-only clippy skips test code).
|
|
rust-tests:
|
|
name: Rust Unit Tests
|
|
runs-on: ubuntu-22.04
|
|
timeout-minutes: 30
|
|
defaults:
|
|
run:
|
|
working-directory: Client/src-tauri/
|
|
steps:
|
|
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
|
|
|
|
- name: Install Linux system dependencies
|
|
run: |
|
|
sudo apt-get update
|
|
sudo apt-get install -y \
|
|
libwebkit2gtk-4.1-dev \
|
|
libgtk-3-dev \
|
|
libayatana-appindicator3-dev \
|
|
libsecret-1-dev \
|
|
libdbus-1-dev \
|
|
libasound2-dev \
|
|
libssl-dev \
|
|
librsvg2-dev
|
|
|
|
- name: Install Rust
|
|
uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable
|
|
with:
|
|
components: clippy, rustfmt
|
|
|
|
- name: Rust cache
|
|
uses: swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2
|
|
with:
|
|
workspaces: Client/src-tauri
|
|
|
|
# Ahead of clippy: a formatting failure is cheap to produce and cheap to
|
|
# fix, and there is no reason to spend a clippy pass to surface one.
|
|
- name: Rustfmt check
|
|
run: cargo fmt --all -- --check
|
|
|
|
- name: Clippy lint (including test targets)
|
|
run: cargo clippy --all-targets -- -D warnings
|
|
|
|
- name: Rust unit tests
|
|
run: cargo test --lib
|
|
|
|
# Playwright e2e against the mocked-Tauri dev server. Runaway protection
|
|
# lives in playwright.config.ts (maxFailures: 20 aborts a systemic cascade
|
|
# early; globalTimeout: 20 min self-terminates with a usable report) with
|
|
# timeout-minutes below as the outer backstop.
|
|
#
|
|
# BLOCKING since 2026-08-05 (DC-07): the post-repair soak recorded green
|
|
# full-suite runs at 270, 276 and 291 tests across the 08-04/08-05 audit
|
|
# branches, and the one hard CI failure in that window was a real spec bug
|
|
# (updater install-settle race), which a non-blocking job would have let
|
|
# rot. retries: 2 absorbs the known rare flake class (see E2E-ISSUES.md's
|
|
# flake accounting).
|
|
# See docs/audit-test-coverage-2026-07-25.md T-2026-07-25-21.
|
|
# The native config (playwright.config.native.ts) is deliberately not wired
|
|
# up — it needs a real server and a built desktop binary.
|
|
client-e2e:
|
|
name: Client E2E (Playwright)
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 25
|
|
defaults:
|
|
run:
|
|
working-directory: Client/
|
|
steps:
|
|
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
|
|
|
|
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
|
with:
|
|
node-version: 24
|
|
cache: npm
|
|
cache-dependency-path: Client/package-lock.json
|
|
|
|
- name: Install npm dependencies
|
|
run: npm ci
|
|
|
|
- name: Install Playwright browser
|
|
run: npx playwright install --with-deps chromium
|
|
|
|
- name: Run Playwright tests
|
|
run: npx playwright test --config=playwright.config.ts
|
|
|
|
# Browser-mode unit tests (tests/browser/): real AudioContext + WASM
|
|
# behind the same Chromium, so the noise-suppression pipeline has a
|
|
# test that actually runs somewhere (test-audit 2026-08-19, T-22).
|
|
- name: Run browser-mode unit tests
|
|
run: npm run test:browser
|
|
|
|
- name: Upload Playwright report
|
|
if: always()
|
|
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
|
with:
|
|
name: playwright-report
|
|
path: |
|
|
Client/playwright-report/
|
|
Client/test-results/
|
|
retention-days: 7
|
|
|
|
# Admin-panel journey against a REAL server (no mocks): start-server.sh
|
|
# builds the Go binary and boots it with a fresh temp data dir, and the
|
|
# suite drives the embedded SPA through the first-run wizard, dashboard,
|
|
# channel CRUD, audit log and re-login — the one DC-04 surface the mocked
|
|
# suites cannot reach. Non-blocking while it earns its soak, same
|
|
# graduation convention client-e2e followed.
|
|
# GRADUATION CRITERION (recorded 2026-08-15): flip continue-on-error to
|
|
# false once the job has ~30 consecutive green runs on main with no
|
|
# infra-flake reruns — the same evidence bar client-e2e cleared (270+ green
|
|
# runs cited in docs/audit-2026-08-04-docs-and-coverage.md) scaled to this
|
|
# job's lower traffic. Check with: gh run list -w CI -b main --json
|
|
# conclusion | jq '[.[] | .conclusion] | index("failure")'.
|
|
admin-e2e:
|
|
name: Admin Panel E2E (real server, non-blocking)
|
|
runs-on: ubuntu-latest
|
|
continue-on-error: true
|
|
timeout-minutes: 20
|
|
defaults:
|
|
run:
|
|
working-directory: Client/
|
|
steps:
|
|
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
|
|
|
|
- uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5.6.0
|
|
with:
|
|
go-version: "1.26"
|
|
cache-dependency-path: Server/go.sum
|
|
|
|
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
|
with:
|
|
node-version: 24
|
|
cache: npm
|
|
cache-dependency-path: Client/package-lock.json
|
|
|
|
- name: Install npm dependencies
|
|
run: npm ci
|
|
|
|
- name: Install Playwright browser
|
|
run: npx playwright install --with-deps chromium
|
|
|
|
- name: Run admin-panel journey
|
|
run: npx playwright test --config=playwright.config.admin.ts
|
|
|
|
- name: Upload Playwright report
|
|
if: always()
|
|
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
|
with:
|
|
name: admin-e2e-report
|
|
path: |
|
|
Client/playwright-report/
|
|
Client/test-results/
|
|
retention-days: 7
|
|
|
|
# Blocking e2e subset: the parity-feature specs (tagged "@parity"), covering
|
|
# the wire paths added in v1.2.0 (mentions/badges, per-channel mute, NSFW
|
|
# gate, group DMs, role change, custom-emoji autocomplete, voice moderation).
|
|
# These are new and authored green, so unlike the full legacy suite above they
|
|
# gate PRs: a regression on one of these features must fail CI. Kept as its own
|
|
# job (not folded into the non-blocking suite) so the legacy suite can keep
|
|
# earning its "few green pushes" before it too graduates to blocking.
|
|
client-e2e-parity:
|
|
name: Client E2E (parity subset, blocking)
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 15
|
|
defaults:
|
|
run:
|
|
working-directory: Client/
|
|
steps:
|
|
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
|
|
|
|
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
|
with:
|
|
node-version: 24
|
|
cache: npm
|
|
cache-dependency-path: Client/package-lock.json
|
|
|
|
- name: Install npm dependencies
|
|
run: npm ci
|
|
|
|
- name: Install Playwright browser
|
|
run: npx playwright install --with-deps chromium
|
|
|
|
- name: Run parity e2e specs
|
|
run: npx playwright test --config=playwright.config.ts --grep "@parity"
|
|
|
|
- name: Upload Playwright report
|
|
if: always()
|
|
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
|
with:
|
|
name: playwright-report-parity
|
|
path: |
|
|
Client/playwright-report/
|
|
Client/test-results/
|
|
retention-days: 7
|
|
|
|
# Image build is verification only, so it is skipped on dev to keep day-to-day
|
|
# work on the fast check suite. Runs for main pushes and PRs targeting main.
|
|
server-docker-build:
|
|
name: Server Docker Build (verify)
|
|
if: github.ref_name == 'main' || github.base_ref == 'main'
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
|
|
|
|
- name: Set up Docker Buildx
|
|
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0
|
|
|
|
- name: Build image (no push)
|
|
uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6.19.2
|
|
with:
|
|
context: Server/
|
|
push: false
|
|
load: true
|
|
tags: owncord-smoke:candidate
|
|
build-args: VERSION=ci
|
|
cache-from: type=gha
|
|
cache-to: type=gha,mode=max
|
|
|
|
# Same script release.yml runs before it signs or pushes anything. A
|
|
# boot regression — or a bug in the smoke harness itself, as happened on
|
|
# the first v1.2.0-alpha.3 release run — must fail here, not at tag time.
|
|
- name: Boot-smoke Docker image
|
|
run: bash Server/scripts/docker-smoke.sh owncord-smoke:candidate
|
|
|
|
# Full Tauri build only on PRs to main (expensive: ~15 min x2 multiplier).
|
|
#
|
|
# Skipped for Dependabot: its PRs run under the separate `dependabot` secrets
|
|
# scope, so TAURI_SIGNING_PRIVATE_KEY arrives empty and `npm run tauri build`
|
|
# always aborts with "failed to decode secret key" while signing the updater
|
|
# artifact — after a successful compile and bundle. That burned ~50 min of
|
|
# runner time per dependency PR to produce a red check that never carried any
|
|
# signal. Granting Dependabot the signing key would fix the symptom but hands
|
|
# a release key to workflows triggered by third-party dependency updates.
|
|
#
|
|
# What still covers Dependabot PRs: the required `rust-tests` job compiles the
|
|
# crate (cargo clippy --all-targets + cargo test --lib), so a dependency bump
|
|
# that breaks the Rust build is still caught.
|
|
# What this gives up on those PRs: bundling (NSIS/AppImage/deb), Windows and
|
|
# ARM-specific compilation, and the `cargo audit` step below — that last one
|
|
# overlaps with Dependabot's own cargo scanning, which is what opens these PRs
|
|
# in the first place.
|
|
tauri-build:
|
|
name: Tauri Full Build (${{ matrix.os }})
|
|
needs: client-check
|
|
if: >-
|
|
github.event_name == 'pull_request'
|
|
&& github.base_ref == 'main'
|
|
&& github.actor != 'dependabot[bot]'
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
include:
|
|
- os: windows-latest
|
|
- os: ubuntu-22.04
|
|
- os: ubuntu-22.04-arm
|
|
runs-on: ${{ matrix.os }}
|
|
defaults:
|
|
run:
|
|
working-directory: Client/
|
|
steps:
|
|
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
|
|
|
|
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
|
with:
|
|
node-version: 24
|
|
cache: npm
|
|
cache-dependency-path: Client/package-lock.json
|
|
|
|
- name: Install Linux system dependencies
|
|
if: startsWith(matrix.os, 'ubuntu')
|
|
run: |
|
|
sudo apt-get update
|
|
sudo apt-get install -y \
|
|
libwebkit2gtk-4.1-dev \
|
|
libgtk-3-dev \
|
|
libayatana-appindicator3-dev \
|
|
libsecret-1-dev \
|
|
libdbus-1-dev \
|
|
libasound2-dev \
|
|
libssl-dev \
|
|
patchelf \
|
|
librsvg2-dev \
|
|
xdg-utils
|
|
|
|
- name: Install Rust
|
|
uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable
|
|
with:
|
|
components: clippy
|
|
|
|
- name: Rust cache
|
|
uses: swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2
|
|
with:
|
|
workspaces: Client/src-tauri
|
|
|
|
- name: Install npm dependencies
|
|
run: npm ci
|
|
|
|
- name: Clippy lint (Rust)
|
|
working-directory: Client/src-tauri/
|
|
run: cargo clippy -- -D warnings
|
|
|
|
# Rust unit tests moved to the standalone `rust-tests` job so they run on
|
|
# every event, not just PRs to main.
|
|
|
|
- name: Security audit (Rust dependencies)
|
|
working-directory: Client/src-tauri/
|
|
run: |
|
|
cargo install cargo-audit@0.22.1 --quiet
|
|
cargo audit
|
|
|
|
- name: Build Tauri app
|
|
env:
|
|
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
|
|
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
|
|
run: npm run tauri build
|