Skip to content

trunk-merge/pr-110700/09492e3e-7f72-4406-a312-1f11594d193d - #110853

Closed
trunk-io[bot] wants to merge 12 commits into
masterfrom
trunk-merge/pr-110700/09492e3e-7f72-4406-a312-1f11594d193d
Closed

trunk-io[bot] wants to merge 12 commits into
masterfrom
trunk-merge/pr-110700/09492e3e-7f72-4406-a312-1f11594d193d

Conversation

@trunk-io

@trunk-io trunk-io Bot commented Oct 2, 2026

Copy link
Copy Markdown
Trunk Merge Pull Request Banner

This pull request was created and is being managed by Trunk Merge.

This pull request is based on the master branch at SHA e6b3dfe3fa152fd136497b749e3287220b23368f.

See more details about each PR in the batch here:

When CI completes, this pull request will be closed automatically.

Pull Requests Being Tested

This pull request is testing a batch with the changes from pull requests 110700, 110666, 110732, and 110409 - batching documentation.

posthog Bot and others added 12 commits October 1, 2026 23:51
…stem tables

Add system.autoresearch_training_runs, system.autoresearch_iterations and system.autoresearch_models as HogQL PostgresTables with access_scope "autoresearch", team_id, and a description on every field. Add team isolation factories through the autoresearch testing facade.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Generated-By: PostHog Desktop
Task-Id: 36443bea-8bcc-43cb-a5d1-ad8b183eb0d0
The overview counts series per service by scanning every series-hour
row of the last day. The services_by_hour projection keeps that rollup
per team, hour and service, so the query can read a few small rows.

ReplacingMergeTree needs deduplicate_merge_projection_mode = 'rebuild'
before it accepts a projection and to keep it through merges. A
settings-only ALTER changes only the replica that runs it, so that
statement runs on every LOGS host.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Generated-By: PostHog Desktop
Task-Id: 3538a544-5668-418a-aebd-c3430d18864a
The open head used the impressed cohort, so an open from a deeplink, the desktop app or another direct link never became a training example. The head now uses the everyone cohort, and the label still counts an open from any surface within 3 days.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Generated-By: PostHog Desktop
Task-Id: 3150e9d0-d077-4e0e-b755-b3da29781c73
…bles

Add addon_attachments, pipeline_couplings and team_monthly_usage tables to the Heroku source. Generalize fan-out to take any parent endpoint so usage can fan out over Enterprise teams. Skip `plan`: Heroku has no plan list endpoint, and add-ons already carry billed_price and the plan name.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Generated-By: PostHog Desktop
Task-Id: 38158f29-8b9a-45da-89d2-76452341e736
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Generated-By: PostHog Desktop
Task-Id: 38158f29-8b9a-45da-89d2-76452341e736
Adopt the repository's frozen dataclass default for the Heroku resume and endpoint configuration value objects, satisfying the developer-experience Semgrep rule.
Adopt the repository's frozen dataclass default for the Heroku resume and endpoint configuration value objects, satisfying the developer-experience Semgrep rule.
@coderabbitai

coderabbitai Bot commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

🧰 Additional context used
📚 Code guidelines (17)
posthog/clickhouse/hcl/README.md — configured
.agents/skills/clickhouse-migrations/SKILL.md — configured
.agents/security.md — configured
products/signals/dags/inbox_ranking/AGENTS.md — auto-discovered
.agents/skills/sending-notifications/SKILL.md — configured
docs/published/handbook/engineering/type-system.md — configured
.agents/skills/writing-tests/SKILL.md — configured
posthog/clickhouse/migrations/README.md — configured
docs/published/handbook/engineering/databases/clickhouse-event-table-migrations.md — configured
docs/internal/person-data-access.md — configured
.agents/skills/adopting-generated-api-types/SKILL.md — configured
.agents/skills/django-migrations/SKILL.md — configured
docs/published/handbook/engineering/safe-django-migrations.md — configured
docs/published/handbook/engineering/databases/schema-changes.md — configured
.agents/skills/writing-clickhouse-queries/SKILL.md — configured
.claude/commands/conventions.md — configured
… and 1 more
📝 Walkthrough

Walkthrough

The change updates metrics4_series with a services_by_hour projection and a migration to apply it. It exposes three autoresearch tables and adds test record helpers. The inbox ranking open head now includes reports without impressions. The Heroku source adds three resources and supports configurable parent fan-out and monthly usage windows.

