Skip to content
Closed
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
8 changes: 4 additions & 4 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ reimplementing them:
tree assembly (`reconstruct.py`'s `_walk`/`thread_messages` calls).
- [RankWeave](https://github.com/ContextualWisdomLab/RankWeave) for
multi-channel score fusion (`weighted_convex_fuse` in
`reconstruct.py`) and the buyer-facing Rankings port
`reconstruct.py`) and the reader-facing Rankings port
(`rankweave_client.py`) -- never invent a fused score or a theta.
- [TEPP](https://github.com/ContextualWisdomLab/TEPP)'s published wire
contract for calibrated measurement (`tepp_client.py`) -- never
Expand Down Expand Up @@ -127,7 +127,7 @@ contextual-orchestrator owns model discovery and selection.
retaining the original asset and provenance. Recognize image DOM/visual
regions before OCR, descriptions, Keyman extraction, or embeddings. Store
region-level evidence; never show an internal LLM instruction such as
`This post is an image` to a buyer.
`This post is an image` to a reader.

## Source parsing and semantic units

Expand All @@ -146,10 +146,10 @@ contextual-orchestrator owns model discovery and selection.
- Remove presentation-only visual line alignment inside a paragraph (for
example continuation lines manually aligned after `-`, `*`, `1.`, or `.`)
from derived semantic text, while retaining the source body and meaningful
list/heading nesting. A buyer-facing post view must render semantic
list/heading nesting. A reader-facing post view must render semantic
paragraphs, not the authoring application's spacing workaround.
- Image descriptions, OCR text, and region evidence are analysis artifacts,
not buyer-facing prompt instructions. Buyer UI shows the source content and
not reader-facing prompt instructions. The UI shows the source content and
useful captions/evidence only, with provenance where appropriate.

## Pluggable channels: never fake a missing signal
Expand Down
10 changes: 5 additions & 5 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ flowchart LR
| `chunking.py` | Splits a document into meaning-identifiable units (paragraph, sentence, DOM, conversation-turn) plus embedded-image extraction, in document order |
| `embedding_client.py` | Pluggable text-embedding channel (`Null` default, `OpenAiCompatible` real impl) + `chunked_max_similarity` |
| `adjudication_client.py` | Pluggable LLM-judgment channel (`Null` default, `ContextualOrchestrator` real impl) |
| `image_content.py` | Pluggable vision channel: OCR + object recognition/tagging for embedded images (`Null` default, `OpenAiCompatibleVisionClient` real impl). The product popup (`frontend/src/PostBody.tsx`) renders each `data:image` payload in document order so the buyer sees the picture, not the base64 string; GET does not call the vision client. |
| `image_content.py` | Pluggable vision channel: OCR + object recognition/tagging for embedded images (`Null` default, `OpenAiCompatibleVisionClient` real impl). The product popup (`frontend/src/PostBody.tsx`) renders each `data:image` payload in document order so the reader sees the picture, not the base64 string; GET does not call the vision client. |
| `tepp_client.py` | TEPP's published `AnalysisRunRequest` wire contract, pluggable transport |
| `rankweave_client.py` | Fail-closed RankWeave ranking port (`weighted_reciprocal_rank_fuse` in-process; never invent a fused score or a theta) |
| `reconstruct.py` | The pipeline: group → candidate window → score → fuse → thread |
Expand Down Expand Up @@ -326,7 +326,7 @@ Keymen are affiliated with (`lineageweave/affiliate_tree.py`, loaded by
set of those leaves, not the whole company directory -- a sibling the
post never mentions is omitted. People on the tree are buttons that
reuse `GET /api/keymen/{person_id}/related` so the popup Keyman walk
starts from the affiliation the buyer clicked. A resolved organization
starts from the affiliation the reader clicked. A resolved organization
is the same walk via `GET /api/corporate-entities/{id}/related`. An affiliation that did not resolve to
a `corporate_entity` row stays as its own root (`resolved=false`); that
is the same never-guess-a-parent rule
Expand Down Expand Up @@ -467,15 +467,15 @@ truly have no dated open tickets.

## Phase 6-M2: authorized analysis-run evidence (read projection)

Issue #79's first buyer-visible Milestone 2 slice is a source-redacting
Issue #79's first reader-visible Milestone 2 slice is a source-redacting
read of the #89 registry. `GET /api/analysis-runs` and
`GET /api/analysis-runs/{id}` require `post_read` and apply the scope
in SQL: the requester always sees their own run; a corporate-entity or
process-unit scope is visible only to affiliated accounts; a
thread-group scope is visible only when the account can already see a
post in that group; `all_visible` is requester-only. Hidden runs 404. Detail also lists ABAC-visible post titles in the
run's scope whose `created_at` is at or before `knowledge_cutoff`
(ADR 0016) so a buyer can open a post the run was allowed to know
(ADR 0016) so a reader can open a post the run was allowed to know
without seeing later live rows or hidden bodies. Detail also returns
revision and configuration digest prefixes.
`POST /api/analysis-runs` records a Pending lineage run on a new
Expand Down Expand Up @@ -925,7 +925,7 @@ code. Wired into both `keyman_ingestion.py`'s affiliation loop and
## Standards-complete W3C PROV-O provenance layer

ADR 0011 separates standards-complete provenance from the compact
buyer-facing navigation graph. `lineageweave/prov_o.py` validates
reader-facing navigation graph. `lineageweave/prov_o.py` validates
and materializes all 50 normative PROV-O properties, including
literal-valued times/values and qualified Influence resources.
`migrations/0017_prov_o_standard_relations.sql` stores definitions,
Expand Down
6 changes: 3 additions & 3 deletions backend/app/analysis_run_ingestion.py
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,7 @@ async def fetch_outbox_deliveries(
"""Labeled claim/delivery events for one already-visible run.

Missing outbox tables mean migration 0023 is not applied. Stream
entry ids stay off the payload -- they are not buyer evidence.
entry ids stay off the payload -- they are not reader evidence.
"""
try:
rows = await conn.fetch(
Expand Down Expand Up @@ -285,7 +285,7 @@ async def _serialize_runs(
conn: asyncpg.Connection,
rows: list[asyncpg.Record],
) -> list[dict[str, Any]]:
"""Project registry rows into the authorized buyer-facing payload."""
"""Project registry rows into the authorized reader-facing payload."""
if not rows:
return []
count_rows = await _counts_by_run(conn, [str(row["analysis_run_id"]) for row in rows])
Expand Down Expand Up @@ -346,7 +346,7 @@ async def fetch_visible_analysis_runs(
"""Runs the account requested or whose scope they may already walk.

Once real source-import evidence is visible, the synthetic `make seed`
Demo Corp runs stop appearing here -- a buyer must not mistake that
Demo Corp runs stop appearing here -- a reader must not mistake that
fabricated narrative for real evidence (ADR 0001 / ADR 0042).
"""
# Safe SQL: this immutable module query contains only closed schema SQL; request values remain bound below.
Expand Down
2 changes: 1 addition & 1 deletion backend/app/demo_scope.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
something to show. Once an account can see at least one post carrying real
source-import evidence, the synthetic Demo Corp tree is no longer needed to
fill an empty screen and must stop appearing next to real evidence -- a
buyer must never mistake a fabricated contact (e.g. Ada West, Priya Nair)
reader must never mistake a fabricated contact (e.g. Ada West, Priya Nair)
for a real one.
"""

Expand Down
4 changes: 2 additions & 2 deletions backend/app/entity_relationship_ingestion.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,11 +128,11 @@ async def fetch_relationship_network(
can be a customer in one post, a competitor in another (their own
product line competes with ours elsewhere), the customer of our
customer in a third, or a supplier -- Customer Master's per-post
reads never rolled these up, so buyers could only see one role at
reads never rolled these up, so readers could only see one role at
a time and never the entity's whole network. This groups every
visible, eligible post's classifications by counterparty name,
keeping every distinct relationship type observed (not just the
most frequent), so a buyer can see a name marked both Customer and
most frequent), so a reader can see a name marked both Customer and
Competitor and know that reflects the real, mixed relationship
rather than a classification error.

Expand Down
2 changes: 1 addition & 1 deletion backend/app/issue_ticket_ingestion.py
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ async def upsert_commitment_ticket(

Re-deriving the same post must not stack duplicate calendar rows --
an existing open ticket with a commitment_summary is updated in place.
A closed ticket is left alone so the buyer can keep the historical
A closed ticket is left alone so the reader can keep the historical
record and still derive a fresh open one.
"""
existing = await conn.fetchrow(
Expand Down
2 changes: 1 addition & 1 deletion backend/app/knowledge_graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -576,7 +576,7 @@ async def fetch_person_role_history(
responsibility, in posts at different times (a job change, a title
change, a move between projects). ``post_summary_role`` already
carries this per post; this simply orders it chronologically for
one person instead of leaving a buyer to open every post that
one person instead of leaving a reader to open every post that
mentions them and compare manually.

``visible_post_ids`` must already be ABAC-filtered by the caller
Expand Down
6 changes: 3 additions & 3 deletions backend/app/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -1463,7 +1463,7 @@ async def read_post_content(
pool: asyncpg.Pool = Depends(get_pool),
valkey: redis.Redis = Depends(get_valkey),
) -> dict[str, Any]:
"""Return persisted content evidence; never derive or invent buyer copy."""
"""Return persisted content evidence; never derive or invent reader copy."""
await _load_visible_post(post_id, account, pool)
queue_event: tuple[str, str] | None = None
async with pool.acquire() as conn:
Expand Down Expand Up @@ -2517,7 +2517,7 @@ class ChatRequest(BaseModel):


class GlobalAskRequest(BaseModel):
"""JSON body for the buyer's source-grounded Global Ask Agent."""
"""JSON body for the reader's source-grounded Global Ask Agent."""

question: str

Expand Down Expand Up @@ -2618,7 +2618,7 @@ async def ask_agent(
account: CurrentAccount = Depends(get_current_account),
pool: asyncpg.Pool = Depends(get_pool),
) -> dict[str, Any]:
"""Answer a buyer question from authorized post and graph evidence."""
"""Answer a reader question from authorized post and graph evidence."""
question = request.question.strip()
if not question:
raise HTTPException(status.HTTP_422_UNPROCESSABLE_ENTITY, "question is required")
Expand Down
2 changes: 1 addition & 1 deletion backend/app/post_eligibility.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""Shared source-post eligibility SQL for buyer evidence reads."""
"""Shared source-post eligibility SQL for reader evidence reads."""

SOURCE_CONTEXT_COLUMNS = (
"source_author_code",
Expand Down
2 changes: 1 addition & 1 deletion backend/app/post_summary_ingestion.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ async def fetch_persisted_summary(
(ADR 0019 / 0027). This function does not join ``corporate_entity``
by ``entity_name``. Person chips read ``cataloged_person_id``. A stale
row is returned only when ``allow_stale`` is explicit so a caller can
preserve buyer continuity without presenting old semantics as current.
preserve reader continuity without presenting old semantics as current.
"""
header = await conn.fetchrow(
"select korean_summary, summary_contract_version "
Expand Down
2 changes: 1 addition & 1 deletion backend/app/ranking_ingestion.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ async def load_visible_ranking_posts(
conn: "asyncpg.Connection",
can_see_post: Callable[[Mapping[str, Any]], bool],
) -> list[dict[str, Any]]:
"""Read ``source_post`` rows the buyer may rank."""
"""Read ``source_post`` rows the reader may rank."""
posts = await conn.fetch(
"select post_id, post_title, created_at, visibility_code, "
"corporate_entity_id from source_post"
Expand Down
30 changes: 15 additions & 15 deletions backend/tests/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -661,7 +661,7 @@ def test_create_analysis_run_records_pending_without_inventing_a_score(
json={
"run_kind_code": "analysis_run_lineage",
"corporate_entity_id": seeded_db["own_corp_id"],
"idempotency_key": "buyer-create-2026-w02",
"idempotency_key": "run-create-2026-w02",
},
)
assert created.status_code == 201
Expand All @@ -682,7 +682,7 @@ def test_create_analysis_run_records_pending_without_inventing_a_score(
json={
"run_kind_code": "analysis_run_lineage",
"corporate_entity_id": seeded_db["own_corp_id"],
"idempotency_key": "buyer-create-2026-w02",
"idempotency_key": "run-create-2026-w02",
},
)
assert replay.status_code == 201
Expand All @@ -694,7 +694,7 @@ def test_create_analysis_run_records_pending_without_inventing_a_score(
json={
"run_kind_code": "analysis_run_tepp",
"corporate_entity_id": seeded_db["own_corp_id"],
"idempotency_key": "buyer-create-tepp",
"idempotency_key": "run-create-tepp",
},
)
assert tepp.status_code == 422
Expand All @@ -707,7 +707,7 @@ def test_create_analysis_run_records_pending_without_inventing_a_score(
json={
"run_kind_code": "analysis_run_report",
"corporate_entity_id": seeded_db["own_corp_id"],
"idempotency_key": "buyer-create-report",
"idempotency_key": "run-create-report",
},
)
assert report.status_code == 422
Expand All @@ -720,7 +720,7 @@ def test_create_analysis_run_records_pending_without_inventing_a_score(
"run_kind_code": "analysis_run_lineage",
"corporate_entity_id": seeded_db["own_corp_id"],
"knowledge_cutoff": "2026-01-01T00:00:00Z",
"idempotency_key": "buyer-create-2026-w02",
"idempotency_key": "run-create-2026-w02",
},
)
assert conflict.status_code == 409
Expand All @@ -731,14 +731,14 @@ def test_create_analysis_run_records_pending_without_inventing_a_score(
json={
"run_kind_code": "analysis_run_lineage",
"corporate_entity_id": seeded_db["other_corp_id"],
"idempotency_key": "buyer-create-hidden-corp",
"idempotency_key": "run-create-hidden-corp",
},
)
assert hidden.status_code == 404

unauthenticated = client.post(
"/api/analysis-runs",
json={"idempotency_key": "buyer-create-unauthenticated"},
json={"idempotency_key": "run-create-unauthenticated"},
)
assert unauthenticated.status_code == 401

Expand Down Expand Up @@ -781,7 +781,7 @@ def test_start_analysis_run_recovers_the_a100_fork(
"run_kind_code": "analysis_run_lineage",
"corporate_entity_id": seeded_db["own_corp_id"],
"knowledge_cutoff": "2026-02-15T00:00:00Z",
"idempotency_key": "buyer-start-2026-w07",
"idempotency_key": "run-start-2026-w07",
},
)
assert created.status_code == 201, created.text
Expand Down Expand Up @@ -853,7 +853,7 @@ def test_start_analysis_run_recovers_the_a100_fork(
"run_kind_code": "analysis_run_tepp",
"corporate_entity_id": seeded_db["own_corp_id"],
"knowledge_cutoff": "2026-02-15T00:00:00Z",
"idempotency_key": "buyer-start-tepp-2026-w07",
"idempotency_key": "run-start-tepp-2026-w07",
},
)
assert tepp_create.status_code == 422
Expand Down Expand Up @@ -887,7 +887,7 @@ def test_start_analysis_run_recovers_the_a100_fork(
requested_by_account_id, knowledge_cutoff,
configuration_schema_version, configuration_sha256,
code_revision_sha, requested_at)
values (%s, 'analysis_run_tepp', 'buyer-start-tepp-seeded',
values (%s, 'analysis_run_tepp', 'run-start-tepp-seeded',
%s, '2026-02-15T00:00:00Z', 'tepp-run-v1', %s, %s,
'2026-02-15T12:30:00Z')
returning analysis_run_id
Expand Down Expand Up @@ -956,7 +956,7 @@ def test_start_analysis_run_recovers_the_a100_fork(
requested_by_account_id, knowledge_cutoff,
configuration_schema_version, configuration_sha256,
code_revision_sha, requested_at)
values (%s, 'analysis_run_report', 'buyer-start-report',
values (%s, 'analysis_run_report', 'run-start-report',
%s, '2026-01-12T12:00:00Z', 'lineage-run-v1', %s, %s,
'2026-01-12T12:30:00Z')
returning analysis_run_id
Expand Down Expand Up @@ -999,7 +999,7 @@ def test_start_analysis_run_recovers_the_a100_fork(
requested_by_account_id, knowledge_cutoff,
configuration_schema_version, configuration_sha256,
code_revision_sha, requested_at)
values (%s, 'analysis_run_lineage', 'buyer-start-running',
values (%s, 'analysis_run_lineage', 'run-start-running',
%s, '2026-01-12T12:00:00Z', 'lineage-run-v1', %s, %s,
'2026-01-12T12:30:00Z')
returning analysis_run_id
Expand Down Expand Up @@ -1072,7 +1072,7 @@ def test_start_analysis_run_recovers_the_a100_fork(
requested_by_account_id, knowledge_cutoff,
configuration_schema_version, configuration_sha256,
code_revision_sha, requested_at)
values (%s, 'analysis_run_lineage', 'buyer-start-outbox-resume',
values (%s, 'analysis_run_lineage', 'run-start-outbox-resume',
%s, '2026-02-15T00:00:00Z', 'lineage-run-v1', %s, %s,
'2026-02-15T12:30:00Z')
returning analysis_run_id
Expand Down Expand Up @@ -1568,7 +1568,7 @@ def test_persisted_summary_is_returned_without_an_llm(client, demo_analyst_token
def test_stale_summary_is_returned_labeled_when_orchestrator_is_unavailable(
client, demo_analyst_token, seeded_db
) -> None:
"""A legacy saved summary preserves buyer continuity with an explicit label."""
"""A legacy saved summary preserves reader continuity with an explicit label."""
os.environ.pop("ORCHESTRATOR_BASE_URL", None)
os.environ.pop("ORCHESTRATOR_API_KEY", None)
admin_conn = psycopg2.connect(seeded_db["dsn"])
Expand Down Expand Up @@ -4633,7 +4633,7 @@ def test_seed_period_report_member_click_lands_on_decorated_fixture(
client, demo_analyst_token, seeded_db
) -> None:
"""The first W02 report member must already have Event Lineage,
Keyman, and evaluation -- otherwise the buyer click opens a dummy
Keyman, and evaluation -- otherwise the reader click opens a dummy
high/low band row.
"""
from lineageweave.fixtures import fixture_thread_cast, fixture_titles_in_iso_week
Expand Down
2 changes: 1 addition & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ services:
# Gateway credentials and URL are supplied only by env_file (${HOME}/.env).
# Do not repeat them under environment:, where Compose interpolation can
# overwrite env_file values with an empty host-shell value.
# The upstream default remains 64 KiB for ordinary text APIs. Buyer
# The upstream default remains 64 KiB for ordinary text APIs. Reader
# image blocks are base64 data URIs, so the multimodal boundary gets an
# explicit bounded 8 MiB limit rather than an unbounded request size.
CONTEXTUAL_ORCHESTRATOR_MAX_BODY_BYTES: ${CONTEXTUAL_ORCHESTRATOR_MAX_BODY_BYTES:-8388608}
Expand Down
Loading
Loading