ci: add PR validation workflow#723
Conversation
Promote staging to main after clean combined validation: make check, make test, and make test-e2e (144 passed).
Remove the web-side Drizzle/Better Auth runtime path reintroduced by the staging release and restore the last successfully deployed web architecture. Verified with web build, make check, make test, and infra/docker/web.Dockerfile build.
This reverts commit c1246a4.
Restore the release web runtime path and repair Stripe checkout/portal through the Go API with deploy configuration.
Keep apps/web UI-only by removing Better Auth/Drizzle runtime modules and adding a boundary guard.
fix: enforce relationship authz
docs: refresh codebase documentation
…g alarms
- prepare-ecs-deploy-env.sh: when ALARM_EMAIL is set, idempotently create
an SNS topic (${APP_NAME}-alarms) and subscribe the operator email; store
the resulting ARN as ALARM_TOPIC_ARN in .env and print a confirmation
reminder about the required subscription email. Falls back gracefully when
neither ALARM_EMAIL nor ALARM_TOPIC_ARN is provided.
- deploy-ecs.sh: export ALARM_TOPIC_ARN before invoking
configure-ecs-autoscaling.sh so the variable is available in the
subprocess even when it was not set in the calling shell.
- check-deploy-scripts.mjs: add guard assertions verifying that the SNS
wiring (sns create-topic, sns subscribe, ALARM_EMAIL, ALARM_TOPIC_ARN) is
present in prepare-ecs-deploy-env.sh and that deploy-ecs.sh exports
ALARM_TOPIC_ARN.
Closes #638
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Add quickstart one-liner, comparison table vs Linear/Jira/Plane, workflow GIF placeholder with instructions, MCP and CLI sections, ELv2 license statement, and set GitHub repo topics via API. Closes #643 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…irect in ECS preflight Extends scripts/preflight.sh to consume ACM_CERT_ARN: when set, creates an HTTPS:443 ALB listener with TLS 1.3 policy wired to the existing target groups, converts HTTP:80 to a permanent 301 redirect, and routes /api/* on the HTTPS listener to the Go API. Runs are idempotent — existing listeners are detected and updated. Documents the full ACM certificate request, DNS validation (manual and Route 53-automated), and app URL configuration steps in docs/self-hosting.md. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Adds a GitHub Actions workflow that builds and pushes multi-arch (linux/amd64 + linux/arm64) exponential-api and exponential-web images to GHCR on every release, tagged with the release version and `latest`. Introduces docker-compose.images.yml as the recommended self-hosting path (no build step, <5 min to running instance), and updates docs/self-hosting.md to present the image-based quick start first. Adds IMAGE_TAG to .env.example for version pinning. Closes #634 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Self-hosted community deployments are blocked mid-script by Stripe secrets they don't have and don't need. This change makes STRIPE_WEBHOOK_SIGNING_SECRET and STRIPE_SECRET_KEY (plus the price-ID vars) optional throughout the ECS deploy path: - prepare-ecs-deploy-env.sh: guard Stripe secret upserts behind presence checks so the script succeeds without any Stripe configuration - render-ecs-task-definitions.mjs: treat Stripe ARN and price-ID vars as optional; prune their task-definition entries when absent rather than rendering empty ARNs that ECS would reject - render-ecs-task-definitions.test.mjs: cover both billing-enabled and non-billing rendering modes - docs/self-hosting.md: document that Stripe is billing-only for ECS Closes #636 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… script - Add .github/workflows/release.yml: tag-triggered GitHub Release pipeline that validates package.json versions match the tag, runs tests, builds tarballs, publishes both SDK and CLI to npm (with provenance), generates per-release changelog notes, and creates a GitHub Release with tarballs. - Add scripts/tag-release.sh: guards (main branch, clean tree, version match) then creates an annotated tag and pushes it to trigger the release workflow. - Add expn --version / expn version command that reads the installed version from package.json at runtime, no token required. - Add CHANGELOG.md documenting the v0.1.0 initial release and future release cadence, following Keep a Changelog format. - Update .github/workflows/publish-cli.yml header to note it is the manual/ emergency path; normal releases use tag-release.sh + release.yml. - Update .github/workflows/README.md with full release runbook. - Add 3 new tests for --version flag (with EXPN_VERSION test seam, without token requirement, semver format assertion). Closes #639 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Adds a plain SMTP sender to apps/api/internal/email alongside SES and Opensend so self-hosters can use any SMTP relay (Mailhog, Mailgun, Postmark, Gmail app password, etc.) without an AWS account or Opensend key. - New smtpSender: STARTTLS (port 587 default) and implicit-TLS (port 465, SMTP_TLS=true) transports; optional SMTP_USERNAME/SMTP_PASSWORD AUTH PLAIN; graceful no-auth path for loopback relays (Mailhog). - Auto-selection order: smtp (SMTP_HOST set) > opensend (OPENSEND_API_KEY) > ses (SENDER_EMAIL) > Disabled. - EMAIL_PROVIDER=smtp|ses|opensend explicit override still works. - docker-compose.dev.yml: wire api to Mailhog via SMTP envs so the magic-link flow works out-of-the-box in the dev stack. - .env.example: document SMTP_HOST/PORT/USERNAME/PASSWORD/TLS vars. - docs/self-hosting.md: document provider selection order, SMTP quick-start, and Mailhog dev recipe. - 11 new Go table tests covering provider selection, Mailhog delivery, raw-message building (multipart and HTML-only paths), and edge cases. Closes #635 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…comment, triage)
Extends packages/mcp-server beyond read-only with four write tools:
- create_issue: POST /issues with title, team_id and optional fields
- update_issue: PATCH /issues/{id} for status/priority/assignee/labels
- add_comment: POST /issues/{id}/comments
- triage_issue: PATCH /teams/{key}/triage/{issueID} with accept/decline action
All write tools are annotated readOnlyHint:false; triage_issue also sets
destructiveHint:true. Input schemas use strict Zod validation. Errors map
API problem JSON to MCP tool errors while redacting auth fields.
Read-only tool annotations and registration are unchanged; the tool
registration loop now reads annotations from each ToolDefinition.
Tests updated: removed read-only-only assertion, added per-annotation check
and happy-path tests for all four write tools (12 tests total, all passing).
docs/mcp.md updated to document write tools, scope, and follow-up work.
Closes #640
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…bstrate Implements the integration runtime substrate (issue #568): - Migration 0007: adds lifecycle_state, health timestamps, credential_ref, disconnected_at, and integration_job table with backoff/retry fields - Go: lifecycle helpers (BackoffDuration, TransitionJobStatus, IsTerminal), HealthInfo struct serialized without any secret/credential fields, disconnectProvider that revokes credentials and disables jobs while preserving historical links (replaces hard-delete) - OpenAPI: adds IntegrationHealth schema; Integration gains nullable health field - Web: settings/integrations page shows lifecycle badge, last event/success/ failure timestamps, health summary, and reconnect/disconnect CTAs Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… and ECS command - Move security callout above the quickstart one-liner and strengthen it to warn before network connections, not only before sharing - Add inline comment and build-time note (~15 min, ~8 GiB RAM) with reference to #634 for the image-based path - Add bash prefix to RUN_PROD_SMOKE=true scripts/deploy-ecs.sh for consistency with the two preceding lines Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…min visibility - Add webhook_delivery table (migration 0007) tracking delivery attempts, status, response codes, retry schedule, and dead-letter state - New apps/api/internal/webhooks package: HMAC-SHA256 signing, outbound HTTP delivery, exponential backoff retry (5 max attempts), dead-letter state, and EnqueueEvent/ProcessPending helpers - Emit webhook events fire-and-forget after issue, comment, and label mutations (created/updated/deleted) so they never block the user response path - Add /webhook-deliveries admin endpoint (list + per-delivery retry) requiring manager role - Validate webhook event types against known list on createWebhook - Expose supportedWebhookEvents in workspace API payload - Background delivery processor ticker (10s interval) wired into main.go - 11 unit tests covering signing, verification, backoff, HTTP delivery (success, 4xx, 5xx, network error), event type validation, and helpers Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…dempotency - Exit 1 (was WARNING+continue) when ACM cert status is not ISSUED; message now explicitly warns that HTTP:80 would also stop working via the 301 redirect. - Guard create-listener return value against 'None' before passing ARN to ensure_listener_rule, preventing a broken partial-state after HTTP is already converted to a redirect. - In HTTP-only mode, delete any orphaned HTTPS:443 ALB listener left from a previous HTTPS run and remove stale ALB_HTTPS_LISTENER_ARN from .env via a new del_env_file helper, restoring true downgrade idempotency. - docs/self-hosting.md: replace the append-only 'echo >> .env' with a grep+sed in-place edit pattern consistent with set_env_file, preventing duplicate ACM_CERT_ARN keys on re-runs. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…currency, pinned .env ref - Add scope=api / scope=web to cache-from and cache-to in publish-images.yml so parallel build-api and build-web jobs use isolated GHA cache buckets and cannot evict each other's layers. - Add top-level concurrency group (publish-images, cancel-in-progress: false) to prevent simultaneous workflow runs from racing the same GHCR tags. - Update docs/self-hosting.md quick-start curl to fetch .env.example from the pinned IMAGE_TAG ref rather than main, so variable sets stay aligned with the image being deployed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Scope --version flag check to rawArgs[0] only, preventing `expn issues watch --version N` from being intercepted and printing the CLI version instead of running the watch command. - Fix GitHub Actions tag glob patterns from POSIX-regex `+` quantifiers (invalid in minimatch) to `*` wildcards so the release workflow actually fires on real semver tags like v1.2.3. - Strengthen tag-release.sh clean-tree guard from `git diff --quiet HEAD` (misses untracked files) to `git status --porcelain` so untracked secrets or generated files are caught before a release tag is pushed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ool tests - Replace optionalStringField cast with stringField for required `action` field (finding #1) - Replace unsafe priority cast with new optionalNullableIssuePriority helper (finding #2) - Use WRITE_ANNOTATIONS instead of destructiveHint:true for triage_issue (finding #3) - Add request body assertions to all four write-tool happy-path tests (finding #4) - Add rejection test for triage_issue missing required action field (finding #5) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…configuration error, and test races
- Use mime/quotedprintable.NewWriter to properly encode text/plain and
text/html body parts; fixes protocol violation for non-ASCII content
and lines >998 bytes that would produce corrupt messages.
- Replace smtp.PlainAuth-only auth with smtpAuth() helper that inspects
the server's AUTH advertisement and falls back to AUTH LOGIN for
shared-hosting relays (cPanel, some Gmail SMTP configs) that only
advertise LOGIN; update the doc comment to reflect actual behaviour.
- Return a descriptive error (not Disabled{}) when EMAIL_PROVIDER=smtp
is explicit but SMTP_HOST or SENDER_EMAIL is missing, so the warning
path in router.go is actually reached.
- Fix TestSMTPSendAgainstMailhog race: send to delivered channel before
writing "221 Bye" so the channel is populated before smtp.Client.Quit()
returns; replace non-blocking default select with a 5s timeout.
- Replace conn.Read(buf) in mock SMTP server with bufio.Scanner for
per-line reading, preventing TCP-coalesced multi-command reads from
silently dropping commands on fast machines.
- buildRawMessage now returns ([]byte, error) to propagate QP encoder
failures up the call stack.
- Add TestSMTPBuildRawMessageNonASCII to catch the QP encoding regression.
- Add TestNewSMTPExplicitMissingHostReturnsError and
TestNewSMTPExplicitMissingFromReturnsError for the new error path.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- CRITICAL: wrap FOR UPDATE SKIP LOCKED and status='delivering' UPDATE in
an explicit pgx transaction in ProcessPending so row locks are held across
both statements, preventing duplicate deliveries on concurrent workers
- CRITICAL: add OpenAPI spec entries for GET /workspaces/current/webhook-deliveries
and POST /workspaces/current/webhook-deliveries/{id}/retry plus WebhookDelivery
schema so check-openapi-coverage passes
- HIGH: rename migrations to fix 0006 prefix collision — 0006_stripe_webhook_event
becomes 0007, and 0007_webhook_delivery becomes 0008
- HIGH: add validateWebhookURL with SSRF protection blocking loopback, private,
link-local, and IMDS (169.254.169.254) addresses; add SSRF test coverage
- MEDIUM: move /webhook-deliveries mount to /workspaces/current/webhook-deliveries
matching established workspace-management URL pattern
- MEDIUM: extract isManager to auth.IsManager shared helper, removing the
duplicate definition in webhooks/handler.go and delegating workspaces/handler.go
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The outbound webhooks branch (PR #663) introduced a 0007_stripe_webhook_event.sql rename from the existing 0006_stripe_webhook_event.sql (which is already applied on all deployed databases). Even after that branch is corrected to restore 0006 and use 0007 for webhook_delivery, the integration lifecycle migration needs to be 0008 to sit above the webhook delivery migration and avoid a version-key collision in the migration runner (which uses filepath.Base as the unique key). - Rename packages/proto/migrations/0007_integration_lifecycle.sql to packages/proto/migrations/0008_integration_lifecycle.sql - All 13 integrations package tests pass; go build ./... clean Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ta race) DESTRUCTIVE MIGRATION RENAME (finding #2): - Restore packages/proto/migrations/0006_stripe_webhook_event.sql to its original name. The previous fix commit incorrectly renamed it to 0007_stripe_webhook_event.sql, which would cause the migration runner (filepath.Base as version key) to re-execute it on any DB that already has "0006_stripe_webhook_event.sql" applied, creating a ghost version record and breaking migration history. - Rename 0008_webhook_delivery.sql back to 0007_webhook_delivery.sql since 0006_stripe_webhook_event.sql is now restored to its correct slot. DATA RACE on skipSSRFValidation (finding #3): - Remove the package-level `var skipSSRFValidation atomic.Bool` global. - Add `skipSSRFCheck bool` field to the Deliverer struct, a `WithSSRFCheckDisabled()` functional option, and a `NewDeliverer` constructor. - Change `sendWebhookRequest` to accept an explicit `skipSSRFCheck bool` parameter, eliminating all shared mutable state between parallel tests. - Update deliver_test.go: remove `setupSSRFBypass` helper and pass `true` directly to sendWebhookRequest in tests that use httptest 127.0.0.1 servers. - All 12 webhooks tests pass under `go test -race`. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
feat(mcp): add write tools to local MCP server (create/update issue, comment, triage)
feat: outbound webhook delivery with signing, retry, and admin visibility
…tform feat: integration platform lifecycle, health, secrets, and job substrate (#568)
… directory Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
chore: remove stray .release tarballs from main
# Conflicts: # apps/api/internal/integrations/handler.go # apps/api/internal/integrations/handler_test.go # apps/web/src/app/(app)/settings/integrations/page.tsx # apps/web/src/lib/db/schema.ts # apps/web/tests/integrations-view.test.tsx # packages/proto/openapi.yaml
…in-666 # Conflicts: # apps/api/README.md
* fix(web): restore TTY terminal login design The faithful TTY/terminal auth surface shipped in 0b723a6 was silently overwritten by the staging merge 94e8a7f, which resurrected origin/main's stale rounded-pill "chooser" auth-page during a path relocation. Follow-up commits then realigned tests around the regressed chooser, cementing it. Re-skin auth-page.tsx back to the redesign.html `TtyLogin` mock — two-column terminal layout, top chrome bar, sharp bordered method tiles, preflight doctor panel (real data), recent-sessions table, CLI pairing block, and the vim-style command bar — WITHOUT regressing auth behavior. All current logic is preserved: Google OAuth, email magic-link, SAML SSO, passkey, Turnstile, signup wizard, workspace slug-check, recent-sessions/preflight fetches. The right column no longer collapses to an empty void (preflight + CLI pairing always render). Uses --auth-* tokens, works in dark and light. Validation: - make check, make test - web vitest: 1069 passed - e2e (unauth project, live stack): 20 passed - visual: dark, light, and email step verified Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(web): make TTY auth surface fill the viewport height The root used h-full inside a min-h-screen flex-col layout, so it collapsed to content height — leaving a black void below and the command bar floating mid-screen. Use flex-1 so it grows to fill. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(web): gate login to Google only, mark other methods coming soon Disable email magic-link, SAML SSO, and passkey buttons on the auth chooser and add a "coming soon" caption to each; Google OAuth remains the only active sign-in method. Underlying handlers are unchanged — only the UI entry points are gated. Update tests to the new contract: assert Google stays enabled while email/SAML/passkey are disabled and show "coming soon"; remove e2e flows that drove the now-disabled email step. Validation: make check, make test, web vitest (1068), e2e unauth (18). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
The runner precheck added in #670 calls the GitHub self-hosted runners API, which requires repository Administration access. With no GH_RUNNER_STATUS_TOKEN configured it falls back to the default GITHUB_TOKEN and gets 401/403, hard-failing every deploy since Jun 15 — even though the Mac mini runner is online. Treat an auth failure (401/403) as "cannot verify" and skip the precheck with a warning instead of failing the deploy. The gate stays strict when a capable token is present, and the deploy job still fails safely if no runner picks it up. Set GH_RUNNER_STATUS_TOKEN to re-enable the gate. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* feat: add github app integration ingress * fix(integrations): correct GitHub connect redirect and webhook 202 for no-installation events - Web: read installationUrl (not authorizationUrl) when provider is github so Connect/Reconnect actually redirects to the GitHub App installation page - Web: handle ?github=connected / ?github=canceled query params on return from GitHub so a success/error notice is shown and the URL is cleaned up - API: return HTTP 202 with ignored=no_installation_context (instead of 400) for webhook events that carry no installation field (e.g. ping, github_app_authorization), matching the existing unknown_installation path so GitHub marks deliveries as succeeded Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: jaeyunha <jaeyunha@users.noreply.github.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…in-702 # Conflicts: # apps/api/internal/integrations/github.go # apps/api/internal/integrations/handler.go # apps/api/internal/integrations/handler_test.go # apps/web/src/app/(app)/settings/integrations/page.tsx
Promote validated staging integration batch to main.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 244598ad89
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| where wd.status in ('pending', 'delivering') | ||
| and (wd.next_attempt_at is null or wd.next_attempt_at <= now()) |
There was a problem hiding this comment.
Prevent concurrent workers from re-sending deliveries
When the API service is scaled to more than one task, one worker can mark a row as delivering and commit before the HTTP POST finishes, while another worker immediately selects the same row because delivering is still eligible and next_attempt_at is unchanged. That makes outbound webhooks duplicate side effects for any delivery that overlaps another worker tick; use only pending here or add a lease/timeout before recovering delivering rows.
Useful? React with 👍 / 👎.
| req.Header.Set("X-Hub-Signature-256", sig) | ||
| } | ||
|
|
||
| resp, err := deliveryClient.Do(req) |
There was a problem hiding this comment.
Block redirects before sending webhook requests
For attacker-controlled webhook URLs, this validates only the original URL, but Go's default http.Client follows redirects automatically. A public endpoint can return a 302 to 127.0.0.1, link-local metadata, or another private address and bypass the SSRF checks above; configure CheckRedirect to revalidate or reject redirects for webhook delivery.
Useful? React with 👍 / 👎.
| $ref: '#/components/responses/Problem' | ||
| /workspaces/current/webhook-deliveries: | ||
| get: | ||
| operationId: listWebhookDeliveries |
There was a problem hiding this comment.
Regenerate clients for webhook delivery endpoints
These new OpenAPI operations are not present in the generated SDK or Go strict-server stubs: I checked packages/sdk/src/generated.ts and apps/api/internal/openapi/openapi.gen.go, and neither contains listWebhookDeliveries or retryWebhookDelivery. Consumers generated from this repo cannot call the delivery-log endpoints until the SDK/stubs are regenerated from this contract change.
Useful? React with 👍 / 👎.
| AWS_REGION: ${AWS_REGION:-us-east-1} | ||
| S3_BUCKET: ${S3_BUCKET:-} | ||
| INBOUND_EMAIL_WEBHOOK_SECRET: ${INBOUND_EMAIL_WEBHOOK_SECRET:-} | ||
| EXPONENTIAL_INBOUND_DOMAIN: ${EXPONENTIAL_INBOUND_DOMAIN:-} |
There was a problem hiding this comment.
Pass email provider env to the API image
In the prebuilt-image compose stack, self-hosted operators who set SENDER_EMAIL/SMTP_* or Opensend variables in .env still get email disabled because those variables are not forwarded to the API container; only the web container receives SENDER_EMAIL later, but apps/api/internal/email.New is what sends magic-link mail. This makes login email fail in docker-compose.images.yml deployments even when the documented email settings are configured.
Useful? React with 👍 / 👎.
|
Controller disposition for current head Current blocker:
Controller evidence:
Required before merge: change setup-go to the repo Go version ( |
Summary
.github/workflows/ci.yml— a PR validation workflow that runs on every PR tostaging/mainand on push to those branches.What runs
Job 1:
Go API (build + tests + contract guards)— REQUIREDgo build ./...go test ./internal/...node scripts/check-openapi-coverage.mjsnode scripts/check-go-openapi-generated.mjsnode scripts/check-sqlc-generated.mjsnode scripts/check-migrations.mjsbash infra/docker/api.Dockerfile.test.shnode scripts/check-ecs-task-definitions.mjsnode scripts/render-ecs-task-definitions.test.mjsbash -n ... && node scripts/check-deploy-scripts.mjssh -n ... && node scripts/check-smoke-script.mjsJob 2:
Node (lint + architecture guards + unit tests)— REQUIREDpnpm lintpnpm web-api-emptynode scripts/check-web-sdk-usage.mjsnode scripts/check-web-runtime-boundaries.mjspnpm test --filter '!@exponential/web'Job 3:
Web typecheck (non-blocking)— NON-BLOCKING (continue-on-error: true)pnpm typecheckcurrently fails with pre-existing TypeScript syntax errors in:apps/web/src/app/(app)/settings/integrations/page.tsxapps/web/src/components/issue-detail-view.tsxapps/web/tests/integrations-view.test.tsxThese are not regressions introduced by this PR. The job is visible in CI so progress is tracked, but it does not block merging. ACTION REQUIRED once fixed: remove
continue-on-error: truefrom theweb-typecheckjob to make it a required gate.Checks intentionally excluded
make test-e2e): requires a running dev server (Postgres + Redis + API + web). Environment-blocked in GitHub-hosted runners without significant service setup. Excluded entirely — should be added as a separate workflow once a suitable runner or service configuration is established.pnpm typecheckfor@exponential/webin the Vitest job: same pre-existing TS errors prevent compilation. The non-blockingweb-typecheckjob covers visibility.🤖 Generated with Claude Code