Priority: ➖ Normal

Merge Risk: 🟠 High · up to 5bef9

Restricted autoresearch pipeline records may be visible to users denied access to those pipelines. Correct the three table mappings before merging; update the Heroku catalog summary as well.

Security Architecture Review

Security architecture risk: 🔵 Low · up to 5bef9

The change expands access to training metadata and external account usage data. Team filtering and sensitive-URL redaction remain in the inspected paths, and no introduced authorization bypass was established. Import ownership, final persistence, and recovery coverage remain incomplete.

Retained concerns
No architecture-level concerns identified.

Security review details

Security Blast Radius

  • inferred — The autoresearch change broadens query access to training and model metadata within the caller's team on the inspected federated-read path. The mandatory context-derived team predicate bounds that path; no cross-team read or new execution authority was established.
  • observed — Monthly-usage fan-out can read every Enterprise team returned to the supplied Heroku credential, rather than one explicitly selected provider team. This expands imported data scope without changing the credential mechanism. A provider team is not inherently the same identity as the destination project; the complete authorization binding between them remains unverified.

Security Findings and Attack Paths

  • inferred — The proposed attack through denied pipeline IDs was not substantiated under current effective autoresearch policy: object-control registration requires a mixin absent from the inspected autoresearch viewsets. The canonical review retains no verified findings, but its three deferred candidates remain unresolved at the verification-receipt level.

Trust Boundaries and Controls

  • observed — Heroku requests retain a fixed API base URL and bearer authentication. Raw-response capture is disabled, the API key is supplied to the tracking session's redaction configuration, and configured capability URLs are nulled before row yield. Add-on attachment log-input URLs join the existing protected capability fields.

Resilience and Maintainability Implications

  • observed — Fan-out checkpoints distinguish completed parents from an in-progress child; deleted-parent 404 responses are ignored, while authorization errors propagate. Resume state is keyed by local team and job, and staged checkpoints are committed separately from source enumeration. The manager provides cleanup, but successful cleanup invocation and concurrent attempts sharing one job were not established.

Hardening Proposals

  • proposed — If pipeline-level restrictions are introduced later, define whether child records inherit pipeline authority and align all three table identity mappings before enabling those restrictions. This is future control-drift prevention, not a finding about current policy.
🚥 Pre-merge checks | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description only documents a Trunk Merge batch and lists included pull requests. It does not explain the user-facing problem, changes, testing, release status, documentation impact, or required ag… Replace the merge-queue boilerplate with a standalone description using the required sections: Problem, Changes, How did you test this code?, Test rationale, Release status, Automatic notifications, Docs update, and Agent context. Summarize…
Full details: Description check

Explanation

The description only documents a Trunk Merge batch and lists included pull requests. It does not explain the user-facing problem, changes, testing, release status, documentation impact, or required agent context for this repository.

Resolution

Replace the merge-queue boilerplate with a standalone description using the required sections: Problem, Changes, How did you test this code?, Test rationale, Release status, Automatic notifications, Docs update, and Agent context. Summarize the bundled changes, testing evidence, feature-flag status, and agent details.

  • Fix all pre-merge checks with AI
✨ Finishing Touches
📝 Generate docstrings
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Autopilot is currently an internal CodeRabbit preview.


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

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 3

Note

Quiet mode is enabled, so only the most important comments were posted inline. Other review comments are grouped below.

🟡 Other comments (1)
products/warehouse_sources/backend/temporal/data_imports/sources/COVERAGE_GAPS_APPENDIX.md-4004-4004 (1)

4004-4004: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Update the Heroku coverage summary.

The summary at Line 3995 still lists eleven tables. These changes add three tables, so readers do not see the current catalog there. Update the count to fourteen and add addon_attachments, pipeline_couplings, and team_monthly_usage to that list.


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository: PostHog/posthog/.coderabbit.yaml

Review profile: QUIET

Plan: Enterprise

Run ID: 5d16a213-1914-4d9a-abae-0f7efdcf60cd

📥 Commits

Reviewing files that changed from the base of the PR and between e6b3dfe and 5bef9db.

