Skip to content

feat: a11y route announcer, log redaction, OpenAPI route drift check, compose integration CI (#726, #746, #748, #753) - #873

Merged
devJaja merged 8 commits into
Epta-Node:mainfrom
ExcelDsigN-tech:feat/a11y-contract-redaction-compose-ci-l7140q
Oct 20, 2026
Merged

devJaja merged 8 commits into
Epta-Node:mainfrom
ExcelDsigN-tech:feat/a11y-contract-redaction-compose-ci-l7140q

Conversation

@ExcelDsigN-tech

Copy link
Copy Markdown
Contributor

Summary

Closes #726
Closes #746
Closes #748
Closes #753

This PR covers four issues, each in its own commit. Getting #753 working turned up some things that were already broken on main. Those fixes are in a separate fix: commit, listed at the end.

Changes

#726 Accessibility: skip link, focus and live announcements

  • When you navigate within the app, AppShell moves focus to <main> and announces "Navigated to {page}" in a polite live region.
    • A fresh page load keeps the browser's own focus order, even under StrictMode.
    • Switching language is not treated as a navigation.
  • The toast container is now the only live region; individual toasts no longer nest aria-live.
  • The task detail page announces task and stream status changes.
  • New a11y.navigatedTo string in en, es and zh.
  • New Playwright spec frontend/tests/e2e/a11y-skip-link.spec.ts covers:
    • the skip link
    • focus moving to <main> on route change
    • focus left alone on first load

#746 Log redaction for secrets, tokens and API keys

  • Signature, JWT and credential keys are redacted.
  • Prompt, completion and request/response bodies become [REDACTED_BODY].
  • JWTs are scrubbed out of free-text strings.
  • req/res serializers now log only these fields: id, method, path (query string dropped), ip and statusCode.
  • logger.child() bindings are sanitized. Pino serializes them once and never passes them through logMethod or formatters.bindings, so a secret bound to a child logger used to be written as-is on every line.
  • Canary test logger.redaction.test.ts logs a known secret through every logging path and fails if it reaches the output.
  • The redaction layers are documented in docs/SECURITY_CHECKLIST.md.

#748 Contract-test live routes against the OpenAPI spec in CI

  • New backend/tests/openapiRouteDiff.test.ts walks the live Express router and fails with a two-sided diff:
    • routes the app serves that the spec doesn't document
    • routes the spec documents that the app doesn't serve
  • It runs in npm run test:contract, which CI already calls.
  • The existing response-contract suite (feat(backend): Add automated OpenAPI contract tests that validate every route against its spec schema #815) now sends requests to app.httpServer. createApp() returns a handle, not an Express app, so before this every request threw app.address is not a function.

#753 docker-compose integration profile for full-stack CI

  • docker-compose.yml gets a test profile with a smoke runner service.

  • The compose file itself is fixed:

    • The Stellar healthcheck grepped for text Horizon never returns, so the node never became healthy.
    • DATABASE_URL was a sqlite:// URL, which resolveDatabasePath() rejects.
    • The volume path was wrong.
    • The backend now sets STELLAR_NETWORK=local.
  • tests/e2e/compose-smoke.mjs checks, in order:

    1. backend liveness
    2. agent registration with a signed challenge
    3. task submission
    4. the first SSE event

    It uses Node built-ins only, so there is no install step.

  • New integration-compose CI job:

    • builds the backend image with a buildx layer cache
    • runs the smoke suite
    • checks the backend exits 0 on SIGTERM
    • uploads the compose logs if anything fails

    It has a 20 min timeout, only runs when a paths filter matches, and is part of ci-gate.

  • Documented in tests/e2e/README.md.

Fixes for things already broken on main

The issues above needed these to work:

  • Backend Dockerfile:
    • It ran npm install --workspace=backend, but the repo has no workspaces. It now installs from backend/package-lock.json.
    • Moved to Node 24, since better-sqlite3@13 needs Node 22 or later.
    • Uses --ignore-scripts, because the prebuilt binaries ship in the package.
    • Gives the non-root user a writable data directory.
  • .dockerignore: added, so host node_modules (glibc builds) never get into the Alpine image.
  • config/index.ts: package.json is now imported, so tsc emits dist/package.json. Before this, npm start after npm run build crashed.
  • copy-migrations.js: it copied SQL to dist/db/migrations, but the compiled loader reads dist/src/db/migrations.
  • Horizon and Soroban RPC clients: they now allow http:// only when STELLAR_NETWORK=local, so the backend can reach the compose standalone node. TLS is still required everywhere else.

Verification & Testing

  • Backend npx jest --runInBand, compared with main (at 1d5ccc6, before the latest upstream merge) on the same machine:
    • main: 138 failing, 2152 passing
    • this branch: 132 failing, 2189 passing
    • The same 30 suites fail on both, and no test that passes on main fails here.
  • Canary test logger.redaction.test.ts: 29/29 pass.
  • Route drift test openapiRouteDiff.test.ts: passes.
  • Frontend unit tests for the touched files: no new failures compared with main.
  • Playwright a11y-skip-link.spec.ts: 3/3 pass, stable across repeated runs, including after merging the latest main.
  • docker compose --profile test up --exit-code-from smoke smoke: passes in about 22s against real containers. docker compose stop backend then exits 0 after a clean graceful shutdown.
  • Backend tsc --noEmit is clean. Frontend has the same 107 type errors as current main, and none are new.
  • cargo test / cargo fmt / clippy: N/A, no contract changes.

Contributor Checklist

  • PR title follows Conventional Commits
  • All CI checks pass locally. Not all of them, and these failures are already on main:
  • Documentation updated (docs/SECURITY_CHECKLIST.md, tests/e2e/README.md)
  • No secrets or credentials committed

Follow-ups (not in this PR)

SUPPORTED_LANGUAGES includes 'es' but LANGUAGE_OPTIONS did not, so the
language switcher read undefined.nativeName and every in-app route fell
into the error boundary.
…ode#726)

- AppShell moves focus to <main> on client-side navigation and announces
  the new page title in a polite live region; a fresh load keeps the
  browser's own focus order
- Toast container is the single live region (no nested aria-live)
- Task detail page announces task and stream status changes
- a11y.navigatedTo string in en, es, zh
- Playwright spec covering the skip link and route focus
…#746)

- Redact signature, jwt and credential keys; replace prompt, completion
  and request/response bodies with [REDACTED_BODY]; scrub JWTs from
  free-text strings
- Whitelisting req/res serializers (id, method, path, ip, statusCode)
- Sanitize logger.child() bindings, which pino serializes once and never
  passes through logMethod or formatters.bindings
- Canary test logs a known secret through every path and fails if it
  reaches the output
- Document the redaction layers in SECURITY_CHECKLIST.md
…de#748)

openapiRouteDiff.test.ts walks the live Express router tree and fails
with a two-sided diff (served but undocumented / documented but not
served). It runs in npm run test:contract, which CI already calls.

Also point the response-contract suite at app.httpServer: createApp()
returns a handle, not an Express app, so every request threw
'app.address is not a function'.
- Dockerfile: install from backend's own lockfile (the repo is not an npm
  workspace), Node 24 (better-sqlite3 13 needs >= 22), skip install
  scripts (prebuilds ship in the package), writable data dir for the
  non-root user
- .dockerignore keeps host node_modules and dist out of the context
- Import package.json so tsc emits it next to dist/src; the bare require
  only resolved from src/
- copy-migrations.js copies SQL to dist/src/db/migrations, where the
  compiled loader looks
- Horizon and Soroban RPC clients allow http only when STELLAR_NETWORK
  is local, so the backend can reach the compose standalone node
- docker-compose.yml test profile adds a smoke runner; fix the Stellar
  healthcheck (it grepped for text Horizon never returns), the SQLite
  path and volume, and set STELLAR_NETWORK=local for the backend
- tests/e2e/compose-smoke.mjs: liveness, signed agent registration, task
  submit, first SSE event; Node built-ins only
- integration-compose CI job: buildx-cached backend image, smoke suite,
  graceful shutdown check, compose logs uploaded on failure, 20 min
  timeout, gated by a compose paths filter and wired into ci-gate
- Document the profile in tests/e2e/README.md
Resolve docker-compose.yml (keep the test smoke profile and the new
monitoring profile) and payment.ts imports (toW3cTraceId plus
stellarClientOptions).
Resolve config/index.ts (keep the package.json import and
stellarClientOptions, in upstream's single-quote style) and
reconciliation.ts imports (fetchWithTimeout plus stellarClientOptions).
@drips-wave

drips-wave Bot commented Oct 20, 2026

Copy link
Copy Markdown

@ExcelDsigN-tech Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

@vercel

vercel Bot commented Oct 20, 2026

Copy link
Copy Markdown

@ExcelDsigN-tech is attempting to deploy a commit to the Jaja's projects Team on Vercel.

A member of the Team first needs to authorize it.

@devJaja
devJaja merged commit ac6fa93 into Epta-Node:main Oct 20, 2026
12 of 25 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants