Release staging to main#745
Conversation
* feat: ingest Gong customer call findings
* fix(gong): signature verification, TOCTOU race, empty-id guard, speaker fallback, RFC 7807 412
- Add per-integration sharedSecret generated at OAuth state-save time and
persisted through completeGongInstall into workspace_integration metadata.
GongIngestCall reads body bytes, verifies HMAC-SHA256 via X-Gong-Signature
header using verifyGongSignature (crypto/subtle.ConstantTimeCompare) before
any DB write.
- Fix resolveGongInstall to return a hard error (surfaced as 400) when
integrationID is empty, eliminating silent fallback to most-recent
integration.
- Fix TOCTOU race in createOrLinkGongFinding: extracted tryCreateGongFinding
with SELECT ... FOR UPDATE on the issue table, wrapped in a retry loop
(max 3 attempts) that retries on pgconn code 23505 unique violation.
- Fix gongExternalSpeaker: change fallback return from true to false so
internal members missing from the participants list are not mis-classified
as external/customer speakers.
- Fix GongConnect 412 response: replace raw map[string]string via problem.JSON
with problem.Write to emit RFC 7807 application/problem+json.
- Regenerate packages/sdk/src/generated.ts from packages/proto/openapi.yaml;
gong paths now appear in the TypeScript SDK.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(gong): move call ingestion webhook to public router
Gong's servers post to POST /gong/{integrationID}/calls with only an
X-Gong-Signature header — no session cookie or bearer token. The route
was previously mounted under the session-authenticated protected group,
so every inbound webhook received 401 before the HMAC check could run.
Move the route to the publicProvider group (same group as SlackEvents
and GitLabWebhook) so unauthenticated Gong requests reach the handler.
Remove the canManage/auth.FromContext guard from GongIngestCall (the
HMAC signature check is the correct auth boundary for this endpoint).
Add resolveGongWebhookInstall to look up the integration by ID alone,
mirroring the resolveGitLabWebhookInstall pattern.
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>
* fix: add zendesk ticket issue linking * fix(zendesk): address code review findings in ticket linking - Replace dynamic SQL string concatenation in resolveZendeskInstall with two separate static QueryRow calls (one for integrationID path, one for subdomain path) to eliminate SQL injection risk from Go string concat - Restore early-return guard in queueSentryAutomations when links slice is empty so the workflow_state category query no longer runs on every issue state-change with no Sentry/Zendesk links - Change ZendeskTicketCreate error response from 400 to 500 for internal server failures (DB errors, missing workflow state); 400 is reserved for input validation failures - Assign getZendeskSourceLink(event) to a const in issue-detail-view.tsx to avoid calling the function twice in the same render 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>
* feat: add Intercom conversation issue linking
* fix(intercom): address security and correctness issues from code review
- intercomSigningSecret: drop AUTH_INTERCOM_SECRET fallback; return ""
when INTERCOM_SIGNING_SECRET is absent so the 503 path fires correctly
instead of accepting requests signed with the OAuth client secret
- IntercomIssueUnlink: check RowsAffected() and return 404 when no row
matched, consistent with IntercomIssueLink behaviour
- intercomIssueDescriptionHTML: guard Permalink with HasPrefix("https://")
to block javascript: URI injection before building the <a href>
- IntercomConnect 412 path: replace problem.JSON map with problem.Write
for RFC 7807 consistency
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>
* feat: add Front conversation issue integration * fix(integrations): harden Front integration security and correctness - SSRF: replace unvalidated frontAPIBaseURL with normalizeFrontBaseURL that requires https and a .frontapp.com host on admin-supplied values - HTTP client timeout: pass 10s-timeout client to validateFrontToken instead of http.DefaultClient (no timeout) - Dead branch: failFrontJob now sets status "error" (not "degraded") on 401/403 permission failures so operators know the token needs rotation - FrontIssueCreate: distinguish validation sentinels (400) from DB/infra errors (500) via isFrontValidationError helper - frontIssueDescriptionHTML: build combined HTML first, call sanitizehtml.RichText once instead of twice 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>
* fix: add salesforce case links * fix(salesforce): address SSRF, auth bypass, and double-call findings from code review - Validate fetchSalesforceUserInfo endpoint against salesforceOAuthBaseURL() before making the outbound HTTP request to prevent SSRF via a crafted token.ID in the OAuth response. - Remove the Authorization: Bearer fallback in salesforceSignedAction so HMAC signature verification is mandatory for every request; no static secret bypass is accepted. - Move HTTP status checks before json.Decode in exchangeSalesforceOAuth and fetchSalesforceUserInfo; include response body in error messages so non-2xx failures are surfaced accurately. - Assign getSalesforceSourceLink(event) to a const in the render path of issue-detail-view.tsx to avoid calling the function twice per render. 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>
* feat: add Jira guided import slice
* fix(jira-import): address security and correctness issues from code review
- [CRITICAL] Fix silent import truncation: getJSON now decodes 2xx
responses directly via json.NewDecoder (no 4096 limit); limit kept
only on error branch for safe logging
- [CRITICAL] Fix JQL injection: validate projectKey against
^[A-Z][A-Z0-9_]{0,9}$ before use; wrap value in double-quoted JQL
- [HIGH] Require HTTPS in normalizeJiraBaseURL; reject http:// with a
clear error
- [HIGH] Fix N+1 query: fetch team key once before the issue import loop
instead of querying per issue via jiraTeamIdentifier
- [HIGH] Add pagination to issues(): startAt-based loop fetches all
pages; surface jiraTruncatedError warning when a hard cap truncates;
add Total/StartAt to jiraSearchResponse
- [MEDIUM] openapi.yaml: add writeOnly:true and description to token
field on WorkspaceImportExportActionRequest
- [LOW] web: change jiraBaseUrl initial state to "" (placeholder only)
- Update handler_test.go to expect quoted JQL and include total in mock
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(jira-import): make maxResults=0 fetch all pages; remove dead helper; add pagination test
- issues(): replace the maxResults<=0→100 reset with an `unlimited` bool.
When unlimited, only startAt>=response.Total or an empty page terminates
the loop — fetching 100-issue batches across as many pages as needed.
When a positive maxResults cap is supplied the hard-cap exit fires as
before and surfaces a jiraTruncatedError. The empty-page guard remains
so a misbehaving server can never cause an infinite loop.
- Remove dead jiraTeamIdentifier() — confirmed unreferenced after the N+1
fix that inlined the team-key lookup into importJiraIssues().
- Add TestJiraIssuesPaginatesAllPages: mock server returns total=150 across
two pages (100+50); asserts all 150 issues are returned and exactly 2
HTTP calls are made when maxResults=0.
- Add TestNormalizeJiraBaseURLRejectsHTTP: asserts http:// base URLs are
rejected with an HTTPS error.
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>
…jira-cloud-server-sync-plu
* feat: add Figma issue previews * fix(figma): code review fixes for PR #686 - Rename migration 0009_figma_sources.sql → 0010_figma_sources.sql to avoid collision with 0009_gitlab_merge_request_links.sql on staging - Change timestamp columns to timestamptz in figma_source table - Add format: uuid to the id path param on the refresh endpoint in OpenAPI - Update OpenAPI summary to be truthful: endpoint stamps refreshed_at only, does not call the Figma API - Regenerate SDK from updated OpenAPI spec - Fix RefreshFigmaSource auth check: use p, ok := auth.FromContext and return 401 if not ok (was silently discarding the ok bool) - Use SDK client (createBrowserApiClient) for handleFigmaRefresh instead of hardcoded fetch URL; remove local FigmaSource interface redeclaration and use components["schemas"]["FigmaSource"] from the SDK - Validate thumbnailUrl in FigmaPreviewCard: only render <img> when URL starts with https:// to prevent XSS via untrusted URLs - Update UI button label from "Refresh" to "Mark seen" to match reality 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>
* feat: enrich triage routing metadata * fix(triage): address code review correctness issues - [CRITICAL] applyTriageDecision no longer writes to http.ResponseWriter; server errors return (nil, 500) so BulkTriage can abort the batch safely before the response is sent, preventing double-write and nil-map dereference panics on mid-batch 500-path failures. DecideTriage checks status >= 500 and writes a generic title so DB internals are never surfaced in the response. - [HIGH] triageIssues now batch-loads labels (triageIssueLabelsBatch) and source context (triageIssueSourceContextBatch) with WHERE issue_id = ANY($1::uuid[]) instead of one query per issue, eliminating the N+1 on the triage list endpoint. - [HIGH] optionProjects adds "completed_at is null and canceled_at is null" to filter dead projects for both triage routing and issue creation — offering them as targets only produced validation errors. - [HIGH] settings_members.go (~253) now checks errors.As(*triageValidationError) before using the message as a 400 title; raw DB errors become 500s instead of leaking DB internals into the response. validate* helpers return *triageValidationError for user-visible messages. - [MEDIUM] triageSourceContext: renamed local var "context" to "out" to stop shadowing the "context" package import. - [LOW] validateTriageAssignee: added ::uuid cast on $2 for consistency with other UUID parameters in the package. Deferred (as noted in review): OpenAPI schema tightening MEDIUM, debounce/E2E LOWs. 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>
…ntax - Remove incorrect jira/zendesk fallback config block: both configure via their own admin setup forms, not env var checks - Fix missing closing braces in handler_test.go caused by naive union merge of figma and intercom test blocks Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Squash merge PR #711 after controller validation.
Co-authored-by: jaeyunha <jaeyunha@users.noreply.github.com> 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>
* fix: port google sheets scheduled sync * fix: record google sheets oauth failures * fix: trim google sheets worker whitespace --------- Co-authored-by: jaeyunha <jaeyunha@users.noreply.github.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* feat: add guided GitHub import foundation * fix: use shared bool parser for GitHub imports --------- Co-authored-by: jaeyunha <jaeyunha@users.noreply.github.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Brings the released TTY terminal login redesign (#695) and the insights-overlay opacity fix into staging, plus the rest of current main. Conflict resolutions: - comments/issues/labels handlers: keep both realtime sync publish (#699) and outbound webhook enqueue (main). - integrations/handler.go: keep required (non-omitempty) details field and the HealthSummary row column; drop duplicate Metadata field. - teams/triage.go: keep staging's no-writer (map,int) decision contract and due_date support; graft in main's issueauthz relationship validation and drop the duplicate tx.Begin. - teams/handler_test.go: keep both added tests. - settings/integrations page: use the named IntegrationDetails type, extended with the Google Sheets fields. Merge fixups: - repoint 6 integration disconnect handlers from the removed revokeProvider to main's disconnectProvider (arg order swapped). Migration reconciliation: - allowlist the parallel-branch 0010/0011 duplicate prefixes in check-migrations.mjs (same convention used for 0006-0008). - add 0012_customer_requests_reconcile.sql to unify the customer / customer_request schema introduced independently by the Gong ingestion (0010) and the customer-requests CRM (0011): make Gong-only provider/excerpt columns nullable so CRM inserts succeed, and guarantee the superset columns and upsert indexes regardless of apply order. make check passes. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ity (#726) - fix hardcoded /home/jaeyunha path -> $HOME/wt (was broken on macOS) - fix npm install -> pnpm install (this is a pnpm monorepo) - add remote-branch support (origin/foo: fetch + upstream tracking) - flatten '/' in branch names for the worktree dir - resolve repo root via git rev-parse (works from any cwd) - copy .codex/.agents alongside .claude; clean up + bail on install failure - add EXPONENTIAL_WORKTREE_OVERRIDE_BASE override Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Focused controller QA passed: apps/api go test ./..., import/export Vitest 5/5, diff-check clean.
Co-authored-by: jaeyunha <jaeyunha@users.noreply.github.com>
Co-authored-by: jaeyunha <jaeyunha@users.noreply.github.com>
Co-authored-by: Hermes Controller <hermes-controller@users.noreply.github.com>
Controller-validated squash merge to staging for #588.
Co-authored-by: Hermes Conductor <conductor@local>
* fix: revert web drizzle runtime 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. * Revert "fix: revert web drizzle runtime" This reverts commit c1246a4. * fix: repair stripe billing release path * fix: remove web auth drizzle runtime * fix: enforce relationship authz * docs: refresh codebase documentation * feat: create SNS alarm topic and wire ALARM_TOPIC_ARN into autoscaling 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> * docs: overhaul README as product landing page 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> * feat: automate HTTPS listener, ACM cert wiring, and HTTP-to-HTTPS redirect 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> * feat: publish pre-built GHCR images and image-based compose path 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> * fix: make Stripe secrets optional for non-billing ECS deployments 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> * feat: add semver release workflow, expn --version, changelog, and tag 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> * feat: add generic SMTP email provider for self-hosted magic-link sign-in 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> * feat(mcp): add write tools to local MCP server (create/update issue, 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> * feat: add integration platform lifecycle, health, secrets, and job substrate 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> * docs: address PR #655 review findings on README security, build time, 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> * feat: implement outbound webhook delivery with signing, retry, and admin 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> * fix: harden HTTPS preflight against unvalidated certs and downgrade idempotency - 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> * fix: address PR #657 review findings — scoped GHA cache, workflow concurrency, 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> * fix: address review findings on release engineering PR - 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> * fix(mcp): address review findings on triage_issue handler and write-tool 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> * fix: correct SMTP quoted-printable encoding, LOGIN auth fallback, misconfiguration 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> * fix: address PR #663 review findings for outbound webhooks - 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> * fix: renumber integration lifecycle migration from 0007 to 0008 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> * fix: address remaining PR #663 review findings (migration rename + data 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> * fix: make Stripe ECS deploy vars optional with all-or-none coherence check - deploy-ecs.sh: move Stripe vars out of unconditional require_env loop; add all-or-none coherence check treating empty strings as absent so non-billing operators can deploy without Stripe vars set - prepare-ecs-deploy-env.sh: guard existing_or_synced_secret_arn against syncing an empty value over a pre-existing secret ARN (SYNC_DEPLOY_SECRET_VALUES=true) - render-ecs-task-definitions.test.mjs: add environment assertions for STRIPE_CLOUD_*_PRICE_ID pruning in the fully-absent-Stripe test; replace delete statements with destructuring rest to satisfy Biome noDelete rule Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(mcp): satisfy Biome useLiteralKeys and formatting in server.ts Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix: apply Biome formatting to integrations settings page Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(mcp): apply Biome formatting to server.test.ts Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * test(integrations): update view tests to match new page UI contract The new integrations page shows configuration_required integrations as active cards on the main page (not behind an empty state), so update the test fixtures and assertions to match the actual rendered contract. - Give Slack canConnect:true so the Connect button renders (tests real actionability, not just presence of a card) - Assert both provider cards appear on the main page after load instead of checking for the old "No active integrations" empty state text - Assert setup requirement messages surface inline (anti-placeholder check preserved with equivalent strength) - Keep the Slack connect API error test intact: only update the load anchor (findByText("Slack") instead of the removed empty-state text) and remove the now-unnecessary dialog-open step; error surfacing via role="alert" is unchanged Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * chore: remove accidentally committed .release tarballs and ignore the directory Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix: align webhook event names after release merge * test: stabilize auth diagnostics during e2e * fix: keep insights overlay opaque in light theme * fix: account for applied migration prefixes * style: format migration guard * fix(web): restore TTY terminal login design (#695) * 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> * fix(ci): don't block deploy when runner status is unreadable (#697) 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 (#696) * 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> * fix: reconcile GitHub integration release batch * test: scope integration connect action * ci: add PR validation workflow (build + Go tests + contract guards) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> Co-authored-by: jaeyunha <jaeyunha@users.noreply.github.com>
#743) * perf(web): seed page data on the server to cut the hydration waterfall Authenticated content pages fetched their own data in the browser via a post-hydration useEffect, so content-visible = TTFB + full-shell hydration + a client round-trip. Convert the data pages to the RSC server-seeding pattern: an async server component fetches via the server SDK and seeds the client component's initial state, so content ships in the first paint. Pages converted (server helper + seeded client + async page): - team/[key]/all and board (getTeamIssues) - inbox (getInboxNotifications) - my-issues/[tab] (getMyIssues) - initiatives (getInitiatives) - projects + projects/all (getProjects) - settings/members (getWorkspaceMembers) Also fixes the projects slug-variant routes, which were their own non-seeded files rather than re-exports of the converted canonical page. Supporting hydration/latency cuts: - app-shell: dynamic-import the always-mounted overlays (ask-assistant, command-palette, create-issue-modal) with ssr:false to shrink the hydration-blocking bundle. - layout: overlap the /workspaces/current and /teams round-trips. - web-session: short-circuit anonymous visitors (no session cookie) instead of paying an /auth/session round-trip. - auth pages: drop force-dynamic. Result (local expperf stack): every page now emits fully data-seeded HTML in ~13-20ms server-render (curl TTFB), with the client-fetch waterfall eliminated. Server helpers use runtime type guards (no casts on API data); a server miss returns null and degrades gracefully to the client fetch. Adds perf measurement scripts under apps/web/scripts. Typecheck, biome, and the full Vitest suite (1085 tests) pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * style(web): biome-format perf measurement scripts Fix Biome lint/format errors (noUnusedTemplateLiteral, useNumberNamespace, formatting) in the perf-measure scripts that broke the Node CI check on #743. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(ci): point members SDK-usage guard at the post-seeding file layout The members settings surface was split into an RSC page.tsx (server-SDK seeding) plus members-client.tsx (browser SDK client). Update the guard to require createBrowserApiClient in the client file and ban direct member/invite fetches across both files, preserving the guard's intent. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- 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: 4d191ed354
ℹ️ 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".
| export async function getWorkspaceMembers( | ||
| userId: string, | ||
| ): Promise<WorkspaceMembersResponse | null> { | ||
| const workspaceId = await resolveActiveWorkspaceId(userId); |
There was a problem hiding this comment.
Honor the URL workspace when seeding members
When a user opens a workspace-prefixed route such as /acme/settings/members while their activeWorkspaceId cookie still points at another workspace, this resolves the cookie-selected workspace and later sends it as X-Workspace-Id. The API's requestedWorkspace() path gives X-Workspace-Id precedence over the forwarded X-Workspace-Slug, and MembersClient skips its browser fetch when initialData is present, so the page can display and manage the wrong workspace's members/invites under the requested workspace URL.
Useful? React with 👍 / 👎.
| if (seededRef.current) { | ||
| seededRef.current = false; | ||
| return; |
There was a problem hiding this comment.
Still load project labels and templates after seeding
When initialProjects is provided by the new server page, this early return prevents fetchProjects() from running, so the follow-up /api/project-labels and /api/project-templates requests never happen on mount. As a result the label filter and the create-project form's label/template selectors remain empty until a later sync event or create action happens to trigger a full client fetch.
Useful? React with 👍 / 👎.
|
Controller release validation update for head
Disposition: release remains blocked; not merge-ready until the E2E failures are reduced to accepted flakes or fixed and the full suite passes. This is not active implementation capacity ( |
|
Post-merge deploy blocker for merge commit Evidence:
Blocker: production deploy is not healthy until the RED metrics smoke path is restored or the deploy smoke contract is updated intentionally. Next: controller should route a focused deploy-smoke fix/retry before treating the release as production-complete. |
Staging → main release
Promotes current
stagingtomainafter the latest staged PR #743.Current delta
4d191ed36fe5e1c3Included staged PRs / themes
Validation
In progress from
/home/jaeyunha/wt/exponential/staging-main:make checkmake testmake test-e2eRelease notes
Fresh release branch
release/staging-main-743was created fromorigin/stagingbecause the olderrelease/staging-main-713branch was stale after #713 was squash-merged and #743 landed.