Post-merge audit of #3272 found two regressions:
1. ListOAuthRefreshCandidates used "AND NOT (a AND b)" which, under PG
3-valued logic, evaluates to NULL when both temp_unschedulable_until
and temp_unschedulable_reason are NULL — i.e., the common healthy
account state. Such rows were silently excluded from the background
token refresh worker, so their OAuth access tokens would never get
refreshed and eventually start returning 401.
Verified empirically against PostgreSQL: only 3 of 5 test rows
matched before the fix; after switching to "(a AND b) IS NOT TRUE"
the expected 4 rows match.
2. The new ListOAuthRefreshCandidates method on AccountRepository was
not implemented on stubAccountRepo in api_contract_test.go (build
tag "unit"), breaking "make test-unit" which CI runs in
.github/workflows/backend-ci.yml.
Tests:
- Added IS NOT TRUE and "AND NOT (" assertions to the SQL-shape unit
test so the predicate can't regress to the broken form again.
- "go test -tags=unit ./internal/..." now passes cleanly.
fix: cleanup consumed scheduler outbox rows / 添加 scheduler_outbox 表的清理代码,避免表过大
Additional hardening: WHERE clause adds a 10s grace via
created_at < NOW() - INTERVAL '10 seconds' to defend against the PG
sequence-id vs commit race (id assigned in tx, commit delayed past
watermark advance). Without it, slow committers could lose their
outbox row before the snapshot poller reads it.
PG sequences advance outside transactions, so a slow committer can hold
an id that gets surpassed by the watermark before its commit becomes
visible. Without the grace period, cleanup would delete such rows before
the snapshot poller ever sees them. 10s is a comfortable upper bound
on realistic enqueueSchedulerOutbox commit latency.
Resolves GHSA-hmw2-7cc7-3qxx (CRLF injection) flagged by
frontend-security CI. axios pulls form-data ^4.0.5 which locked to the
vulnerable 4.0.5; override forces all transitive consumers to 4.0.6+
without needing an audit exception.
LegalDocumentView.vue (admin-compliance acknowledgement gate) build-time
imports ../../../../docs/legal/*.md?raw. The Docker image build broke
because the frontend-builder stage only COPYs frontend/ (never docs/) and
.dockerignore excludes both docs/ and *.md from the build context.
Upstream CI runs `pnpm build` from the repo root (docs/ resolvable via
../docs/) and never exercises the Docker path, so this stayed hidden until
the buildkit package job surfaced "Could not resolve docs/legal/...md?raw".
Fix:
- COPY docs/legal/ into /app/docs/legal in Dockerfile and deploy/Dockerfile
so it sits beside /app/frontend (WORKDIR), matching the relative import.
Only the required subtree is copied to keep the build dependency minimal.
- Re-include docs/legal/*.md in .dockerignore so buildkit ships the subtree.
apiToForm read bedrock_cc_compat as Record<string, boolean> (nested map)
but formToAPI saves it as a plain bool. Reading true["anthropic"] returns
undefined, so the toggle always appeared off after save.
Align the read path with the write path: fc?.bedrock_cc_compat === true.