📒 Files selected for processing (28)
  • posthog/clickhouse/hcl/golden/dev/logs.hcl
  • posthog/clickhouse/hcl/golden/local-multi/logs.hcl
  • posthog/clickhouse/hcl/golden/local-single/all.hcl
  • posthog/clickhouse/hcl/golden/prod-eu/logs.hcl
  • posthog/clickhouse/hcl/golden/prod-us/logs.hcl
  • posthog/clickhouse/hcl/roles/logs/metrics/metrics4.hcl
  • posthog/clickhouse/hcl/sql/dev/logs.sql
  • posthog/clickhouse/hcl/sql/local-multi/logs.sql
  • posthog/clickhouse/hcl/sql/local-single/all.sql
  • posthog/clickhouse/hcl/sql/prod-eu/logs.sql
  • posthog/clickhouse/hcl/sql/prod-us/logs.sql
  • posthog/clickhouse/metrics/metrics4.py
  • posthog/clickhouse/migrations/0345_metrics4_series_services_projection.py
  • posthog/clickhouse/migrations/max_migration.txt
  • posthog/clickhouse/test/__snapshots__/test_schema.ambr
  • posthog/hogql/database/schema/system.py
  • posthog/hogql/database/schema/test/test_system_tables.py
  • posthog/hogql/database/test/__snapshots__/test_database.ambr
  • products/autoresearch/backend/facade/testing.py
  • products/signals/dags/inbox_ranking/README.md
  • products/signals/dags/inbox_ranking/tests/test_training.py
  • products/signals/dags/inbox_ranking/training/heads.py
  • products/warehouse_sources/backend/temporal/data_imports/sources/COVERAGE_GAPS_APPENDIX.md
  • products/warehouse_sources/backend/temporal/data_imports/sources/heroku/canonical_descriptions.py
  • products/warehouse_sources/backend/temporal/data_imports/sources/heroku/heroku.py
  • products/warehouse_sources/backend/temporal/data_imports/sources/heroku/settings.py
  • products/warehouse_sources/backend/temporal/data_imports/sources/heroku/source.py
  • products/warehouse_sources/backend/temporal/data_imports/sources/heroku/tests/test_heroku.py

Included review availability: This review used your included allowance. Your plan provides up to 12 included reviews per hour; 0 remain after this review.

Comment on lines +1068 to +1108
autoresearch_training_runs: PostgresTable = PostgresTable(
name="autoresearch_training_runs",
postgres_table_name="autoresearch_autoresearchtrainingrun",
access_scope="autoresearch",
description="Autoresearch training runs; one row per bounded agent session that searches for a better model for a pipeline.",
fields={
"id": UUIDDatabaseField(name="id", description="Training run UUID."),
"team_id": IntegerDatabaseField(name="team_id", description="Team the training run belongs to."),
"pipeline_id": UUIDDatabaseField(
name="pipeline_id", description="Pipeline the run trains for; joins to autoresearch_pipelines.id."
),
"task_id": UUIDDatabaseField(
name="task_id", nullable=True, description="Task that runs the agent sandbox; joins to tasks.id."
),
"task_run_id": UUIDDatabaseField(
name="task_run_id", nullable=True, description="Task run of the agent sandbox; joins to task_runs.id."
),
"status": StringDatabaseField(name="status", description="One of pending, running, completed, failed."),
"iteration_budget": IntegerDatabaseField(
name="iteration_budget", description="Maximum iterations the run may record."
),
"iteration_count": IntegerDatabaseField(name="iteration_count", description="Iterations the run recorded."),
"best_holdout_score": FloatDatabaseField(
name="best_holdout_score",
nullable=True,
description="Best holdout AUC (0 to 1) of the run's iterations (NULL before the first scored iteration).",
),
"error": StringDatabaseField(name="error", description="Failure message; blank when the run did not fail."),
"summary": StringJSONDatabaseField(
name="summary",
description="JSON summary written on completion: champion, kept iterations, dead ends, and next steps.",
),
"started_at": DateTimeDatabaseField(
name="started_at", nullable=True, description="When the run started (NULL while pending)."
),
"completed_at": DateTimeDatabaseField(
name="completed_at", nullable=True, description="When the run finished (NULL while it is not finished)."
),
"created_at": DateTimeDatabaseField(name="created_at", description="When the run was created."),
},
)

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.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🔎 Supported by static analysis

🏁 Script executed:

sed -n '1020,1225p' posthog/hogql/database/schema/system.py
sed -n '3300,3320p' posthog/hogql/database/schema/system.py
sed -n '900,965p' posthog/hogql/database/schema/test/test_system_tables.py

Repository: PostHog/posthog

Length of output: 15870


🏁 Script executed:

#!/bin/bash
set -e
printf '%s\n' '--- PostgresTable and access-scope bindings ---'
rg -n --glob '*.py' 'class PostgresTable|access_scope|PostgresTable\(' posthog/hogql posthog | head -240
printf '%s\n' '--- autoresearch model declarations and team ownership ---'
rg -n --glob '*.py' 'class Autoresearch|class AutoResearch|autoresearch_autoresearch|autoresearch_training_runs|AutoresearchTrainingRun|AutoResearchTrainingRun' posthog ee products | head -240
printf '%s\n' '--- isolation test references ---'
rg -n --glob '*.py' 'SYSTEM_TABLE_FACTORIES|system table|access_scope|team_id.*filter|PostgresTable' posthog/hogql/database/schema/test posthog/hogql | head -260

Repository: PostHog/posthog

Length of output: 41795


🏁 Script executed:

#!/bin/bash
set -e
printf '%s\n' '--- PostgresTable implementation ---'
sed -n '1,240p' posthog/hogql/database/postgres_table.py
printf '%s\n' '--- scoped-table validation tests ---'
sed -n '470,645p' posthog/hogql/database/test/test_postgres_table.py
printf '%s\n' '--- autoresearch models ---'
sed -n '230,330p' products/autoresearch/backend/models.py
printf '%s\n' '--- autoresearch factories and isolation test ---'
rg -n -C 12 --glob '*.py' '_create_autoresearch_(training_run|pipeline|iteration|model)|autoresearch_training_runs|test_access_scoped_system_tables_are_filtered|team_id WHERE|isolation' posthog/hogql/database/schema/test/test_system_tables.py posthog/hogql/database/schema/test/test_information_schema.py

Repository: PostHog/posthog

Length of output: 41718


🏁 Script executed:

#!/bin/bash
set -e
printf '%s\n' '--- query-time table filtering ---'
rg -n -C 18 --glob '*.py' '_print_table_ref|team_id.*filter|team_id.*equals|blocked_resource_ids_by_scope|access_control_id' posthog/hogql posthog/hogql_queries | head -360
printf '%s\n' '--- autoresearch scope and access-control registration ---'
rg -n -C 12 --glob '*.py' 'autoresearch|RESOURCE_INHERITANCE_MAP|scope_object|_object_grant_registry|_object_grant_scopes' posthog/scopes.py posthog/hogql_queries posthog/api products/autoresearch | head -360
printf '%s\n' '--- model inheritance and isolation assertions ---'
rg -n -C 20 --glob '*.py' 'class PipelineScopedModel|class AutoresearchPipeline|class AutoresearchTrainingRun|def test_system_table_is_isolated|execute_hogql_query|other_team' products/autoresearch/backend/models.py posthog/hogql/database/schema/test/test_system_tables.py | head -420

Repository: PostHog/posthog

Length of output: 42169


🏁 Script executed:

#!/bin/bash
set -e
printf '%s\n' '--- ClickHouse table-reference guard ---'
rg -n -C 28 --glob '*.py' 'def _print_table_ref|def build_access_control_guard|team_id.*context.team_id|context.team_id not found' posthog/hogql/printer posthog/hogql_queries
printf '%s\n' '--- autoresearch resource registrations ---'
rg -n -C 18 --glob '*.py' 'autoresearch.*scope|scope.*autoresearch|APIScopeObject|RESOURCE_INHERITANCE_MAP|scope_object|AccessControlViewSetMixin' posthog/scopes.py posthog/hogql_queries/access_controlled_resources.py products/autoresearch posthog/api | head -420
printf '%s\n' '--- model base classes and team fields ---'
rg -n -C 18 --glob '*.py' 'class TeamScopedRootMixin|class PipelineScopedModel|team_id|TeamScoped' products/autoresearch/backend/models.py posthog/models | head -300

Repository: PostHog/posthog

Length of output: 42371


🏁 Script executed:

#!/bin/bash
set -e
printf '%s\n' '--- access-control resource registry ---'
sed -n '1,260p' posthog/hogql_queries/access_controlled_resources.py
printf '%s\n' '--- autoresearch API and access-control declarations ---'
rg -n -C 20 --glob '*.py' 'Autoresearch|autoresearch|AccessControlViewSetMixin|scope_object|access_level' products/autoresearch posthog/api posthog | grep -E 'autoresearch|Autoresearch|AccessControlViewSetMixin|scope_object' | head -360
printf '%s\n' '--- changed table and comparable child-table declarations ---'
sed -n '1020,1185p' posthog/hogql/database/schema/system.py
rg -n -C 8 --glob '*.py' 'access_control_id_field=.*pipeline|access_control_id_field=.*_id' posthog/hogql/database/schema/system.py | head -220

Repository: PostHog/posthog

Length of output: 41624


🏁 Script executed:

#!/bin/bash
set -e
printf '%s\n' '--- object-grant registry and autoresearch scope ---'
rg -n -C 14 --glob '*.py' 'def _object_grant_registry|def _object_grant_scopes|AutoresearchPipeline|autoresearch' posthog/hogql/database/test/test_postgres_table.py products/access_control posthog/scopes.py | head -360
printf '%s\n' '--- exact ClickHouse wrapping block ---'
sed -n '1122,1185p' posthog/hogql/printer/clickhouse.py
printf '%s\n' '--- exact isolation test ---'
sed -n '1035,1110p' posthog/hogql/database/schema/test/test_system_tables.py

Repository: PostHog/posthog

Length of output: 14736


🏁 Script executed:

#!/bin/bash
set -e
sed -n '60,145p' posthog/hogql/database/test/test_postgres_table.py
printf '%s\n' '--- exact autoresearch scope-object declarations ---'
rg -n -C 8 --glob '*.py' 'scope_object\s*=\s*["'\"']autoresearch|scope_object.*autoresearch|autoresearch.*scope_object|class .*Autoresearch' products posthog/api posthog | head -240

Repository: PostHog/posthog

Length of output: 24907


🏁 Script executed:

#!/bin/bash
set -e
printf '%s\n' '--- facade object-grant table mapping ---'
sed -n '1,65p' posthog/hogql/database/test/test_postgres_table.py
printf '%s\n' '--- exact access-control guard ---'
rg -n -C 45 --glob '*.py' '^def build_access_control_guard|access_control_id|blocked_resource_ids_by_scope|allowlisted_resource_ids_by_scope' posthog/hogql/printer/access_control.py posthog/hogql/printer/clickhouse.py posthog/hogql_queries | head -320

Repository: PostHog/posthog

Length of output: 24015


🏁 Script executed:

#!/bin/bash
set -e
rg -n -C 20 --glob '*.py' 'class TeamAndOrgViewSetMixin|class AccessControlViewSetMixin|scope_object' posthog/api products/access_control products/autoresearch/backend/presentation/views/views.py | head -280

Repository: PostHog/posthog

Length of output: 27962


Filter training runs by their pipeline access grant.

When a user has autoresearch access but lacks access to a pipeline, the guard compares blocked resource IDs with the training run's own id. The blocked pipeline ID does not match, so the query can return runs for that pipeline. Set access_control_id_field to pipeline_id.

Suggested fix
     postgres_table_name="autoresearch_autoresearchtrainingrun",
     access_scope="autoresearch",
+    access_control_id_field="pipeline_id",
     description="Autoresearch training runs; one row per bounded agent session that searches for a better model for a pipeline.",
📝 Committable suggestion

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

