ci/woodpecker/pr/ci Pipeline was successful
Round-two remediation of the four blockers gate-ultron-01 raised onf8d04d1b. All four were confirmed by my own measurement before being touched; none is taken on the reviewer's word. 1. The --config/-K refusal never ran. It sat nested inside `if API_SHAPED`, and API_SHAPED is a test for a provider URL in the command text — which is exactly what a config file removes. The check was guarded by the condition that the capability it guards against defeats, so `curl --config /tmp/write.cfg` walked past it. It now keys on curl itself, ahead of the URL gate, and covers the attached (`-K/tmp/f`) and bundled (`-sK`) spellings a space-separated test cannot see. 2. Percent-encoded endpoints are a live route, not a theoretical one. Measured against the provider: `…/issues/1174` and `…/iss%75es/1174` both return HTTP 200 for the same object. A write carrying any percent-escape is now refused rather than decoded — a decoder has to be exactly right about depth (%2569 -> %69 -> i) and about the provider's own normalisation, and being approximately right there is indistinguishable from not checking. Scoped to writes: a read is never this hook's business and a query string carrying %20 is an ordinary URL. 3. HOME was still expanded unguarded at the `W=` fallback, which runs before any of the new HOME adjudication — so a guard deployed without its siblings still died on an unset HOME, upstream of the fix that was supposed to survive it. Moving a fail-open earlier in the file is not closing it. HOME is now resolved once, above every use, and every later site reads the resolved value. The existing harness could not have caught this: it runs the guard beside its siblings, so `[ -x "$W/pr-review.sh" ]` always succeeded and the fallback was never reached. A test's blind spot can be a property of the harness rather than of the code. The new lone_case() block copies the guard alone into an empty directory and re-asserts the four behaviours there. 4. test-mosaic-worktree-large-repo.sh shipped at mode 100644 and appeared in no CI step, so the enumeration guard (#1017) redded pipeline 2386 — correctly. Committed mode is now 100755 and the test is enumerated in the sanitization step. My own process miss: I verified the CI queue before pushing and never verified terminal CI after. Controls: the 25 fixtures added here all FAIL against029af418(rc 0 or 1 where 2 is required) and all pass at this head, 143/143.
164 lines
7.6 KiB
YAML
164 lines
7.6 KiB
YAML
# &node_image is the pre-baked CI base built by .woodpecker/ci-image.yml:
|
|
# node:24-alpine + python3/make/g++/postgresql-client + pnpm + a warm pnpm
|
|
# store. The install step resolves from the baked store (--prefer-offline)
|
|
# instead of paying a ~731s cold fetch + native compile every run.
|
|
variables:
|
|
- &node_image 'git.mosaicstack.dev/mosaicstack/stack/ci-base:latest'
|
|
- &enable_pnpm 'corepack enable'
|
|
|
|
when:
|
|
# PR + manual CI run on any branch — the pull_request pipeline is the merge gate.
|
|
# push CI is restricted to protected branches (main) so a feature-branch push no
|
|
# longer fires a redundant SECOND pipeline alongside its PR pipeline. This ~halves
|
|
# CI load on the storage-constrained runner with zero loss of gating (branch
|
|
# protection requires no push/ci status context; main still gets full push CI).
|
|
- event: [pull_request, manual]
|
|
- event: push
|
|
branch: main
|
|
|
|
# Turbo remote cache (turbo.mosaicstack.dev) is configured via Woodpecker
|
|
# repository-level environment variables (TURBO_API, TURBO_TEAM, TURBO_TOKEN).
|
|
# This avoids from_secret which is blocked on pull_request events.
|
|
# If the env vars aren't set, turbo falls back to local cache only.
|
|
|
|
steps:
|
|
install:
|
|
image: *node_image
|
|
commands:
|
|
- corepack enable
|
|
# python3/make/g++ are baked into ci-base; --prefer-offline resolves from
|
|
# the baked pnpm store.
|
|
- pnpm install --frozen-lockfile --prefer-offline
|
|
|
|
# Blocking gate: public framework package must contain no operator-specific
|
|
# personal data or private $HOME defaults. Runs early (no node_modules needed).
|
|
sanitization:
|
|
image: *node_image
|
|
commands:
|
|
- apk add --no-cache bash
|
|
- bash packages/mosaic/framework/tools/quality/scripts/verify-sanitized.sh
|
|
# Resident line-count ceiling over framework-owned resident files
|
|
# (Constitution + dispatcher + each RUNTIME.md slice). See DESIGN §7 / R9.
|
|
- bash packages/mosaic/framework/tools/quality/scripts/check-resident-budget.sh --self-test
|
|
- bash packages/mosaic/framework/tools/quality/scripts/check-resident-budget.sh
|
|
# Test-membership guard (#1017): also first link of test:framework-shell.
|
|
# Invoked from BOTH surfaces it audits (F2, PR #1018) — the guard is link
|
|
# [0] of the pnpm chain, so severing that chain would silence it together
|
|
# with everything it guards; this direct line keeps one instrument running.
|
|
- bash packages/mosaic/framework/tools/quality/scripts/check-test-enumeration.sh
|
|
# Tool-index gate: a shipped wrapper that appears in no resident index doc
|
|
# is undiscoverable from inside a session, and an agent that cannot learn a
|
|
# wrapper exists reaches for raw curl instead — which is how a Gitea review
|
|
# got filed PENDING three times. Ships-and-documented is one commit, or red.
|
|
- bash packages/mosaic/framework/tools/quality/scripts/check-tools-index.sh --self-test
|
|
- bash packages/mosaic/framework/tools/quality/scripts/check-tools-index.sh
|
|
# Hermetic regression for issue-close.sh (#1081): mocks tea/curl onto PATH
|
|
# and sandboxes a throwaway git repo, so it resolves no real credentials and
|
|
# joins CI directly rather than the exclusions file.
|
|
- bash packages/mosaic/framework/tools/git/test-issue-close-fail-closed.sh
|
|
# Hermetic behavioural regression for the PreToolUse wrapper guard: proves
|
|
# it still blocks the three mistakes AND still lets reads, unwrapped
|
|
# endpoints and ordinary commands through. Both directions are asserted —
|
|
# a guard that over-blocks gets routed around, which fails just as hard.
|
|
- bash packages/mosaic/framework/tools/git/test-wrapper-guard.sh
|
|
# Hermetic regression for mosaic-worktree.sh at fleet scale: stubs git onto
|
|
# PATH so `list` faces ~450 KB of porcelain. The defect it pins is invisible
|
|
# at small size — `git … | awk '…exit'` gives the producer SIGPIPE, which
|
|
# under `set -euo pipefail` aborts the caller silently with rc=141 and no
|
|
# output. A repo only reaches that once it has enough worktrees, so the
|
|
# stub supplies the scale instead of the host's own checkout.
|
|
- bash packages/mosaic/framework/tools/git/test-mosaic-worktree-large-repo.sh
|
|
|
|
# Blocking gate (#791): a framework upgrade must never write or delete an
|
|
# operator-owned path. The HARD GATE proves an unanticipated operator sentinel
|
|
# survives a keep-mode reseed byte-identical (with rsync present AND absent —
|
|
# keep mode is a single cp-based path that must not depend on rsync), and that a
|
|
# corrupt/empty/missing manifest aborts fail-closed leaving operator files
|
|
# untouched (B2/B3). The rollback gate proves a mid-sync failure is rolled back
|
|
# from the pre-update snapshot (B1). The durable-snapshot gate (#791 PR2) proves
|
|
# the retained, operator-scoped pre-update backup is taken before any mutation
|
|
# (0700/0600, secret never logged, retention-pruned) and that the post-sync
|
|
# verify net restores any operator file a manifest bug lets the sync touch. The
|
|
# migration matrix pins the v2→v3 contract-file semantics. Pure bash, no
|
|
# node_modules — runs early alongside sanitization.
|
|
upgrade-guard:
|
|
image: *node_image
|
|
commands:
|
|
- apk add --no-cache bash rsync
|
|
- bash packages/mosaic/framework/tools/quality/scripts/test-upgrade-manifest-guard.sh
|
|
- bash packages/mosaic/framework/tools/quality/scripts/test-upgrade-rollback.sh
|
|
- bash packages/mosaic/framework/tools/quality/scripts/test-upgrade-durable-snapshot.sh
|
|
- bash packages/mosaic/framework/tools/quality/scripts/test-install-migration.sh
|
|
|
|
typecheck:
|
|
image: *node_image
|
|
commands:
|
|
- *enable_pnpm
|
|
- pnpm typecheck
|
|
depends_on:
|
|
- install
|
|
- sanitization
|
|
- upgrade-guard
|
|
|
|
# lint, format, and test are independent — run in parallel after typecheck
|
|
lint:
|
|
image: *node_image
|
|
commands:
|
|
- *enable_pnpm
|
|
- pnpm lint
|
|
depends_on:
|
|
- typecheck
|
|
|
|
format:
|
|
image: *node_image
|
|
commands:
|
|
- *enable_pnpm
|
|
- pnpm format:check
|
|
depends_on:
|
|
- typecheck
|
|
|
|
test:
|
|
image: *node_image
|
|
environment:
|
|
# Avoid the namespace-level Woodpecker DB service named "postgres".
|
|
# The Kubernetes backend exposes service containers by step name.
|
|
DATABASE_URL: postgresql://mosaic:mosaic@ci-postgres:5432/mosaic
|
|
commands:
|
|
- *enable_pnpm
|
|
# openssl (#912) is the wake HMAC signer: the digest H1/H2, beacon B12,
|
|
# and install I8 legs hard-require it in CI. It is baked into ci-base via
|
|
# Dockerfile.ci, but ci-base only rebuilds on push-to-main/tag — this
|
|
# `apk add` guarantees openssl is present on PR pipelines too (and is a
|
|
# fast no-op once the rebuilt image already ships it).
|
|
- apk add --no-cache openssl
|
|
# postgresql-client (pg_isready) is baked into ci-base.
|
|
# Wait up to 60s for CI postgres to be ready; fail fast if it never comes up.
|
|
- |
|
|
ready=0
|
|
for i in $(seq 1 60); do
|
|
if pg_isready -h ci-postgres -p 5432 -U mosaic; then
|
|
ready=1
|
|
break
|
|
fi
|
|
echo "Waiting for ci-postgres ($i/60)..."
|
|
sleep 1
|
|
done
|
|
if [ "$ready" -ne 1 ]; then
|
|
echo "ci-postgres did not become ready" >&2
|
|
exit 1
|
|
fi
|
|
# Run migrations (DATABASE_URL is set in environment above)
|
|
- pnpm --filter @mosaicstack/db run db:migrate
|
|
# Run all tests
|
|
- pnpm test
|
|
depends_on:
|
|
- typecheck
|
|
|
|
services:
|
|
ci-postgres:
|
|
image: pgvector/pgvector:pg17
|
|
environment:
|
|
POSTGRES_USER: mosaic
|
|
POSTGRES_PASSWORD: mosaic
|
|
POSTGRES_DB: mosaic
|