Skip to content

feat: Plan 6 PR A — network score computation core - #12

Merged
messagesgoel-blip merged 5 commits into
mainfrom
feat/network-score-computation
Jul 29, 2026
Merged

feat: Plan 6 PR A — network score computation core#12
messagesgoel-blip merged 5 commits into
mainfrom
feat/network-score-computation

Conversation

@messagesgoel-blip

@messagesgoel-blip messagesgoel-blip commented Jul 29, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Migrations 009011: deferrable network_scores FK, weight [0,1] checks, network_score_history.entity_kind
  • Graph loader with mandatory expiry + active-principal filters and shared evaluationTime
  • observation_id grouping; score writer with pg_advisory_xact_lock + score.upsert/score.delete
  • Empty-roots: cold-start no-op vs clear stale scores
  • Live gRPC RunVeriRank client (@grpc/proto-loader); recomputeNow orchestration
  • API trust_weight validation in [0,1]; sync appendEvent uses xact lock
  • Unit tests for grouping, score diff, weight bounds

Out of this PR (Plan 6 PR B): single-flight scheduler, ingest markDirty, mandatory CI trust-engine integration test.

Test plan

  • cd control-plane && npm run test:unit
  • Migrations applied on local verilink_test (port 15432)
  • CI Gate / Proto / Go integration / Control plane integration green
  • Optional: start trust-engine and call recomputeNow against seeded graph

@coderabbitai review

Summary by CodeRabbit

  • New Features
    • Added active-attestation-based network score recomputation with cold-start and stale-clearing paths.
    • Introduced new scoring graph loading (including network score count) plus split-visibility attestation grouping.
    • Added trust-engine gRPC execution with streaming and retry support; expanded score write/clear operations.
    • Added configurable score recomputation timing.
  • Bug Fixes
    • Enforced issuer trust-weight bounds and strengthened score database constraints (FK behavior, weight bounds, and score-history entity kind).
    • Improved graph eligibility calculations based on the provided evaluation time.
  • Tests
    • Added coverage for attestation grouping, score change detection, trust-weight validation, and gRPC drain/error handling.

Add migrations for deferrable score FK, [0,1] weight bounds, and history entity_kind; load/group active attestations; stream RunVeriRank over gRPC; apply scores and sync events under pg_advisory_xact_lock.

Co-authored-by: Cursor <cursoragent@cursor.com>
@cursor

cursor Bot commented Jul 29, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

@gemini-code-assist

Copy link
Copy Markdown

Caution

The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased.

@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

You’ve reached a temporary PR review limit under our Fair Usage Limits Policy.

Your recent review volume is higher than typical usage, so adaptive limits are currently applied.

Next review available in: 53 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: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 1707c458-98da-41e6-98f7-5845b4f10d9b

📥 Commits

Reviewing files that changed from the base of the PR and between 0614d98 and caf7413.

📒 Files selected for processing (1)
  • docs/superpowers/plans/2026-07-28-network-score-computation.md

Walkthrough

Adds an end-to-end network score recomputation pipeline: active attestation graph loading, observation grouping, TrustEngine gRPC execution with retries, transactional score/history persistence, sync events, trust-weight validation, and database migrations.

Changes

Network score pipeline

Layer / File(s) Summary
Graph loading and attestation grouping
control-plane/src/domains/graph/attestationGraphLoader.ts, control-plane/src/domains/graph/observationGrouping.ts, control-plane/src/domains/graph/observationGrouping.test.ts
Loads eligible graph data, groups split-visibility attestations, and validates grouping behavior.
Trust-engine execution and recomputation
control-plane/src/grpc/runVeriRankClient.ts, control-plane/src/domains/graph/scoreComputationService.ts, control-plane/src/config.ts, control-plane/package.json, docs/superpowers/plans/HANDOVER.md
Streams graph data to the TrustEngine, retries failures, and handles recomputation outcomes.
Transactional score persistence and event ordering
control-plane/src/domains/graph/scoreDiff.ts, control-plane/src/domains/graph/scoreDiff.test.ts, control-plane/src/domains/graph/scoreWriter.ts, control-plane/src/domains/sync/syncRepository.ts
Diffs engine results, writes current and historical scores, deletes stale rows, and emits ordered sync events.
Trust-weight validation and database guards
control-plane/src/domains/principal/*, control-plane/migrations/009_network_scores_fk_deferrable/migration.sql, control-plane/migrations/010_weight_bounds/migration.sql
Enforces bounded trust weights and adds deferred foreign-key and weight constraints.
History schema finalization
control-plane/migrations/011_network_score_history_entity_kind/migration.sql, control-plane/migrations/012_validate_score_constraints/migration.sql, docs/superpowers/plans/2026-07-28-network-score-computation.md
Backfills historical entity kinds and validates deferred constraints in a separate migration.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant ScoreComputationService
  participant AttestationGraphLoader
  participant TrustEngine
  participant ScoreWriter
  participant PostgreSQL
  ScoreComputationService->>AttestationGraphLoader: load graph at evaluationTime
  AttestationGraphLoader->>PostgreSQL: query roots, principals, attestations, and score count
  ScoreComputationService->>TrustEngine: stream graph to RunVeriRank
  TrustEngine-->>ScoreComputationService: return score rows and computed timestamp
  ScoreComputationService->>ScoreWriter: apply score table
  ScoreWriter->>PostgreSQL: upsert history, emit sync events, and delete stale scores
Loading

Possibly related PRs

  • Numeracode/verilink#4: Extends the shared control-plane configuration and service foundations used by this change.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 30.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the PR’s main scope: implementing the network score computation core for Plan 6 PR A.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ 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 feat/network-score-computation

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

@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Materialize network scores through RunVeriRank

✨ Enhancement 🐞 Bug fix 🧪 Tests 📝 Documentation ⚙️ Configuration changes 🕐 40+ Minutes

Grey Divider

AI Description

• Loads active attestation graphs and streams VeriRank scoring with one shared evaluation time.
• Atomically diffs scores, records history, and emits ordered synchronization events.
• Enforces trust-weight bounds and handles missing bootstrap roots without retaining stale scores.
Diagram

sequenceDiagram
    actor Caller
    participant Recompute as Score Service
    participant Loader as Graph Loader
    participant DB as PostgreSQL
    participant Client as gRPC Client
    participant Engine as Trust Engine
    participant Writer as Score Writer
    Caller->>Recompute: recomputeNow
    Recompute->>Loader: Load active graph
    Loader->>DB: Query graph rows
    DB-->>Loader: Principals and edges
    alt Eligible roots
        Recompute->>Client: Stream graph
        Client->>Engine: RunVeriRank
        Engine-->>Client: Score table
        Recompute->>Writer: Apply score diff
        Writer->>DB: Lock and persist
    else No eligible roots
        Recompute->>DB: Count or clear scores
    end
    Recompute-->>Caller: Recompute result
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Generate typed gRPC stubs
  • ➕ Provides compile-time request and response validation
  • ➕ Avoids dynamic package traversal and broad any casts
  • ➕ Detects protobuf contract drift during builds
  • ➖ Adds protobuf generation tooling and generated artifacts
  • ➖ Expands this PR beyond the computation-core boundary
  • ➖ Requires regeneration discipline in CI
2. Use a set-based score merge
  • ➕ Reduces database round trips for large score tables
  • ➕ Can compare, upsert, and delete scores through staging tables
  • ➖ Makes per-principal sync-event allocation more complex
  • ➖ Increases SQL and migration complexity
  • ➖ Obscures the explicit score-change and history semantics

Recommendation: Keep the current streamed computation and transactional diff writer for PR A: they preserve one evaluation time and atomically order score state with sync events. Generated gRPC stubs are the strongest follow-up for type safety; a set-based writer should only replace row-wise persistence if production graph sizes demonstrate a bottleneck.

Files changed (19) +786 / -18

Enhancement (6) +550 / -0
attestationGraphLoader.tsLoad the active VeriRank graph +149/-0

Load the active VeriRank graph

• Loads active bootstrap roots, principals, and non-superseded attestations using a shared evaluation time and half-life cutoff. It groups observations and defensively removes edges or roots whose principals are unavailable.

control-plane/src/domains/graph/attestationGraphLoader.ts

observationGrouping.tsCollapse duplicate observation edges +51/-0

Collapse duplicate observation edges

• Groups attestations by issuer, subject, and non-empty observation ID. It selects participant-visible records first, then the newest record, while preserving every unpaired attestation.

control-plane/src/domains/graph/observationGrouping.ts

scoreComputationService.tsOrchestrate immediate score recomputation +52/-0

Orchestrate immediate score recomputation

• Coordinates graph loading, RunVeriRank execution, and materialized score application with one evaluation time. Empty roots produce a cold-start no-op or clear previously materialized stale scores.

control-plane/src/domains/graph/scoreComputationService.ts

scoreDiff.tsDetect meaningful score changes +26/-0

Detect meaningful score changes

• Defines existing and engine score shapes and compares every persisted score attribute, including entity kind.

control-plane/src/domains/graph/scoreDiff.ts

scoreWriter.tsAtomically materialize computed scores +136/-0

Atomically materialize computed scores

• Diffs engine results against current scores under a transaction-scoped advisory lock. It emits ordered score upsert/delete events, records changed-score history, and removes scores absent from the engine result.

control-plane/src/domains/graph/scoreWriter.ts

runVeriRankClient.tsStream graphs to the live trust engine +136/-0

Stream graphs to the live trust engine

• Loads the trust protobuf at runtime and implements client-streaming RunVeriRank calls with deadlines, response mapping, and bounded retries. Graph headers, principals, roots, and attestations are sent as individual chunks.

control-plane/src/grpc/runVeriRankClient.ts

Bug fix (4) +62 / -15
migration.sqlConstrain trust weights to the engine range +28/-0

Constrain trust weights to the engine range

• Rejects migration when existing issuer or bootstrap weights fall outside [0,1], then replaces both database checks with inclusive bounds matching the gRPC contract.

control-plane/migrations/010_weight_bounds/migration.sql

principalService.tsValidate issuer trust weights at service entry +2/-0

Validate issuer trust weights at service entry

• Checks optional trust weights before creating or promoting a principal to an issuer, preventing invalid values from reaching persistence.

control-plane/src/domains/principal/principalService.ts

trustWeight.tsEnforce finite trust weights within bounds +10/-0

Enforce finite trust weights within bounds

• Adds a shared validator requiring supplied trust weights to be finite numbers in the inclusive [0,1] range.

control-plane/src/domains/principal/trustWeight.ts

syncRepository.tsSerialize sync versions within transactions +22/-15

Serialize sync versions within transactions

• Extracts event insertion for callers already holding the sync lock and replaces session-level advisory locking with transaction-scoped locking. Score writes can now allocate events inside the same transaction as materialized state changes.

control-plane/src/domains/sync/syncRepository.ts

Tests (3) +124 / -0
observationGrouping.test.tsTest observation-based edge grouping +80/-0

Test observation-based edge grouping

• Covers unpaired attestations, visibility preference, issued-time tie breaking, and isolation between distinct observation identifiers.

control-plane/src/domains/graph/observationGrouping.test.ts

scoreDiff.test.tsTest materialized score change detection +21/-0

Test materialized score change detection

• Verifies that score, blacklist status, reason, and entity-kind changes trigger persistence while identical rows do not.

control-plane/src/domains/graph/scoreDiff.test.ts

trustWeight.test.tsTest inclusive trust-weight validation +23/-0

Test inclusive trust-weight validation

• Confirms that zero, one, and omitted weights are accepted while negative and above-one values produce bad-request errors.

control-plane/src/domains/principal/trustWeight.test.ts

Documentation (1) +3 / -3
HANDOVER.mdUpdate the Plan 6 implementation handover +3/-3

Update the Plan 6 implementation handover

• Records that Plan 6 documentation has landed and identifies PR A completion followed by scheduler and CI integration work as the next steps.

docs/superpowers/plans/HANDOVER.md

Other (5) +47 / -0
migration.sqlDefer the network score principal foreign key +10/-0

Defer the network score principal foreign key

• Recreates the network_scores principal foreign key as initially deferred. This allows score and principal lifecycle changes to commit atomically while retaining cascading deletion.

control-plane/migrations/009_network_scores_fk_deferrable/migration.sql

migration.sqlPersist entity kind in score history +31/-0

Persist entity kind in score history

• Adds and backfills entity_kind on score history, fails when rows cannot be resolved, and enforces non-null allowed values. Kind-only score changes are therefore auditable.

control-plane/migrations/011_network_score_history_entity_kind/migration.sql

package-lock.jsonLock the protobuf loader dependency +1/-0

Lock the protobuf loader dependency

• Records @grpc/proto-loader as a runtime dependency for dynamically loading the trust-engine service definition.

control-plane/package-lock.json

package.jsonAdd runtime protobuf loading support +1/-0

Add runtime protobuf loading support

• Adds @grpc/proto-loader for constructing the live RunVeriRank client from trust.proto.

control-plane/package.json

config.tsConfigure score recomputation timing +4/-0

Configure score recomputation timing

• Adds environment-backed debounce and periodic interval settings for score recomputation, with one-minute and one-hour defaults respectively.

control-plane/src/config.ts

@qodo-code-review

qodo-code-review Bot commented Jul 29, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (1) 📘 Rule violations (1) 📜 Skill insights (0)

Context used
✅ Compliance rules (platform): 18 rules

Grey Divider


Action required

1. Stale recompute overwrites newer 🐞 Bug ≡ Correctness
Description
The advisory lock covers only score application, so overlapping recomputations can finish out of
order and let an older result acquire the lock after a newer result. The older run then replaces the
score table and emits higher sync versions for stale upserts or deletions, including through the
empty-root clear path.
Code

control-plane/src/domains/graph/scoreComputationService.ts[45]

+  const applied = await applyScoreTable(result.rows, computedAt);
Relevance

⭐⭐⭐ High

PR #11 explicitly accepted complete-sequence single-flight to prevent overlapping recomputations.

PR-#11

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
recomputeNow performs load and the potentially long RPC before entering applyScoreTable; the
writer acquires its lock only afterward and performs no freshness comparison. The accepted design
history explicitly requires single-flight across the complete sequence to prevent this race.

control-plane/src/domains/graph/scoreComputationService.ts[21-45]
control-plane/src/domains/graph/scoreWriter.ts[79-129]
PR-#11

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Prevent overlapping load, engine, and apply sequences from committing in completion order rather than evaluation order. Add single-flight coordination covering both normal application and empty-root clearing, with a dirty follow-up latch where needed.

## Issue Context
The writer advisory lock serializes transactions but does not ensure that the newest evaluation commits last.

## Fix Focus Areas
- control-plane/src/domains/graph/scoreComputationService.ts[21-45]
- control-plane/src/domains/graph/scoreWriter.ts[79-129]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Invalid gRPC metadata argument ✓ Resolved 🐞 Bug ≡ Correctness
Description
runVeriRank passes a plain object where the three-argument grpc-js overload expects a Metadata
instance, causing stream setup to fail before graph chunks are sent. Every recomputation reaching
this path retries the same invalid invocation and ultimately fails.
Code

control-plane/src/grpc/runVeriRankClient.ts[80]

+    const call = client.RunVeriRank({}, { deadline }, (err: Error | null, res: any) => {
Relevance

⭐⭐⭐ High

Deterministic gRPC invocation bug blocks every recomputation; team commonly accepts concrete
correctness fixes.

PR-#6

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The new dynamic grpc-js client is invoked with {}, deadline options, and a callback, but the
module never constructs valid grpc metadata. This RPC is mandatory before the score writer is
called.

control-plane/src/grpc/runVeriRankClient.ts[69-104]
control-plane/src/domains/graph/scoreComputationService.ts[41-45]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The client-streaming call passes `{}` in the grpc-js metadata position. Use `new grpc.Metadata()` or the appropriate options-only overload, and verify that the stream successfully transmits chunks.

## Issue Context
`recomputeNow` cannot apply any scores when stream construction fails, and retries repeat the same invalid call.

## Fix Focus Areas
- control-plane/src/grpc/runVeriRankClient.ts[69-115]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

3. Ignored gRPC backpressure ✓ Resolved 🐞 Bug ➹ Performance
Description
runVeriRank issues every graph write without checking whether call.write requests backpressure.
Since the loader materializes an unbounded graph, outgoing messages can accumulate additional
unbounded memory pressure and may exhaust the process on sufficiently large datasets.
Code

control-plane/src/grpc/runVeriRankClient.ts[106]

+      call.write({ principal: mapPrincipal(p) });
Relevance

⭐⭐ Medium

Plausible scalability risk, but fixing streaming backpressure is semantic and lacks close repository
precedent.

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The loader gathers all eligible rows into arrays without pagination or limits, while the client
loops over those arrays and ignores every write result before immediately ending the stream.

control-plane/src/domains/graph/attestationGraphLoader.ts[70-118]
control-plane/src/grpc/runVeriRankClient.ts[104-114]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Check each `call.write` result and wait for the writable stream's `drain` event before issuing more chunks when backpressure is requested. Preserve header-first ordering and close the client on pump failures.

## Issue Context
The graph loader has no row limit, so the number of chunks scales directly with stored principals and attestations.

## Fix Focus Areas
- control-plane/src/grpc/runVeriRankClient.ts[78-115]
- control-plane/src/domains/graph/attestationGraphLoader.ts[70-118]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


4. Control-plane integration remains unverified 📘 Rule violation ▣ Testability
Description
The PR changes control-plane scoring, database, and gRPC behavior, but its test plan does not
confirm that npm run test:integration passed. Integration regressions may therefore reach merge
without validation.
Code

control-plane/src/domains/graph/scoreComputationService.ts[R21-25]

+export async function recomputeNow(
+  evaluationTime: Date = new Date(),
+  opts: { trustEngineAddr?: string } = {}
+): Promise<RecomputeResult> {
+  const graph = await loadAttestationGraph(evaluationTime);
Relevance

⭐⭐ Medium

Explicit integration rule supports it, but mandatory trust-engine integration is deliberately
deferred to PR B.

PR-#7
PR-#9

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
PR Compliance ID 2383202 requires control-plane changes to pass npm run test:integration. The
changed recomputeNow orchestration affects control-plane functionality, while the supplied PR test
plan leaves the Control plane integration gate unchecked; the branch files confirm both the
integration command and its CI job exist.

Rule 2383202: Run and pass control-plane integration tests for changes affecting control-plane functionality
control-plane/src/domains/graph/scoreComputationService.ts[21-45]
control-plane/package.json[11-13]
.github/workflows/ci.yml[117-161]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The control-plane integration suite has not been confirmed successful for the new network-score functionality.

## Issue Context
Run `npm run test:integration` with the required PostgreSQL environment and resolve any failures before merge. The repository already defines the command and a CI job for it.

## Fix Focus Areas
- control-plane/src/domains/graph/scoreComputationService.ts[21-45]
- control-plane/package.json[11-13]
- .github/workflows/ci.yml[117-161]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

To customize comments, go to the Qodo configuration screen, or learn more in the docs.

Qodo Logo

Comment thread control-plane/src/domains/graph/scoreComputationService.ts
Comment thread control-plane/src/grpc/runVeriRankClient.ts Outdated
Comment thread control-plane/src/domains/graph/scoreComputationService.ts
Comment thread control-plane/src/grpc/runVeriRankClient.ts Outdated

@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: 11

🤖 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 `@control-plane/migrations/009_network_scores_fk_deferrable/migration.sql`:
- Around line 4-10: Update the network_scores.principal_id foreign-key
recreation to add it with NOT VALID, then validate it in a separate ALTER TABLE
... VALIDATE CONSTRAINT statement. Keep the existing constraint name, referenced
table, cascade behavior, and deferrable settings unchanged.

In `@control-plane/migrations/010_weight_bounds/migration.sql`:
- Around line 20-28: Update the migration’s constraint replacement for
issuers.trust_weight and bootstrap_issuers.current_weight to add temporary NOT
VALID check constraints, validate each constraint in a controlled step, then
drop the old constraints and rename the validated temporary constraints to the
existing names. Apply the same sequence to both tables while preserving the 0–1
bounds.

In
`@control-plane/migrations/011_network_score_history_entity_kind/migration.sql`:
- Around line 26-31: The migration should avoid blocking full-table scans by
replacing ALTER COLUMN entity_kind SET NOT NULL with a NOT VALID CHECK for
non-null values, validating that constraint, then setting entity_kind NOT NULL;
also add network_score_history_entity_kind_check as NOT VALID followed by
VALIDATE CONSTRAINT.

In `@control-plane/src/domains/graph/attestationGraphLoader.ts`:
- Around line 67-118: Update loadAttestationGraph to acquire one PoolClient and
execute the roots query, attestations query, and principalRows query within a
single transaction using REPEATABLE READ isolation; commit on success and
rollback on failure, releasing the client in all cases. Replace the independent
pool.query calls while preserving the existing evaluationTime and halfLifeCutoff
parameters and graph-building behavior.

In `@control-plane/src/domains/graph/observationGrouping.test.ts`:
- Around line 73-79: Add a test alongside “does not merge different observation
ids” that creates attestations sharing the same observation_id but using
different issuer_id and/or subject_id, then asserts groupAttestationsForScoring
returns both groups. Preserve the existing representative-selection behavior
while covering the cross issuer/subject case.

In `@control-plane/src/domains/graph/scoreWriter.ts`:
- Around line 15-23: Update loadExistingScores to avoid unconditionally loading
the full network_scores table during each recompute; restrict the query to only
scores for principals/entities included in the current recomputation, passing
that scope into the function and using parameterized filtering while preserving
the existing Map<string, ExistingScore> result.
- Around line 114-126: Update the delete branch in the score-writing loop to
insert a final network_score_history row using the pre-delete values from
existing.get(principalId), including entity_kind, score, blacklisted, and
score_reason, before deleting from network_scores. Preserve the existing
score.delete event and deletion flow, and remove the comment indicating deletes
have no history row.

In `@control-plane/src/domains/principal/trustWeight.test.ts`:
- Around line 13-22: Extend the test case around assertTrustWeightInRange to
assert that NaN, Infinity, and -Infinity each throw an AppError with code
CODES.BAD_REQUEST, covering the explicit finite-number validation branch.

In `@control-plane/src/grpc/runVeriRankClient.ts`:
- Around line 118-136: The runVeriRankWithRetry loop currently retries every
error; update its catch logic to retry only errors with transient gRPC statuses
UNAVAILABLE, DEADLINE_EXCEEDED, or RESOURCE_EXHAUSTED. Immediately propagate
permanent errors such as INVALID_ARGUMENT or PERMISSION_DENIED, while preserving
the existing backoff and final-error behavior for retryable failures.
- Around line 74-76: Update the TrustEngine client construction in
runVeriRankClient to select transport credentials based on the configured
config.trustEngine.addr, using secure TLS credentials for non-local or
externally reachable endpoints while retaining insecure credentials only for
explicitly local development addresses. Ensure TRUST_ENGINE_ADDR cannot cause
principal or attestation data to be sent over plaintext on an untrusted network.
- Around line 78-116: Update the RunVeriRank invocation in the surrounding
client method to pass a grpc.Metadata instance as the call metadata argument
instead of the plain object currently containing deadline. Preserve the
60-second deadline by supplying it through the appropriate call options
argument, while leaving the stream writing and response handling unchanged.
🪄 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: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: d2b0b57a-e0ff-475c-8d7a-c3362659ef78

📥 Commits

Reviewing files that changed from the base of the PR and between 232e409 and 298df2b.

⛔ Files ignored due to path filters (1)
  • control-plane/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (18)
  • control-plane/migrations/009_network_scores_fk_deferrable/migration.sql
  • control-plane/migrations/010_weight_bounds/migration.sql
  • control-plane/migrations/011_network_score_history_entity_kind/migration.sql
  • control-plane/package.json
  • control-plane/src/config.ts
  • control-plane/src/domains/graph/attestationGraphLoader.ts
  • control-plane/src/domains/graph/observationGrouping.test.ts
  • control-plane/src/domains/graph/observationGrouping.ts
  • control-plane/src/domains/graph/scoreComputationService.ts
  • control-plane/src/domains/graph/scoreDiff.test.ts
  • control-plane/src/domains/graph/scoreDiff.ts
  • control-plane/src/domains/graph/scoreWriter.ts
  • control-plane/src/domains/principal/principalService.ts
  • control-plane/src/domains/principal/trustWeight.test.ts
  • control-plane/src/domains/principal/trustWeight.ts
  • control-plane/src/domains/sync/syncRepository.ts
  • control-plane/src/grpc/runVeriRankClient.ts
  • docs/superpowers/plans/HANDOVER.md

Comment thread control-plane/migrations/009_network_scores_fk_deferrable/migration.sql Outdated
Comment thread control-plane/migrations/010_weight_bounds/migration.sql Outdated
Comment thread control-plane/migrations/011_network_score_history_entity_kind/migration.sql Outdated
Comment thread control-plane/src/domains/graph/attestationGraphLoader.ts
Comment thread control-plane/src/domains/graph/observationGrouping.test.ts
Comment thread control-plane/src/domains/graph/scoreWriter.ts
Comment thread control-plane/src/domains/principal/trustWeight.test.ts
Comment thread control-plane/src/grpc/runVeriRankClient.ts Outdated
Comment thread control-plane/src/grpc/runVeriRankClient.ts
Comment thread control-plane/src/grpc/runVeriRankClient.ts
Pass grpc.Metadata instead of a plain object, honor write backpressure, and load roots/attestations/principals/score-count in one REPEATABLE READ transaction so torn reads cannot clear the score table.

Co-authored-by: Cursor <cursoragent@cursor.com>
@messagesgoel-blip

Copy link
Copy Markdown
Collaborator Author

Fixes for review findings (this push)

P0 / your callouts

  1. Invalid gRPC metadataRunVeriRank now takes new grpc.Metadata() + { deadline } (not a plain {} as metadata).
  2. Torn graph readsloadAttestationGraph runs all queries (roots, attestations, principals, network_scores count) in one REPEATABLE READ transaction; empty-roots clear uses that same snapshot count.

Also addressed

  • Client-stream write backpressure (drain)
  • Retry skips permanent gRPC codes (INVALID_ARGUMENT, etc.)
  • Loopback-only insecure creds; TLS via createSsl() otherwise
  • Migration constraints added NOT VALID then VALIDATE
  • Extra unit tests (issuer/subject grouping, non-finite weights)

Deferred to Plan 6 PR B (as planned)

  • Overlapping recompute / single-flight (scheduler) — still required before Plan 6 complete
  • Full live RunVeriRank integration test in CI

Integration: npm run test:integration passed locally (9/9).

@messagesgoel-blip messagesgoel-blip left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-review of commit 2a6ceaf:

  • Validated the original two blockers as fixed: RunVeriRank now receives grpc.Metadata plus call options and honors stream backpressure; graph roots, attestations, principals, and score count now share one REPEATABLE READ snapshot.
  • The retry and loopback/TLS changes are present, the additional grouping and non-finite weight tests are present, and all four CI jobs are green on this head. The reported local integration result is 9/9.
  • Single-flight remains intentionally deferred to PR B, matching the locked plan.
  • The score-delete history and scoped score-load suggestions should be closed with design rationale: Decisions 10/12 require no history row on delete, and the writer needs the complete existing-score set to identify stale rows.

One remediation is incomplete: migration 011_network_score_history_entity_kind still executes ALTER COLUMN entity_kind SET NOT NULL before establishing and validating a temporary non-null check. The later NOT VALID check only validates the enum values, so it does not remove the blocking full-table scan called out in the review. If production-safe migration behavior is required, add CHECK (entity_kind IS NOT NULL) NOT VALID, validate it, then set the column NOT NULL.

Outcome: fixes validated; the migration-011 item above is the only remaining actionable issue from this pass. Submitted as a comment review because the authenticated account is the PR author and GitHub does not permit self-approval.

@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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
control-plane/migrations/011_network_score_history_entity_kind/migration.sql (1)

26-27: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Past review comment only half-addressed: SET NOT NULL still causes a blocking table scan.

The prior request was to add a NOT VALID CHECK for non-null, validate it, then run SET NOT NULL (so Postgres can skip the redundant scan). The entity_kind_check CHECK constraint below was split into NOT VALID + VALIDATE CONSTRAINT, but this ALTER COLUMN entity_kind SET NOT NULL was left unchanged and still forces a full-table scan under a blocking lock.

🔧 Proposed fix
+ALTER TABLE network_score_history
+  ADD CONSTRAINT network_score_history_entity_kind_not_null
+  CHECK (entity_kind IS NOT NULL) NOT VALID;
+
+ALTER TABLE network_score_history
+  VALIDATE CONSTRAINT network_score_history_entity_kind_not_null;
+
 ALTER TABLE network_score_history
   ALTER COLUMN entity_kind SET NOT NULL;
+
+ALTER TABLE network_score_history
+  DROP CONSTRAINT network_score_history_entity_kind_not_null;
🤖 Prompt for 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.

In `@control-plane/migrations/011_network_score_history_entity_kind/migration.sql`
around lines 26 - 27, Update the migration’s entity_kind constraint sequence:
after validating the existing entity_kind_check constraint, change the ALTER
COLUMN entity_kind SET NOT NULL operation to the PostgreSQL form that reuses the
validated CHECK constraint and avoids another blocking table scan. Preserve the
prior NOT VALID and VALIDATE CONSTRAINT steps.
🤖 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 `@control-plane/migrations/009_network_scores_fk_deferrable/migration.sql`:
- Around line 7-14: Remove the VALIDATE CONSTRAINT statement from
control-plane/migrations/009_network_scores_fk_deferrable/migration.sql, leaving
the foreign key creation as NOT VALID, and add its validation to a later
migration. Likewise remove validation of network_score_history_entity_kind_check
from
control-plane/migrations/011_network_score_history_entity_kind/migration.sql and
place it in a later migration, so both scans run outside their creation
migrations’ lock windows.

In `@control-plane/migrations/010_weight_bounds/migration.sql`:
- Around line 20-24: Rework the migration’s constraint replacement sequence so
the new uniquely named NOT VALID trust-weight constraints are added first and
validated in a separate transaction, confirming the migration runner supports
that transaction boundary. Only after validation, use a short transaction to
drop the existing issuers trust-weight constraints and rename the validated
replacements to the original constraint names; apply the same ordering to the
additional constraint block.

In `@control-plane/src/grpc/runVeriRankClient.ts`:
- Line 129: Update the deadline calculation in runVeriRankClient to avoid a
fixed 60-second timeout: source the timeout from the existing configuration if
available, or scale it using the graph’s principals and attestations counts to
accommodate drain backpressure for larger graphs. Preserve the current deadline
behavior for small graphs and ensure the computed deadline remains a valid
future Date.
- Around line 71-77: Update writeChunk so the wait after call.write(chunk)
returns false races the 'drain' event against stream 'error' and 'close' events,
rejecting or otherwise terminating promptly when either failure event occurs
instead of hanging. Preserve the immediate return when write succeeds.

---

Outside diff comments:
In
`@control-plane/migrations/011_network_score_history_entity_kind/migration.sql`:
- Around line 26-27: Update the migration’s entity_kind constraint sequence:
after validating the existing entity_kind_check constraint, change the ALTER
COLUMN entity_kind SET NOT NULL operation to the PostgreSQL form that reuses the
validated CHECK constraint and avoids another blocking table scan. Preserve the
prior NOT VALID and VALIDATE CONSTRAINT steps.
🪄 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: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 7637f2b8-247b-49f1-82f0-8d9cf55f957d

📥 Commits

Reviewing files that changed from the base of the PR and between 298df2b and 2a6ceaf.

📒 Files selected for processing (8)
  • control-plane/migrations/009_network_scores_fk_deferrable/migration.sql
  • control-plane/migrations/010_weight_bounds/migration.sql
  • control-plane/migrations/011_network_score_history_entity_kind/migration.sql
  • control-plane/src/domains/graph/attestationGraphLoader.ts
  • control-plane/src/domains/graph/observationGrouping.test.ts
  • control-plane/src/domains/graph/scoreComputationService.ts
  • control-plane/src/domains/principal/trustWeight.test.ts
  • control-plane/src/grpc/runVeriRankClient.ts

Comment thread control-plane/migrations/009_network_scores_fk_deferrable/migration.sql Outdated
Comment thread control-plane/migrations/010_weight_bounds/migration.sql Outdated
Comment thread control-plane/src/grpc/runVeriRankClient.ts
Comment thread control-plane/src/grpc/runVeriRankClient.ts Outdated
Validate replacement FK/CHECK constraints before dropping live ones, use a validated NOT NULL check before SET NOT NULL, and race drain against stream error/close so writeChunk cannot hang.

Co-authored-by: Cursor <cursoragent@cursor.com>
@messagesgoel-blip

Copy link
Copy Markdown
Collaborator Author

Follow-up fixes (drain + migration ordering)

  • writeChunk: race drain vs error/close (no hang on failed stream)
  • Deadline: scales with streamed chunk count (base 60s + 5ms/chunk, cap 10m)
  • 009 / 010: add+validate _v2 constraints before dropping live ones, then rename
  • 011: validated CHECK (entity_kind IS NOT NULL) before SET NOT NULL

Unit + integration still green locally.

@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: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
control-plane/migrations/011_network_score_history_entity_kind/migration.sql (1)

7-17: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Do not populate historical rows from only current-state kinds.

This assigns every pre-migration history row the current network_scores.entity_kind or principals.entity_kind. If a principal changed kind, earlier history is rewritten with the new kind and cannot audit that change. Use an authoritative historical source, or explicitly document and test that pre-011 rows are only current-state backfills.

🤖 Prompt for 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.

In `@control-plane/migrations/011_network_score_history_entity_kind/migration.sql`
around lines 7 - 17, Revise the migration’s entity_kind backfill statements for
network_score_history so historical rows are not assigned solely from current
network_scores or principals values. Use an authoritative historical source that
preserves each row’s kind; if unavailable, explicitly document the current-state
backfill limitation and add tests covering principals whose kind changed before
migration 011.
🤖 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 `@control-plane/migrations/009_network_scores_fk_deferrable/migration.sql`:
- Around line 11-12: Separate constraint creation from validation so validation
does not run within the originating migration transaction. In
control-plane/migrations/009_network_scores_fk_deferrable/migration.sql lines
11-12, move network_scores_principal_id_fkey_v2 validation to a later migration;
do the same for the temporary non-null and entity-kind constraints in
control-plane/migrations/011_network_score_history_entity_kind/migration.sql
lines 31-32 and 44-45, and the issuer and bootstrap weight constraints in
control-plane/migrations/010_weight_bounds/migration.sql lines 24 and 31.

In `@control-plane/src/grpc/runVeriRankClient.ts`:
- Around line 76-85: The Promise.race in the backpressure handling flow must
remove losing event listeners after settlement. Update the logic around call and
the drain/error/close listeners to attach handlers once, settle on the first
event, and clean up all remaining listeners whether drain, error, or close wins,
while preserving the existing error propagation and closed-stream failure
behavior.

---

Outside diff comments:
In
`@control-plane/migrations/011_network_score_history_entity_kind/migration.sql`:
- Around line 7-17: Revise the migration’s entity_kind backfill statements for
network_score_history so historical rows are not assigned solely from current
network_scores or principals values. Use an authoritative historical source that
preserves each row’s kind; if unavailable, explicitly document the current-state
backfill limitation and add tests covering principals whose kind changed before
migration 011.
🪄 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: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 2c8bb667-d007-418c-ad89-2a32f09623f3

📥 Commits

Reviewing files that changed from the base of the PR and between 2a6ceaf and 98b73d9.

📒 Files selected for processing (4)
  • control-plane/migrations/009_network_scores_fk_deferrable/migration.sql
  • control-plane/migrations/010_weight_bounds/migration.sql
  • control-plane/migrations/011_network_score_history_entity_kind/migration.sql
  • control-plane/src/grpc/runVeriRankClient.ts

Comment thread control-plane/migrations/009_network_scores_fk_deferrable/migration.sql Outdated
Comment thread control-plane/src/grpc/runVeriRankClient.ts Outdated

@messagesgoel-blip messagesgoel-blip left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-review of commit 98b73d9:

CI run 28 is green, and the intended fixes are directionally correct, but two major issues remain:

  1. writeChunk now races drain, error, and close, but the losing once() listeners are not removed when drain wins. Every backpressured write leaves an error and close listener attached until stream termination, so a large graph accumulates listeners and closures and can trigger MaxListenersExceededWarning. Use one promise with explicit handlers and a shared cleanup function, or abort the losing listeners after the first event settles.

  2. The migration runner wraps every migration.sql file in a single BEGIN/COMMIT. Adding a NOT VALID constraint and validating it later in the same file therefore keeps the stronger creation lock until validation finishes, defeating the production-safe lock reduction. Move validation and the subsequent drop/rename into later migration transactions for 009 and 010; similarly validate the temporary non-null and enum checks from 011 in a later migration before SET NOT NULL/cleanup.

The dynamic deadline change is reasonable, all tests are green, and single-flight remains correctly scoped to Plan 6 PR B. Outcome: two blocking remediations remain. Submitted as a comment review because GitHub does not allow the PR author account to self-approve.

Replace Promise.race(once) with cleaned-up drain/error/close handlers, and move VALIDATE/drop/rename/SET NOT NULL into migration 012 so validation scans run in a separate migration transaction.

Co-authored-by: Cursor <cursoragent@cursor.com>
@messagesgoel-blip

Copy link
Copy Markdown
Collaborator Author

Remaining blockers fixed

  1. Backpressure listener leakwaitForDrainOrFailure now removes drain/error/close handlers on settle (unit-tested: 20 drains → 0 leftover listeners).
  2. Same-TX VALIDATE009011 only ADD … NOT VALID; new 012_validate_score_constraints does VALIDATE + swap/SET NOT NULL in its own migration transaction.

Plan 6 doc updated for 012. Entity_kind current-state backfill unchanged (locked).

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
control-plane/migrations/011_network_score_history_entity_kind/migration.sql (1)

8-18: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Do not backfill historical kinds from current state.

network_score_history is keyed by (principal_id, sync_version) (control-plane/migrations/002_attestations/migration.sql, Lines 50-58), but these updates join only by principal and read the latest network_scores/principals kind. If entity_kind changed over time—as the plan permits—every older history row receives the current kind, making the audit trail incorrect. Backfill from a source versioned by sync_version; otherwise require an explicit legacy-data policy instead of claiming an exact historical backfill.

🤖 Prompt for 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.

In `@control-plane/migrations/011_network_score_history_entity_kind/migration.sql`
around lines 8 - 18, The migration’s entity_kind backfill incorrectly applies
current network_scores or principals values to every historical row for a
principal. Remove these non-versioned updates and either backfill
network_score_history using a source keyed/versioned by sync_version, or adopt
an explicit legacy-data policy that does not claim exact historical
reconstruction.
🤖 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.

Outside diff comments:
In
`@control-plane/migrations/011_network_score_history_entity_kind/migration.sql`:
- Around line 8-18: The migration’s entity_kind backfill incorrectly applies
current network_scores or principals values to every historical row for a
principal. Remove these non-versioned updates and either backfill
network_score_history using a source keyed/versioned by sync_version, or adopt
an explicit legacy-data policy that does not claim exact historical
reconstruction.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 4d2f6ef8-677c-410a-a216-6fa33778d09d

📥 Commits

Reviewing files that changed from the base of the PR and between 98b73d9 and 0614d98.

📒 Files selected for processing (7)
  • control-plane/migrations/009_network_scores_fk_deferrable/migration.sql
  • control-plane/migrations/010_weight_bounds/migration.sql
  • control-plane/migrations/011_network_score_history_entity_kind/migration.sql
  • control-plane/migrations/012_validate_score_constraints/migration.sql
  • control-plane/src/grpc/runVeriRankClient.test.ts
  • control-plane/src/grpc/runVeriRankClient.ts
  • docs/superpowers/plans/2026-07-28-network-score-computation.md

Co-authored-by: Cursor <cursoragent@cursor.com>
@messagesgoel-blip

Copy link
Copy Markdown
Collaborator Author

Review threads resolved

Checked HEAD caf7413: CI green; no remaining merge blockers for Plan 6 PR A.

Resolved all open threads:

  1. Integration suitetest:integration + CI Control plane integration already green; live RunVeriRank coverage is PR B
  2. Stale recompute / single-flight — deferred to Plan 6 PR B by design
  3. Full-table score load — MVP-acceptable; not a PR A blocker
  4. No history on delete — intentional (Plan 6 locked: score.delete only)
  5. Retry permanent gRPC errors — already fixed via isRetryableGrpcError (stale thread)

CodeRabbit entity_kind backfill outside-diff note remains dismissed per locked Plan 6 current-state backfill policy.

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.

1 participant