Suggested change
autoresearch_training_runs: PostgresTable = PostgresTable(
name="autoresearch_training_runs",
postgres_table_name="autoresearch_autoresearchtrainingrun",
access_scope="autoresearch",
description="Autoresearch training runs; one row per bounded agent session that searches for a better model for a pipeline.",
fields={
"id": UUIDDatabaseField(name="id", description="Training run UUID."),
"team_id": IntegerDatabaseField(name="team_id", description="Team the training run belongs to."),
"pipeline_id": UUIDDatabaseField(
name="pipeline_id", description="Pipeline the run trains for; joins to autoresearch_pipelines.id."
),
"task_id": UUIDDatabaseField(
name="task_id", nullable=True, description="Task that runs the agent sandbox; joins to tasks.id."
),
"task_run_id": UUIDDatabaseField(
name="task_run_id", nullable=True, description="Task run of the agent sandbox; joins to task_runs.id."
),
"status": StringDatabaseField(name="status", description="One of pending, running, completed, failed."),
"iteration_budget": IntegerDatabaseField(
name="iteration_budget", description="Maximum iterations the run may record."
),
"iteration_count": IntegerDatabaseField(name="iteration_count", description="Iterations the run recorded."),
"best_holdout_score": FloatDatabaseField(
name="best_holdout_score",
nullable=True,
description="Best holdout AUC (0 to 1) of the run's iterations (NULL before the first scored iteration).",
),
"error": StringDatabaseField(name="error", description="Failure message; blank when the run did not fail."),
"summary": StringJSONDatabaseField(
name="summary",
description="JSON summary written on completion: champion, kept iterations, dead ends, and next steps.",
),
"started_at": DateTimeDatabaseField(
name="started_at", nullable=True, description="When the run started (NULL while pending)."
),
"completed_at": DateTimeDatabaseField(
name="completed_at", nullable=True, description="When the run finished (NULL while it is not finished)."
),
"created_at": DateTimeDatabaseField(name="created_at", description="When the run was created."),
},
)
autoresearch_training_runs: PostgresTable = PostgresTable(
name="autoresearch_training_runs",
postgres_table_name="autoresearch_autoresearchtrainingrun",
access_scope="autoresearch",
access_control_id_field="pipeline_id",
description="Autoresearch training runs; one row per bounded agent session that searches for a better model for a pipeline.",
fields={
"id": UUIDDatabaseField(name="id", description="Training run UUID."),
"team_id": IntegerDatabaseField(name="team_id", description="Team the training run belongs to."),
"pipeline_id": UUIDDatabaseField(
name="pipeline_id", description="Pipeline the run trains for; joins to autoresearch_pipelines.id."
),
"task_id": UUIDDatabaseField(
name="task_id", nullable=True, description="Task that runs the agent sandbox; joins to tasks.id."
),
"task_run_id": UUIDDatabaseField(
name="task_run_id", nullable=True, description="Task run of the agent sandbox; joins to task_runs.id."
),
"status": StringDatabaseField(name="status", description="One of pending, running, completed, failed."),
"iteration_budget": IntegerDatabaseField(
name="iteration_budget", description="Maximum iterations the run may record."
),
"iteration_count": IntegerDatabaseField(name="iteration_count", description="Iterations the run recorded."),
"best_holdout_score": FloatDatabaseField(
name="best_holdout_score",
nullable=True,
description="Best holdout AUC (0 to 1) of the run's iterations (NULL before the first scored iteration).",
),
"error": StringDatabaseField(name="error", description="Failure message; blank when the run did not fail."),
"summary": StringJSONDatabaseField(
name="summary",
description="JSON summary written on completion: champion, kept iterations, dead ends, and next steps.",
),
"started_at": DateTimeDatabaseField(
name="started_at", nullable=True, description="When the run started (NULL while pending)."
),
"completed_at": DateTimeDatabaseField(
name="completed_at", nullable=True, description="When the run finished (NULL while it is not finished)."
),
"created_at": DateTimeDatabaseField(name="created_at", description="When the run was created."),
},
)

