All checks were successful
ci/woodpecker/pr/ci Pipeline was successful
Make the wake digest/HMAC suite RUN and PASS in the real Woodpecker CI
runner (Alpine/musl, root), then hard-require the HMAC legs in CI.
Root cause of the runner-only D1/D4/D5/D6 failures (a masked-local run
passed, so it was runner-specific): a TOOLCHAIN divergence, not locale or
root. digest.sh's _scrub_ctrl used GNU-sed `\xNN` hex-escape byte matching.
The CI runner is node:24-alpine, whose sed is BusyBox — BusyBox sed REJECTS
a `\xNN` character range ("bad regex ... Invalid character range"), aborting
the whole scrub sed and silently VOIDING the scrub. Every scrubbed value
collapsed to empty, cascading into D1 (blank locators), D4 (no scrub/redact,
SHA blanked), D5 (blank agent prefix), D6 (blank [digest] class). Confirmed
by reproducing the exact 9-assertion failure in the ci-base image as root.
Fix (at the correct layer — a wake digest must render identically on any
runner):
- digest.sh _scrub_ctrl: patterns are now LITERAL bytes (printf %b), matching
byte-identically under GNU sed (glibc dev) and BusyBox sed (Alpine CI).
Verified identical output on both. Contract preserved: two-tier trust,
exit-4 hard-locator FAIL-LOUD, secret-scrub, and 40-hex SHA preservation
all unchanged — deterministic, not weakened.
- test-wake-digest-hmac.sh D4: replaced PCRE `grep -qP` (BusyBox grep has no
-P; the `&&` silently skipped the check in CI) with portable literal-byte
`grep -E` ranges (two disjoint bidi/zero-width ranges, excluding legit
U+2014 em-dash).
CI enablement:
- Dockerfile.ci + .woodpecker/ci.yml test step: add openssl (the non-circular
HMAC signer) so H1/H2, beacon B12, install I8 can run. The apk add in the
test step covers PR pipelines before ci-base rebuilds.
- Flip the 3 openssl skip-guards (digest whole-file, beacon B12, install I8)
to HARD-REQUIRE openssl when CI is set (Woodpecker CI=woodpecker) and FAIL
loud if absent; KEEP the skip for openssl-less local dev.
- manifest.txt: wake 0.6.2 -> 0.6.3 (digest.sh scrub portability; precedent).
Red-first verified in the ci-base container (root): D4 catches a broken
redaction, H1 catches a tamper that doesn't break the MAC, B12/I8 catch a
corrupted signer.
Closes #912
Part of #892
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0158NZqN2n2ymKFeJAZ4GUCb
137 lines
5.4 KiB
YAML
137 lines
5.4 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
|
|
|
|
# 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
|