Skip to content

fix(dashboard,ui): live StatusBar, honest Settings, and a failed read is not an empty result - #352

Open
LamaSu wants to merge 25 commits into
masterfrom
fix/shell-truth-chrome
Open

LamaSu wants to merge 25 commits into
masterfrom
fix/shell-truth-chrome

Conversation

@LamaSu

@LamaSu LamaSu commented Sep 24, 2026

Copy link
Copy Markdown
Owner

Product Wave 0 ("stop lying"), shell lane, PX-3 shell part. Live pages and shell chrome now show what the gateway actually returned, or say they could not load it. Previously they showed plausible values.

What was wrong on master

  • StatusBar: App.tsx passed kernelsOnline={2} activeJobs={3} networkStatus="connected" on every page. The component defaulted to 0 and connected, and always printed base-sepolia.
  • Settings: every value was hard-coded:
    • wallet 0x1234…5678, "Base Sepolia", "1,000.00 USDC";
    • "Default Assurance Tier: Tier 1", "Auto-fund Escrow: Enabled".
  • Every live page read const { data = [] } = useX(). During an outage it showed "No jobs yet", "0/0 kernels", "$0.00 locked" or "Welcome to PCC, ready". This is acceptance test 9 of the UX reconciliation.
  • Command Center:
    • "Evidence Events 0 / last 24 hours" was hard-coded.
    • "Recent Activity: No activity yet" was a static claim.
    • Every job showed $0.00: job.amount is not on JobDTO.
    • Pending, in-progress and paused jobs rendered as "offline".
  • Total Value Locked (Command Center and Escrow) summed every escrow's totalAmount, so refunded and released escrows counted as locked.
  • Escrow: "Challenge Windows 0" was hard-coded, and the milestone total read a field the DTO doesn't have.
  • Revenue: summed job.amount over completed jobs, which presents completion as payment.
  • Crashes:
    • DiscoverPage and KernelLeaderboardPage called useMemo after the loading early-return ("Rendered more hooks than during the previous render" on first load).
    • KernelsPage rendered kernel.location, a {lat, lng} object, as a React child.

What changed

  • @pcc/ui StatusBar:

    • an unknown count renders as —;
    • networkStatus defaults to unknown;
    • the network label is an optional prop with no default.
  • LiveStatusBar derives the bar from the same react-query reads as the Command Center:

    • /api/health, re-checked every 30s;
    • /api/kernels: online requires a fresh heartbeat (isStale);
    • /api/jobs: active = the gateway's in-flight set from /api/agent/me.

    A failed or pending read shows as unknown.

  • Settings shows:

    • the account behind the API key (GET /api/agent/me: operator, key, scopes, count of * keys);
    • the wallet wagmi reports as connected.

    Balance and preferences are removed until something serves them.

  • components/LiveState.tsx:

    • UnavailableState: the read failed and there is no earlier data;
    • StaleNotice: the refresh failed and earlier data is shown with its time.
  • Command Center, Escrow, Kernels, Discover, Kernel leaderboard and Revenue use these states. An empty state now means the read succeeded and returned nothing.

  • Money totals are removed until fix(ui-kit,dashboard,spec): one canonical money-status map - no refund/allocated/unknown as settled-green #313's exact map or a server read model serves funds held.

  • lib/live-status.ts holds one definition of "active job" and "online kernel". The StatusBar and the pages use it.

Tests

  • packages/ui: StatusBar.test.ts (7). 5 fail against the old component.
  • apps/dashboard:
    • lib/__tests__/live-status.test.ts (8), including a source check that App.tsx passes no literal counts.
    • pages/__tests__/live-pages-honesty.test.tsx (18). It renders the real pages with the real hooks and a stubbed fetch, in outage / partial / empty / data cases. 15 of 18 fail against master's pages.
  • Dashboard 246/246, ui 16/16, tsc --noEmit clean, pnpm build (tsc -b + vite) green.

Coordination

  • fix(ui-kit,dashboard,spec): one canonical money-status map - no refund/allocated/unknown as settled-green #313 (genui) also edits EscrowPage.tsx, only its import block and the GlowBadge line. This PR leaves both untouched, so the two merge in either order.
  • JobsPage.tsx: readmodels (c255d7dc) claimed it for PX-6. I'll drop it from this PR if they take it over (bus #2278).
  • Not touched here, left to their owners and listed on the bus (#2244):
    • JobDetailPage, EvidenceExplorer, Settlement: readmodels
    • OperatorMobile, OperatorDashboard: operator-ux
    • IP pages: economics
    • onboarding wizard: adk
  • To follow from this lane:
    • silent mock fallbacks on the remaining pages, a demo-mode gate, and a no-production-mock ratchet;
    • the route model (/ vs /dashboard, and deep links swallowed by the default spatial mode).

🤖 Generated with Claude Code

https://claude.ai/code/session_01VU6exGFC7uLukeDF2GBUpQ

LamaSu and others added 9 commits September 2, 2026 13:12
…o refund-as-payment)