Comment on lines +1110 to +1119
autoresearch_iterations: PostgresTable = PostgresTable(
name="autoresearch_iterations",
postgres_table_name="autoresearch_autoresearchiteration",
access_scope="autoresearch",
description="Autoresearch iterations; one row per model attempt inside a training run.",
fields={
"id": UUIDDatabaseField(name="id", description="Iteration UUID."),
"team_id": IntegerDatabaseField(name="team_id", description="Team the iteration belongs to."),
"pipeline_id": UUIDDatabaseField(
name="pipeline_id", description="Pipeline of the iteration; joins to autoresearch_pipelines.id."

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.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Map iteration access control to pipeline_id.

autoresearch_iterations omits access_control_id_field, so its autoresearch guard compares the iteration id with denied pipeline IDs. A same-team iteration can therefore pass the team_id filter and be returned even when its pipeline_id is denied. Add the mapping to this declaration independently of the training-run table.

Suggested fix
    access_scope="autoresearch",
+    access_control_id_field="pipeline_id",
    description="Autoresearch iterations; one row per model attempt inside a training run.",
📝 Committable suggestion

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

Suggested change
autoresearch_iterations: PostgresTable = PostgresTable(
name="autoresearch_iterations",
postgres_table_name="autoresearch_autoresearchiteration",
access_scope="autoresearch",
description="Autoresearch iterations; one row per model attempt inside a training run.",
fields={
"id": UUIDDatabaseField(name="id", description="Iteration UUID."),
"team_id": IntegerDatabaseField(name="team_id", description="Team the iteration belongs to."),
"pipeline_id": UUIDDatabaseField(
name="pipeline_id", description="Pipeline of the iteration; joins to autoresearch_pipelines.id."
autoresearch_iterations: PostgresTable = PostgresTable(
name="autoresearch_iterations",
postgres_table_name="autoresearch_autoresearchiteration",
access_scope="autoresearch",
access_control_id_field="pipeline_id",
description="Autoresearch iterations; one row per model attempt inside a training run.",
fields={
"id": UUIDDatabaseField(name="id", description="Iteration UUID."),
"team_id": IntegerDatabaseField(name="team_id", description="Team the iteration belongs to."),
"pipeline_id": UUIDDatabaseField(
name="pipeline_id", description="Pipeline of the iteration; joins to autoresearch_pipelines.id."

Comment on lines +1158 to +1167
autoresearch_models: PostgresTable = PostgresTable(
name="autoresearch_models",
postgres_table_name="autoresearch_autoresearchmodel",
access_scope="autoresearch",
description="Autoresearch models; one row per trained model of a pipeline, with role champion, challenger, or archived.",
fields={
"id": UUIDDatabaseField(name="id", description="Model UUID."),
"team_id": IntegerDatabaseField(name="team_id", description="Team the model belongs to."),
"pipeline_id": UUIDDatabaseField(
name="pipeline_id", description="Pipeline of the model; joins to autoresearch_pipelines.id."

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.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Map autoresearch models to pipeline_id for access control.

autoresearch_models omits access_control_id_field, so its autoresearch deny guard compares the model's id with denied pipeline IDs. The existing team_id filter does not prevent a same-team model from being returned. Set the mapping independently on this table.

Suggested fix
     postgres_table_name="autoresearch_autoresearchmodel",
     access_scope="autoresearch",
+    access_control_id_field="pipeline_id",
     description="Autoresearch models; one row per trained model of a pipeline, with role champion, challenger, or archived.",
📝 Committable suggestion

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

Suggested change
autoresearch_models: PostgresTable = PostgresTable(
name="autoresearch_models",
postgres_table_name="autoresearch_autoresearchmodel",
access_scope="autoresearch",
description="Autoresearch models; one row per trained model of a pipeline, with role champion, challenger, or archived.",
fields={
"id": UUIDDatabaseField(name="id", description="Model UUID."),
"team_id": IntegerDatabaseField(name="team_id", description="Team the model belongs to."),
"pipeline_id": UUIDDatabaseField(
name="pipeline_id", description="Pipeline of the model; joins to autoresearch_pipelines.id."
autoresearch_models: PostgresTable = PostgresTable(
name="autoresearch_models",
postgres_table_name="autoresearch_autoresearchmodel",
access_scope="autoresearch",
access_control_id_field="pipeline_id",
description="Autoresearch models; one row per trained model of a pipeline, with role champion, challenger, or archived.",
fields={
"id": UUIDDatabaseField(name="id", description="Model UUID."),
"team_id": IntegerDatabaseField(name="team_id", description="Team the model belongs to."),
"pipeline_id": UUIDDatabaseField(
name="pipeline_id", description="Pipeline of the model; joins to autoresearch_pipelines.id."

@trunk-io

trunk-io Bot commented Oct 2, 2026 •

Copy link
Copy Markdown
Author

Static Badge   Static Badge   Static Badge

Failed Test Failure Summary Logs
Event Pipeline E2E tests (fold+groupBatchCreates=false) we do not leave things in inconsistent state if $identify is run concurrently A test failed because the expected value was 2, but the actual received value was 1. Logs ↗︎

View Full Report ↗︎ ⋅ Docs

@trunk-io trunk-io Bot closed this Oct 2, 2026
@trunk-io
trunk-io Bot deleted the trunk-merge/pr-110700/09492e3e-7f72-4406-a312-1f11594d193d branch October 2, 2026 14:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants