Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.d/0.98.1-analysis-run-sql-constants.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# 0.98.1 Analysis-run SQL constants

List and detail visibility queries are complete parameterized literals.
The $1 / $2 / $3 binds are unchanged.
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,15 @@ All notable changes to this project are documented here. Format follows
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/); versioning follows
[Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.98.1] - 2026-08-17

### Fixed

- Authorized analysis-run list and detail queries are now complete
parameterized SQL literals. Semgrep no longer treats the visibility
predicate as string-concatenated user input. The $1 / $2 / $3 binds
are unchanged. No TEPP theta is invented.

## [0.98.0] - 2026-08-17

### Added
Expand Down
108 changes: 78 additions & 30 deletions backend/app/analysis_run_ingestion.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,35 +35,58 @@
"analysis_run_tepp": "tepp-run-v1",
}

_VISIBLE_RUN_SQL = """
run.requested_by_account_id = $1
or (
scope.scope_kind_code = 'analysis_scope_corporate_entity'
and scope.corporate_entity_id = any($2::uuid[])
)
or (
scope.scope_kind_code = 'analysis_scope_process_unit'
and exists (
select 1 from account_affiliation aff
where aff.user_account_id = $1
and aff.process_unit_id = scope.process_unit_id
_RUN_LIST_SQL = """
select
run.analysis_run_id,
run.run_kind_code,
run.knowledge_cutoff,
run.requested_at,
run.configuration_schema_version,
run.configuration_sha256,
run.code_revision_sha,
scope.scope_kind_code,
scope.corporate_entity_id,
scope.process_unit_id,
scope.scope_key,
corp.entity_name as scope_entity_name,
status.status_code,
status.failure_code
from analysis_run run
join analysis_run_scope scope on scope.analysis_run_id = run.analysis_run_id
left join analysis_run_current_status status
on status.analysis_run_id = run.analysis_run_id
left join corporate_entity corp
on corp.corporate_entity_id = scope.corporate_entity_id
where
run.requested_by_account_id = $1
or (
scope.scope_kind_code = 'analysis_scope_corporate_entity'
and scope.corporate_entity_id = any($2::uuid[])
)
)
or (
scope.scope_kind_code = 'analysis_scope_thread_group'
and exists (
select 1 from source_post p
where p.thread_group_key = scope.scope_key
and p.created_at <= run.knowledge_cutoff
and (
p.visibility_code = 'public'
or p.corporate_entity_id = any($2::uuid[])
)
or (
scope.scope_kind_code = 'analysis_scope_process_unit'
and exists (
select 1 from account_affiliation aff
where aff.user_account_id = $1
and aff.process_unit_id = scope.process_unit_id
)
)
)
or (
scope.scope_kind_code = 'analysis_scope_thread_group'
and exists (
select 1 from source_post p
where p.thread_group_key = scope.scope_key
and p.created_at <= run.knowledge_cutoff
and (
p.visibility_code = 'public'
or p.corporate_entity_id = any($2::uuid[])
)
)
)
order by run.requested_at desc
"""

_RUN_SELECT = f"""
_RUN_DETAIL_SQL = """
select
run.analysis_run_id,
run.run_kind_code,
Expand All @@ -85,7 +108,34 @@
on status.analysis_run_id = run.analysis_run_id
left join corporate_entity corp
on corp.corporate_entity_id = scope.corporate_entity_id
where {{where}}
where run.analysis_run_id = $3
and (
run.requested_by_account_id = $1
or (
scope.scope_kind_code = 'analysis_scope_corporate_entity'
and scope.corporate_entity_id = any($2::uuid[])
)
or (
scope.scope_kind_code = 'analysis_scope_process_unit'
and exists (
select 1 from account_affiliation aff
where aff.user_account_id = $1
and aff.process_unit_id = scope.process_unit_id
)
)
or (
scope.scope_kind_code = 'analysis_scope_thread_group'
and exists (
select 1 from source_post p
where p.thread_group_key = scope.scope_key
and p.created_at <= run.knowledge_cutoff
and (
p.visibility_code = 'public'
or p.corporate_entity_id = any($2::uuid[])
)
)
)
)
order by run.requested_at desc
"""

Expand Down Expand Up @@ -285,7 +335,7 @@ async def fetch_visible_analysis_runs(
) -> list[dict[str, Any]]:
"""Runs the account requested or whose scope they may already walk."""
rows = await conn.fetch(
_RUN_SELECT.format(where=_VISIBLE_RUN_SQL),
_RUN_LIST_SQL,
account_id,
affiliated_entity_ids,
)
Expand All @@ -300,9 +350,7 @@ async def fetch_visible_analysis_run(
) -> dict[str, Any] | None:
"""One visible run, or None when it is missing or hidden."""
rows = await conn.fetch(
_RUN_SELECT.format(
where=f"run.analysis_run_id = $3 and ({_VISIBLE_RUN_SQL})"
),
_RUN_DETAIL_SQL,
account_id,
affiliated_entity_ids,
analysis_run_id,
Expand Down
2 changes: 1 addition & 1 deletion frontend/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "frontend",
"private": true,
"version": "0.98.0",
"version": "0.98.1",
"type": "module",
"scripts": {
"dev": "vite",
Expand Down
2 changes: 1 addition & 1 deletion lineageweave/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,4 +55,4 @@
"sentence_excerpts",
]

__version__ = "0.98.0"
__version__ = "0.98.1"
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "lineageweave"
version = "0.98.0"
version = "0.98.1"
description = "Reconstructs git-branch-style lineage DAGs from scattered short records using multi-channel score fusion and LLM adjudication."
readme = "README.md"
license = { text = "MIT" }
Expand Down
11 changes: 11 additions & 0 deletions tests/test_analysis_run_authorization.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
import pytest
from psycopg2 import sql

from backend.app.analysis_run_ingestion import _RUN_DETAIL_SQL, _RUN_LIST_SQL

_ROOT = Path(__file__).resolve().parents[1]
_INITIAL_MIGRATION = _ROOT / "migrations" / "0001_initial_schema.sql"
_REGISTRY_MIGRATION = _ROOT / "migrations" / "0018_analysis_run_registry.sql"
Expand All @@ -20,6 +22,15 @@
)


def test_visible_run_sql_is_parameterized_literals() -> None:
"""List and detail queries bind $1/$2/$3; they do not format user SQL."""
assert "$1" in _RUN_LIST_SQL
assert "$2" in _RUN_LIST_SQL
assert "$3" in _RUN_DETAIL_SQL
assert "{" not in _RUN_LIST_SQL
assert "{" not in _RUN_DETAIL_SQL


def _postgres_available() -> bool:
"""Return whether the configured administrator DSN is reachable."""
try:
Expand Down
2 changes: 1 addition & 1 deletion uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.