Skip to content

feat(analytics): presentation_ro read-only ClickHouse role (#1963) - #2019

Merged
cyberantonz merged 5 commits into
mainfrom
pres/1963-presentation-ro-role
Jul 29, 2026
Merged

feat(analytics): presentation_ro read-only ClickHouse role (#1963)#2019
cyberantonz merged 5 commits into
mainfrom
pres/1963-presentation-ro-role

Conversation

@cyberantonz

@cyberantonz cyberantonz commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

What

Adds the presentation_ro ClickHouse role — the second barrier behind the single-SELECT gate (#1962) in the presentation-layer split. Contract reads run under a role that makes writing the source impossible by construction, not by convention.

Grant matrix (matches the governed PRD/DESIGN — SELECT on the contract, CREATE/INSERT only in presentation, no DROP/ALTER/TRUNCATE anywhere):

Scope Grants
silver.*, person.*, identity.*, insight.* (contract, incl. legacy gold) SELECT
presentation.* (writable namespace) SELECT, INSERT, CREATE

Changes

  • bootstrap-db/presentation-role.sql — canonical, idempotent role DDL.
  • apply-ch-migrations.sh — provisions the role, guarded + non-fatal: creating a role needs access_management on the applying admin, so an admin without it is skipped with a warning instead of aborting the deploy. Bootstrap picks this up via its existing step-4 call.
  • clickhouse-access-management.xml + README — enable access_management on the local/throwaway (official-image) ClickHouse so bootstrap provisions the role; the official image ships it disabled.
  • tests/test_presentation_role.py — opt-in integration test (repo's skip-unless-*_CH_URL pattern) asserting the full allow/deny grant matrix against a real ClickHouse. This is the adversarial-write half of NFR cpt-presentation-nfr-source-immutability.
  • PRD/DESIGN — mark cpt-presentation-fr-read-only-role implemented; add the cpt-presentation-component-read-only-role component. cfs per-artifact validate + check-language pass clean.

Verification

  • Integration test passes against a throwaway CH 25.7.5; the exact allow/deny matrix (contract read-only; presentation create/insert-only; DROP/ALTER/TRUNCATE denied everywhere) was confirmed with a probe user carrying only the role.
  • The guarded provisioning step was verified to warn-and-continue (no set -e abort) on a CH whose admin lacks access_management.

Scope / follow-ups

  • Wiring the analytics service to connect as presentation_ro (needs the runtime user + sealed secret) and creating the empty presentation database are the next slices ([pres] Create presentation DB + wire analytics to presentation_ro #1964 + connection wiring), per the DESIGN implementation plan.
  • ⚠️ Prod enforcement depends on the bitnami CH admin having access_management. If it does not, the guard silently skips the role (WARN). This is harmless today (no consumer yet), but the analytics-wiring PR must confirm/enable it before relying on prod enforcement.

Closes #1963
Part of #1803

Summary by CodeRabbit

  • New Features

    • Added automatic provisioning of a presentation_ro ClickHouse role.
    • Grants read-only access to contract data and controlled create, insert, and read access within the presentation area.
    • Provisioning is safe to repeat and does not block deployments when permissions are unavailable.
  • Documentation

    • Updated design and product documentation to describe the role’s permissions, setup, and deployment behavior.
    • Added local testing guidance for enabling access management.
  • Tests

    • Added integration coverage verifying allowed operations and blocked destructive changes.

Second barrier behind the single-SELECT gate (#1962): contract reads run
under a role that makes writing the source impossible by construction.

- presentation-role.sql: SELECT on the contract (silver, identity/person,
  legacy gold in `insight`); SELECT/INSERT/CREATE only in `presentation`;
  no DROP/ALTER/TRUNCATE anywhere. Idempotent.
- apply-ch-migrations.sh provisions it, guarded + non-fatal: creating a role
  needs access_management on the admin, so an admin without it is skipped
  with a warning instead of aborting the deploy.
- clickhouse-access-management.xml + README: enable access_management on the
  local/throwaway ClickHouse so bootstrap provisions the role.
- Opt-in integration test asserting the contract-read-only / presentation-
  create-insert-only grant matrix against a real ClickHouse.
- PRD/DESIGN: mark cpt-presentation-fr-read-only-role implemented and add the
  read-only-role component.

Part of #1803.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Anton Zelenov <antonz@constructor.tech>
@cyberantonz
cyberantonz requested a review from a team as a code owner July 29, 2026 09:44
@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@cyberantonz, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 2 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: d74f8d34-9b73-43cf-991d-f988edca5d9e

📥 Commits

Reviewing files that changed from the base of the PR and between c0af56f293df6919dfe626c87820756c7494e181 and 9997894.

📒 Files selected for processing (5)
  • docs/domain/presentation-layer/specs/DESIGN.md
  • docs/domain/presentation-layer/specs/PRD.md
  • src/ingestion/scripts/bootstrap-db/README.md
  • src/ingestion/scripts/bootstrap-db/presentation-role.sql
  • src/ingestion/scripts/tests/test_presentation_role.py
📝 Walkthrough

Walkthrough

Adds the presentation_ro ClickHouse role, guarded migration provisioning, local access-management configuration, updated presentation-layer documentation, and opt-in HTTP integration tests covering allowed and denied operations.

Changes

Presentation role

Layer / File(s) Summary
Role contract and grants
src/ingestion/scripts/bootstrap-db/presentation-role.sql, docs/domain/presentation-layer/specs/PRD.md, docs/domain/presentation-layer/specs/DESIGN.md
Defines idempotent presentation_ro grants for contract reads and limited presentation operations, with matching requirement and design documentation.
Migration provisioning and ClickHouse setup
src/ingestion/scripts/apply-ch-migrations.sh, deploy/gitops/system/clickhouse/values.yaml, src/ingestion/scripts/bootstrap-db/README.md
Attempts role provisioning during migrations, continues with a warning when access management is unavailable, and documents or preserves the required ClickHouse setting.
Permission-matrix integration tests
src/ingestion/scripts/tests/test_presentation_role.py
Adds an opt-in HTTP integration test that validates permitted reads, writes, creation, and rejection of destructive operations.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant clickhouse_migrate as apply-ch-migrations.sh
  participant clickhouse as ClickHouse
  participant role_sql as presentation-role.sql
  participant integration_test as test_presentation_role.py
  clickhouse_migrate->>clickhouse: Attempt role creation
  clickhouse_migrate->>role_sql: Apply grants when permitted
  role_sql->>clickhouse: Grant presentation_ro permissions
  integration_test->>clickhouse: Create probe user and test queries
  clickhouse-->>integration_test: Allow permitted operations and deny destructive operations
Loading

Possibly related PRs

Suggested reviewers: ktursunov, mitasovr

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning The role grants contract SELECT, but it does not satisfy the issue's CREATE/INSERT/ALTER/DROP-in-presentation requirement because ALTER/DROP are denied everywhere. Grant the required write DDL permissions in presentation, or update the issue if ALTER/DROP should be forbidden globally.
Docstring Coverage ⚠️ Warning Docstring coverage is 71.43% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is concise and accurately names the main change: adding the presentation_ro ClickHouse role.
Out of Scope Changes check ✅ Passed The changes stay within role provisioning, documentation, and test coverage for presentation_ro with no unrelated feature work.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch pres/1963-presentation-ro-role

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@github-actions

Copy link
Copy Markdown
Contributor

⚠️ Regenerate the connectors-ddl snapshot

This PR changes src/ingestion/**. If your change affects any
bronze / silver / gold schema, regenerate the committed DDL snapshot
and include it in this PR.

Prerequisites (details: src/ingestion/scripts/bootstrap-db/README.md):

  • docker + a fresh throwaway ClickHouse 25.7.5 (README "Local ClickHouse for testing")
  • .env from .env.bootstrap.example pointing at it; use the host LAN IP,
    reachable from both the host and connector containers
    (host.docker.internal does not resolve on the macOS host itself)
  • python3.12 or python3.11 on PATH (pinned dbt venv)
  • HubSpot + Salesforce credentials in .env — their discover calls the
    live APIs; without them, apply ../connectors-ddl/{hubspot,salesforce}.sql
    (relative to bootstrap-db/) to seed their bronze, then run the dbt step
cd src/ingestion/scripts/bootstrap-db
set -a; source pins.env; source .env; set +a
./bootstrap-db.sh connectors-config.yaml   # fresh ClickHouse 25.7.5
./dump-ddl.sh                              # writes scripts/connectors-ddl/*.sql

Commit the resulting scripts/connectors-ddl/*.sql diff. If nothing
changed, no snapshot update is needed. (Regeneration is manual for now.)

Comment thread src/ingestion/scripts/tests/test_presentation_role.py Fixed

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

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/domain/presentation-layer/specs/PRD.md`:
- Around line 166-168: Do not mark runtime role enforcement as shipped before
analytics executes queries as presentation_ro. In
docs/domain/presentation-layer/specs/PRD.md lines 166-168, keep
cpt-presentation-fr-read-only-role unchecked or limit it to role provisioning.
In docs/domain/presentation-layer/specs/DESIGN.md lines 238-248, remove the
shipped enforcement claim and state that presentation_ro is provisioned but not
active on the analytics query path.
- Around line 166-168: Align the documented and implemented access for the
presentation_ro role: in docs/domain/presentation-layer/specs/PRD.md lines
166-168, add person to the listed contract databases, and in
src/ingestion/scripts/bootstrap-db/presentation-role.sql lines 9-13, remove the
person.* grant if person is not intended to be contract access. Update the PRD
and SQL consistently based on the intended access scope.

In `@src/ingestion/scripts/tests/test_presentation_role.py`:
- Around line 79-82: Expand the contract-namespace permission test beyond silver
by creating probe tables in silver, person, identity, and insight. Parameterize
the allowed-read and denied-write assertions across all four namespaces,
preserving the existing database setup and admin execution through _admin.
- Around line 85-88: Reorder the setup in the test fixture around _admin so
CREATE USER no longer assigns DEFAULT ROLE presentation_ro before the role
grant. Create the probe user first, grant presentation_ro to PROBE_USER, then
assign presentation_ro as its default role using the appropriate admin
statement, preserving the existing assertions.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 727e8471-0c01-4381-9c2c-ff24e964e08d

📥 Commits

Reviewing files that changed from the base of the PR and between a5314a4 and c0af56f293df6919dfe626c87820756c7494e181.

📒 Files selected for processing (7)
  • deploy/gitops/system/clickhouse/values.yaml
  • docs/domain/presentation-layer/specs/DESIGN.md
  • docs/domain/presentation-layer/specs/PRD.md
  • src/ingestion/scripts/apply-ch-migrations.sh
  • src/ingestion/scripts/bootstrap-db/README.md
  • src/ingestion/scripts/bootstrap-db/presentation-role.sql
  • src/ingestion/scripts/tests/test_presentation_role.py

Comment thread docs/domain/presentation-layer/specs/PRD.md Outdated
Comment thread src/ingestion/scripts/tests/test_presentation_role.py Outdated
Comment thread src/ingestion/scripts/tests/test_presentation_role.py
…espoke XML

Access-management is already enabled on every real ClickHouse surface via the
official image's CLICKHOUSE_DEFAULT_ACCESS_MANAGEMENT env var — root
docker-compose.yml and tests/e2e/compose both set it, and the bitnami prod
admin has it by default. The bootstrap-only users.d XML I added was
reinventing that mechanism.

- Delete clickhouse-access-management.xml; the bootstrap README/test now use
  `-e CLICKHOUSE_DEFAULT_ACCESS_MANAGEMENT=1` on the throwaway container,
  matching the compose stacks.
- Note the bitnami access-management dependency in gitops clickhouse values.

Part of #1803.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Anton Zelenov <antonz@constructor.tech>
@cyberantonz
cyberantonz force-pushed the pres/1963-presentation-ro-role branch from c0af56f to cf0ea46 Compare July 29, 2026 10:34
#1963)

- test: pin CH_URL scheme to http(s) before urlopen (Semgrep
  dynamic-urllib) and suppress the audited rule with justification.
- test: cover every contract namespace (silver/person/identity/insight),
  not just silver, so a widened/narrowed grant regresses loudly.
- test: grant presentation_ro before setting it as the user's default role
  (ClickHouse rejects a default role that is not yet granted).
- PRD/DESIGN: role is provisioned by #1963 but not yet the active query-path
  identity (analytics still connects as admin), so leave the FR/component
  unchecked and say so; add the granted `person` database to the PRD list.

Part of #1803.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Anton Zelenov <antonz@constructor.tech>
@cyberantonz

Copy link
Copy Markdown
Contributor Author

Addressed the review in c4d504c4:

Semgrep (dynamic-urllib, test) — the test now pins CH_URL to an http(s) scheme before urlopen (rejecting file:// etc.) and suppresses the audited rule with justification. The value is an operator-supplied test endpoint, but the scheme guard makes that explicit and safe.

CodeRabbit — don't mark enforcement shipped — correct: the role is provisioned by #1963 but not yet the active query-path identity (analytics still connects as the admin until the connection wiring lands). Left cpt-presentation-fr-read-only-role and the DESIGN component unchecked and stated the role is provisioned-but-dormant.

CodeRabbit — document person.*person is contract (identity domain), so I kept the grant and added person to the PRD's listed contract databases.

CodeRabbit — cover every contract namespace — the read-only test is now parameterized over silver/person/identity/insight (each gets a probe table; reads allowed, writes/DDL denied).

CodeRabbit — grant before default role — fixed: CREATE USERGRANT presentation_roALTER USER … DEFAULT ROLE.

Also, per maintainer feedback, reverted the earlier deploy/gitops/system/clickhouse/values.yaml comment so gitops config stays untouched — the role is provisioned by the existing clickhouse-migrate hook and the bitnami admin already has access-management.

The connectors-ddl regen reminder is a false positive here: this PR adds a role + provisioning, no bronze/silver/gold schema change.

)
try:
# nosemgrep: python.lang.security.audit.dynamic-urllib-use-detected.dynamic-urllib-use-detected
with urllib.request.urlopen(req) as resp: # noqa: S310 (scheme pinned to http(s) above)
@cyberantonz

Copy link
Copy Markdown
Contributor Author

The failing secrets (diff) check is a false positive not introduced by this PR — TruffleHog's Lob detector matching the test function name test_divergent_watermark_fetches_its_own in src/ingestion/connectors/git/bitbucket-cloud/tests/test_request_budget.py:199 (a file this PR doesn't touch; it also fails #2023). Tracked in #2025. This PR's own diff scans clean.

@cyberantonz
cyberantonz enabled auto-merge (squash) July 29, 2026 13:11
@cyberantonz
cyberantonz merged commit ac031b3 into main Jul 29, 2026
43 checks passed
@cyberantonz
cyberantonz deleted the pres/1963-presentation-ro-role branch July 29, 2026 13:18
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.

[pres] presentation_ro ClickHouse role

3 participants