Skip to content

feat(readmodels): ProductHomeDTO, GET /api/product/home (PX-7) - #409

Draft
LamaSu wants to merge 10 commits into
feat/readmodels-operator-workfrom
feat/readmodels-product-home
Draft

LamaSu wants to merge 10 commits into
feat/readmodels-operator-workfrom
feat/readmodels-product-home

Conversation

@LamaSu

@LamaSu LamaSu commented Sep 24, 2026 •

Copy link
Copy Markdown
Owner

PX-7: ProductHomeDTO (GET /api/product/home)

The shell's StatusBar and Command Center get their platform-wide numbers from one typed read model, so the UI projects them instead of counting (shell #2007 / #2051, product-steward #2170). Schema pcc.product-home/v1.

Stack: on #389 (feat/readmodels-operator-work), which is on #353. CI suites run only on PRs based on master (#2474), so the counts below are from local runs.

What each section says

Section Source Rule
kernels kernel rows + capability rows online / stale / other, using the kernel read model's staleness rule (isKernelStale: a 5-minute heartbeat window, with the listing grace for kernels that list a capability). The rule is stated in rule.
capabilities capability rows + the kernels section's states total, onOnlineKernels (the capability's kernel row exists and is online by the kernels rule; stale, offline or missing kernels never count), and byType (a row with no type is type: null, sorted last). A listing is not a promise of capacity.
jobs job rows byPhase over the execution phases (executionPhaseOf, the same mapping as JobExecutionDTO). active counts phases that are known and not finished; unknown is never active.
settlementNetwork gateway config (PCC_NETWORK) Name + chain id for known names, basis: "gateway_config". It is not evidence that any escrow lives on that network.
escrowHeld escrow_milestones + escrows Sums of milestone amounts in held states, per currency, exact in base units. Mock escrows (mock-escrow- addresses) are left out and counted in excludedSimulatedEscrows. confirmation: "record_only": no chain read.

A section whose read fails is {state: "unavailable", reason}, never a zero. "Gateway reachable" is not a field, because the DTO arriving already proves it. The served build commit is on /api/health (N5, #369).

What it replaces (the negative tests pin each one)

  • Kernel and job counts computed from the first page of a list, or invented.
  • A "Total Value Locked" that summed every escrow's totalAmount, including refunded, released and mock escrows. Escrow totals are never summed here, because an active escrow can hold released milestones.
  • Unknown status words guessed into a bucket. They are counted in unclassifiedMilestones. An amount that doesn't convert to base units exactly, or has an unknown currency, is counted in uncountedMilestones.

Held / not-held milestone words (@pcc/spec)

  • Held: FUNDED, LOCKED, EVIDENCE_SUBMITTED, RELEASING, DISPUTED, and the V-next states FUNDED_ACTIVE through REFUND_ALLOCATED.
  • Not held: CREATED, UNFUNDED, PENDING, RELEASED, SETTLED_RELEASED, REFUNDED, SETTLED_REFUNDED, SLASHED.
  • The two sets are disjoint and in normalized form.
  • A spec test checks that every EscrowStatus word is in one of the sets (at compile time), along with the words the paid-job flow writes outside that type (pending, evidence_submitted).
  • The escrow lane is asked to confirm the sets. SLASHED and REFUND_ALLOCATED are the judgment calls.

Tenancy

With TENANT_ENFORCE on:

  • Job counts are the caller's tenant's.
  • A caller with no tenant gets job counts withheld, and no cross-tenant read happens.
  • escrowHeld is withheld, because escrow records carry no tenant.

The route states each withheld reason at the source. cache-control: no-store.

Evidence

  • gateway: pnpm test gives 3086 passed / 6 skipped / 0 failed (190 files, solo run at bf7bd95). One earlier run alongside other suites hit the known completion-real-tier flake; that test passed 3/3 when run alone.
  • spec: 875 passed.
  • tsc clean for gateway and spec.
  • Mutation check: 22/22 killed. The mutations covered stale counted as online, capabilities counted as live on a stale or missing kernel, typeless rows merged into a real type, the null type sorted first, unavailable capabilities read as zero, listing grace ignored, unknown counted as active, mock escrows summed, not-held words summed, unclassified words dropped, inexact amounts rounded, unsorted currencies, unavailable read as zero (kernels and escrow), an invented chain id, tenant gates removed (escrow and jobs), no-store dropped, PENDING dropped, RELEASED counted as held, and the withheld reason lost.
  • The first pass found one mutation that changed no output: a duplicate tenant gate. The route now carries a single gate, at the source.

Not in this PR

  • Binding the StatusBar and Command Center to the DTO; the shell owns that.
  • An opportunities section. It will come with OpportunityDTO (charter item 8: public bounties plus policy-approved demand aggregates).

🤖 Generated with Claude Code

https://claude.ai/code/session_01A2ZvsqsAb7jC7AC8Viisqn

pcc.product-home/v1: the platform-wide facts a home page shows, each
from a named gateway source, for the shell's StatusBar and Command Center
(shell #2007, product-steward #2170).

- kernels: online / stale / other with the kernel read model's rule
- jobs: counts by execution phase and `active` (known, not finished)
- settlementNetwork: the CONFIGURED network (basis gateway_config), with
  the chain id of known network names; not proof an escrow lives there
- escrowHeld: sums of milestone amounts in held states per currency, in
  base units, mock escrows excluded; confirmation record_only
- a section that could not be read is {state: "unavailable", reason}

HELD_MILESTONE_STATUSES and NOT_HELD_MILESTONE_STATUSES are disjoint and
in normalized form; every EscrowStatus word and every milestone word the
gateway writes (PENDING, EVIDENCE_SUBMITTED) is in one of them, checked
at compile time for EscrowStatus.

agent: pcc-readmodels (c255d7dc)
One read per section; a section whose read fails is unavailable with a
reason, never a zero. Held funds are summed from escrow_milestones rows
in held states (never escrow totals: an active escrow can hold released
milestones), exactly in base units or counted as uncounted; unknown
status words are counted as unclassified, never guessed.

Under TENANT_ENFORCE the job counts are the caller's tenant's, a caller
with no tenant gets them withheld (no cross-tenant read happens), and the
held total is withheld because escrow records carry no tenant. The route
states each withheld reason at the source. cache-control: no-store.

Tests: 13 gateway (pure builders + the route on a seeded store) and 5
spec. Mutation check: 17/17 killed.

agent: pcc-readmodels (c255d7dc)
…ine kernels

The charter's ProductHome is a health / jobs / capabilities summary, so
the DTO gains `capabilities`: capability rows, and how many sit on a
kernel that is online by the kernels section's own rule (a capability on
a stale, offline or missing kernel is listed but never counted as on an
online kernel), per type, with a null type for a row that has none. A
listing is not a promise of capacity. Built from the same read as the
kernels section, so it is unavailable exactly when that read fails.

Tests: gateway product-home 15 (was 13). Mutation check: 22/22 killed.
Suites: gateway 3086/6/0, spec 875.

agent: pcc-readmodels (c255d7dc)
Brings #313 @8f946499 and #353's evidence fix through #389. No conflicts.

agent: pcc-readmodels (c255d7dc)
…356)

Escrow's answer (#3356) on the held-word sets:
- RELEASE_ALLOCATED (V-next 6) holds only the unit's remaining job
  liability, which can be less than the milestone amount: allocation
  pushes each payout leg and some can succeed before one fails
  (VNextSettlementEscrow.sol:1451-1476). It moves out of the held sum
  into escrowHeld.releaseDecided {byCurrency, bound: "at_most"},
  published with releaseDecidedStatuses. It is never added to held.
- Legacy V3 EVIDENCED and ATTESTED are held.
- SLASHED stays not held: the refund moves in the same transaction.
- Challenge bonds are never read.

Tests: spec 7 (held / release-decided / not-held disjoint and
normalized; every observable V-next state classified), gateway
product-home 17. Mutation check 26/26 killed.
Suites: gateway 3093/6/0, spec 881, dashboard 249.

agent: pcc-readmodels (c255d7dc)
#389 answered astra round 1: operator pay is escrowed only while the
record holds the milestone (not_held otherwise), no update_status the
route would refuse, a failed capability read is not "no offers", and
the work list and income rows page with an offset. #409 stacks on #389.

agent: pcc-readmodels (c255d7dc)
…d a heartbeat that is not a time is no heartbeat (PX-7)

Astra round 1 on #409 (62a91f5), SHIP-WITH-FIXES, both MEDIUMs.
Reproduced first: 3 new tests failed at 28b8a3d (#389's head merged).

MEDIUM 1: mock detection was the `mock-escrow-` prefix alone. The
seed's escrows say they are mock data but sit at addresses like
0xESCROW_CONTRACT_001, so their releasing 27.00 USDC milestone counted
as held, and the seeded home page showed held money.
isSimulatedEscrowAddress (job-execution.ts) now also treats any
contract address that is not a 20-byte hex address as simulated: no
contract can hold money there. The product home and the job-execution
settlement record both use it. Effects:
- the seeded store's escrowHeld.byCurrency is empty, and the seed's
  escrows are counted in excludedSimulatedEscrows;
- job-004's record is simulated, so its payout reads simulated, not
  unknown (that test now pins this).

MEDIUM 2: isKernelStale subtracted an unparsed date. "not-a-date" gave
NaN, NaN > threshold is false, and the kernel read as online. A
heartbeat that does not parse to a finite time now counts as no
heartbeat: stale.

agent: pcc-readmodels (c255d7dc)
#389 found three more things while packing its round 2: a repeated page
parameter was a 500 (now 400), identity now comes before paging, and a
failed capability read names what kernel jobs lack. #409 stacks on #389.

agent: pcc-readmodels (c255d7dc)

This branch has not been deployed

No deployments
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