Skip to content

Promote dev to main - #430

Merged
parthrohit22 merged 444 commits into
mainfrom
dev
Sep 9, 2026
Merged

parthrohit22 merged 444 commits into
mainfrom
dev

Conversation

@parthrohit22

Copy link
Copy Markdown
Collaborator

Summary

Promote dev to main. main has been frozen at commit #38 (9 Jul 2026)
while the entire product was built on dev; this brings main current.

  • main is a direct ancestor of dev → clean fast-forward, no conflicts.
  • 444 commits, 674 files, +99k / −6.6k. Highlights since release: v0.1.0 - Engineering Intelligence Platform Foundation #38: the sealed
    ri.v1 snapshot pipeline, Architecture / Dependencies / Engineering Review /
    Insights / Documentation / Export surfaces, Repository Intelligence query API,
    repository lineage, AI provider integration + workspace, invite-gated auth +
    OAuth, the durable analysis worker, and the standalone marketing site.
  • dev CI is green on a41180a (this HEAD).

Pre-merge end-to-end QA (this session)

Ran against a live stack (Postgres 15 partha_dev, Redis, Ollama).

Area Result
Backend pytest (PG + Redis) 1180 passed / 0 failed / 0 skipped
RI golden benchmark PASS — precision 1.0000 / recall 1.0000, citation validity 1.0000
Frontend vitest 443 passed
ruff / ruff format / mypy / eslint / builds / API-contract all green
Flagship workflow (API + browser UI) register → login → upload and GitHub import → analyse → seal → Architecture / Dependencies / Review / Insights / Docs / Auth-explanation / Evidence-source / lineage / intelligence-v1, export 4 targets × 4 formats incl. PDF, AI test + query via Ollama, owner-scoping + FK cascade — all working
Migrations DB at head 0016_approved_emails; migrate-from-empty validated by CI's isolated-Postgres rehearsal job

Two QA findings were fixed on dev before this PR (#429): lineage docs
corrected to match the shipped read API + UI, and the SameSite=Lax refresh
cookie's same-site deployment requirement documented.

Deployment notes for whoever cuts over main

  • Anything that deploys from main jumps 444 commits at once. The Vercel
    marketing project
    is wired to this repo; confirm which branch its
    Production deployment tracks before/after merging.
  • The backend refuses to boot outside development/test without real
    AUTH_SECRET_KEY (≥32), a valid Fernet AI_ENCRYPTION_KEY, explicit
    CORS_ORIGINS, and — for a local/custom Ollama endpoint — the egress
    allowlists. This is by design.
  • SPA and API must be served same-site (SameSite=Lax refresh cookie) —
    see docs/architecture/SYSTEM_OVERVIEW.md § Authentication and session flow.
  • main currently carries committed dist/ artifacts; this merge removes them
    (dev is hygiene-clean).
  • A v0.x.0 tag after the merge triggers release.yml (validation + GitHub
    Release).

Testing performed

dev CI green on a41180a; full local suite + live E2E as tabled above.

Security / data considerations

No new code in this PR — it is a branch promotion. All controls
(owner-scoping, invite gate, encrypted provider keys, egress allowlist, DNS
pinning) are unchanged from dev.

parthrohit22 and others added 30 commits July 25, 2026 15:42
…ency-graph

feat(dependencies): serve the dependency graph from the sealed ri.v1 snapshot
)

data_source was written as the literal "real" on every repository and never
derived from any actual distinction, so it presented a hardcoded value as a
computed one. Removed the column (reversible migration 0007), the schema
field, and both service write sites; the API no longer returns it.

