Skip to content

fix(ci): restore apps/web/vercel.json as Vercel SSOT (H7 hot-fix)#1600

Merged
Skords-01 merged 1 commit into
mainfrom
devin/1777883503-fix-vercel-build
May 4, 2026
Merged

fix(ci): restore apps/web/vercel.json as Vercel SSOT (H7 hot-fix)#1600
Skords-01 merged 1 commit into
mainfrom
devin/1777883503-fix-vercel-build

Conversation

@Skords-01
Copy link
Copy Markdown
Owner

@Skords-01 Skords-01 commented May 4, 2026

Summary

Hot-fix for the production-Vercel build break introduced by the H7 closure in #1595. Vercel project's Root Directory is apps/web, not / — so deleting apps/web/vercel.json silently removed the installCommand / buildCommand Vercel was actually reading, and post-merge production deploys started failing with rolldown's cannot resolve @sergeant/db-schema/sqlite/migrations error.

This PR swaps the H7 SSOT to the file Vercel actually reads:

  • Restore apps/web/vercel.json with the merged headers contract (COOP/COEP, CSP-Report-Only pointed at api.sergeant.app/api/csp-report from C2, Permissions-Policy, well-known caching) and outputDirectory: "dist" (relative to Root Directory).
  • Delete root vercel.json — it was never read by Vercel (Root Directory ≠ /) and only created the silent-drift exposure that H7 was trying to close in the first place.
  • Invert scripts/check-vercel-config.sh: now fails if any vercel.json exists outside apps/web/, and fails if apps/web/vercel.json is missing.
  • Refresh docs/deploy/vercel.md: out-of-repo settings table now lists Root Directory = apps/web (was /); Output Directory = dist; incident playbook step 1 calls out the rolldown failure mode that took prod down on 2026-05-04.
  • Append incident log to docs/security/hardening/H7-vercel-config-drift.md documenting the wrong-SSOT decision in feat(server): security hardening — close H2 + H7 + ship C2 Phase 1 (CSP report sink) #1595 and the live-rollback in this PR.

Governing Skill

  • Primary skill: sergeant-deploy-and-observability
  • Secondary skill (if truly needed): sergeant-bugfix-and-regression

Playbook

Verification

bash scripts/check-vercel-config.sh
# → vercel.json: OK (single source of truth at apps/web/vercel.json)

python3 -c "import json; json.load(open('apps/web/vercel.json'))"
# → no exception (valid JSON)

Post-merge verification (will confirm in the PR thread):

  • Vercel preview status flips from failure to success on this PR's HEAD.
  • curl -sI <preview-url> | grep -i 'cross-origin-opener-policy' returns same-origin.
  • curl -sI <preview-url> | grep -i 'content-security-policy-report-only' contains report-uri https://api.sergeant.app/api/csp-report.

Additional checks:

  • Local smoke / manual validation completed (bash scripts/check-vercel-config.sh green, JSON valid)
  • Surface-specific checks completed (Vercel-bot comment metadata verified Root Directory = apps/web)

Docs and Governance

  • I updated docs that changed with the behavior, contract, workflow, or rollout.
  • I checked whether AGENTS.md needed an update — no policy change.
  • I checked whether a playbook or skill needed an update — none required beyond the docs already touched.
  • I checked whether governance docs or review docs needed an update — n/a.

Updated docs:

  • docs/deploy/vercel.md — SSOT path, Vercel project settings table (Root Directory + Output Directory corrected), incident playbook.
  • docs/security/hardening/H7-vercel-config-drift.md — incident log entry for 2026-05-04 wrong-SSOT decision, corrected Verification + Recommendation sections, status note clarified.

Risk and Rollout

  • User-visible risk: none — headers contract is identical (CSP, COOP, COEP, Permissions-Policy, cache-control, well-known mime types all preserved); apps/web/vercel.json adds back what Vercel was already using before feat(server): security hardening — close H2 + H7 + ship C2 Phase 1 (CSP report sink) #1595 plus the COOP/COEP that lived only in the dead root copy until now.
  • Rollout / deploy order: merge → Vercel auto-deploys preview → confirm success status → main is live again.
  • Backout plan: revert this PR; Vercel falls back to the broken state of 849e3b62. Do not revert without a follow-up that re-restores apps/web/vercel.json from f912ed6b plus the C2 report-uri change.

