Skip to content

FE-1569: Add durable Brunch storage and telemetry - #9487

Open
lunelson wants to merge 8 commits into
ln/fe-1569-brunch-agent-containerfrom
ln/fe-1569-brunch-agent-deployment
Open

FE-1569: Add durable Brunch storage and telemetry#9487
lunelson wants to merge 8 commits into
ln/fe-1569-brunch-agent-containerfrom
ln/fe-1569-brunch-agent-deployment

Conversation

@lunelson

@lunelson lunelson commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

🌟 What is the purpose of this PR?

Add the runtime behavior needed to take the containerized Brunch service to the application-to-infrastructure deployment boundary: durable Flue Postgres state, content-free operational telemetry, and explicit diagnostics for restricted deployment and recovery.

This PR does not claim that Brunch is deployed. The downstack container PR publishes to ECR and GHCR, but no Brunch ECS service, RDS target, hosted collector, restricted ingress, or deployment owner is confirmed, so Mission 8 remains open at its required handoff boundary.

🔗 Related links

🚫 Blocked by

  • Infrastructure owner confirms and provisions the Brunch ECS, RDS/IAM, collector, restricted ingress, and deployment-owner targets.
  • The remote Mission 8 proof matrix passes against one immutable image digest.

🔍 What does this change?

  • Replaces deployed SQLite with fail-closed @flue/postgres persistence using dedicated fields, verified TLS, RDS IAM tokens per physical connection, and a runtime-password fallback.
  • Adds content-free Flue and Node OpenTelemetry export over OTLP/gRPC with graceful lifecycle flush and operational database-failure spans.
  • Adds RDS IAM and restricted deployment probes plus a disposable TLS Postgres/OTel container smoke.
  • Documents runtime configuration, ingress restrictions, local proof, GHCR publication, and the outstanding infrastructure handoff.
🏗️ Agent notes

The branch authority is libs/@hashintel/brunch-agent/MISSION.md. It records Mission 8 as live but stopped at the explicit application-to-infrastructure handoff boundary.

Locally established:

  • fail-closed production configuration and Flue Postgres migration before listen;
  • IAM-token callback refresh, transaction pinning/rollback, TLS verification, and static-password fallback;
  • content-free OTLP traces, logs, and metrics with graceful shutdown;
  • disposable TLS Postgres and OTel collector integration smoke.

Still open:

  • real task-role/RDS IAM connectivity;
  • restricted streamed Anthropic/tool execution;
  • in-place and cross-host replacement recovery;
  • bounded provider/database/stream failure inspection in the hosted telemetry backend;
  • singleton replacement, rollback, and owner acceptance.

The downstack PR registers ECR and GHCR publication with no ECS target. Infrastructure provisioning and remote proof remain outside this local application artifact.

Pre-Merge Checklist 🚀

🚢 Has this modified a publishable library?

This PR:

  • does not modify any publishable blocks or libraries, or modifications do not need publishing

📜 Does this require a change to the docs?

The changes in this PR:

  • require changes to docs which are made as part of this PR

🕸️ Does this require a change to the Turbo Graph?

The changes in this PR:

  • affected the execution graph, and the turbo.json files have been updated to reflect this

⚠️ Known issues

  • Brunch is not deployed; the deploy catalog publishes to ECR and GHCR but has no ECS target.
  • The local Docker smoke uses the runtime-password path; the real task-role RDS IAM probe remains unrun.
  • /health proves process liveness only. Database connection and migration gate startup before the server listens.
  • Public identity, authorization, rate/spend controls, retention/deletion policy, backup restoration, and multi-replica ownership remain outside this restricted singleton threshold.

🐾 Next steps

  • Provision and record the ECS, RDS/IAM, secret, collector, ingress, health, timeout, drain, and ownership contract from the deployment handoff.
  • Run the remote proof matrix with one immutable digest and update this PR, Mission 8, and FE-1569 with observed results.

🛡 What tests cover this?

  • 80 Vitest unit tests covering configuration, IAM token refresh, Postgres transactions, health, telemetry lifecycle, artifact wiring, and existing Brunch behavior.
  • TypeScript typecheck, application build, and Oxlint with eight pre-existing warnings and no errors.
  • The previously completed Docker integration smoke covers TLS Postgres migration/startup, fail-closed configuration, OTel export, non-root identity, packaged resources, repository immutability, health, and graceful shutdown.

❓ How to test this?

  1. Run yarn workspace @apps/brunch-agent lint:tsc.
  2. Run yarn workspace @apps/brunch-agent test:unit.
  3. Run yarn workspace @apps/brunch-agent build.
  4. Run yarn workspace @apps/brunch-agent test:integration with Docker available.
  5. After infrastructure exists, follow apps/brunch-agent/README.md and the Mission 8 deployment handoff for the IAM and remote smoke commands.

📹 Demo

No visual UI change. The proof artifact is the production container and its deployment diagnostics.

@vercel

vercel Bot commented Sep 1, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated
hash Ready Ready Preview Sep 4, 2026 12:45pm UTC
petrinaut Ready Ready Preview Sep 4, 2026 12:45pm UTC
petrinaut-docs Ready Ready Preview Sep 4, 2026 12:45pm UTC
1 Skipped Deployment
Project Deployment Actions Updated
hashdotdesign-tokens Ignored Ignored Preview Sep 4, 2026 12:45pm UTC

Request Review

@github-actions github-actions Bot added area/deps Relates to third-party dependencies (area) area/infra Relates to version control, CI, CD or IaC (area) area/libs Relates to first-party libraries/crates/packages (area) type/eng > frontend Owned by the @frontend team area/tests New or updated tests area/apps labels Sep 1, 2026
Comment thread apps/brunch-agent/src/postgres.ts Dismissed
@codspeed-hq

codspeed-hq Bot commented Sep 1, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 98 untouched benchmarks


Comparing ln/fe-1569-brunch-agent-deployment (4dc89cd) with ln/fe-1569-brunch-agent-container (9144fc8)

Open in CodSpeed

lunelson commented Sep 1, 2026

Copy link
Copy Markdown
Contributor Author

Warning

This pull request is not mergeable via GitHub because a downstack PR is open. Once all requirements are satisfied, merge this PR as a stack on Graphite.
Learn more

This stack of pull requests is managed by Graphite. Learn more about stacking.

@TimDiekmann TimDiekmann left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It would be good if we could isolate the Dockerfile changes into an own PR. The changes to .github/workflows/deploy.yml will make sure that the build is covered in CI and will upload it on merge to main. I will make sure that a ECR is set up where we can upload the image to and extend the definition with that.

Comment thread apps/brunch-agent/package.json
Comment thread apps/brunch-agent/docker/Dockerfile
@lunelson
lunelson changed the base branch from ln/fe-1563-redesign-runbook-workpiece to graphite-base/9487 September 2, 2026 11:12
@lunelson
lunelson force-pushed the ln/fe-1569-brunch-agent-deployment branch from 157730c to a118473 Compare September 2, 2026 11:12
@lunelson
lunelson changed the base branch from graphite-base/9487 to ln/fe-1569-brunch-agent-container September 2, 2026 11:12
@lunelson lunelson changed the title FE-1569: Containerize and safely deploy Brunch on HASH infrastructure FE-1569: Add durable Brunch storage and telemetry Sep 2, 2026
@vercel
vercel Bot temporarily deployed to Preview – petrinaut September 2, 2026 11:13 Inactive
@lunelson
lunelson force-pushed the ln/fe-1569-brunch-agent-deployment branch from 6b78c5c to b3ae475 Compare September 4, 2026 09:58
@lunelson
lunelson force-pushed the ln/fe-1569-brunch-agent-container branch from bda25ab to 9c3440d Compare September 4, 2026 09:58
@lunelson
lunelson marked this pull request as ready for review September 4, 2026 09:59
@lunelson
lunelson requested a review from a team as a code owner September 4, 2026 09:59
Copilot AI balanced review requested due to automatic review settings September 4, 2026 09:59
@cursor

cursor Bot commented Sep 4, 2026

Copy link
Copy Markdown

PR Summary

High Risk
Changes production conversation durability, database authentication (RDS IAM/password), and telemetry export—misconfiguration or connection bugs would block startup or affect persisted chat state.

Overview
Adds the production runtime contract for containerized Brunch: durable Flue conversation state in Postgres, mandatory content-free observability, and operator-facing smoke/probe tooling—without claiming an ECS/RDS deployment is live.

Persistence: In NODE_ENV=production, db.ts wires @flue/postgres through a new loadDatabaseConfig / createPostgresRunner stack (verified TLS, 10s connect timeout, IAM token per physical connection or password fallback). Legacy inputs like DATABASE_URL and local SQLite paths are rejected; non-production still uses SQLite.

Telemetry: OpenTelemetry moves from inline app.ts setup to telemetry-bootstrap.ts, registering HASH OTLP/gRPC exporters (traces/logs/metrics) before Flue, keeping Flue content: false, and flushing/shutting down on Postgres close. Production requires HASH_OTLP_ENDPOINT.

Ops & proof: New scripts start, probe:rds-iam, smoke:deployment, and test:integration (Docker smoke with TLS Postgres + OTel collector). README and Mission 8 handoff document env vars, ingress restrictions, and infrastructure gaps.

Tests: Unit coverage for config, Postgres/IAM, telemetry lifecycle, deployment smoke validation; build-artifact asserts production store wiring in the bundle.

Reviewed by Cursor Bugbot for commit 4dc89cd. Bugbot is set up for automated code reviews on this repo. Configure here.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

Moderate issues remain in recovery validation, database timeouts, and Flue telemetry export.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

Adds durable PostgreSQL persistence, RDS IAM support, operational telemetry, and deployment diagnostics for the Brunch service.

Changes:

  • Adds fail-closed PostgreSQL configuration with TLS and IAM/password authentication.
  • Adds OTLP telemetry lifecycle and database-failure reporting.
  • Adds deployment probes, integration tests, and infrastructure handoff documentation.
File summaries
File Review
yarn.lock Locks new database, AWS, and telemetry dependencies.
libs/@hashintel/brunch-agent/MISSION.next.md Updates deployment backlog and planning context.
libs/@hashintel/brunch-agent/MISSION.md Defines the restricted-deployment mission.
libs/@hashintel/brunch-agent/docs/evidence/implementations/mission-8-deployment-handoff.md Records proof status and infrastructure handoff.
apps/brunch-agent/turbo.json Adds telemetry environment and integration-test wiring.
apps/brunch-agent/test/telemetry.test.ts Tests telemetry configuration and disposal.
apps/brunch-agent/test/postgres.test.ts Tests pooling, IAM tokens, transactions, and shutdown.
apps/brunch-agent/test/database-config.test.ts Tests production database validation.
apps/brunch-agent/test/container-smoke.integration.ts Exercises the production container with PostgreSQL and OTel.
apps/brunch-agent/test/build-artifact.test.ts Verifies persistence and telemetry bundle wiring.
apps/brunch-agent/test/architecture/boundaries.integration.ts Registers the telemetry architecture boundary.
apps/brunch-agent/src/telemetry.ts Configures OTLP providers and failure spans. Moderate (1 vote): Flue metrics use the unregistered global meter and are not exported. Moderate (1 vote): Flue also receives no logger, dropping its logs as well as metrics.
apps/brunch-agent/src/telemetry-bootstrap.ts Installs telemetry before application initialization.
apps/brunch-agent/src/rds-iam-probe.ts Adds the two-connection IAM diagnostic.
apps/brunch-agent/src/postgres.ts Implements the Flue PostgreSQL runner. Moderate (1 vote): No connection timeout is configured, allowing database operations and diagnostics to wait indefinitely.
apps/brunch-agent/src/deployment-smoke.ts Adds remote turn and history diagnostics. Moderate (2 votes): Recovery can report success for missing/malformed history, and streamed turns do not require a successful finish.
apps/brunch-agent/src/db.ts Selects PostgreSQL in production and SQLite locally.
apps/brunch-agent/src/database-config.ts Defines fail-closed production database configuration.
apps/brunch-agent/src/app.ts Bootstraps telemetry before route setup.
apps/brunch-agent/README.md Documents runtime and deployment requirements.
apps/brunch-agent/package.json Adds dependencies and operational scripts.
apps/brunch-agent/docs/task-dependencies.json Documents the new task dependencies.
Review details

Suppressed comments (4)

apps/brunch-agent/src/postgres.ts:102

  • A pg pool emits an error event when an idle connection is broken; without a listener, Node treats that event as unhandled and terminates the process. A routine RDS restart or network partition can therefore crash Brunch instead of producing the operational database-failure telemetry added by this PR. Attach a pool error listener and report it through reportDatabaseFailure.
export const createPostgresPool = (
  config: PostgresDatabaseConfig,
  options?: ConnectionOptions,
): Pool => new Pool(createPostgresPoolConfig(config, options));

apps/brunch-agent/src/postgres.ts:200

  • If one pool.connect() resolves and the other rejects, the Promise.all assignment never completes, so clients remains empty and the successful checked-out client is omitted from the finally cleanup. The failed IAM probe then leaks that client and can leave pool.end() waiting indefinitely. Acquire while appending each client (the first remains checked out, so the second is still guaranteed to be a distinct physical connection).
  let clients: PoolClient[] = [];
  try {
    clients = await Promise.all([pool.connect(), pool.connect()]);

apps/brunch-agent/src/postgres.ts:128

  • A transaction that fails while acquiring a pooled connection bypasses the try block, so no database_operation failure span is recorded. This is the exact path taken when Postgres is unavailable during Flue startup/migration, contradicting the operational database-failure telemetry contract. Report and rethrow acquisition failures before entering the transaction body.
      const client = await pool.connect();

apps/brunch-agent/src/postgres.ts:156

  • Both expressions passed to Promise.allSettled are invoked immediately, so telemetry shutdown begins before pool.end() has completed. The unit test only observes synchronous mock side effects and therefore does not enforce the documented “close Postgres, then flush OTel” lifecycle; with a real pool, final database activity can race provider shutdown. Await the Postgres result first, then invoke the telemetry closer while still collecting both failures.
      const results = await Promise.allSettled([pool.end(), afterClose?.()]);
  • Files reviewed: 21/22 changed files
  • Comments generated: 4
  • Review effort level: Balanced

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +32 to +36
const history = (await response.json()) as { messages?: readonly unknown[] };
process.stdout.write(
`${JSON.stringify({
conversationId,
messages: history.messages?.length ?? 0,

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 4dc89cd. History mode now requires a stable conversation ID plus BRUNCH_SMOKE_EXPECTED_TEXT and verifies a text part contains it; turn mode parses the UI stream, rejects error/abort, and requires finish.

Comment on lines +67 to +71
const common: PoolConfig = {
application_name: "brunch-agent",
database: config.database,
host: config.host,
port: config.port,

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 4dc89cd. Every pool now sets connectionTimeoutMillis to the documented 10-second bound, and the pool-config test pins that value.

Comment on lines +85 to +89
const unregisterInstrumentations = registerInstrumentations({
instrumentations: [new HttpInstrumentation(), new UndiciInstrumentation()],
meterProvider,
tracerProvider: traceProvider,
});

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 4dc89cd. The HASH setup now supplies its application-owned tracer, meter, and structural logger explicitly when constructing Flue instrumentation.

Comment thread apps/brunch-agent/src/telemetry.ts Outdated
Comment on lines +148 to +151
const createFlue =
dependencies.createFlueInstrumentation ??
createOpenTelemetryInstrumentation;
const flueInstrumentation = createFlue({ content: false });

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 4dc89cd. Flue receives meterProvider.getMeter(...) explicitly, so its instruments bind to the exporting provider without relying on the global metrics API.

Comment thread apps/brunch-agent/src/telemetry.ts
Comment thread apps/brunch-agent/src/database-config.ts
Comment thread apps/brunch-agent/src/deployment-smoke.ts
@github-actions

github-actions Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Benchmark results

@rust/hash-graph-benches – Integrations

policy_resolution_large

Function Value Mean Flame graphs
resolve_policies_for_actor user: empty, selectivity: high, policies: 2002 $$28.7 \mathrm{ms} \pm 171 \mathrm{μs}\left({\color{gray}1.62 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: empty, selectivity: low, policies: 1 $$3.57 \mathrm{ms} \pm 30.0 \mathrm{μs}\left({\color{gray}1.46 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: empty, selectivity: medium, policies: 1002 $$13.7 \mathrm{ms} \pm 96.3 \mathrm{μs}\left({\color{gray}0.140 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: seeded, selectivity: high, policies: 3314 $$45.0 \mathrm{ms} \pm 443 \mathrm{μs}\left({\color{gray}0.325 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: seeded, selectivity: low, policies: 1 $$15.1 \mathrm{ms} \pm 167 \mathrm{μs}\left({\color{gray}0.055 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: seeded, selectivity: medium, policies: 1527 $$25.4 \mathrm{ms} \pm 224 \mathrm{μs}\left({\color{gray}1.87 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: system, selectivity: high, policies: 2078 $$30.1 \mathrm{ms} \pm 242 \mathrm{μs}\left({\color{gray}2.91 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: system, selectivity: low, policies: 1 $$3.87 \mathrm{ms} \pm 31.6 \mathrm{μs}\left({\color{gray}1.26 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: system, selectivity: medium, policies: 1033 $$15.3 \mathrm{ms} \pm 120 \mathrm{μs}\left({\color{gray}4.07 \mathrm{\%}}\right) $$ Flame Graph

policy_resolution_medium

Function Value Mean Flame graphs
resolve_policies_for_actor user: empty, selectivity: high, policies: 102 $$3.99 \mathrm{ms} \pm 31.3 \mathrm{μs}\left({\color{gray}2.73 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: empty, selectivity: low, policies: 1 $$3.07 \mathrm{ms} \pm 21.4 \mathrm{μs}\left({\color{gray}0.938 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: empty, selectivity: medium, policies: 52 $$3.50 \mathrm{ms} \pm 26.8 \mathrm{μs}\left({\color{gray}2.13 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: seeded, selectivity: high, policies: 269 $$5.42 \mathrm{ms} \pm 52.7 \mathrm{μs}\left({\color{gray}0.778 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: seeded, selectivity: low, policies: 1 $$3.68 \mathrm{ms} \pm 26.8 \mathrm{μs}\left({\color{gray}1.41 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: seeded, selectivity: medium, policies: 108 $$4.36 \mathrm{ms} \pm 38.6 \mathrm{μs}\left({\color{gray}0.494 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: system, selectivity: high, policies: 133 $$4.69 \mathrm{ms} \pm 42.5 \mathrm{μs}\left({\color{gray}2.50 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: system, selectivity: low, policies: 1 $$3.55 \mathrm{ms} \pm 24.9 \mathrm{μs}\left({\color{gray}2.50 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: system, selectivity: medium, policies: 63 $$4.43 \mathrm{ms} \pm 43.1 \mathrm{μs}\left({\color{red}5.43 \mathrm{\%}}\right) $$ Flame Graph

policy_resolution_none

Function Value Mean Flame graphs
resolve_policies_for_actor user: empty, selectivity: high, policies: 2 $$2.82 \mathrm{ms} \pm 21.2 \mathrm{μs}\left({\color{gray}0.497 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: empty, selectivity: low, policies: 1 $$2.73 \mathrm{ms} \pm 15.3 \mathrm{μs}\left({\color{gray}-0.231 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: empty, selectivity: medium, policies: 2 $$2.86 \mathrm{ms} \pm 20.1 \mathrm{μs}\left({\color{gray}-0.062 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: system, selectivity: high, policies: 8 $$3.15 \mathrm{ms} \pm 22.0 \mathrm{μs}\left({\color{gray}1.07 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: system, selectivity: low, policies: 1 $$2.96 \mathrm{ms} \pm 20.5 \mathrm{μs}\left({\color{gray}1.26 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: system, selectivity: medium, policies: 3 $$3.25 \mathrm{ms} \pm 28.6 \mathrm{μs}\left({\color{gray}0.728 \mathrm{\%}}\right) $$ Flame Graph

policy_resolution_small

Function Value Mean Flame graphs
resolve_policies_for_actor user: empty, selectivity: high, policies: 52 $$3.19 \mathrm{ms} \pm 24.9 \mathrm{μs}\left({\color{gray}1.40 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: empty, selectivity: low, policies: 1 $$2.90 \mathrm{ms} \pm 21.1 \mathrm{μs}\left({\color{gray}2.37 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: empty, selectivity: medium, policies: 26 $$3.09 \mathrm{ms} \pm 22.2 \mathrm{μs}\left({\color{gray}1.99 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: seeded, selectivity: high, policies: 94 $$3.69 \mathrm{ms} \pm 22.9 \mathrm{μs}\left({\color{gray}2.92 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: seeded, selectivity: low, policies: 1 $$3.15 \mathrm{ms} \pm 21.4 \mathrm{μs}\left({\color{gray}1.16 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: seeded, selectivity: medium, policies: 27 $$3.41 \mathrm{ms} \pm 23.4 \mathrm{μs}\left({\color{gray}0.080 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: system, selectivity: high, policies: 66 $$3.58 \mathrm{ms} \pm 30.6 \mathrm{μs}\left({\color{gray}-0.001 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: system, selectivity: low, policies: 1 $$3.19 \mathrm{ms} \pm 24.0 \mathrm{μs}\left({\color{gray}2.28 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: system, selectivity: medium, policies: 29 $$3.50 \mathrm{ms} \pm 27.2 \mathrm{μs}\left({\color{gray}0.683 \mathrm{\%}}\right) $$ Flame Graph

read_scaling_complete

Function Value Mean Flame graphs
entity_by_id;one_depth 1 entities $$33.3 \mathrm{ms} \pm 242 \mathrm{μs}\left({\color{gray}2.41 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id;one_depth 10 entities $$72.6 \mathrm{ms} \pm 505 \mathrm{μs}\left({\color{gray}0.249 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id;one_depth 25 entities $$37.2 \mathrm{ms} \pm 217 \mathrm{μs}\left({\color{gray}0.937 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id;one_depth 5 entities $$40.5 \mathrm{ms} \pm 286 \mathrm{μs}\left({\color{gray}0.622 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id;one_depth 50 entities $$43.2 \mathrm{ms} \pm 228 \mathrm{μs}\left({\color{gray}0.928 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id;two_depth 1 entities $$34.9 \mathrm{ms} \pm 217 \mathrm{μs}\left({\color{gray}1.38 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id;two_depth 10 entities $$424 \mathrm{ms} \pm 1.31 \mathrm{ms}\left({\color{gray}-0.497 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id;two_depth 25 entities $$92.9 \mathrm{ms} \pm 677 \mathrm{μs}\left({\color{gray}-1.032 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id;two_depth 5 entities $$79.6 \mathrm{ms} \pm 429 \mathrm{μs}\left({\color{gray}-1.606 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id;two_depth 50 entities $$311 \mathrm{ms} \pm 1.26 \mathrm{ms}\left({\color{red}11.5 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id;zero_depth 1 entities $$10.8 \mathrm{ms} \pm 71.1 \mathrm{μs}\left({\color{gray}1.21 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id;zero_depth 10 entities $$10.9 \mathrm{ms} \pm 83.0 \mathrm{μs}\left({\color{gray}-0.779 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id;zero_depth 25 entities $$11.2 \mathrm{ms} \pm 99.4 \mathrm{μs}\left({\color{gray}1.43 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id;zero_depth 5 entities $$11.0 \mathrm{ms} \pm 79.3 \mathrm{μs}\left({\color{gray}1.97 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id;zero_depth 50 entities $$10.9 \mathrm{ms} \pm 71.3 \mathrm{μs}\left({\color{gray}-1.219 \mathrm{\%}}\right) $$ Flame Graph

read_scaling_linkless

Function Value Mean Flame graphs
entity_by_id 1 entities $$10.8 \mathrm{ms} \pm 66.8 \mathrm{μs}\left({\color{gray}0.898 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id 10 entities $$10.8 \mathrm{ms} \pm 81.5 \mathrm{μs}\left({\color{gray}0.564 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id 100 entities $$10.9 \mathrm{ms} \pm 73.3 \mathrm{μs}\left({\color{gray}0.301 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id 1000 entities $$11.0 \mathrm{ms} \pm 78.4 \mathrm{μs}\left({\color{gray}1.30 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id 10000 entities $$11.2 \mathrm{ms} \pm 116 \mathrm{μs}\left({\color{gray}1.45 \mathrm{\%}}\right) $$ Flame Graph

representative_read_entity

Function Value Mean Flame graphs
entity_by_id entity type ID: https://blockprotocol.org/@alice/types/entity-type/block/v/1 $$11.4 \mathrm{ms} \pm 86.3 \mathrm{μs}\left({\color{gray}2.92 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id entity type ID: https://blockprotocol.org/@alice/types/entity-type/book/v/1 $$11.3 \mathrm{ms} \pm 66.0 \mathrm{μs}\left({\color{gray}2.24 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id entity type ID: https://blockprotocol.org/@alice/types/entity-type/building/v/1 $$11.2 \mathrm{ms} \pm 76.0 \mathrm{μs}\left({\color{gray}0.722 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id entity type ID: https://blockprotocol.org/@alice/types/entity-type/organization/v/1 $$11.2 \mathrm{ms} \pm 71.7 \mathrm{μs}\left({\color{gray}1.57 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id entity type ID: https://blockprotocol.org/@alice/types/entity-type/page/v/2 $$11.5 \mathrm{ms} \pm 96.4 \mathrm{μs}\left({\color{gray}2.81 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id entity type ID: https://blockprotocol.org/@alice/types/entity-type/person/v/1 $$11.2 \mathrm{ms} \pm 81.8 \mathrm{μs}\left({\color{gray}0.103 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id entity type ID: https://blockprotocol.org/@alice/types/entity-type/playlist/v/1 $$11.2 \mathrm{ms} \pm 75.5 \mathrm{μs}\left({\color{gray}3.23 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id entity type ID: https://blockprotocol.org/@alice/types/entity-type/song/v/1 $$11.4 \mathrm{ms} \pm 99.2 \mathrm{μs}\left({\color{gray}0.797 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id entity type ID: https://blockprotocol.org/@alice/types/entity-type/uk-address/v/1 $$11.2 \mathrm{ms} \pm 76.4 \mathrm{μs}\left({\color{gray}-0.426 \mathrm{\%}}\right) $$ Flame Graph

representative_read_entity_type

Function Value Mean Flame graphs
get_entity_type_by_id Account ID: bf5a9ef5-dc3b-43cf-a291-6210c0321eba $$8.25 \mathrm{ms} \pm 51.8 \mathrm{μs}\left({\color{gray}1.55 \mathrm{\%}}\right) $$ Flame Graph

representative_read_multiple_entities

Function Value Mean Flame graphs
entity_by_property traversal_paths=0 0 $$59.9 \mathrm{ms} \pm 417 \mathrm{μs}\left({\color{red}5.54 \mathrm{\%}}\right) $$
entity_by_property traversal_paths=255 1,resolve_depths=inherit:1;values:255;properties:255;links:127;link_dests:126;type:true $$113 \mathrm{ms} \pm 540 \mathrm{μs}\left({\color{gray}1.36 \mathrm{\%}}\right) $$
entity_by_property traversal_paths=2 1,resolve_depths=inherit:0;values:0;properties:0;links:0;link_dests:0;type:false $$65.5 \mathrm{ms} \pm 543 \mathrm{μs}\left({\color{gray}4.22 \mathrm{\%}}\right) $$
entity_by_property traversal_paths=2 1,resolve_depths=inherit:0;values:0;properties:0;links:1;link_dests:0;type:true $$74.8 \mathrm{ms} \pm 674 \mathrm{μs}\left({\color{gray}2.26 \mathrm{\%}}\right) $$
entity_by_property traversal_paths=2 1,resolve_depths=inherit:0;values:0;properties:2;links:1;link_dests:0;type:true $$84.3 \mathrm{ms} \pm 546 \mathrm{μs}\left({\color{gray}3.31 \mathrm{\%}}\right) $$
entity_by_property traversal_paths=2 1,resolve_depths=inherit:0;values:2;properties:2;links:1;link_dests:0;type:true $$91.2 \mathrm{ms} \pm 581 \mathrm{μs}\left({\color{gray}2.25 \mathrm{\%}}\right) $$
link_by_source_by_property traversal_paths=0 0 $$47.8 \mathrm{ms} \pm 302 \mathrm{μs}\left({\color{gray}1.79 \mathrm{\%}}\right) $$
link_by_source_by_property traversal_paths=255 1,resolve_depths=inherit:1;values:255;properties:255;links:127;link_dests:126;type:true $$76.4 \mathrm{ms} \pm 513 \mathrm{μs}\left({\color{gray}2.71 \mathrm{\%}}\right) $$
link_by_source_by_property traversal_paths=2 1,resolve_depths=inherit:0;values:0;properties:0;links:0;link_dests:0;type:false $$54.2 \mathrm{ms} \pm 419 \mathrm{μs}\left({\color{gray}3.08 \mathrm{\%}}\right) $$
link_by_source_by_property traversal_paths=2 1,resolve_depths=inherit:0;values:0;properties:0;links:1;link_dests:0;type:true $$62.8 \mathrm{ms} \pm 426 \mathrm{μs}\left({\color{gray}2.62 \mathrm{\%}}\right) $$
link_by_source_by_property traversal_paths=2 1,resolve_depths=inherit:0;values:0;properties:2;links:1;link_dests:0;type:true $$65.3 \mathrm{ms} \pm 411 \mathrm{μs}\left({\color{gray}3.92 \mathrm{\%}}\right) $$
link_by_source_by_property traversal_paths=2 1,resolve_depths=inherit:0;values:2;properties:2;links:1;link_dests:0;type:true $$65.5 \mathrm{ms} \pm 487 \mathrm{μs}\left({\color{gray}2.99 \mathrm{\%}}\right) $$

scenarios

Function Value Mean Flame graphs
full_test query-limited $$114 \mathrm{ms} \pm 573 \mathrm{μs}\left({\color{gray}-1.244 \mathrm{\%}}\right) $$ Flame Graph
full_test query-unlimited $$127 \mathrm{ms} \pm 638 \mathrm{μs}\left({\color{gray}-1.060 \mathrm{\%}}\right) $$ Flame Graph
linked_queries query-limited $$24.1 \mathrm{ms} \pm 137 \mathrm{μs}\left({\color{gray}-0.341 \mathrm{\%}}\right) $$ Flame Graph
linked_queries query-unlimited $$523 \mathrm{ms} \pm 1.73 \mathrm{ms}\left({\color{gray}0.179 \mathrm{\%}}\right) $$ Flame Graph

Comment thread apps/brunch-agent/src/postgres.ts
lunelson and others added 8 commits September 4, 2026 14:23
Co-authored-by: Cursor <cursoragent@cursor.com>

Normalize deployment dependency lockfile

Co-authored-by: Cursor <cursoragent@cursor.com>

Publish the Brunch image to GHCR

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 4dc89cd. Configure here.

export const createPostgresPool = (
config: PostgresDatabaseConfig,
options?: ConnectionOptions,
): Pool => new Pool(createPostgresPoolConfig(config, options));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Idle pool errors crash the process

High Severity

The new Pool never listens for error. When RDS drops an idle client, pg emits that event on the pool; Node then treats it as an uncaught exception and exits. A routine failover or idle timeout can take the whole Brunch task down after it has already become healthy.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 4dc89cd. Configure here.

throw failure;
} finally {
client.release();
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Transaction connect failures skip telemetry

Medium Severity

transaction calls pool.connect() outside the try that reports database failures. Checkout failures, including IAM token errors raised from the password callback, never emit the operational database_operation span that query records, so hosted telemetry misses the main write-path outage.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 4dc89cd. Configure here.

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

Labels

area/apps area/deps Relates to third-party dependencies (area) area/infra Relates to version control, CI, CD or IaC (area) area/libs Relates to first-party libraries/crates/packages (area) area/tests New or updated tests type/eng > frontend Owned by the @frontend team

Development

Successfully merging this pull request may close these issues.

4 participants