data_source is unrelated to DataSourceBadge, which renders the real
'source: upload|github' origin field and is left untouched.
Removes the DataSource type, Repository.dataSource / RepositoryResponse.dataSource,
the backend.ts passthrough, and the hardcoded dataSource: 'real' literal in
useAnalysisPipeline.ts, matching the backend field removal. DataSourceBadge is
unaffected -- it renders the separate, real 'source' (upload/github) field.
--reload-dir app scopes the watcher to apps/backend/app instead of the whole
working directory, so writes under .local/storage during ingestion and
analysis no longer trigger a mid-job server restart that drops open
connections.
…lysis failed (#164)

useAnalysisPipeline's status poll caught every thrown error identically,
rendering a single dropped NetworkError/TimeoutError as the terminal
'Analysis Failed' state even though the durable job survives server-side.

Distinguishes transport errors from a real API-reported job failure: a
NetworkError/TimeoutError now retries with exponential backoff (1s/2s/4s/8s/
15s, 5 attempts) behind a new, non-terminal 'retrying' connection state;
retry exhaustion surfaces a distinct 'connection lost' state with a manual
retry action, never the Analysis Failed banner. A real status: 'failed'
response still renders immediately, unaffected. Progress is never advanced
or reset while retrying -- only the last known real state is shown.
…alues

fix(honesty): remove data_source placeholder and disclose chunked AI delivery
…ad-watcher

chore(dev): restrict uvicorn reload watcher to backend source (Closes #161)
…status-polling

fix(analysis): retry transient poll failures instead of declaring analysis failed (Closes #164)
)

AUTO_CREATE_TABLES's create_all creates missing tables but never alters an
existing one and never advances the alembic_version stamp, so an existing
local dev database silently drifts from the code after a schema-changing
merge -- requests fail with opaque IntegrityError/OperationalError 500s
instead of a clear migration error.

app/core/schema_sync.py compares the database's Alembic revision against
head at startup (development/test only). A clean behind-head database is
upgraded automatically and logged. A database where a pending migration
would create a table that already exists physically -- the
create-all-without-stamp state -- refuses to start with the exact
stamp-then-upgrade recovery instead of crashing into "table already
exists". A brand-new empty database is stamped at head directly after
create_all, since create_all already builds head's exact shape.
Production/staging are unaffected.
…es (#166)

Adds tests/test_schema_sync.py (fresh-db stamping, clean auto-upgrade,
physical-drift refusal with actionable recovery, a regression test that the
recommendation never silently skips a pending column-only migration,
production/staging no-op, up-to-date no-op).

The client fixture in conftest.py and the near-duplicate fixture bodies in
test_ingestion_resource_budgets.py/test_rate_limit.py called create_all
directly before the app's lifespan ran, leaving every table present but
unstamped -- the new drift check misread that as physical drift on every
test run. Fixed by having those fixtures stamp head the same way the
lifespan does for a genuinely fresh database.

Documents the behaviour and manual recovery commands in
apps/backend/README.md.
…nd API (#162)

The analysis worker persists many facts via frequent small commits during
extraction while API request handlers poll concurrently. SQLite's default
rollback-journal mode briefly locks readers out around each individual
commit -- not for a transaction's whole open-but-uncommitted duration as
might be assumed -- and under sustained concurrent commit/read pressure
that adds up to real 'database is locked' errors.

Every SQLite connection now gets PRAGMA journal_mode=WAL and a 5s
PRAGMA busy_timeout (app/core/database.py, guarded to SQLite only, a no-op
on PostgreSQL). WAL lets a reader always see the last committed snapshot
independent of what the writer is doing, removing the collision entirely.

Deliberately left the analysis worker's transaction boundaries unchanged:
audited them and found them tightly coupled to the job-recovery guarantees
(leases, retries, stale-worker takeover) documented in that module's own
docstring. WAL removes the actual symptom without needing to touch that.
…tection

fix(core): detect and resolve dev database schema drift at startup (Closes #166)
fix(core): eliminate SQLite lock contention between analysis worker and API (Closes #162)
docs: add Partha logo v1 and v2 SVG assets
…oyment grouping

Documentation's Architecture section only listed flat module.role buckets with
no notion of layering, and its Deployment classifier relied on `and` silently
binding tighter than `or` to keep unrelated YAML out of the deployment list.

Rebuild layering from sealed ri.v1 facts already available via
SnapshotQueryService: modules now carry a heuristic layer (shared with
ArchitectureAnalyzer via a single app.intelligence.classification helper, so
Documentation and the Architecture page cannot disagree), and file-to-file
RiEdge facts are aggregated into cross-layer relationship counts. Group the
deployment predicate with explicit parentheses so the extension+token
requirement can't be silently broken by a future added clause.
Recall was enforced at a looser 0.90 since Issue #94's provisional Phase-0 bar,
while precision was already 0.95. Re-running the full 23-fixture golden corpus
against the current real extractors (python-ast, typescript, dependency
manifest) on this branch measures precision 1.0000 / recall 1.0000 with zero
false positives/negatives, comfortably clearing a 0.95 bar on both metrics, so
raise recall to match precision and pin both in a regression test guarding
against a future silent threshold decrease.
…ark-hardening

fix(documentation): layer architecture, fix yaml deployment grouping, raise benchmark recall to 0.95
…p tracked in #182

SYSTEM_OVERVIEW.md incorrectly stated symlink-escape rejection applied to the GitHub clone path. Only upload extraction guards symlinks (storage/local.py:121); the clone tree walk follows symlinks (issue #182, filesystem disclosure + recursion). Corrected the import diagram, validation node, and the untrusted-input note to scope the guard to upload and reference #182.
docs(system-overview): scope symlink-escape claim to upload; clone gap tracked in #182
The poll loop rebuilt its interval on every progress tick (repository
object identity changed each response) and kept scheduling another
request even after a completed/failed/cancelled response, leaving the
"Analysing Repository" page stuck until a manual reload. The loop now
stops itself on a terminal status and the effect only restarts on an
actual status/error change, not every progress percentage.
Direct navigation to /dashboard or /analysis/{id}/architecture hit
react-router's default error boundary ("Unexpected Application Error!
404 Not Found") because no route matched them and there was no
catch-all. /dashboard now aliases the Dashboard surface at /, the
architecture deep link selects that repository and hands off to
/architecture, and any other unmatched path renders a NotFound page
instead of crashing. The auth guard still gates all of this.
parthrohit22 and others added 26 commits September 4, 2026 00:08
…mport (#398) (#399)

RepositoryParser now skips .DS_Store, __MACOSX/, and ._<name> AppleDouble
sidecars when walking an extracted archive or cloned checkout -- both the
upload and GitHub import paths call the same parser before anything
downstream reads the tree, so one fix covers both. A sidecar shares its real
counterpart's extension while being opaque binary, so left unfiltered it
looks like a second source file to every downstream extension-based check.

LocalStorage.extract_archive also physically strips these from the
repository's own storage directory after extraction, so the artifacts
that only ever come from a zip (not a git clone) don't linger on disk
either.
README.md said repository lineage was future work and that RFC-0002 was
'design only: no table, column, or surface exists yet.' Both were wrong --
#299 implemented it: the repository_lineages table, owner-scoped grouping,
sequence allocation, and duplicate-revision detection all exist and run on
every GitHub import. What's actually still missing is a read API and UI,
tracked separately as #400.

Added a repository-lineage row to the capability registry
(app/extraction/support_matrix.py) reflecting that same split -- partial
support, data layer implemented, no read surface yet -- and regenerated
README's generated capability table from it.
…ng (#403) (#404)

Every route is code-split via lazy, so the first render always has at
least one route whose module hasn't downloaded yet. With no
HydrateFallback on any ancestor, react-router rendered nothing for that
gap and logged 'No `HydrateFallback` element provided to render during
initial hydration' on every route load.

Wrapped the route tree under one pathless root route with a
HydrateFallback (the same spinner RequireAuth already uses for its own
session-initialising gap).
ruff check/format --check only ever ran against app scripts, so tests/
accumulated real lint issues nothing caught: a genuinely undefined name
(tests/benchmark/runner.py referenced Fact without importing it -- silently
harmless only because the file uses `from __future__ import annotations`,
which stores the annotation as a string instead of evaluating it), a few
unused imports, and two f-strings with no placeholders. Fixed all of them.

tests/benchmark/fixtures is excluded: it holds deliberately malformed and
adversarial Python source used as golden-benchmark fixture input (Issue
#94), not our own code, so linting it is a category error.

Reformatted every file under tests/ with ruff format to match -- a
mechanical, one-time diff -- and updated ci.yml and DEVELOPMENT.md to run
both checks against the same tests scope from here on. mypy stays scoped
to app/scripts; that's a separate, larger effort tracked on its own.
GET /repositories/{id}/lineage returns the history of repository imports
the requested repository belongs to, most recent first -- the read surface
RFC-0002's implementation (#299) never got.

A standalone import (an upload, or a GitHub import whose ref never
resolved -- RFC §4.3/§6) has no lineage row to read: this returns
isLineaged: false and a one-entry history containing only the requested
repository, rather than fabricating a lineage or a 404. Every entry
reports its own sequence, revision, status, and uploadedAt, plus
isCurrent for whichever repository id was requested -- the same entry
carries isCurrent regardless of which member of the lineage you ask
through, so a client can render one shared timeline no matter which
import it started from.

Two new RepositoryRepository accessors (get_lineage_for_owner,
list_lineage_members) do the owner-scoped reads; everything stays scoped
through the existing RepositoryService/_get_record 404 semantics, so a
cross-owner or nonexistent repository id behaves exactly like every
other repository route.

Task 9 (the History UI) is a separate PR after this one, per repo
convention.
…literals (#408) (#409)

requests.get(f"https://api.example.com/users/{user_id}") and
fetch(`https://api.example.com/users/${userId}`) previously got no
destination fact at all -- the extractor only accepted a plain string
literal, so any interpolation anywhere in the URL fell back to a
'dynamic HTTP destination is unsupported' diagnostic, even when the
origin itself never depended on the interpolated value.

Both extractors now also try the literal text an f-string/template
literal opens with, up to its first interpolation. That's only trusted
once the authority is fully closed off within the literal text --
already containing the '/' that starts the path -- since otherwise the
interpolation could still be extending the host itself
(f"https://{tenant}.example.com"), or the more dangerous
f"https://api.example.com{suffix}" where a suffix that doesn't start
with '/' would silently become part of the hostname. Neither case
extracts anything; both keep the existing diagnostic.

app/extraction/http.py: refactored describe_destination's origin-parsing
core into a shared _split_origin, added
describe_destination_from_literal_prefix for the new case.
Adds a History tab to the repository detail page, backed by the read API
from #407. Shows every import in the current repository's lineage, most
recent first, with its sequence, status, revision, and upload time, and
links to every other member. A standalone import (upload, or a GitHub
import whose ref never resolved) gets a one-entry view with a short note
explaining why -- not an empty state or a hidden tab.

useRepositoryLineage follows the same fetch/status pattern as
useInsights/useDependencies; repositoryService.getLineage and
backendService.fetchRepositoryLineage wire it to the existing API-client
layers.
…ry with 7 updates (#395)

Bumps the frontend-minor-and-patch group with 7 updates in the /apps/frontend directory:

| Package | From | To |
| --- | --- | --- |
| [@xyflow/react](https://github.com/xyflow/xyflow/tree/HEAD/packages/react) | `12.11.3` | `12.11.5` |
| [react-router-dom](https://github.com/remix-run/react-router/tree/HEAD/packages/react-router-dom) | `7.18.2` | `7.18.3` |
| [@testing-library/react](https://github.com/testing-library/react-testing-library) | `16.3.2` | `16.3.3` |
| [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/tree/HEAD/packages/plugin-react) | `6.1.0` | `6.1.1` |
| [eslint](https://github.com/eslint/eslint) | `10.8.1` | `10.9.1` |
| [eslint-plugin-react-refresh](https://github.com/ArnaudBarre/eslint-plugin-react-refresh) | `0.5.4` | `0.5.5` |
| [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint) | `8.67.0` | `8.69.0` |



Updates `@xyflow/react` from 12.11.3 to 12.11.5
- [Release notes](https://github.com/xyflow/xyflow/releases)
- [Changelog](https://github.com/xyflow/xyflow/blob/main/packages/react/CHANGELOG.md)
- [Commits](https://github.com/xyflow/xyflow/commits/@xyflow/react@12.11.5/packages/react)

Updates `react-router-dom` from 7.18.2 to 7.18.3
- [Release notes](https://github.com/remix-run/react-router/releases)
- [Changelog](https://github.com/remix-run/react-router/blob/react-router-dom@7.18.3/packages/react-router-dom/CHANGELOG.md)
- [Commits](https://github.com/remix-run/react-router/commits/react-router-dom@7.18.3/packages/react-router-dom)

Updates `@testing-library/react` from 16.3.2 to 16.3.3
- [Release notes](https://github.com/testing-library/react-testing-library/releases)
- [Changelog](https://github.com/testing-library/react-testing-library/blob/main/CHANGELOG.md)
- [Commits](testing-library/react-testing-library@v16.3.2...v16.3.3)

Updates `@vitejs/plugin-react` from 6.1.0 to 6.1.1
- [Release notes](https://github.com/vitejs/vite-plugin-react/releases)
- [Changelog](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite-plugin-react/commits/plugin-react@6.1.1/packages/plugin-react)

Updates `eslint` from 10.8.1 to 10.9.1
- [Release notes](https://github.com/eslint/eslint/releases)
- [Commits](eslint/eslint@v10.8.1...v10.9.1)

Updates `eslint-plugin-react-refresh` from 0.5.4 to 0.5.5
- [Release notes](https://github.com/ArnaudBarre/eslint-plugin-react-refresh/releases)
- [Changelog](https://github.com/ArnaudBarre/eslint-plugin-react-refresh/blob/main/CHANGELOG.md)
- [Commits](ArnaudBarre/eslint-plugin-react-refresh@v0.5.4...v0.5.5)

Updates `typescript-eslint` from 8.67.0 to 8.69.0
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/typescript-eslint/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.69.0/packages/typescript-eslint)

---
updated-dependencies:
- dependency-name: "@xyflow/react"
  dependency-version: 12.11.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: frontend-minor-and-patch
- dependency-name: react-router-dom
  dependency-version: 7.18.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: frontend-minor-and-patch
- dependency-name: "@testing-library/react"
  dependency-version: 16.3.3
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: frontend-minor-and-patch
- dependency-name: "@vitejs/plugin-react"
  dependency-version: 6.1.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: frontend-minor-and-patch
- dependency-name: eslint
  dependency-version: 10.9.1
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: frontend-minor-and-patch
- dependency-name: eslint-plugin-react-refresh
  dependency-version: 0.5.5
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: frontend-minor-and-patch
- dependency-name: typescript-eslint
  dependency-version: 8.69.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: frontend-minor-and-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…uping (#396) (#397)

- entrypoint role now maps to a genuine 'entrypoint' node type instead of
  being misclassified as 'frontend' regardless of language
- package.json/pyproject.toml/lockfiles no longer get grouped into an
  architecture module of their own -- they already surface as dependency
  evidence, and the manifest basename filter now strips the 'file:' prefix
  before matching so it also catches root-level manifests, not just nested
  ones
- a bare top-level file's module display name is no longer Title Cased,
  which was corrupting real filenames like app.py into App.Py
* refactor(scale): claim analysis jobs through an explicit control plane

Analysis jobs were already durable, leased, cancellable and recoverable, but
"who owns a job" was expressible only as private methods of AnalysisWorker: the
claim compare-and-swap, the lease-renewal compare-and-swap, the expired-lease
scan and the ownership predicate all lived inside the executor. A second worker
process could not participate in the queue without importing the class that
runs the extraction pipeline.

Introduce app/workers/control_plane.py as that boundary. AnalysisControlPlane
is the protocol; DatabaseAnalysisControlPlane is the v1 implementation, and it
is the existing analysis_jobs table rather than a new broker — the durable row
is already the authority for identity, attempt budget, cancellation and lease
expiry, so a queue service would only add a second, weaker source of truth.

JobLease is a value, not a handle: ownership is re-proved by a guard on every
mutation, so a stale lease can never write to a reclaimed job. LeaseRenewal
distinguishes renewed / lost / deferred, making the previously unnamed SQLite
single-writer case explicit instead of an inline exception filter.

AnalysisWorker keeps execution only: running a job it already owns through the
ri.v1 pipeline and deciding its terminal transition. Every statement moved
across is unchanged, so the claim/lease/cancellation contract is identical;
_claim stays as a row-shaped delegation for existing callers.

Refs #324, #210

* refactor(scale): move the analysis worker loop out of the API app

app.main did not merely host a worker, it *was* the control loop: it minted the
ownership token, built the worker, owned the poll cadence, decided when to sweep
stale leases, and joined the thread on shutdown. Nothing outside a FastAPI
lifespan could run a worker without copying that policy, which is the tight
coupling #324 exists to remove.

AnalysisWorkerRunner now owns it. run_forever is a plain blocking call, so the
loop a standalone worker process needs already exists here rather than being
locked inside an async context manager; threading is an implementation detail of
this runner, not of the boundary. app.main is reduced to start/stop, and the
in-process single-worker path is preserved exactly as #324 requires during
migration. Building the standalone deployment stays with #210.

new_worker_id moves alongside it, since worker identity is what every
control-plane ownership guard compares; its test moves with it and now asserts
uniqueness across many tokens rather than two.

Refs #324, #210

* test(scale): cover queue ownership, leases and cancellation

31 cases over the boundary itself, not the extraction pipeline: claiming and
eligibility (including that next_attempt_at backoff hides a job from the queue),
the duplicate-claim race, separate-job claiming, expired-lease reclaim, active-
lease protection, ownership enforcement on both mutation and renewal, the
lost-lease handoff signal, cancellation visibility across a reclaim, the
cancel-not-requested guard that keeps cancellation idempotent, and the runner's
drain/sweep/failure-isolation/shutdown behaviour.

Every race is expressed as an explicit interleaving or a threading.Barrier;
nothing sleeps waiting for a race, so a slow machine cannot turn a correctness
assertion into a flake. The duplicate-claim case pins the candidate each worker
read so both genuinely reach the compare-and-swap — verified by mutation:
deleting the status='queued' guard fails it, and an earlier version of the test
that let the loser re-read the queue did not.

next_eligible_job_id is split out of claim for that reason: the candidate read
is the half of a claim that carries no ownership, and naming it makes the race
window visible to a reader as well as reachable by a test.

The threaded race is gated on PARTHA_TEST_PG_URL, following the repository's
established pattern — SQLite serialises writers, so only a real MVCC server
exercises two claims genuinely in flight.

Refs #324, #210

* docs(scale): document the analysis control-plane boundary

Record what now runs: the control plane and runner as separate component rows,
the claim/lease exchange in the ingestion sequence, and the fact that the API
process hosts a worker without owning the queue.

The known-limits entry is widened rather than softened — analysis is still
whole-repository and still runs one in-process worker per API process. No
standalone worker deployment exists, and this documents current behaviour only.

Refs #324, #210

* chore(scale): reformat for ruff's now-widened tests/ scope (#405)

One line in this file didn't match the formatter after tests/ came under
ruff format --check in #405, which landed after this branch opened.
Mechanical only, no logic change.

---------

Co-authored-by: PARTH J ROHIT <parthrohit60@gmail.com>
…indings (#412) (#413)

A real analysis of a Django project surfaced 608 medium-severity Unresolved
relationship findings, almost entirely 'imports has no resolvable target'
on boilerplate config files (asgi.py/wsgi.py/settings.py/urls.py) whose
imports are mostly import os, from django.core.wsgi import
get_wsgi_application, from pathlib import Path -- a standard-library or
correctly-declared third-party import, not a gap. Same root-cause pattern
#392 fixed for builtin calls, on the import side.

New app/review/import_dispositions.py decides, purely at review time,
whether an unresolved import-kind diagnostic should be promoted to a
finding: not when its target is the Python standard library or a Node
builtin module, and not when it matches a package the repository's own
manifest declares (checked against the sealed snapshot's real dependency
nodes, using the same package-root extraction and pypi/npm identity the
resolver itself uses -- pulled out of intelligence/resolution.py into a
shared extraction/naming.py function so the two can never independently
drift on what 'the package' means for a given import).

The resolver's own output is untouched -- same diagnostics, same facts,
same ri.v1 data. Only whether an import diagnostic is surfaced as a
finding changes. A relative import, or a bare specifier that matches
neither stdlib/builtin nor a declared dependency, still stays a finding:
the genuine case (a typo, a missing dependency declaration, a real
extractor gap). route_handler/injects/calls_service diagnostics are
untouched.

Verified against the real Django-boilerplate pattern (12 raw diagnostics,
unchanged; 12 findings before this fix, 5 after -- the 7 suppressed are
exactly the stdlib imports; the 5 remaining are calls-kind diagnostics,
issue #393's separate, distinct territory, not touched here).
)

Ollama runs inference on the same machine PARTHA runs on, and PARTHA had
no concurrency control anywhere in its provider call path -- every request
that reached the app was forwarded to Ollama immediately and
independently. Measured directly: Ollama's own backend already serializes
actual token generation to one request at a time by default, so extra
concurrency here never got anything done faster -- it only meant more
requests held open at once (more peak memory) and the elevated CPU/memory
load running as one long unbroken stretch instead of several shorter ones.

A module-level anyio.Semaphore(1) in OllamaProvider.complete() caps
in-flight requests to Ollama at 1. A request beyond that queues and waits
-- it never fails or gets rejected; this is resource management, not a
rate limit. Scoped to Ollama specifically: the four hosted providers
(OpenAI, Anthropic, Gemini, OpenRouter) share the same post() transport
but each call a fixed remote URL with their own server-side
infrastructure, so a concurrent request to one of them doesn't compete
with the user's own machine the way local inference does -- they stay
unlimited.

anyio.Semaphore rather than asyncio.Semaphore: the module-level object is
shared process-wide, and asyncio.Semaphore permanently binds itself to
whichever event loop first contends it -- fine for the one persistent
loop a real server runs on, but it broke exactly that way the first time
two of my own new tests each drove real concurrent contention through a
separate asyncio.run(). anyio's version (already used elsewhere in this
provider layer) doesn't have that failure mode.

Verified for real: reproduced the same 3-concurrent-request burst against
a real Ollama instance through PARTHA's own /ai/query endpoint. Before:
llama-server held ~190-200% CPU (near 2 full cores) continuously for the
whole ~34s burst, growing from 16GB to 17GB resident. After, the same
burst: ~95-97% CPU (~1 core), memory flat around 700MB, completions at
9.5s/17.7s/27.2s -- a clean queue, the first request finishing in exactly
its own solo time rather than being slowed by the other two.
…lama timeout, provider doc (#421)

Squash of 5 commits:

- fix(marketing): replace the slide-in drawer with a centered modal dialog
- fix(backend): stop Ollama requests being cut off mid-generation (per-request
  timeout; tight connect, long read budget for local inference)
- fix(backend): split Repository Insights unresolved-relationship count into
  genuine in-repo gaps vs. references into external/platform code (read-time
  only; no stored fact changes)
- docs: add docs/operations/AI_PROVIDER_SETUP.md, an end-to-end guide for
  connecting an AI provider
- chore(deps): bump js-yaml override to 4.3.2 for GHSA-2883-xcg3-v3hh

All CI checks green. Merged with admin bypass at the repository owner's
direction.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ences (#423)

PR #421 fixed the Repository Insights "Unresolved relationships" number.
The Architecture page has the same problem from the same cause: any
RI-RES-UNRESOLVED diagnostic marked its containing module "unresolved"
(red badge) and was listed in full under Diagnostics -- so a module whose
only unresolved references are calls into `fs`, `flask`, stdlib, or a
language builtin showed up as broken, and the diagnostics list ran to
thousands of entries.

Reuse the same classifier. The shared helper is generalised: the
per-diagnostic DB context load and the external-vs-in-repo test now live
in `app/insights/relationship_diagnostics.py` as
`load_unresolved_relationship_context` + `is_external_unresolved`, and
both `RepositoryInsightsBuilder` and `ArchitectureAnalyzer` call them.

`ArchitectureAnalyzer._edges_for_modules` now drops external-reference
RI-RES-UNRESOLVED diagnostics before it marks module relationship state
*and* before it builds the returned diagnostics list. Genuine in-repo
gaps and every RI-RES-AMBIGUOUS are untouched; the raw resolver
diagnostics stay available via the intelligence evidence API.

Measured on real snapshots: files that would flag a module drop 200->41
and 87->18; the architecture diagnostics list drops 2659->228 and
676->95.

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Six dead "coming soon" footer toasts (Privacy, ri.v1 spec, Language matrix,
Changelog, About, Legal) and the generic LinkedIn/X links now all resolve to
real content: README sections, the ri.v1 RFC, REPOSITORY_INTELLIGENCE.md,
GitHub Releases, LICENSE, linkedin.com/in/parthrohit, and Discord for X (no X
account yet). The unavailable-notice toast + state are removed.

apps/marketing typecheck/lint/build green; CI 11/11 green.
)

Documentation-follows-behaviour catch-up for this session's backend
changes (PR #421, PR #423):

- REPOSITORY_INTELLIGENCE_RESOLUTION.md: new "How read consumers present
  unresolved warnings" subsection -- sealed RI-RES-UNRESOLVED warnings are
  never edited, but Insights and Architecture classify a reference into a
  declared dependency / the language platform as expected rather than a
  gap. Names the shared classifier and the raw-total escape hatch.
- REPOSITORY_INTELLIGENCE.md consumers table: Architecture and Repository
  insights rows note the same split.
- apps/backend/README.md: Ollama's longer read budget vs. hosted 60s, the
  10s connect timeout, and the one-in-flight cap; link to the new
  AI provider setup guide from the egress section.
- README.md: link docs/operations/AI_PROVIDER_SETUP.md from the
  documentation index.
The landing page was a single 1728px-wide authored design image with
invisible tap targets overlaid at percentage coordinates. On a phone it
only shrank, so every bit of text became unreadable; tablets were a
squashed desktop layout.

Add a breakpoint split at 1024px:
- >= 1024px: the authored canvas + hotspots, unchanged.
- < 1024px: MobileLanding -- a real responsive layout (sticky header,
  hero, "how it works" steps, capabilities grid, a native <details> FAQ
  accordion, CTA band, stacked footer) built from the same content and
  wired to the same DemoModal / RunItYourselfModal dialogs.

Only one layout is ever in the DOM (useMediaQuery via useSyncExternalStore),
so the 3.3 MB design SVG never downloads on mobile. Footer links, FAQ
copy, and site URLs are now shared data modules (src/data/site.ts,
src/data/faq.ts) so the two layouts can't drift; the desktop overlay's
positioned footer list is derived from the same FOOTER_COLUMNS. The
mobile footer's fourth column is correctly headed "Connect" (the artwork
still reads "COMPANY" twice -- that text is outlined vector paths in the
SVG and needs the asset regenerated).

Verified on apps/marketing: typecheck, lint, build. Manual: 375 / 768 /
1280px in light and dark -- no horizontal overflow, hero/steps/cards/FAQ
readable, accordion expands, anchor links scroll under the sticky header,
theme toggle works in both layouts, desktop canvas unchanged at >= 1024,
layout switches on a real resize event.
…landing

feat(marketing): responsive landing page for phones and tablets
The < 1024px layout from #426 was readable but generic. Rework it to
carry the authored design canvas's visual language, since there is still
no authored mobile comp to match pixel-for-pixel:

- Heavy Title Case display headings (Inter 800), the design's own hero
  line, and an editorial serif italic (Newsreader) for the hero sub-copy.
- The "Meet Partha" script flourish (Caveat) and the outline eyebrow pill
  with an orange dot.
- Pill CTAs with icons: outline + a filled play triangle, solid orange +
  an external-link glyph.
- Three alternating peach / teal-blue tinted story cards
  ("See How The System Fits Together", "Know Where Every Finding Came
  From", "Honest About Its Limits"), matching the desktop card treatment.
- How-it-works, capabilities, FAQ, and the CTA band restyled to the same
  system (warm borders, tinted number / icon chips, + toggles).

Adds --brand-blue (the design's #006298 teal) as a token with a dark-mode
value, a dark-mode --secondary, and font-serif / font-script families;
extends the Google Fonts link with Inter 800, Newsreader italic, Caveat.
Desktop (>= 1024px) is untouched.

Verified on apps/marketing: typecheck, lint, build. Manual at 390 / 844
and 1300px, light and dark: fonts load (no fallback), no horizontal
overflow, every section renders, desktop canvas unchanged.
…tions v1

Rework against the PARTHA Foundations v1 handoff (Typography and Color)
rather than colours and fonts eyeballed off the design SVG:

Type — the three assigned families, real weights loaded (the handoff
treats fallback rendering as a failure):
  - font-display = Montserrat Alternates (500/600/700): headings, buttons,
    short feature titles, nav.
  - font-sans    = Proza Libre (400/500, +italic): all reading text,
    metadata, labels. Replaces Inter as the default body family.
  - font-accent  = Cormorant Upright: exactly one expressive phrase
    ("Partha" in "Meet Partha"). Newsreader / Caveat removed.

Colour — tokens set to the approved hex values in their roles:
  - --primary        Signal Orange #FA4D01, CTA / focus only, kept scarce.
  - --secondary      Deep Blue #006298, architecture / technical.
  - --burnt-orange   #AA3D00 (new token), selective secondary emphasis.
  - --foreground     Deep Plum #392135, headlines and body text.
  - dark mode uses Deep Plum as the dark surface, not pure black; the
    pre-hydration flash guard matches.

MobileLanding restyled to the type scale and the ~70% neutral / 15% plum /
10% blue / 5% orange balance: subtle tinted story cards, blue technical
labels, Signal Orange confined to the one primary CTA, the eyebrow dot,
and the accent word. Shared dialog headings pick up font-display.

Desktop (>= 1024px, the authored canvas) is unaffected. Verified on
apps/marketing: typecheck, lint, build; 390 / 844 and 1300px, light and
dark -- all three fonts load (no fallback), no horizontal overflow.
…d-match

feat(marketing): responsive mobile landing, aligned to Foundations v1
… layout

The mobile layout was type + colour + icon chips. Compose the designer's
own exported SVG assets (Partha Assets/) so it carries the same
illustrations as the authored desktop canvas:

- hero: the soft radial-glow + wireframe visual behind the headline, and
  the asterisk accent mark in the eyebrow pill.
- product story cards: each now shows its real graphic -- the resolved
  service graph ("see how the system fits together"), the
  auth.service.ts evidence trace ("know where every finding came from"),
  and the extracted / partial / not-assessed summary ("honest about its
  limits").
- how it works: each step shows its own designed illustration (the
  revision -> repository -> sealed snapshot loop; the
  files/symbols/dependencies/relationships extraction; the finding traced
  to evidence).
- CTA: the plus-blob mark as a faint corner decoration.

Assets are copied into src/assets/landing/ (self-contained build),
rendered with loading="lazy", ~230 KB gzipped total and only on the
< 1024px path -- the 3.3 MB desktop canvas still never loads on a phone.
Capabilities collapse to a plain list; the hero decoration is clipped so
there is no horizontal overflow.

Foundations v1 type and colour roles are unchanged. Desktop (>= 1024px)
is unaffected. Verified on apps/marketing: typecheck, lint, build; 390 /
844 and 1280px, light and dark -- fonts load, no overflow, all
illustrations render, desktop canvas unchanged.
…gner-assets

feat(marketing): use the designer's illustration assets in the mobile landing
… ci: harden browser-acceptance apt step (#429)

QA-pass findings #1 (lineage read API + UI exist — docs said they didn't;
support_matrix.py + regenerated README block + README prose + backend README
endpoint row) and #2 (SameSite=Lax refresh cookie → SPA+API must be same-site;
SYSTEM_OVERVIEW.md + DEVELOPMENT.md). Plus a CI fix: strip the flaky
dl.google.com Chrome apt source before `playwright install --with-deps` so a
Google-side index hash-mismatch can't red the browser-acceptance job.

Docs + CI only, no product behaviour change. All checks green.
@vercel

vercel Bot commented Sep 9, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated
partha-frontend Ready Ready Preview Sep 9, 2026 6:17pm UTC

@parthrohit22
parthrohit22 merged commit 2c9977c into main Sep 9, 2026
20 checks passed
parthrohit22 added a commit that referenced this pull request Sep 9, 2026
chore: sync dev with main after the #430 promotion
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.

5 participants