Hard Rule #15

  • I read AGENTS.md before coding.
  • Internal docs I touched are in Ukrainian — n/a, the touched docs (docs/deploy/vercel.md, hardening card H7) were already in English per existing convention; no Ukrainian-language doc touched.
  • I did not use --no-verify.

Reviewer Notes

  • The H7 card's previous "Recommendation" recommended keeping the root vercel.json. That was based on a misread of the Vercel UI (Root Directory was assumed /, actually apps/web). The correction is documented in the H7 implementation log and the new "Decision (2026-05-04)" line.
  • The check-script inversion changes which file is "allowed" — confirm in the diff that apps/web/vercel.json is now the only allowed path.
  • After merge, please verify the Vercel UI Root Directory is still apps/web (it should not have changed; this PR depends on that invariant).

Summary by cubic

Restore apps/web/vercel.json as Vercel’s single source of truth to match the project Root Directory and fix the production build failure (“cannot resolve @sergeant/db-schema/sqlite/migrations”). No user-visible changes; security headers remain the same and deploys should pass again.

  • Bug Fixes
    • Restored apps/web/vercel.json with merged headers and outputDirectory: "dist"; kept installCommand/buildCommand that pre-build @sergeant/db-schema then @sergeant/web.
    • Removed root vercel.json (unused by Vercel).
    • Inverted scripts/check-vercel-config.sh to allow only apps/web/vercel.json and fail on any other or if missing; updated docs (docs/deploy/vercel.md, docs/security/hardening/H7-vercel-config-drift.md) to reflect Root Directory = apps/web.

Written for commit 6119612. Summary will update on new commits.

Summary by CodeRabbit

Release Notes

  • Chores

    • Updated Vercel deployment configuration output directory path.
    • Implemented centralized Vercel configuration enforcement with automated CI validation to prevent configuration inconsistencies.
  • Documentation

    • Updated deployment and security documentation to reflect current Vercel configuration structure and validation procedures.

…= apps/web)

H7 closure in #1595 deleted apps/web/vercel.json on the wrong assumption
that the Vercel project's Root Directory was '/'. Real Root Directory is
apps/web (visible in Vercel-bot PR comment metadata: rootDirectory:apps/web).
Vercel reads vercel.json from the configured Root Directory only — so the
deletion silently dropped installCommand / buildCommand that pre-build
@sergeant/db-schema, and post-merge Vercel deploys started failing with
rolldown 'cannot resolve @sergeant/db-schema/sqlite/migrations'.

This PR swaps the SSOT to the file Vercel actually reads:

- Restore apps/web/vercel.json with the merged headers contract (COOP/COEP,
  CSP-Report-Only pointed at api.sergeant.app/api/csp-report from C2,
  Permissions-Policy, well-known caching) and outputDirectory: 'dist'
  (relative to Root Directory).
- Delete root vercel.json — it was never read by Vercel and only created
  the silent-drift exposure described in H7.
- Invert scripts/check-vercel-config.sh: it now fails if any vercel.json
  exists outside apps/web/, and fails if apps/web/vercel.json is missing.
- Refresh docs/deploy/vercel.md: SSOT path, out-of-repo Vercel settings
  table now lists Root Directory = apps/web (was '/'), incident playbook
  step 1 calls out the rolldown failure mode.
- Append incident to docs/security/hardening/H7-vercel-config-drift.md
  with the 2026-05-04 wrong-SSOT log entry and corrected verification.

Production-incident remediation, no behavioural change to end users.

Co-Authored-By: Вася Пупкін <steppupa@gmail.com>
@vercel
Copy link
Copy Markdown
Contributor

vercel Bot commented May 4, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
sergeant Ready Ready Preview, Comment May 4, 2026 8:34am

Request Review

@github-actions github-actions Bot added the size/M label May 4, 2026
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 4, 2026

📝 Walkthrough

Walkthrough

This PR consolidates Vercel configuration to a single source of truth at apps/web/vercel.json by removing the split config across the repo root, updating the outputDirectory to a relative path, enforcing the location via CI, and documenting the consolidation and related security hardening.

Changes

Vercel Configuration Consolidation

Layer / File(s) Summary
Config Update
apps/web/vercel.json
outputDirectory changes from "apps/web/dist" to "dist" (relative to Vercel Root Directory apps/web).
CI Enforcement
scripts/check-vercel-config.sh
Script updated to enforce single SSOT at apps/web/vercel.json, failing if any other vercel.json files exist outside that location. Error messages and checks now reference the enforced path.
Deployment Documentation
docs/deploy/vercel.md
Vercel project settings, headers contract, and incident playbook updated to reflect apps/web as Root Directory, dist as output, and CI enforcement via check script. Cross-references align to hardening and integration docs.
Security Hardening
docs/security/hardening/H7-vercel-config-drift.md
New/updated document stating apps/web/vercel.json is live SSOT, root vercel.json is dead code, and documents impact, recommendations, and CI guard requirements with concrete bash script snippet.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Poem

🐰 Hops through configs, one path stays true,
Apps-web holds the SSOT view,
No more splits, no more drift,
CI guards each script and gift,
Config peace: a carrot-earned gift!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: restoring apps/web/vercel.json as the Vercel single source of truth (SSOT) and addressing the H7 hardening requirement, which is the primary objective across all modified files.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch devin/1777883503-fix-vercel-build

Review rate limit: 7/10 reviews remaining, refill in 16 minutes and 53 seconds.

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/deploy/vercel.md`:
- Around line 65-69: Update the docs/deploy/vercel.md header-scope description
to match the actual config: state that cross-cutting headers (CSP, COOP/COEP,
Permissions-Policy, Referrer-Policy) are defined in apps/web/vercel.json under
headers[*] with the source pattern "/((?!\\.well-known).*)" (not "/(.*)"), and
keep the note that path-scoped headers (e.g. cache-control on /assets/*,
well-known mime types) get their own block; reference the exact source pattern
string and the headers[*] blocks to ensure the wording aligns with the live
config.

In `@docs/security/hardening/H7-vercel-config-drift.md`:
- Line 4: Replace the status badge line that currently reads "> **Status:**
Closed (2026-05-04 — SSOT at `apps/web/vercel.json` + CI guard, after
live-rollback of an incorrect SSOT choice)." with one of the allowed badge
values: Active, Scaffolded, Deprecated, or Archived (e.g., "> **Status:**
Archived"), and move the closure details ("2026-05-04 — SSOT at
`apps/web/vercel.json` + CI guard, after live-rollback of an incorrect SSOT
choice") into the prose or a table elsewhere in the document so the badge
strictly uses an allowed value.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 2996af62-61a2-45e7-8fcc-61234a77eec0

📥 Commits

Reviewing files that changed from the base of the PR and between 9a8a458 and 6119612.

📒 Files selected for processing (4)
  • apps/web/vercel.json
  • docs/deploy/vercel.md
  • docs/security/hardening/H7-vercel-config-drift.md
  • scripts/check-vercel-config.sh

Comment thread docs/deploy/vercel.md
Comment on lines +65 to +69
Every header that ships to browsers from `apps/web` is defined in
`apps/web/vercel.json` `headers[*]` blocks. Cross-cutting headers (CSP,
COOP/COEP, Permissions-Policy, Referrer-Policy) live under `source: "/(.*)"`.
Path-scoped headers (e.g. cache-control on `/assets/*`, well-known mime types)
get their own block.
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Header-scope description is out of sync with apps/web/vercel.json.

The doc says COOP/COEP are under source: "/(.*)", but the live config scopes them to source: "/((?!\\.well-known).*)". Please align the wording to avoid operational confusion during incident checks.

Suggested edit
-Every header that ships to browsers from `apps/web` is defined in
-`apps/web/vercel.json` `headers[*]` blocks. Cross-cutting headers (CSP,
-COOP/COEP, Permissions-Policy, Referrer-Policy) live under `source: "/(.*)"`.
-Path-scoped headers (e.g. cache-control on `/assets/*`, well-known mime types)
+Every header that ships to browsers from `apps/web` is defined in
+`apps/web/vercel.json` `headers[*]` blocks. Most cross-cutting headers (CSP,
+Permissions-Policy, Referrer-Policy) live under `source: "/(.*)"`, while
+COOP/COEP are scoped to `source: "/((?!\\.well-known).*)"` to exempt
+`.well-known/*`. Path-scoped headers (e.g. cache-control on `/assets/*`, well-known mime types)
 get their own block.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
Every header that ships to browsers from `apps/web` is defined in
`apps/web/vercel.json` `headers[*]` blocks. Cross-cutting headers (CSP,
COOP/COEP, Permissions-Policy, Referrer-Policy) live under `source: "/(.*)"`.
Path-scoped headers (e.g. cache-control on `/assets/*`, well-known mime types)
get their own block.
Every header that ships to browsers from `apps/web` is defined in
`apps/web/vercel.json` `headers[*]` blocks. Most cross-cutting headers (CSP,
Permissions-Policy, Referrer-Policy) live under `source: "/(.*)"`, while
COOP/COEP are scoped to `source: "/((?!\\.well-known).*)"` to exempt
`.well-known/*`. Path-scoped headers (e.g. cache-control on `/assets/*`, well-known mime types)
get their own block.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/deploy/vercel.md` around lines 65 - 69, Update the docs/deploy/vercel.md
header-scope description to match the actual config: state that cross-cutting
headers (CSP, COOP/COEP, Permissions-Policy, Referrer-Policy) are defined in
apps/web/vercel.json under headers[*] with the source pattern
"/((?!\\.well-known).*)" (not "/(.*)"), and keep the note that path-scoped
headers (e.g. cache-control on /assets/*, well-known mime types) get their own
block; reference the exact source pattern string and the headers[*] blocks to
ensure the wording aligns with the live config.


> **Last validated:** 2026-05-04 by @Skords-01. **Next review:** 2026-08-04.
> **Status:** Closed (2026-05-04 — single source of truth + CI guard)
> **Status:** Closed (2026-05-04 — SSOT at `apps/web/vercel.json` + CI guard, after live-rollback of an incorrect SSOT choice).
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Use an allowed documentation status badge value.

Closed is not one of the allowed badge states for published docs. Please switch Line 4 to one of Active | Scaffolded | Deprecated | Archived and keep closure detail in prose/table if needed.

Suggested edit
-> **Status:** Closed (2026-05-04 — SSOT at `apps/web/vercel.json` + CI guard, after live-rollback of an incorrect SSOT choice).
+> **Status:** Active

As per coding guidelines: docs/**/*.md: Every published documentation file must include a status badge under the freshness marker: > **Status:** Active | Scaffolded | Deprecated | Archived.

