Repository navigation
feat(portfolio): move repository observation and state assembly into Core - #5
Conversation
chubirka
left a comment
There was a problem hiding this comment.
Not acceptable as-is. I would request changes before approving. Reviewed commit dfd123a.
The design is sound—store GitHub/GitLab measurements, then derive current repository state—but two reproduced parsing defects can understate repository changes.
Required corrections
1. GitLab omitted diffs are treated as complete measurements
The parser ignores GitLab’s collapsed and too_large flags. GitLab documents these as indicating omitted diff content.
Replaying those responses produced:
status="ok", changed_lines=0, compare_truncated=false
That incorrectly presents unmeasured changes as a complete zero-line comparison.
Correction: account for these flags; fetch omitted content where possible or mark the comparison incomplete. Add regression coverage for both flags.
2. Invalid GitHub commit counts are silently coerced
int(payload.get("ahead_by") or 0) accepts malformed post---jq payloads:
Input ahead_by |
Accepted count |
|---|---|
null |
0 |
true |
1 |
1.5 |
1 |
Correction: validate the required response fields and types before constructing Comparison. Invalid measurements should raise SourceError, not become successful observations. Add regression tests for these cases.
Verification
- 330 local unit tests passed; Ruff lint and formatting passed.
- Reviewed PostgreSQL CI logs: 26 integration tests passed.
- Live smoke exercised
Context.repository_observation()through the real GitHub CLI and SQLite persistence. - Confirmed state recomputation after findings were resolved, without additional network calls; baseline-change rejection; persistence across reopening; distinct URL-less filings; and
network=False.
The normal path works. The two parser corrections and regression tests are needed before LGTM.
smith-xyz
left a comment
There was a problem hiding this comment.
Really solid work — the design is sound (agree with @chubirka's assessment there), the forge commands all run through ProcessRunner, persistence goes through SqlUnitOfWork, and I ran a drift scan over your branch: zero new hand-wiring, exactly the sanctioned wrappers. The test coverage (malformed forge replies, stale-write rejection, baseline invalidation) is the right test list.
Merge plan from my side: #7 (symbol pass) lands first, then this rebases. Heads-up on what that rebase carries, so you can budget for it — details inline. Plus one design ask (the repo_state composition) that I want to fold in while you're in there anyway.
The two forge parser fixes @chubirka requested are required before merge from my side too — no need to re-litigate those here, just noting agreement.
| hosts: Mapping[str, str] | None = None, | ||
| ) -> None: | ||
| self._uow, self._policy = uow, policy | ||
| self._assessment, self._findings, self._sources = assessment, findings, sources |
There was a problem hiding this comment.
One design ask for the rebase: PortfolioService holds FindingUnitOfWork and DesignationRepository directly — two other domains' persistence. The AGENTS.md design rules landing in #7 draw this as: repositories belong to their domain, services compose across domains by calling that domain's service, and cross-domain composite reads get their own query object.
The good news: the shape you were reaching for already has a designated home. #7 defines RepoStateQuery (services/portfolio.py, stub marked replaces traust#58 observe_repo) which composes PortfolioService + AssessmentService + FindingService and owns repo_state() / list(). On rebase, moving your implementation into that shape keeps repo_state() from being a special case — it's the sanctioned composite-read path, and later it can become a direct projection without touching anything else.
None of this is "you did it wrong" — you wrote against main, before the rules existed. It's the natural shape when the rules aren't there yet.
|
|
||
| def release(self, workspace: Workspace) -> None: ... | ||
|
|
||
| def compare(self, repo: HttpsRepoUrl, baseline: str, branch: str) -> Result[Comparison]: ... |
There was a problem hiding this comment.
Rebase heads-up, not a change request: #7's SourceProvider protocol has owners(), list_org(), languages() and doesn't know compare/quota. Yours is additive so conceptually it's a union — but on rebase it's worth deciding together whether forge-comparison ops belong on SourceProvider itself or on a narrower port that CommandSourceProvider additionally implements. Also noting ChangedFile/Comparison/Quota are Model here; #7's boundary-data convention is Dto (tolerant, at the edge) — worth aligning when the files merge.
Two more rebase items so they don't surprise you: (1) #7 renames Provenance → Acquisition, ArtifactIndex → BindingIndex, and folds TransitionPolicy into Disposition — your branch uses all three old names; (2) tests/v1/services/test_assessment.py and test_portfolio.py are deleted in #7 (replaced by the test_stubs.py discovery harness), so your versions will need porting into the new test layout.
Accuracy revalidation: existing changes remain requiredReviewed head: This head is unchanged since the earlier changes-requested review. The existing required corrections remain in the source:
Current-main integrationAn isolated The next step is to rebase and adapt this implementation to the current table/repository model and public interfaces, reconcile the contracts pin, and address the existing parser and composition requests before rerunning integration validation. Passing tests against the older branch baseline would not establish compatibility with current main. Verification limitsThe unchanged parser defects were checked in source against the existing review; their previously reported failures were not rerun merely to reconfirm them. No new unit or PostgreSQL validation is claimed for this PR. The original reported 330-unit/26-PostgreSQL results are not new results from this revalidation. Recommendation: keep changes requested. No PR source changes were made during this review. |
Takes over PR #5: girish's observation flow, re-homed onto the table repositories and keyed by repository_id. No legacy bridges - targets, audits and open counts read the contract tables and the finding_status view; observations persist in memory until contracts lands repository_observation. Includes Michele's required forge-parser corrections with regression tests, the per-role observation modules (models/observation, services/observation, repositories/observation), and a repo commit-prep skill.
dfd123a to
36a2517
Compare
|
@chubirka re-review requested — your two required corrections are in (strict ahead_by validation; GitLab collapsed/too_large marks the comparison incomplete), each with regression tests. Since your review, the branch was taken over and re-homed onto the table-repository architecture from #10: no more legacy projection reads (targets/audits/open counts now come from the contract tables and the finding_status view), and the flow is keyed by repository_id. Your verified behaviors survive — the recorded-reply parity fixtures are unchanged. The write-up above covers the rest. |
|
@chubirka your revalidation landed minutes before the takeover push — please re-review against the new head
Your recorded-reply parity fixtures and the behaviors you verified (baseline-change rejection, stale-write dropping, |
Recheck: not fixed yetReviewed current head The original GitHub
Verification
The passing suite does not currently establish the end-to-end claim. Recommendation: keep changes requested. No PR source changes were made during this recheck. |
- repository_observation is a Context method again (the splice had moved it onto the _Observation dataclass), and the door now opens one session unit of work so collaborators share a live connection - SqlUnitOfWork is re-entrant: nested with-blocks share the outermost connection and transaction; the outer owner rolls back and closes - observation_target reads only completed assessments as baselines - a degraded or failed run is never read as clean (the DDL rule) - via a status filter on AssessmentRepository.latest Regression tests: the door end-to-end over the contract tables, nested unit-of-work sharing, and failed-audit baseline selection.
|
All three reproduced defects are fixed in
Gates on this head: lint clean, 843 unit, 208 Postgres integration, coverage 93.7%. |
|
/lgtm |
What this PR delivers
Repository observation, end to end: gh/glab measurements in, derived routing state out — girish's original design and behavior, taken over and re-homed onto the table-repository architecture that landed in #10.
providers/forge.py):gh/glabthroughProcessRunner, strict reply validation, both of Michele's required corrections (below)models/observation.py(target, facts, derived state — keyed byrepository_id),services/observation.py(measurement machinery: change metrics, ban breaker,observe_forge_facts),repositories/observation.py(the store)RepoStateQuery.observed()derives exposure class, risk tier, freshness, and open critical/high countsContext.repository_observation(designations=…)wires the whole flow over oneSqlTraustUnitOfWorkMichele's required corrections (from review of dfd123a)
collapsed/too_largeflags (per-diff and response-level) now mark the comparisontruncated(incomplete), never a complete zero-line measurement. Regression tests for all three flag shapes.ahead_byis strictly validated —null/true/1.5raiseSourceErrorinstead of coercing to 0/1/1. Regression cases added to the malformed-payload table.Her verified behaviors all survive: baseline-change rejection, stale-write dropping,
network=False, quota-deferred reuse, and the recorded-reply parity fixtures.The philosophy
The data model is the guide; nothing reads around it.
repos/findingstables, portfolio-graphnodes). This version deleted all of that: targets resolve throughrepositories.find_by_url→repository_refs.list_for→assessments.latest; open counts read thefinding_statusview (itsis_openis the one definition of open); inventory membership is therepositorytable itself.portfolio_forge_factsis gone): observation persistence is a protocol + in-memory store until contracts landsrepository_observation— the one named gap below.PortfolioPolicy(thresholds are config); the composite read isRepoStateQuery; the service layer keeps domain naming (PortfolioService) while persistence mirrors the model's sections exactly.OpenCountsis a consumed-where-defined port), boundary data isDto, designation falls back to the filing's value when the config file has no match, unreadable inputs log rather than silently returning empty.What activates it
The flow is live but reads only the contract tables — so it lights up as the data migration populates
repository,repository_refandassessment. A repo not yet in the model is simply not observable (by design: bootstrap targeting comes from inventory, not from scraping).The one open dependency
repository_observation— an append-only measurements table (repository_id,observed_at, status, comparison metrics) in contracts. Until it lands, observations are in-memory only (correct within a run; re-observed across runs). When it lands, the store becomes aTableSpec+SqlTableRepositoryin one small file and theNEEDmarker clears.lines_reviewed(churn denominator) carries aNEED(artifacts)marker for the same reason.Also riding along
.agents/skills/commit-prep/— a repo skill that runs changes through the gates and up as a PR in one trigger (drafted during this work; small and self-contained)Verification
make lint-fix·make test(839) ·make coverage(93.5%, floor 80) ·make test-integration(205, Postgres) — all green. New contract-table tests cover latest-audit selection, never-audited targets, inventory membership, and the view's decision semantics on both dialects.Tracker: XWING-2214