statusClass() used a greedy substring regex that rendered any status containing
settl/releas/complet/done/paid/funded/success/approved/active as a green "settled"
pill. So a REFUND ("refunded" contains "funded"), an UNRELEASED/INCOMPLETE/UNSUCCESSFUL/
NOT_APPROVED/INACTIVE/UNDERFUNDED, and every *_ALLOCATED (decided, not final) rendered as
a completed PAYMENT. A refund shown as a payment is the read-route contract's forbidden-
asserter CRITICAL, live in the shipped on-ramp kit.

Replace it with an EXACT normalized-status map keyed off the sec-A conformance table
(V-next finalState/unitState, the 10-state machine) plus the legacy EscrowSummaryDTO enum:
- SETTLED_RELEASED / COMPLETED -> st-settled (operator distribution discharged)
- SETTLED_REFUNDED / REFUNDED  -> new non-green st-refunded ("payer refunded - operator NOT paid")
- RELEASE_ALLOCATED / REFUND_ALLOCATED / in-flight (0-5) / FUNDED / CREATED -> st-waiting
- unmapped / unknown -> new neutral st-unknown, NEVER green (fail closed)
No substring inference for money state. Add honest sec-A direction labels on the settlement
rail, and drop the dishonest rail fallback that inferred 'settled' from releasedCount
(contract rule 12: never key settlement off count/receipt existence).

Adversarial conformance test extracts the shipped <status-map v1> region verbatim (tests the
real bytes, not a copy) and asserts the sec-A table, the never-green invariant, normalization
(casing/whitespace/separators), generic-state tones, and that the old regex is gone. 7/7 green.

Refs: genui-read-route-contract sec-A + rules 1/12; genui conformance matrix v1.4.
Cross-family (sol/GPT-5.6) verdict: the one-line /refund/ guard is insufficient; this is the
exact-map it prescribed. gen-UI owns the fix (defect is in gen-UI's shipped kit + contract).
…d/dispute as green)

EscrowPage.tsx rendered the escrow status GlowBadge with `... : "green"` as the ternary
DEFAULT, so any status other than active/funded -- refunded, disputed, created, unknown --
rendered GREEN: a refunded or disputed escrow shown as a completed payment. Same false-green
money-display bug as the ui-kit statusClass fix, in a parallel formatter (the blast radius sol
predicted: "fixing one helper doesn't help if dashboards independently infer status").

Replace with an exact map defaulting to gray: completed -> green, disputed -> red,
active -> gold, everything else (funded/created/refunded/unknown) -> gray. Matches the
already-honest MilestoneTimeline statusColors + read-route contract rule 1.

Type-correct by inspection (all branches return valid GlowBadge colors: green|gold|red|gray);
a full dashboard typecheck/build is Spark-offload territory and was NOT run here.
Bring PR #313 current with master before extending it (dry-run merge-tree was
clean; no conflicts). No force-push: the PR history is preserved.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P7daTDp5o3mAwxYNYyRCVy
…state table

Master had five competing escrow-status vocabularies (EscrowStatus,
Escrow.status, the dashboard DTO, the V-next UnitState, the context-pack
summary) and every surface re-inferred them; the shipped kit's substring regex
rendered a REFUND as a green completed payment (read-route contract rule 1).

MONEY_STATUS_MAP is the ONE exact table: normalized key -> semantic tone +
honest, direction-explicit label. Green ('settled') is reserved for the three
documented FINAL releases to the operator (SETTLED_RELEASED, COMPLETED,
RELEASED). Refunds are 'refunded' (final, operator NOT paid); allocated-not-
final states are 'waiting'; unknown fails closed. Bare SETTLED is deliberately
unmapped: SettlementResultDTO uses it for operator-paid but the V-next phase
vocabulary uses it for BOTH released and refunded.

Frozen at every level; compile-time coverage records make tsc fail if
EscrowStatus / Escrow.status gains a value with no entry. Browser-safe.
8 tests (sec-A table, never-green invariant, unknown fail-closed,
normalization, full vocabulary coverage, immutability).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P7daTDp5o3mAwxYNYyRCVy
…ifier

- <status-map v2> mirrors @pcc/spec MONEY_STATUS_MAP verbatim (the vanilla
  kit cannot import it) and now covers every documented escrow vocabulary, so
  canonical states like released/slashed/releasing/expired no longer render
  'unknown'.
- New moneyStatusClass for MONEY surfaces (the receipt window): money table
  only. An off-schema 'success'/'done'/'ok' on a money response is NOT a
  settlement state and never renders paid (it still tones a generic run/list
  surface via statusClass).
- Conformance test moved into CI: #313's node --test file lived under
  apps/dashboard/public/ (served publicly) and no CI job ran it. The new vitest
  file (a) extracts the shipped region verbatim and proves it equals the spec
  map key for key and agrees with classifyMoneyStatus over an adversarial
  battery, and (b) boots the WHOLE kit in jsdom and asserts the rendered receipt
  pill: refund / allocation / off-schema success / missing status (rule 12) are
  never settled-green. 19 tests.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P7daTDp5o3mAwxYNYyRCVy
EscrowPage's escrow badge had its own inline ternary (a sixth formatter). It
now calls moneyBadgeColor(), which takes the semantic tone from @pcc/spec
classifyMoneyStatus and only maps tone -> GlowBadge color: green is reserved
for a documented FINAL release; refunded / allocated / unknown are never green.
GlowBadge itself defaults to green, so money badges must pass an explicit color.

A compile-time record lists every value of the dashboard's EscrowStatus type
(tsc fails if the type gains one) and the test asserts each is a KNOWN state in
the canonical map, so no real escrow status renders 'unknown'. 3 tests;
dashboard tsc --noEmit clean; full dashboard suite 223/223.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P7daTDp5o3mAwxYNYyRCVy
…-sepolia

The StatusBar defaulted kernelsOnline and activeJobs to 0, networkStatus to
"connected", and always printed "base-sepolia". A caller with no data
therefore rendered plausible values instead of saying it had none.

- Counts are number | null | undefined; unknown renders as a dash.
- networkStatus gains "unknown" and defaults to it ("Checking gateway...").
- The network label is an optional prop with no default.

Tests: 7 in StatusBar.test.ts; 5 fail against the previous component.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VU6exGFC7uLukeDF2GBUpQ
…ccount state

StatusBar: App.tsx passed kernelsOnline={2} activeJobs={3}
networkStatus="connected" on every page. LiveStatusBar now derives them from
the same react-query reads the Command Center uses:
- /api/health, re-checked every 30s;
- /api/kernels: online only with a fresh heartbeat (isStale);
- /api/jobs: active = the gateway's in-flight set in /api/agent/me
  (pending, queued, in_progress, paused).
A failed or pending read shows as unknown, not 0.

Settings showed wallet 0x1234...5678, "Base Sepolia", "1,000.00 USDC",
"Tier 1" and "Auto-fund Escrow: Enabled", all hard-coded. It now shows:
- the account behind the API key (GET /api/agent/me: operator, key, scopes,
  and how many keys hold the * scope);
- the wallet wagmi reports as connected, and its chain.
Balance and preferences are removed until something serves them.

Tests: live-status.test.ts (8), including a source check that App.tsx passes
no literal counts to a status bar.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VU6exGFC7uLukeDF2GBUpQ
Every live page read its data as `const { data = [] } = useX()`, so during
an outage it showed "No jobs yet", "0/0 kernels", "$0.00 locked" or
"Welcome to PCC, ready": plausible values in place of "couldn't load".
Each page now has four states:
- loading;
- unavailable: the read failed and nothing was read before;
- stale: the refresh failed, so earlier data is shown with its time;
- data, where empty means the read succeeded and returned nothing.

Also removed, because each showed a value no source serves:
- Command Center: "Evidence Events 0 / last 24 hours" (hard-coded);
  "Recent Activity: No activity yet" (a static claim); job.amount and name
  (not in JobDTO, so every job showed $0.00).
- Total Value Locked (Command Center and Escrow) summed every escrow's
  totalAmount, counting refunded and released escrows as locked. Removed
  until #313's exact map or a read model serves funds held.
- Escrow: "Challenge Windows 0" (hard-coded); milestones read a field the
  DTO doesn't have (now milestoneCount).
- Revenue: summed job.amount over completed jobs, which presents completion
  as payment. Removed until settled income is served. Success rate now uses
  finished jobs, not all jobs.
- Kernels: capability count read a missing field (now capabilityCount).

Crashes fixed:
- DiscoverPage and KernelLeaderboardPage called useMemo after the loading
  early-return. React threw "Rendered more hooks than during the previous
  render" on every first load.
- KernelsPage rendered kernel.location, a {lat, lng} object, as a React
  child. It now shows physicalAddress, the label, or the coordinates.

Active and online counts use lib/live-status.ts, the same definitions as the
StatusBar. Job status chips map the canonical StepStatus values; pending,
in_progress and paused previously rendered as "offline".

Tests: live-pages-honesty.test.tsx (18) renders the real pages with the real
hooks and a stubbed fetch, in outage / partial / empty / data cases.
15 of the 18 fail against master's pages.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VU6exGFC7uLukeDF2GBUpQ
@LamaSu

LamaSu commented Sep 24, 2026

Copy link
Copy Markdown
Owner Author

End-to-end trace against real state (#352 and #354 merged into a scratch tree, 2026-09-24).

Setup:

  • Local gateway at NODE_ENV=production with a fresh SQLite DB and no seed data.
  • The built dashboard is served by a static server that proxies /api.
  • Driven with headless Chromium (Playwright).
  • Everything below was created through the gateway's public API; no fixtures.
Step Real state (API) Dashboard
Fresh DB 0 kernels, 0 jobs —
Created kernel, capability and job 1 kernel online, isStale=false; 1 job queued StatusBar: Gateway online · 1 kernels online · 1 active jobs. Command Center: Active Jobs 1, Kernels Online 1/1, the job row shows the queued chip.
Deep link /jobs/<id> job exists Renders inside the dashboard shell (before #354, every signed-in URL rendered the spatial canvas). The page itself says "Job not found": that is JobDetailPage still reading mocks, readmodels' PX-6.
/legacy/kernels — Redirected to /kernels
/app, /agent — Spatial canvas; the live agent conversation (no AgentChatPage counts)
Real outage: gateway process stopped — StatusBar: Gateway unreachable · — kernels online · — active jobs. Command Center: "Couldn't load the Command Center" (API error: 502). /jobs: "Couldn't load jobs", not "No jobs yet". No zeros, no "ready".
Gateway restarted, same DB same rows StatusBar back to 1 · 1

Where the evidence is (on the lane box, not in this PR): the trace script, trace.json and 8 screenshots under pcc-reconciliation/returns/pcc-shell-work/e2e-20260924/.

LamaSu and others added 5 commits September 24, 2026 09:52
… not a total

Product-steward review of #352 (bus #2409):

MEDIUM. GET /api/jobs returns at most 50 rows when no limit is sent
(job.facade.ts) and reports no total. The StatusBar, Command Center, Jobs
and Revenue pages counted page 1 and showed the result as exact. Now a full
page makes every count over it a lower bound, rendered "N+":
- StatusBar: activeJobsAtLeast
- lib/live-status.ts: JOBS_PAGE_SIZE, mayBeTruncated, formatCount
- the pages note "there may be more"
- Revenue's success rate shows "--" instead of a rate from a partial sample
ProductHomeDTO / a /api/jobs total (readmodels #2289, #2290) will replace
the lower bound with the exact count.

LOW. useKernels and useEscrows mapped an unexpected response shape to [],
which reads as "0 kernels online". They now throw, like readmodels' useJobs
(absence is not evidence). /api/kernels and /api/escrow return full lists,
so they need no lower bound.

Tests:
- live-status +3, StatusBar +1, live-pages-honesty +2.
- live-pages-honesty's settle wait is now condition-based (up to 2 s), not a
  fixed 50 ms that flaked under load.
- dashboard 251/251, ui 17/17. Against master's pages, 17 of the 20 honesty
  tests fail.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VU6exGFC7uLukeDF2GBUpQ
Found while answering coord-watch's review question (#2497): can any surface
still show a fabricated, default or last-known value when a read failed or
came back off-schema?
- Command Center: after a failed refresh, the KPIs kept their last-known
  figures while the banner said failed figures show as "—". A summary now
  shows only what its latest read returned. The list pages keep earlier data
  under a timestamped StaleNotice.
- useJobs mapped an off-schema /api/jobs to [], which reads as "0 active
  jobs". It now throws. The lines are byte-identical to readmodels'
  feat/readmodels-job-execution, so the two branches merge in either order.
- useAgentMe: an answer without the identity block is a failed read.
  Before, Settings crashed.

Tests: +3 in live-pages-honesty; all 3 fail on the previous head
(753ab25). Dashboard 254/254.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VU6exGFC7uLukeDF2GBUpQ
…id; route data surfaces by data

First part of the #313 review fixes (astra via coord-watch #2465, product-qa #2594, reviewer-bravo):
- Normalization rejects rather than erases: only a plain status string ([A-Za-z0-9 _-]) is
  classified. "RELEASED?", "❌RELEASED", "releaſed", ["RELEASED"] and objects whose toString
  returns a green word are now unknown. Spec and kit mirror are changed together.
- COMPLETED is no longer green. It ends many non-money DTOs (jobs, A2A, steps, batch claims
  where paid != completed), so it now reads "completed - settlement not confirmed".
- List rows and run status pick their table from the DATA: money unless the binding is a known
  non-money read and the row carries no money field. So an escrow row's "success" or "done" is
  never green, while a completed job still is. Generic surfaces check generic states first.
- The EscrowPage milestone badge now comes from the canonical map (no dead "fulfilled" green).

Tests: money-status 35/35 (+8); full spec 832/832; dashboard 223/223; the 7 gateway kit suites
95/95; tsc (spec, dashboard) clean. More #313 fixes follow (source-schema receipt adapter).

agent: pcc-genui (4df1e691)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P7daTDp5o3mAwxYNYyRCVy
…el; no bare-word green

Second part of the #313 review fixes: steward rulings #2490/#2688, astra (coord-watch #2465),
escrow #2580, product-qa #2594.

- Classify by SOURCE SCHEMA: classifySettlementRecord (spec) / settlementRecordClass (kit mirror):
  - V-next /lifecycle: unitState is an ordinal 1..9, pinned to VNextSettlementLib.sol's
    `enum UnitState`; 0 is a read error. finalState, isAllocated and isTerminal must agree, and
    a final state needs all three present.
  - V-next /receipt: finalState counts only when terminal with isAllocated true.
    `null` + allocated reads "decided, not yet paid out".
  - A legacy escrow record goes through the flat table on its status.
  - Anything else (a job, an A2A task) is "not a settlement record".
- The flat word table has NO green entry: bare SETTLED_RELEASED, RELEASED and COMPLETED are not
  settlement reads. The only green is a consistent V-next state 8. AWAITING_FUNDING is unknown.
  Labels are fixed per escrow F4-F6: "not final", "payer not yet refunded", "payout distribution
  discharged", "payees NOT paid".
- Receipt: state by schema; nothing invented (no default "USDC", "payer"/"payee" or
  "escrow-milestone"; missing values read "not reported").
- Run window: a read model by its schema; a full snapshot with no status reads unknown (an
  earlier green is never kept). List/run money rows that are read models use the schema.
- The kit tables are frozen. Compile-time coverage now constrains the real map (`satisfies`).
- EscrowPage: the "Total Locked" and selected-escrow panels no longer glow green.

Tests: money-status 18 + conformance 37 = 55. They cover wire fixtures for states 0-9, every
cross-check disagreement, missing corroboration, receipt shapes, job/A2A records, the kit==spec
adapter over the battery, the ordinal table re-read from the Solidity enum, frozen tables,
no-invention, run null snapshot, and money rows. Full spec 852/852; dashboard 223/223 (after a
spec build); gateway kit suites 95/95; tsc spec + dashboard clean.

agent: pcc-genui (4df1e691)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P7daTDp5o3mAwxYNYyRCVy
…rrency

Pins the no-invention rule for the currency line (the earlier test used a receipt with no
amount, so the currency branch never ran; mutation M8 survived). M8 is now red.

agent: pcc-genui (4df1e691)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P7daTDp5o3mAwxYNYyRCVy
…irmed reachable

Operator-ux's real-state trace (#2800): about 2 s after the gateway
stopped, the bar still read "Gateway online | 2 kernels online", and it kept
that until its next 30 s health poll. The counts came from reads that
succeeded before the outage, presented as current.

- deriveLiveStatus shows kernel and job counts only while /api/health
  confirms the gateway is reachable. While liveness is unknown or down they
  are "—".
- recheckHealthOnReadFailure: any failed gateway read (other than health
  itself, so there is no loop) invalidates the health query, so the bar
  flips to "Gateway unreachable" on the next failed read instead of the
  next poll.

Tests: live-status +2; LiveStatusBar.test.ts (3, jsdom). Dashboard 259/259.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VU6exGFC7uLukeDF2GBUpQ
LamaSu added a commit that referenced this pull request Sep 24, 2026
…-truth

Keeps this stacked branch current with its base without a rebase or force-push
(board rule 3). No conflicts: #352's new commit touches LiveStatusBar and
lib/live-status.ts only.

agent: pcc-operator-ux (f0734fab)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013AKuzz2pzRLu5Kmo22czYT
LamaSu and others added 2 commits September 24, 2026 15:42
…ocated/phase semantics

#313's classifier assumed isAllocated was true for every allocated-or-final state (6-9).
The read routes say otherwise: gateway unit-state-mapper isAllocatedState is true for 6/7
ONLY ("outcome decided, money NOT fully moved"), so a settled /lifecycle or /receipt body
carries isAllocated:false. A genuinely settled unit therefore rendered "settlement fields
disagree" and was never shown as settled -- fail-closed, but wrong, and the hand-written
test fixtures encoded the same assumption (one even pinned the real settled receipt as
unknown).

Now, in the spec classifier and the kit mirror alike:
- lifecycle: allocated = state 6 or 7; `phase` must match VNEXT_PHASE (the mapper's
  PHASE_BY_STATE) when present, and a final state needs finalState, isAllocated,
  isTerminal AND phase;
- receipt (finalState, phase, isAllocated): final only with isAllocated:false and
  phase:"settled" (both present); isTerminal, if present, must agree; "allocated" and
  "in flight" cross-check phase too.

A new gateway suite takes its fixtures from the routes themselves (Fastify inject over a
fake reader) for states 1-9 and classifies every body with the spec AND the shipped kit:
only state 8 is green, from either route, and each tampered field is unknown.

Tests: money-status 18 + conformance 39; spec 854/854; dashboard 223/223; new gateway
suite 3/3.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P7daTDp5o3mAwxYNYyRCVy
…lay sum

The receipt window fell back to economics.amount and formatted it with fmtUsd. On the
real /receipt route that field is a raw integer in the token's BASE units (read-surface
contract rule 14) and the route sends no tokenDecimals, so a 1 USDC settlement
("1000000") would have rendered as 1,000,000.00.

Now economics.amount becomes a display amount only with the record's own tokenDecimals
(exact string arithmetic, no float); otherwise it is shown as "N base units (decimals
not reported)", with no invented currency. A malformed value is "amount not reported".
Top-level amount/totalAmount (legacy escrow records, already display units) are unchanged.

Tests: 3 new (conformance 42/42) using the route's exact EconomicsRecord shape.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P7daTDp5o3mAwxYNYyRCVy
… it (escrow ruling #3163)

Escrow ruled that master's settlement routes are the target and asked gateway to ADD the
staticcall-authoritative unitState to /receipt (additive), so a receipt consumer can tell
6 (release decided) from 7 (refund decided); genui keys that direction off unitState,
never off finalState.

A receipt with unitState enters the unitState branch, which required isTerminal for a
final state, and /receipt carries no isTerminal, so settled receipts would have
classified "incomplete" the day the field lands. A final state now needs unitState,
finalState, isAllocated and phase (every present field still cross-checked, isTerminal
included when present). Spec and kit alike.

Tests: the route-derived suite classifies each state's real /receipt plus the lifecycle's
unitState (states 1-9: same tones as /lifecycle, green only at 8, 6 vs 7 named) and four
tampers of it (4/4); money-status 60/60. Requiring isTerminal again turns it red.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P7daTDp5o3mAwxYNYyRCVy
The settle loop stopped at the first tick with no fetch in flight, and a
query can read as idle for one tick between retries. Under load on the
Spark (load average 16-21) this file failed 3 of 23 once (the Kernel
leaderboard, Revenue and Settings cases; reported by implementer-echo). It
now waits for two idle ticks in a row, within 2 s: the same fix #408
applies to its own tests. Test-only. Dashboard 259/259 on three
consecutive runs.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VU6exGFC7uLukeDF2GBUpQ
LamaSu added a commit that referenced this pull request Sep 25, 2026
LamaSu and others added 2 commits September 29, 2026 12:42
…rmed (PX-3, astra r2)

astra's round-2 review of #352 (DO-NOT-SHIP at 459c616) found that the
query-to-view boundary treated data presence and a shallow envelope check
as proof of current, valid state.

The list hooks now reject a response with a malformed row, not just one
without the array:
- jobs need an id and a status;
- kernels need an id, a status and the isStale flag the populator always
  sets;
- escrows need an id and a status;
- capabilities need an id and a kernelId;
- templates must be an array.

/api/agent/me must carry a complete identity: operator, key_id, key_name
and scopes. Settings reads the key section through keyCounts(). That treats
{ active: null, wildcard_keys: 0, unavailable } as unavailable, not as zero
wildcard keys.

The StatusBar shows a count only if it was read after the gateway's last
failed health check and is at most 75 s old. A recovered health check no
longer certifies counts cached before the outage. The bar re-reads its
counts every 30 s, and at once on recovery. isKernelOnline now requires
isStale === false.

Pages:
- Discover, the leaderboard and Revenue label data kept after a failed
  refresh (StaleNotice).
- Discover says when site names couldn't be read, and no longer turns
  missing templates into an empty list.
- The leaderboard reads every capability page at limit 200, the route's
  maximum; it had asked for 500. If paging stops early, it says the ranking
  covers only what was read.
- The Command Center and Jobs say "in the first 50" when only one page was
  read.
- Kernels' capability total and the leaderboard's queue depth show as
  unknown, not zero-filled, when a row doesn't report them.
- The leaderboard's online pulse uses the fresh-heartbeat rule.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…A-H)

Covers astra r2's #352 findings: malformed-row rejection (jobs/kernels/
escrows/templates/capabilities), stale-refresh labelling on Discover/
Kernel-leaderboard/Revenue, Discover's "site names couldn't load" notice,
truncated-page wording on Command Center and Jobs, the kernel capability
total going unavailable (not zero-filled) when one kernel omits it,
useAllCapabilities paging through every page of /api/capabilities (250
rows/2 pages, and a short second page marking the ranking partial), and
three Settings account-section honesty cases (missing key_id, keys.
unavailable with reason, missing keys section entirely) -- two of which
crash on the pre-fix source (identity.key_id.slice on undefined; keys.
active on an undefined keys object).

Also adds two kernel-leaderboard-logic.ts unit cases: buildLeaderboard's
`online` field reflects isKernelOnline (isStale-gated), and `queueDepth`
is null when any capability omits it rather than treating it as zero.

19 new cases total (17 in live-pages-honesty.test.tsx, 2 in
KernelLeaderboardPage.test.ts). Full dashboard suite: 281/281 passing.
tsc --noEmit: clean. Stretch case (LiveStatusBar recovery timing) skipped
per spec -- already covered by lib/__tests__/live-status.test.ts's
"counts are current, not just cached" describe block.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
LamaSu and others added 2 commits September 29, 2026 12:59
… not any digit-plus

Review of test-writer-alpha's 58bbfab: /\d\+/ would match any lower bound on the page.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
product-steward asked for this (#3378): #352 and #313 conflicted in
EscrowPage.tsx, and #408 and #380 inherited the conflict. Resolved with
the recipe from #3124:
- The one conflicting hunk is the stats grid. It takes #352's side: the
  Total Locked panel stays removed, since it was a TVL sum with no read
  model. #313's only edit there was dropping that panel's glow.
- Everything else auto-merges, including #313's moneyBadgeColor on both
  escrow badges.

tsc is clean. The dashboard passes 284/284, @pcc/ui 17/17.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
LamaSu added a commit that referenced this pull request Sep 29, 2026
…shell-no-prod-mock

Brings #352's round-3 honesty fixes (astra pack 18) and #313's money-status
map (merged into #352 per product-steward #3378). Clean merge.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
LamaSu added a commit that referenced this pull request Sep 29, 2026
…shell-route-model

#354 now stacks on #352 (and so on #313), which answers astra's pack-19
finding 2. At #354's own head, App.tsx still passed kernelsOnline={2},
activeJobs={3} and networkStatus="connected". With #352, the dashboard shell
renders <LiveStatusBar /> instead. The RC2 merge order already puts #352
before #354. Clean merge.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
LamaSu added a commit that referenced this pull request Sep 29, 2026
…hat-held-actions

This brings #354's round-3 fixes (astra pack 19) into #413. The spatial and
agent workspaces now own only their own address, and every sign-in and
sign-out empties the query cache. It also brings #352's round-3 honesty
fixes and #313, which #354 now stacks on.

Conflict resolved: #354's new user-switch test in routing.test.tsx set the
key with useAuthStore.setState({ apiKey }). #368's store, which #413
carries, keeps the key out of state, so the test uses adoptApiKey(). That
still flips isAuthenticated, which is what App.tsx's cache-clearing
subscription watches.

tsc is clean and the dashboard passes 387/387.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
LamaSu added a commit that referenced this pull request Sep 29, 2026
…/shell-product-home

#419 lands after #352, so this brings in #352's round-3 honesty fixes
(astra pack 18) and #313. It carries #352's count-freshness rule over to
#419's ProductHome-based StatusBar.

Conflicts resolved:
- LiveStatusBar: keeps #419's ProductHomeDTO read. It adds #352's re-read on
  recovery, so ProductHome is re-read at once when the gateway answers again
  after a failed health check.
- lib/live-status.ts: the freshness rule is now one exported isCurrent(),
  used by both deriveLiveStatus and deriveHomeStatus. A read counts as
  current only if the gateway is reachable, the read succeeded, it came
  after the last failed health check, and it is at most COUNT_MAX_AGE_MS
  old. Before this, deriveHomeStatus showed any successful ProductHome read,
  so a read cached before an outage came back as current on recovery.
- live-status.test.ts: #419's deriveHomeStatus fixtures carry read
  timestamps now. Two new cases: ProductHome read before an outage stays
  hidden until it is re-read, and a read older than COUNT_MAX_AGE_MS is not
  current. Both fail without isCurrent.
- live-pages-honesty.test.tsx: in #419 the Command Center's Active Jobs KPI
  is ProductHome's exact count, a separate read. So the malformed-/api/jobs
  case now asserts that the job list is unavailable and not shown in part,
  instead of asserting that the KPI is missing.

tsc is clean and the dashboard passes 328/328.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
LamaSu added a commit that referenced this pull request Sep 29, 2026
…-truth

Shell #3746: #352 now carries astra's round-3 honesty fixes and #313 @8f946499,
with the EscrowPage conflict resolved. No conflicts with #380, and no product
code of #380 changes. Verified on the merged tree: typecheck clean, dashboard
vitest 344/344 (15 files). No force-push (board rule 3).

agent: pcc-operator-ux (f0734fab)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
LamaSu and others added 2 commits September 29, 2026 19:26
…l at eaedeb4)

Appends an R4 describe block to live-pages-honesty.test.tsx with 18 tests
(R1a-R5b) covering every finding in the 18b astra verdict for eaedeb4:
escrow currency/amount fabrication (Dashboard, Escrow, Revenue), Discover
presenting templates as live capabilities, off-schema rows (bogus job/kernel/
escrow status, capability missing type, negative queue depth, negative/
fractional key counts) still reaching displayed numbers, the capabilities
pager certifying a shrunk-total or duplicated-page read as complete, and two
categorical empty-state claims over a partial read. All 18 fail at eaedeb4
as designed (verified via `pnpm exec vitest run`); the 40 pre-existing tests
in the file still pass. No source file was modified.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ail closed, Discover lists live capabilities (astra 18b)

Each finding was first reproduced at eaedeb4 by 3a87d0b (18 cases,
R1a-R5b, all failing there). All 18 pass now.

F1 (HIGH, money): an ETH escrow showed as "$10.00 USDC", and "not-money"
showed as "$0.00 USDC".
- useEscrows now requires a canonical decimal totalAmount (no sign, exponent,
  grouping or leading zeros) and a currency the escrows table allows (USDC,
  ETH, DAI). Milestone counts, when present, are counts, and the released and
  disputed milestones are among the escrow's milestones.
- Dashboard, Escrow and Revenue pass the escrow's currency to a new
  EscrowAmount. It shows the amount digit for digit, never through a float,
  with a "$" only for USDC, and shows unavailable rather than a value. The
  shared AmountDisplay is design's (#393), and EscrowAmount follows its new
  rules, so @pcc/ui is untouched here.

F2 (MEDIUM): Discover listed the static template catalog as "capabilities
found". It now reads the capabilities operators list (GET /api/capabilities,
every page, each row validated). A read the pager capped is counted "N+",
with a note.

F3 (MEDIUM): statuses such as "bogus" were counted.
- Job, kernel and escrow statuses must be ones the gateway defines. The sets
  in api/wire-vocabulary.ts are what the gateway really writes, with a source
  for each value. That is wider than its advertised JOB_STATUSES: executing,
  evidence_submitted and settled are stored too, and a narrower set would
  turn working pages into outages.
- Capability rows need a type. queueDepth, assuranceScore and reputation must
  be in range when present.
- keyCounts needs non-negative integers, with the wildcard keys no more than
  the active ones.

F4 (MEDIUM): useAllCapabilities fails the read, and its retry starts over,
when:
- the total changes between pages;
- a page answers for another offset;
- hasMore disagrees with the page's offset, limit and total;
- an id repeats;
- more rows arrive than the total.

F5 (MEDIUM):
- The leaderboard says "Nothing to rank yet" when kernels are registered but
  list no capabilities. "No kernels" is kept for when none are registered.
- Revenue says "No completed jobs in the first 50" on a full page.

Changes to the tests:
- bravo's titles now state requirements.
- Its varying stub answers for the offset asked, so R4a and R4b test the
  total and duplicate checks rather than an offset mismatch.
- R3g checks the Active keys row: the key id "key-1234" itself contains "-1".
- The round-2 fixture's "mystery" job status is now a known inactive one,
  because an unknown status fails the read (R3a, R3b).
- New tests cover:
  - every status the gateway writes being accepted;
  - USDC keeping its "$", with every digit of a large amount;
  - DAI showing no "$";
  - malformed amounts;
  - each pager check, including a total change the other checks miss;
  - out-of-range scores;
  - more wildcard keys than active keys.
- Mutation controls: 11 mutations, each reverting one check, and every one
  fails at least one test (returns/pcc-shell-work/px3-r4-controls/).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
LamaSu added a commit that referenced this pull request Sep 30, 2026
Brings astra 18b's fixes (escrow currency and exact amounts, known statuses,
pager consistency, live Discover, scoped empty states) into #354.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
LamaSu added a commit that referenced this pull request Sep 30, 2026
…mock

Brings astra 18b's fixes (escrow currency and exact amounts, known statuses,
pager consistency, live Discover, scoped empty states).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
LamaSu added a commit that referenced this pull request Sep 30, 2026
…-home

Brings astra 18b's fixes (escrow currency and exact amounts, known statuses,
pager consistency, live Discover, scoped empty states).

Textually clean, but two fixes:
- DashboardPage keeps AmountDisplay. #352 dropped the import because its
  escrow rows now use EscrowAmount, and this branch still uses AmountDisplay
  for the Held in Escrow KPI.
- The TTL-sweeper test checks the Kernels page for the kernel counts. Here
  the Command Center's kernel ratio comes from /api/product/home.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
LamaSu added a commit that referenced this pull request Sep 30, 2026
…d-actions

Brings #354's merge of #352 @72911cce (astra 18b's fixes).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

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