Also applies to: 12-12

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/security/hardening/H7-vercel-config-drift.md` at line 4, Replace the
status badge line that currently reads "> **Status:** Closed (2026-05-04 — SSOT
at `apps/web/vercel.json` + CI guard, after live-rollback of an incorrect SSOT
choice)." with one of the allowed badge values: Active, Scaffolded, Deprecated,
or Archived (e.g., "> **Status:** Archived"), and move the closure details
("2026-05-04 — SSOT at `apps/web/vercel.json` + CI guard, after live-rollback of
an incorrect SSOT choice") into the prose or a table elsewhere in the document
so the badge strictly uses an allowed value.

@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 4, 2026

⏱️ CI Pipeline Duration Report

Based on the last 50 successful runs on the default branch.

Overall Pipeline

Metric Value
p50 6m 26s
p95 7m 55s
p99 9m 3s
Current run 6m 12s
vs p95 -21.7%

Trend (last 20 runs): ▃▃▁▂▃▃▃▂▃▃▂▂▄▃▃▆▅▄█▆

Per-Job Breakdown

Job p50 p95 p99 Current vs p95
Accessibility (axe-core) 2m 5s 2m 21s 2m 23s 0s -100.0%
Commit messages (commitlint) 0s 0s 0s 29s N/A
Critical-flow E2E (Playwright) 1m 36s 1m 44s 1m 44s 6m 5s +251.0%
Migration lint (AGENTS rule 0s 0s 0s 11s N/A
Pipeline duration (p95 trend) 26s 27s 27s
Secret scan (gitleaks) 8s 11s 11s 10s -9.1%
Smoke E2E (Playwright) 1m 26s 1m 40s 1m 40s
Test coverage (vitest) 2m 4s 2m 33s 2m 33s 1m 59s -22.2%
Workflow lint (actionlint) 7s 7s 7s 8s +14.3%
check 4m 12s 4m 54s 5m 6s 49s -83.3%
tsconfig strict guard (PR-1.A) 5s 14s 14s 6s -57.1%

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant