Commit Graph

392 Commits

Author SHA1 Message Date
Ran Shem Tov 4151807154 test(showcase): update partner baseline count 2026-08-11 22:55:32 +03:00
Ran Shem Tov 5136097aa0 feat(showcase): add CrewAI conversational flows 2026-08-06 15:33:10 +03:00
Jordan Ritter b04330abdb test(showcase): pin the polarity flip's D1/D2 case and make family folds mutation-detectable
The fail-safe polarity flip's widest case was frozen by nothing. A stripped-
`signal` red on a `health:`/`agent:` row flips a whole column green->red
(achieved 6->0, isRegression true) because those keys are integration-scoped,
but `red-signal-unknown` was only applied by `positionSweep` (D3-D6), and
`liveness-fresh-red-d1/-d2` use `row()`'s default `signal: null` -- which is
not `undefined`, so they classified FAIL_FRESH under the old polarity too.
Measured: restoring `!redSignalKnown -> NO_DATA` killed 4 golden-master entries,
all D3-D6, and left the entire D1/D2 leg byte-identical.

Underneath that sat a structural blind spot: every fixture variant maps ONE
uniform row shape over ALL keys of a rung, so no family anywhere contained two
DIFFERING red rows and no family contained a green row with an absent `signal`
key. A uniform set has nothing to aggregate, so three explicitly load-bearing
family folds were invisible at golden-master level -- `Math.max`->`Math.min` on
`maxNonInfraRedFailCount`, `allRedSoftClass` `every`->`any`, and re-widening the
provenance flag from red-row to family scope each left all 56 entries identical.

Adds 5 fixtures (56 -> 61 baseline entries):

- `liveness-red-signal-unknown-d1` / `-d2` -- a red D1/D2 row with `signal:
  undefined` over a green D3-D6 ladder. Both now fail under a polarity
  re-widening, joining the 4 existing entries.
- `hetero-d4-red-failcount-max` -- D4 {chat red fc1, tools red fc3}, straddling
  D4_FIRST_STRIKE_THRESHOLD. MAX(1,3)=3 -> FAIL_FRESH/red; MIN(1,3)=1 ->
  FIRST_STRIKE_FRESH/amber, i.e. a fresh sibling re-arming tolerance for an
  already-confirmed failure, which the MAX doc explicitly forbids.
- `hetero-d4-green-stripped-infra-red` -- D4 {chat green signal-omitted, tools
  red driver-error}, the production shape `classifyRung` cites verbatim and the
  first fixture with a green row whose `signal` key is absent. Red-row scoping
  keeps this gray; a family-scoped flag paints it red.
- `hetero-starter-mixed-soft-hard` -- starter {transport-error (soft),
  smoke-failed (hard), rest green}, both below SOFT_MISS_TOLERANCE_THRESHOLD so
  the soft-class quantifier is the only deciding factor.

Each mutation is now caught by exactly one fixture while its pre-existing
sibling stays blind, so the teeth come from the new shapes rather than
incidental coupling. Baseline entries were derived by hand from the source
first and inserted additively (140 insertions, 0 deletions) -- no wholesale
regeneration, so no result is self-approving.

Also closes the D1/D2 gap in the dashboard equivalence suite: the `fail-safe
polarity` fixtures seeded no `health:`/`agent:`/D5/D6 rows, so those rungs
contributed ABSENT -- also gray -- and the gray assertions passed via the
no-data path while naming the infra path. A `greenScaffold` helper makes every
rung but the one under test green-fresh, discriminating pill assertions prove
the gray sits over a present red pill, and a new test pins the whole-column
D1/D2 stripped-red case api-vs-browser (equal chip, achieved 0, regression
true).

Fixtures and baseline only -- no classification logic, `combine.ts`, or
coherence invariant touched.
2026-07-24 19:57:35 -07:00
Jordan Ritter ed10b61ffb test(showcase): give the initial-fetch page cap a two-sided guard
The cap "drift guard" did not guard. Every assertion in
useLiveStatus.supplemental-bounds.test.tsx was an UPPER bound
(toBeLessThanOrEqual / not.toContain(cap + 1)), so only an increase was
caught. Measured on the pre-fix branch, MAX_INITIAL_FETCH_PAGES could be
halved to 10 or cut to 5 with all 6 tests still green. A lowered cap is just
as much a silent behavior change -- it moves the cap toward the legitimate
read and starts truncating real collections. The bound assertions were also
vacuous, because Math.max of an empty array is -Infinity, so a supplemental
fetch that never ran at all satisfied them.

Both sites now assert the EXACT page sequence a full walk of the cap produces
(1..cap plus the one terminal lookahead), sorted because fan-out waves are
concurrent, and DISTINCT on the fail-loud bulk path because the throw re-runs
the whole loop once per reconnect attempt. Zero pages now fails, a halved cap
now fails. The literal is additionally pinned MECHANICALLY against the
exported constant, so the "MUST match" claim in its doc is checked rather
than trusted.

New file useLiveStatus.page-cap-boundary.test.tsx covers the boundary the
suites never reached -- a COMPLETE read that ends exactly on the cap page --
in both directions and at both levels.

  - DIRECT on paginateStatusPages. Exactly cap x 500 rows is NOT truncated
    (and issues one lookahead, then stops); one row past the cap IS truncated;
    a read ending on a short page never issues the lookahead; a failed
    lookahead fails safe to truncated.
  - THROUGH the hook. A complete 10,000-row collection renders (status live,
    all rows, red cell still RED from a signal-less bulk row) instead of the
    permanent blank-dashboard outage; one row past the cap still fails LOUD
    with an error and no rows.

It carries its own ../lib/pb mock rather than reusing the pb-query-eval fake
servers. The boundary needs 10,000 rows in the BULK response, which no shared
fixture serves, and nothing this file proves should be able to go green or red
because of a change to those servers' filter fidelity.
2026-07-24 19:57:35 -07:00
Jordan Ritter 75fdfb3aba fix(showcase): make truncated mean "rows unread", not "cap exhausted"
`fetchStatusPages` inferred truncation from cap exhaustion with a full last
page. That is not evidence: the loop learns where the collection ends only
from a SHORT page (`skipTotal: true` drops `totalItems`/`totalPages`), so a
collection of exactly MAX_INITIAL_FETCH_PAGES x INITIAL_PAGE_SIZE rows is read
COMPLETELY and still ends on a full page. The bulk consumer THROWS on
truncation, so at exactly 10,000 rows the dashboard went offline
deterministically and permanently -- every retry, every reload, because the
condition is a pure function of the row count. The function's own JSDoc
asserted the opposite of what it did. This is the same false-outage class
matrix.ts already fixed server-side (any full final page read as truncation
took /api/matrix down on every 500-boundary), reintroduced client-side.

`truncated` now means exactly "there are rows we did not read", and it is proven
rather than inferred: on cap exhaustion with a full last page the loop issues
ONE lookahead read of page MAX_INITIAL_FETCH_PAGES + 1 whose rows are discarded
and only whose emptiness is load-bearing. Chosen over `skipTotal: false` on
page 1 (the matrix.ts shape) because that puts a COUNT(*) on the first-paint
path of EVERY load -- the exact overhead `skipTotal: true` exists to drop --
to disambiguate a case that can only arise after 20 consecutive full pages;
the lookahead puts the whole cost on the pathological path, where it is a
rounding error against the 20 requests that preceded it. A FAILED lookahead
fails safe (unproven completeness is reported as truncated), keeping the bulk
path's fail-loud posture.

The asymmetric on-hit semantics are unchanged and remain correct: supplemental
truncation DEGRADES (reds stay red), bulk truncation THROWS (unread rows are
whole cells that would render gray = masked failure). The false positive is
fixed; the throw is not weakened.

The pagination algorithm is hoisted out of the effect as `paginateStatusPages`
over an injected page reader, so the `truncated` contract can be asserted
directly rather than only through the hook's observable state -- and so every
page of a read, INCLUDING the lookahead, provably carries the same `listOpts`.

MAX_INITIAL_FETCH_PAGES is exported (for a mechanical, two-sided test pin) and
its value is now justified against both bounds it actually has to satisfy:
~3x the measured production worst case below, and bounded runaway damage above;
the value is unchanged at 20.
2026-07-24 19:56:10 -07:00
Jordan Ritter b8b75b7d45 test(showcase): collapse three PocketBase query evaluators into one, verified against a real server
The dashboard's `useLiveStatus` test doubles carried THREE divergent
implementations of the same PocketBase query evaluator — the nominally-shared
`__tests__/pb-query-eval.ts`, a second complete private copy inside
`useLiveStatus.autocancel.test.tsx`, and a third partial one in the
supplemental-bounds fakes. They disagreed on four axes, and the shared one was
the weakest of the three. Three definitions of "what PocketBase does" is no
definition at all.

There is now exactly one, and every semantic in it is a measured observation from
a real PocketBase v0.22.21 server (the version `showcase/pocketbase/Dockerfile`
pins) plus the real SDK's own `filter()`. `pb-query-eval.test.ts` pins that
oracle case by case, with the live result recorded next to each expectation.

Fidelity defects fixed, all of which the module's own docs asserted backwards:

- Single-quoted literals. `pb.filter()` wraps string params in SINGLE quotes with
  `\'` escaping and emits numbers/booleans/null BARE. The shared tokenizer
  handled only double quotes, so every dimension-scoped filter the hook builds
  threw `unsupported character "'"`.
- LIKE is case-INSENSITIVE (it compiles to SQLite `LIKE`).
- The implicit `%…%` wrap is triggered by `%` alone, and it ESCAPES `%` and `_`
  so they match literally. `_` does not suppress the wrap. Both prior copies got
  a different half of this wrong.
- Ordering and equality follow the COLUMN type: `fail_count > 2` selects
  `fail_count` 10, which the string comparison in the private copy excluded.
- `sort=-key` descends instead of silently no-opping.

And it now genuinely fails loud rather than claiming to. An unterminated literal,
a bare word (which real PB reads as a column reference and 400s on), a dangling
operator or escape, an unbalanced paren, trailing input, an unsupported operator,
a sort it cannot apply, a non-numeric `page`, and a field the row does not carry
all throw — matching the real server, which answers every one of them with a 400.

Serving that contract over HTTP is structural now, not per-fake discipline.
`matchesPbFilter` used to be called bare inside two `createServer` handlers, so a
parse error produced NO response: the hook hung and the test died on a 20 s
`waitFor` timeout with the parse message nowhere in sight. Fakes go through
`evaluatePbList`, which cannot throw — it returns either a 200 or the real
server's own 400 carrying the message.

All three supplemental-bounds fakes now answer through it, on both their bulk and
their supplemental legs. Two of them ignored `filter` entirely while that file's
preamble claimed all of them evaluated it, which left the page-cap tests proving
themselves on fixture shape: with the fakes repaired, breaking the supplemental
filter turns the supplemental cap test red, and breaking the bulk filter turns the
bulk cap test red. Both were green before.
2026-07-24 19:56:02 -07:00
Jordan Ritter 938be2accf test(showcase): reconcile the supplemental-fetch discriminator across both dashboard suites
Integration defect, introduced by folding the autocancel fake-server fidelity
repair together with the bounded/projected supplemental fetch. Each was correct
alone; together they disagreed about how a supplemental request is IDENTIFIED.

The fidelity repair's `isSupplementalRequest` keyed on the ABSENCE of a `fields`
projection. That was true when it was written and is now false: the supplemental
fetch sends `STATUS_LIST_FIELDS,signal`. So all three faithful fake servers
classified the supplemental request as a BULK page, which:

  - failed `expect(fields).not.toContain("signal")` on the "every bulk page
    projects `signal` away" wire contract (the supplemental query was in the
    bulk bucket), and
  - left `supplementalQueries` / `firstElemSupplementalPages` EMPTY, so the two
    `toEqual([1])` bounds assertions compared `[]` against `[1]`.

Whole autocancel suite: 3 failed / 3. Not a flake and not a pre-existing
failure — it is exactly the two-conventions collision the integration had to
resolve.

Resolved toward the convention the mocked sibling suite already uses after the
projection landed: a request is supplemental iff `signal` IS in its `fields`.
That is the property that actually defines the request rather than an artifact
of how it happened to be built, so it survives both the projection being added
and the bulk projection changing width. Both dashboard suites now assert the
SAME convention, and dropping `fields: STATUS_SIGNAL_FIELDS` from the hook turns
both of them red (verified), so neither can silently drift back.

Also corrected the three comments that still described the supplemental fetch as
unprojected.

Tests only — no production change. `git diff` against the hook is empty.
2026-07-24 16:37:03 -07:00
Jordan Ritter 2797618c5e docs(showcase): correct and anchor stale cold-load fetch doc claims
Every numeric claim below is re-derived from production PocketBase
(showcase-pocketbase-production.up.railway.app) or from repo config on
2026-07-24, not copied from a neighbouring comment.

- Row/page counts: 2455 rows / 5 pages -> 3082 rows (~3100) / 7 pages,
  with the exact source query recorded so the number can be refreshed.
  The INITIAL_FANOUT_BATCH no-over-fetch note now says that it holds
  because the CURRENT page count is odd, not as an algorithm property.
- Byte estimate: ~1.29 MB -> ~1.6 MB. The retired figure applied the
  (correct) 371 KB -> 113 KB per-500-row basis to the OLD 2455-row
  collection. Measured all 7 pages both ways: 2.34 MB full vs 0.70 MB
  projected = 1.64 MB of signal.
- Worst-case re-delivery window: ~29 min -> ~60 min. staleness.ts and
  harness/config/probes/*.yml schedule e2e-demos, starter_smoke and
  d6-all-pills-e2e HOURLY (10/40 * * * *); aimock-wiring is 6-hourly and
  the drift probes are weekly/monthly.
- Restored the coverage-gap acknowledgement this PR deleted, rewritten
  for what is true today: the supplemental union is neither minimal nor
  complete. It is green-blind outside clause 1 (2646 rows carry a signal
  but sit outside it; the 731 green per-cell rows under the aggregate
  dimensions are excluded by BOTH clauses). Currently latent (0
  uncovered rows carry __fleetCommError) but structurally open, and it
  is the hole that let the mis-scoped signal-provenance misreport ship
  green.
- Anchored the 94%/6% product-vs-infra split with its query, date and
  partitioning predicate (336 vs 21 of 357 red rows), and flagged it as
  a snapshot the polarity argument does not depend on. Softened the
  unreproducible "~20% of the matrix" to the derivable bounds.
- Version-qualified the STATUS_LIST_FIELDS omission-vs-null claim to the
  pinned PocketBase 0.22.21 and recorded the upgrade hazard: PB >=0.23's
  PublicExport isVisible/GetHidden path lets a field-level hidden flag
  omit signal independently of our projection, reintroducing the
  ambiguity the provenance flag exists to avoid.

INTEGRATION RECONCILE (folded in when this was cherry-picked on top of
the signalKnown-scope fix). Three conflicts in useLiveStatus.ts, all
row/page-count prose, resolved to the measured 3082-row / 7-page anchor
and to this commit's corrected INITIAL_FANOUT_BATCH note (the "page 7
ends its own wave" claim is only true at an ODD page count, which the
superseded text asserted as an algorithm property). Additionally, every
doc reference to `RawRung.signalKnown` — a field the scope fix DELETED —
was rewritten to name the surviving red-row-scoped
`FamilyFold.redSignalKnown`, so no comment describes a flag that no
longer exists: live-status.ts STATUS_LIST_FIELDS doc (3 sites),
useLiveStatus.ts coverage-gap note, api-matrix-equivalence.test.ts (2
sites).

Comments only - no logic, no reformatting, no reordering.
2026-07-24 16:31:30 -07:00
Jordan Ritter f5178765fc fix(showcase): bound the dashboard's supplemental signal fetch and stop its failure blanking the matrix
PR #6156 widened the browser's supplemental `signal` fetch from "comm-error
aggregate rows only" to "comm-error aggregates ∪ every non-green row, all
dimensions", and in doing so removed the early return that had bounded the work.
What was left was a `for(;;)` with no page cap, unprojected, awaited before any
row is returned — so it degrades exactly when the dashboard matters most.
Measured against production: nominally 357 of 3082 rows (one page, 429 KB), but
under a full-column outage the non-green set trends toward the whole collection —
7 pages, 1.73 MB — all of it on the critical path to first paint. Against a
server that never returns a short page it does not terminate at all.

Worse than slow: a supplemental rejection propagated through fetchInitial →
connect() → the retry chain → `setRows([])` + `status: "error"`. A failure of an
ENRICHMENT fetch blanked the ENTIRE dashboard, which directly contradicts the
fail-safe rationale the PR was written to establish. (It also leaked an unhandled
rejection per attempt, because the promise rejects while the bulk pages are still
in flight and nothing is awaiting it yet.)

Three changes, all on the dashboard hook:

PAGE CAP. Both initial-fetch loops now share one bounded implementation
(`fetchStatusPages`) and one cap, `MAX_INITIAL_FETCH_PAGES = 20`. Sized as a
runaway guard rather than a budget: ~3x the real collection, so it never
truncates a legitimate read including the degenerate all-non-green case. The
supplemental loop also picks up the wave-concurrency and short-page merge guards
the bulk loop already had and it did not, which is what made the parity
structural instead of a promise two comment blocks made to each other. On-cap
behavior is deliberately ASYMMETRIC: supplemental truncation DEGRADES (warn, use
what arrived — the unreached rows keep their signal-less bulk copy, which
classifyRung paints RED), while bulk truncation THROWS, because rows the bulk
never read are whole cells that would render as no-data GRAY, i.e. exactly the
masked-failure polarity this PR exists to remove.

NON-FATAL ENRICHMENT. The rejection handler is attached at the fetch site, so a
supplemental failure yields an empty row set, the merge degrades to "bulk rows,
unenriched", and the dashboard RENDERS. Reds stay red via the classifier's
fail-safe polarity; the residual cost is that a comm-error overlay on a green
aggregate waits for its next SSE delta — the pre-CF7-F3-#1 behavior for the
duration of one failed request, instead of a blank matrix. Attaching at creation
also removes the unhandled rejection.

PROJECTION. The supplemental fetch now sends `STATUS_LIST_FIELDS + signal`
instead of no projection. That is exactly the declared StatusRow shape, derived
from the drift-guarded constant so the two cannot diverge, which drops the
columns PocketBase adds and nothing reads (collectionId, collectionName, created,
updated, state_written_at, written_by): 429,085 → 362,982 bytes measured on the
real non-green set, ~260 KB at incident scale. Deliberately NOT narrower — the
merge treats a supplemental row as a COMPLETE row (replaces its bulk twin
wholesale, appends one the bulk snapshot missed), so `key,observed_at,signal`
would have saved another 58 KB by forcing a graft onto a row of a different
vintage, which is the chimera `supplementalRowIsOlder` exists to prevent.

It stays on the first-paint path. Enriching after first render would have removed
it from the critical path, but a non-green row's chip can move once its signal
lands (red → gray for a positively-infra red, red → amber for a soft-class first
strike), and a cold-load verdict that corrects itself a beat later is the class of
symptom this PR set out to eliminate. With the fetch bounded, wave-concurrent and
projected it runs inside the bulk fetch's own round-trip budget, so gating first
paint costs nothing on the happy path.

Tests are REAL-SDK over real sockets, with fake endpoints that actually evaluate
`filter`/`fields`/`sort` (new `__tests__/pb-query-eval.ts`) so a response is a
function of the query the hook sent rather than of fixture layout. Verified by
mutation: restoring the unbounded loop, the fatal rejection, or the missing
projection each turns its own test red.
2026-07-24 16:27:25 -07:00
Jordan Ritter ebd7ac681d test(showcase): restore PB filter/fields fidelity to the autocancel fake servers
PR #6156 silently invalidated the dropped-tail pagination guards added by
PR #4504. Regression in test COVERAGE, not production code — which is why
CI stayed green.

The three fake PB servers in the autocancel suite ignored the `filter` query
param (and `fields`). #6156 made the supplemental `signal` fetch UNCONDITIONAL
(dropping the `commAggregateFilter === null` early return and widening the
filter to `state != "green"`), so a filter-ignoring server answered that
now-always-issued fetch with the ENTIRE dataset — and `fetchInitial`'s
leftover-append then REPAIRED whatever rows the bulk pagination dropped.
Mutating the merge to drop the entire short page kept all three tests GREEN.

The servers now answer a list request the way real PocketBase does: filter,
then sort, then the page slice, then the `fields` projection, with the
COUNT(*) envelope omitted only when `skipTotal` is set. The filter evaluator
covers exactly the grammar `useLiveStatus` emits and THROWS on anything else
(answered as 400) — a silently-unparsed clause would degrade to "match
everything", i.e. straight back into the masking bug. Real-SDK quoting is
matched (single-quoted string params, bare numbers/booleans, verified against
the installed SDK); the sibling suite's double-quoting `pb.filter` stub flaw
is deliberately NOT copied forward, and a wire assertion pins the convention.

The supplemental fetch is discriminated by the ABSENCE of a `fields`
projection — the sibling `useLiveStatus.test.tsx` convention, reused rather
than reinvented. This replaces a `filter.includes("state !=")` substring
check that the union filter had already made unreliable.

Broken assertions repaired:

- over-fetch instrumentation no longer conflates the supplemental serial walk
  with the bulk fan-out (separate page arrays), and the split is now asserted
  rather than assumed
- the FIRST-wave-element server serves POISON rows on the page after the
  short page. Real PB pagination is monotonic, so that page is empty and
  appending it changes nothing — which is exactly why deleting the merge's
  boundary logic left the test green. Poison makes a merge that runs past
  the boundary observable
- `.some(r => r.id === "f0699")` replaced with `rows.at(-1)?.id`, the LAST-row
  boundary check the comment already claimed, plus explicit poison-absence
- the mid-wave suite no longer claims a boundary property its fixture cannot
  measure (its short page is the wave's last element); name and comment now
  state what it does measure, and it gained a real tail-boundary assertion

Mutation red-green, both directions. Drop-the-entire-short-page: PASSED 3/3
before, now FAILS 3/3 (1300 to 1000, 1250 to 1000, 700 to 500).
Boundary-logic-removed: PASSED before, now FAILS on the suite that claims it
(700 to 1000). Reversed-merge-order (same row set): PASSED before, now FAILS
on both new `.at(-1)` assertions. Mutations reverted: 3/3 pass.

Test-only change — no production code touched.
2026-07-24 16:27:21 -07:00
Jordan Ritter 2a68282d08 fix(showcase): scope the infra-red gray precondition to the RED rows
`classifyRung`'s U7 gray branch reads the `signal` blobs of a family's RED rows
and nothing else, but the precondition qualifying what it read — "was `signal`
actually delivered, or projected away?" — was `raw.signalKnown`, a boolean AND
over EVERY present row of the family. A predicate about red-row evidence was
answerable "no" by a row the branch never looks at.

That broke exactly the case the precondition was written to protect. The
dashboard's supplemental cold-load fetch restores `signal` for `state != "green"`
only (by design — the classifier needs it solely in the red branch), so in a
MIXED-state family the red rows arrive WITH attribution and the green siblings
arrive WITHOUT it. The family-wide flag therefore read `false` precisely when it
should have read `true`: D4 = green `chat` + infra-red `tools` skipped the gray
branch, fell through first-strike (its only red is infra, so
`maxNonInfraRedFailCount` is null), and landed on FAIL_FRESH — the browser
painting RED a cell that `/api/matrix`, which always has the full `signal`,
reports as gray. That is the §11.4 api-vs-render drift the read-model exists to
forbid, and it made INFRA_RED_FRESH effectively unreachable in the browser for
every multi-row family: D4, and any multi-pill D5/D6 (whose per-cell
`d5:<slug>/<pill>` keys the supplemental filter's clause-1 `key !~ "%/%"`
excludes). Only single-key D3 was unaffected — there the family's one row IS the
red row and the two scopes coincide, which is why every fixture missed it.

The fix derives the flag where the rows are, at the scope that consumes it:
`foldFamily` now reports `redSignalKnown` over the RED rows only, alongside the
red-scoped `hasNonInfraRed` / `maxNonInfraRedFailCount` / `allRedSoftClass` it
already computed. `RawRung.signalKnown` and its `gatherRows` plumbing are removed
rather than left dead — a family-scoped flag named `signalKnown` sitting next to
a red-scoped consumer is the trap that produced this bug. `anyExpectedMissing`
stays family-scoped, correctly: a missing sub-key IS a family property and it
gates a family-scoped verdict.

`redSignalKnown` is today IMPLIED by `!hasNonInfraRed`, since
`signalHasInfraErrorClass(undefined)` is false and a stripped red row forces
`hasNonInfraRed` on its own. It is kept explicit anyway — this is the
masks-real-red guard and it must not rest on a coincidental property of a helper
two modules away — and pinned directly on the fold so the scope cannot regress
silently.

Coverage. The previous tests named `signalKnown: false` but were insensitive to
it: `raw.signalKnown &&` could be deleted outright and all 181 harness
cell-model tests plus 58 dashboard equivalence tests stayed green, because every
fixture stripped `signal` from ALL rows of the target rung and never from a green
sibling. The new tests construct the mixed-state family that ships the bug, on
the real derivation surface (`buildCellModel`, not a hand-passed flag): D4 chip,
multi-key D5 chip, and the D6 `d6Effective` badge — D6 is the soft-parity top so
its chip is amber either way, but its badge drifts red-vs-null. Each asserts
browser == server == `/api/matrix`. `api-matrix-equivalence`'s
`coldLoadChip === serverChip` guard is extended off single-key D3 to the D4
family. Five `foldFamily` tests pin the flag's scope directly, and the fail-safe
polarity keeps its negative control: a red row whose OWN `signal` was stripped
still renders RED.
2026-07-24 16:27:17 -07:00
Jordan Ritter 3198b6bc2e fix(showcase): re-fetch signal for non-green rows on dashboard cold load
The dashboard's bulk initial fetch projects the heavy `signal` blob away
(`STATUS_LIST_FIELDS`) for a real payload win — measured at ~70% of the
response, 371 KB → 113 KB per 500-row page. A supplemental fetch then restored
`signal` for the rows that are read at render time, but it only ever covered
the comm-error AGGREGATE rows: dimensions `d6`/`d4`/`e2e-demos`/
`d5-single-pill-e2e`, narrowed by `key !~ "%/%"`.

`classifyRung` also reads `signal`, to tell an INFRA red from a PRODUCT red,
and the rows it needs were excluded twice over: they are mostly dimension
`d5`/`e2e`/`health` (absent from FLEET_COMM_AGGREGATE_DIMENSIONS entirely) AND
they are per-cell `<dim>:<slug>/<featureId>` keys that `key !~ "%/%"` filters
out. So on every cold load those rungs arrived with no attribution at all, and
self-corrected only when the probe's next sweep rewrote that specific row and
the SSE delta redelivered it with `signal` — up to a full sweep interval
(~29 min observed, ~14 min mean), again on every reload.

The supplemental filter becomes a UNION: the existing comm-error clause OR
`state != "green"`. Scoping by STATE is what keeps it cheap — `classifyRung`
consults `signal` only in its red branch, so green rows never need it, and in
production the non-green rows are ~360 of ~3100 (~430 KB, about a third of what
shipping `signal` on every row would cost) with no schema change. `!= "green"`
rather than an explicit red/degraded list also catches an out-of-vocabulary
state, which `rankOfState` ranks WORST — the rows that matter most can never
fall outside the fetch.

Widening the EXISTING fetch rather than adding a second one reuses its
freshness guard (`supplementalRowIsOlder`) and chimera-avoidance merge verbatim,
keeps first paint to one extra request, and keeps the fail-loud retry posture.
A dimension-scoped hook still narrows the comm-error clause to the matched
literal, and drops it entirely when the scope sits outside the aggregate set —
but the non-green clause always applies, so such a scope no longer skips the
supplemental fetch altogether. Its test is updated accordingly.

Also fixes two test doubles that identified the supplemental request by
`filter` containing `key !~`. That marker now disappears for an out-of-set
dimension scope, so both would silently misclassify a supplemental request as a
BULK page and corrupt the fan-out instrumentation. They key off the absence of
a `fields` projection instead — the property that structurally distinguishes
the two requests and the whole reason this one exists.
2026-07-24 15:25:33 -07:00
Jordan Ritter 3549f3e370 fix(showcase): a red rung with unknown infra-ness must render RED, not gray
`classifyRung` opened its red branch with `if (!raw.signalKnown) return
NO_DATA` — so whenever the dashboard's bulk fetch had projected the `signal`
blob away, a genuinely-failing rung was classified "no data" and painted the
muted gray "nothing to see here" chip, while the depth strip beside it
correctly rendered `1P ✗`.

That polarity is backwards twice over:

- `signalKnown === false` never meant "known to have no signal". PocketBase
  omits the `signal` key ONLY under a `fields=` projection; a row that
  genuinely has no signal arrives as `null`, and `null !== undefined`. So the
  flag means "this row came from a projected fetch" — a PENDING attribution,
  the absence of evidence about WHY the rung failed. It was never evidence
  that the failure was infra.
- Measured against production, ~94% of reds are product-class and only ~6%
  are infra-class. Graying every unattributed red lost 94 real failures to
  suppress 6 false alarms — and a masked red is never investigated, while a
  false alarm is looked at once and closed.

Graying a red now requires POSITIVE infra evidence: `hasNonInfraRed` is false
only when every contributing red row's blob actually carries an
INFRA_ERROR_CLASSES attribution, and `signalKnown` is retained as an explicit
second precondition so a missing blob can never be read as an infra
attribution. The residual cost is over-reporting, which is the direction a
health dashboard has to fail in.

Tests: the harness `§7 I5` case and the dashboard's api/render equivalence
case both ASSERTED the buggy gray as intended behaviour — which is why CI
never caught this. Both are inverted, with the reasoning recorded inline.
Four golden-master fixtures (`pos-d{3,4,5,6}-red-signal-unknown`) are
re-frozen; the regenerated baseline diff touches those 4 of 56 and nothing
else.

Adds coherence invariant INV7: a gray chip sitting above a red depth pill
requires positive infra evidence on every red row. INV1-INV6 only relate
chipColor to the other CHIP-side outputs and never inspect the d3/d4/d5/d6
pills, so the engine could return one object saying both `d5.status === "red"`
and `chipColor === "gray"` with every invariant intact. INV7 fails on the old
polarity and closes the gap in the only safe direction — by fixing the chip
upward, never by muting the strip.
2026-07-24 15:25:15 -07:00
Mark Fogle d67693a809 fix(showcase): show exact distinct hidden-row count in grid subtitle
Per-category subtitle counts overlapped (a feature can be both deprecated
and unique), overstating hidden rows. Show the distinct total instead;
per-category counts remain on the toggle badges. CR round 1 finding F2.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-23 00:02:31 +00:00
Mark Fogle 3bf2d50c67 fix(showcase): correct Show-unique tooltip wording for <2 framework count
Tooltip said 'only one framework' but uniqueCount is frameworkCount<2,
which includes zero-framework demos. CR round 1 finding F3.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-23 00:02:31 +00:00
Mark Fogle 27ed872cad test(showcase): decouple unique-hidden subtitle assertion from segment order
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-23 00:02:31 +00:00
Mark Fogle 75a313360e feat(showcase): note unique-hidden count in feature grid subtitle
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-23 00:02:31 +00:00
Mark Fogle 74f4bb1684 feat(showcase): add Show unique toggle to feature grid
Hides single-framework (non-common) demo rows by default, mirroring the
Show deprecated toggle. Common = shipped by >=2 frameworks (demos[]).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-23 00:02:31 +00:00
Jordan Ritter d1b07d4513 fix(showcase): stage catalog-flatten in generator envs
generate-registry.ts imports the catalog cross-join/flatten fold from
../harness/src/shared/catalog/catalog-flatten.ts, which does
`import yaml from "js-yaml"`. The generator's build/test environments did
not stage that file (or its module-resolution scope), so the fold could
not resolve.

- Dockerfiles (shell, shell-dashboard, shell-docs, shell-dojo): COPY the
  shared catalog source + harness/package.json (its `"type":"module"` is
  required so catalog-flatten resolves as ESM and its named exports bind)
  and provide a node_modules for js-yaml resolution.
- generate-registry-pattern.test.ts (makeHarness): stage catalog-flatten.ts
  and harness/package.json at the exact relative path the generator
  resolves, and symlink the scripts node_modules onto the harness tree so
  the ESM `import yaml from "js-yaml"` resolves.
- js-yaml + @types/js-yaml added to showcase/scripts (package.json and the
  npm package-lock.json), and the root pnpm-lock.yaml regenerated to add
  the matching importer entries for showcase/scripts (js-yaml >=4.1.1 via
  the root override, @types/js-yaml ^4.0.9) so `pnpm install
  --frozen-lockfile` stays in sync.
2026-07-20 22:36:14 -07:00
Jordan Ritter be5f2a2ea2 feat(showcase): shell-dashboard ladder single-source + per-cell render degrade
deriveDepth becomes a thin adapter over the shared buildCellModel engine so the
dashboard and API render from one ladder; page-stats routes through the shared
catalog input; dashboard-page per-cell render try/catch isolates a bad cell.
2026-07-20 21:48:41 -07:00
Jordan Ritter 541839dafc fix(showcase): run dashboard dev on webpack so it resolves the shared cell-model fold
Turbopack has no resolve.extensionAlias parity (Next #82945), so
'next dev --turbopack' can't resolve the shared cell-model fold's
.js->.ts specifiers and fails with Can't resolve './live-status.js'.
The extensionAlias in next.config.ts (added in #5955) is honoured by
webpack, which next build already uses. Drop --turbopack from the dev
script so dev runs on webpack too and resolves the fold.

Deploy path unaffected: the Dockerfile builds with 'next build' (webpack)
and serves with 'next start' -- the dev script is never in the build or
runtime path.
2026-07-13 23:24:13 -07:00
Jordan Ritter e2093fedb4 fix(showcase): resolve dashboard build of shared cell-model fold + close CI gap
PR #5952 (9a8cf615) added explicit `.js` extensions to the relative imports
inside the harness's shared cell-model fold
(showcase/harness/src/shared/cell-model/{cell-model,live-status,staleness}.ts)
— REQUIRED for the harness's pure-Node-ESM runtime and correct as-is.

But the dashboard re-exports that fold via shims
(showcase/shell-dashboard/src/lib/{cell-model,live-status,staleness,format-ts}.ts
`export * from "../../../harness/src/shared/cell-model/*"`), pulling the fold
into the dashboard's `next build`. `export *` does not rewrite the fold's
INTERNAL `.js` edges, and the dashboard's empty next.config.ts had no
extensionAlias, so webpack resolved `./live-status.js` literally, found only
the `.ts` source, and failed:

    Module not found: Can't resolve './live-status.js'
    Module not found: Can't resolve './staleness.js'
    Module not found: Can't resolve './format-ts.js'
    > Build failed because of webpack errors

Two-part fix (one coherent subject):

1. Resolution: add `webpack.resolve.extensionAlias` to
   showcase/shell-dashboard/next.config.ts so `.js`/`.mjs` specifiers resolve
   to `.ts`/`.tsx`/`.mts` sources — the bundler complement to TS NodeNext's
   `.js`-import convention. Covers the `next build` (webpack) path CI uses.
   The harness fold's `.js` imports are left untouched (they are correct).

2. CI gap: the dashboard build did not run on #5952 because the build matrix
   is path-filtered and #5952 only touched `showcase/harness/**`, which
   selects `showcase_harness` but not `shell_dashboard`. Add
   `showcase/harness/src/shared/**` to the `shell_dashboard` paths-filter so
   any change to the shared fold the dashboard compiles in also selects the
   dashboard build — a fold change can never again ship an unbuilt dashboard.

Local red-green proof:
- RED (main, before fix): `next build` in showcase/shell-dashboard emitted the
  4 fold-resolve errors above.
- GREEN (after extensionAlias): same build → 0 fold-resolve errors; the fold
  resolves. Remaining `@/data/*.json` errors are the prebuild-generated files
  (generate-registry/probe-docs) skipped in the local repro, produced in CI's
  Docker build — unrelated to this fix.
2026-07-13 22:01:38 -07:00
Jordan Ritter d171c31f89 fix(showcase): resolve shared cell-model fold in shell-dashboard Docker build
The shell-dashboard Docker build context (repo root) copies scripts,
shared, integrations, shell-docs content, and shell-dashboard — but not
showcase/harness. The dashboard's src/lib/{cell-model,live-status,
staleness,format-ts}.ts re-export barrels forward to
../../../harness/src/shared/cell-model/*, so the isolated next build
failed with Module-not-found (CI build-check (shell-dashboard)). Local
next build passed only because the full monorepo is present.

Copy just the 4-file cell-model fold to the exact relative path the
shims expect, preserving the single-shared-fold invariant (harness
monitor still imports the same canonical copy) without dragging the
whole harness package into the dashboard image.
2026-07-13 15:57:01 -07:00
Jordan Ritter 55afcf4365 fix(showcase): harden prod D0-gone monitor (CR round 1)
Bucket-(a) fixes, each with a local red-green test:

- A1: replace the substring `:${slug}` onset match with an anchored
  exact slug-segment match (`keyBelongsToSlug`) so a prefix-colliding
  sibling (`strands` vs `strands-typescript`) no longer mis-attributes
  the earlier sibling's red onset. RED: strands' sinceAt was pulled to
  the strands-typescript onset; GREEN: each slug gets its own onset.
- A2: recovery/CLOSE is now SYMMETRIC with OPEN — a recovery requires a
  second agreeing fresh-healthy read (confirm scan). RED: a single
  transient healthy read fired a false "recovered"; GREEN: held until
  two reads agree.
- A3: guard an empty/degenerate schedule set — longestPeriodMs 0/NaN
  would make idleWindowMs 0 → isProducerLive permanently false → the
  monitor SUSPENDS forever and never pages. Falls back to a 45m default
  window (DEFAULT_IDLE_WINDOW_MS) and logs at error.
- A4: bound readStatusRows — guard NaN/undefined totalPages (a `page >=
  NaN` break never trips) and add a hard MAX_STATUS_PAGES cap so a full
  page + bad totalPages cannot infinite-loop/OOM. RED: OOM; GREEN:
  terminates at the cap.
- A5: a registry-load failure logs at error with a stable errorId (not a
  silent warn-once permanent no-op), and the monitor accepts a loader
  thunk so it re-reads registry.json each tick while the wired-cell set
  is empty — a transiently-missing file self-heals without a redeploy.
- A6 (verified, no code change): createSlackWebhookTarget already throws
  on every non-2xx (4xx/5xx/429/3xx/network-exhausted); added a test
  asserting the monitor does NOT delete recovery state when the post
  throws.

Bucket-(b): stamp the recovery message with the confirm-scan instant
(evidenceMs) not tick-start; wrap the scheduler tick handler in a
catch+errorId; log the prod env-gate skip at warn with a reason; add a
clarifying comment that the aggregate lastAlertAt reset is intentional
one-message-one-clock cadence; simplify the three dashboard barrel-shim
comments (drop the rot-prone enumerated symbol lists).
2026-07-13 14:30:24 -07:00
Jordan Ritter 2481510c2b feat(showcase): prod D0-gone monitor
Add a harness-native monitor that pages #oss-alerts when a whole
integration column collapses to red-D0 ("completely gone" / backend
unreachable) in production — the incident class the per-cell alert rules
miss (LGT went fully gone on 2026-07-13 and nothing paged).

Detection runs the dashboard's OWN buildCellModel fold (the shared
cell-model module both the dashboard and the monitor import) over the
same PocketBase status rows and applies a column-gone predicate over the
resulting CellModel fields, so the monitor's verdict equals the DepthChip
the dashboard renders by construction — no parallel re-derivation.

- d0-gone-predicate.ts: pure cellGone/columnGone/columnFreshHealthy over
  buildCellModel outputs + registry-derived wired-cell enumeration
  (mirrors the dashboard page-stats iteration / determineCellStatus rule).
- d0-gone-monitor.ts: createD0GoneMonitor factory — producer-liveness
  SUSPENDED gate (reuses the family-silence inflight-aware /api/runs
  reasoning, 3x-longest-period idle window), 60s confirm re-read (never a
  re-probe), 15m-detect vs 1h-repost state machine, positive-fresh-healthy
  CLOSE gate, ONE aggregated outage / consolidated recovery Slack message,
  durable per-slug JSON map in alert_state (getSet/putSet).
- orchestrator.ts: register internal:prod-d0-gone-monitor @ */15, gated on
  SHOWCASE_ENV ?? RAILWAY_ENVIRONMENT_NAME === production + kill-switch,
  control-plane-only (inside runControlPlane), reusing the oss_alerts
  webhook target + shared memoized family summary.
- unified-cell.test.tsx: add the required isStaleCell/observedAtAgeMs
  fields to the CellModel test literal (Phase-1 dashboard tsc gate).

Red-green: a frozen test-only naiveGone (achievedDepth===0 alone)
mislabels gray-D0-no-data and stale columns as gone on committed
fixtures (RED); the real predicate fires only on red-D0-fresh and matches
buildCellModel's own outputs (GREEN). Producer-idle SUSPENDED proven
load-bearing (disabling the gate flips both F1 tests red). Plus
confirm-scan blip-rejection, hourly dedup, recovery-clear, failure modes,
and the prod-only/kill-switch registration gate.
2026-07-13 14:09:56 -07:00
Jordan Ritter d2451be0ed refactor(showcase): relocate pure cell-model fold into harness shared/
Move the pure cell-classification fold cluster (cell-model, live-status,
staleness, format-ts) out of showcase/shell-dashboard/src/lib/ into
showcase/harness/src/shared/cell-model/ so BOTH the dashboard and a new
harness monitor import ONE copy with zero duplication and no behavior change.

The harness builds via tsc -p tsconfig.build.json with rootDir:"src" and
cannot import outside its own src/, so the harness is the correct library
home. The dashboard consumes the cluster via relative path across the package
boundary (established precedent, e.g. d5-cadence-banner.redgreen.test.ts).

- git mv the four files into harness shared/cell-model/; their intra-cluster
  relative imports stay valid (they move together, no external coupling).
- Replace the four original shell-dashboard paths with thin export-* barrels
  so all ~51 existing dashboard import sites resolve unchanged.
- Repoint commError-contract-drift.test.ts's source-text drift parse at the
  new canonical harness location (the barrels carry no derivation body).
- Add cell-model.equivalence.test.ts + committed fixtures + a pre-move
  baseline JSON (generated from the original git-HEAD code) proving the move
  is byte-identical across a red-D0, gray no-data, stale, mixed, all-green,
  and unsupported column.
2026-07-13 13:46:31 -07:00
Jordan Ritter 075807f24a feat(showcase): two-miss tolerance for soft probe errorClass on dashboard (pool-fleet step C)
Wire the starter-smoke probe's keyed errorClass into the dashboard cell-state
flip logic so transient SOFT failures (transport-error / aborted) get two-miss
tolerance: a single soft miss renders amber ~ ("transient, not yet actionable")
instead of flapping the cell red, and only flips red on a second consecutive
miss. HARD failures (smoke-failed) and untagged reds flip immediately.

The flip gate reuses the producer-maintained fail_count (the persisted
consecutive-red counter: 1 on green->red, incremented on sustained red, 0 on
red->green) so the dashboard stays a pure function of the current row — no
dashboard-side counter to thread or reset. Tolerance is applied as a
state->degraded downgrade in buildStarterBadge (same pattern as the existing
stale-green fold), keeping the change additive and self-contained.

Adds STARTER_FAILURE_CLASSES as a dashboard-side mirror of the harness
StarterFailureClass union (the dashboard imports only @/*), guarded by a new
starter-error-class-drift.test.ts set-equality lint against the harness source.
2026-07-06 20:12:11 -07:00
Ran Shemtov 4cc25b56bf Merge branch 'main' into claude/jolly-brown-77c87b 2026-06-29 18:53:01 +02:00
Jordan Ritter af306b52c5 fix(showcase): slow D5 deep sweep cadence to 30min to stop staleness banner flap 2026-06-28 00:15:27 -07:00
Jordan Ritter edb8f8cbe8 fix(showcase): CR polish — soften family-silence comments, prune tautological tests, a11y label, grace-edge test 2026-06-26 13:55:38 -07:00
Jordan Ritter 6c5fc73cba fix(showcase): grace-window suppresses false family-silence on post-deploy worker bounce
A normal harness deploy rebuilds the shared showcase-harness image and
bounces the pool workers (PR #5715). Immediately after the bounce the
workers re-register, the producers re-arm, and every family is mid-sweep:
lastSuccessAt still points at the pre-bounce success, so it reads stale
against the silence thresholds (banner 2x period, Slack alert 3x period +
3 consecutive ticks). The result was a FALSE "worker family X has not
completed successfully" banner AND Slack family-silence alert during the
expected post-bounce drain window.

Fix: a bounce-keyed grace window. The freshest worker registered_at across
the /api/runs workers strip is the fleet's most-recent bounce instant
(independent of CP boot — a worker can bounce while the CP stays up). While
now - bounce < 2 x period, a family with no success yet is DRAINING, not
silent, so neither the §7.4 banner, the §7.3 cell glyph, nor the §9 Slack
alert flags it. Beyond the window with still-no-success, genuine silence
fires exactly as before.

The determination lives in two surfaces (server monitor for the Slack
alert; client isFamilySilent for the banner + glyph), so both now consume
the same new SSOT field (WorkerView.registeredAt) and the same 2x-period
grace constant, keeping them consistent.

- run-view.ts: project registered_at -> WorkerView.registeredAt (server)
- family-silence-monitor.ts: BOUNCE_GRACE_PERIOD_MULTIPLIER + freshest-bounce
  grace gate, keyed off body.workers
- worker-runs-context.tsx: freshestBounceMs + bounceAtMs grace arg on
  isFamilySilent; banner + cell glyph pass it
- ops-api.ts: WorkerView.registeredAt on the client DTO
2026-06-26 13:41:36 -07:00
Jordan Ritter 90d63c2dc3 fix(showcase): polish dashboard SWR chip — SR status, regression spinner-guard, chip-color ring, glyph size 2026-06-26 13:41:35 -07:00
Jordan Ritter 1c82f71928 fix(showcase): stale-while-revalidate for dashboard depth chip
A GREEN coverage cell flapped green -> grey -> green every time its probe
job's worker lease lapsed and the control-plane sweeper re-queued the job
(worker-reclaimed-pending). The data layer already preserves the
last-known-good colour in chipColor while surfaceState flips to "pending",
but depth-chip.tsx's `if (pending)` branch did a destructive grey
early-return that never read chipColor.

Split the pending branch:
- prior-good (chipColor is a real colour, or depth > 0): render the normal
  coloured chip via the default branch's exact ternary (threading regression
  through) PLUS a non-destructive refreshing affordance -- a corner ⟳ glyph
  and a subtle pulsing ring in the chip's own colour, with
  data-refreshing="true" / data-has-prior="true". Colour preserved.
- no-prior (never-run / first load: gray + depth 0): keep today's honest grey
  ⟳ chip, now tagged data-has-prior="false".

The unreachable red  overlay and the "failure never masked" gate are
untouched; red/regression cells pass through with no spinner. The refreshing
cue is conveyed by shape (⟳) + motion (ring), never by colour alone, and the
pulse/spin respect prefers-reduced-motion (motion-reduce:animate-none), so the
static ⟳ carries the meaning for colour-blind and reduced-motion operators.

Renderer-only change; unified-cell.tsx is the sole caller passing `pending`.
2026-06-26 13:41:35 -07:00
Ran Shem Tov b706b9e84c test(showcase): wire a2ui-recovery into the d5/d6 harness fleet
Add a d5-a2ui-recovery probe so the A2UI error-recovery demo runs on every
PR via the d5/d6 fleet harness, not only the manual on-demand workflow.

- New probe d5-a2ui-recovery.ts drives both pills in one session: HEAL
  asserts >=2 newly-mounted declarative-metric tiles and no hard-failure
  card; EXHAUST asserts the "Couldn't generate the UI" card appears and
  no surface paints. Deltas (vs a pre-send baseline) keep the two
  mutually-exclusive negatives correct across the shared session. The
  transient "Retrying..." label is not asserted (timing-flaky).
- Prompts are sent as typed input, keyed per integration slug, mirroring
  each slug's suggestions.ts message verbatim. The recovery prompts are
  unique per slug because the inner render_a2ui calls carry no
  x-aimock-context; a typed message is byte-identical to the pill
  dispatch, so it matches the same fixture. Sending via input (not a
  preFill pill click) lets the runner snapshot its run-lifecycle baseline
  first, avoiding a false done-signal-missing failure.
- Register a2ui-recovery in d5-registry, map it in d5-feature-mapping,
  add its representative fixture, and mirror the mapping in the dashboard
  CATALOG_TO_D5_KEY (kept in lock-step via the drift test).

Verified green locally on both recovery paths: langgraph-python
(backend-owned get_a2ui_tools) and strands (auto-inject middleware).
2026-06-26 18:07:33 +02:00
Ran Shem Tov e223fe0fb9 feat(showcase): register strands-typescript as a dashboard baseline partner
Add strands-typescript to BASELINE_PARTNERS so it gets its own coverage
column alongside its Python sibling (mirroring how langgraph-typescript sits
beside langgraph-python). Bump the partner-count assertion 26 -> 27.
2026-06-23 19:46:43 -07:00
Ran Shem Tov 8768c8a7e7 Merge remote-tracking branch 'origin/main' into claude/trusting-babbage-f4d48a 2026-06-22 16:11:21 +02:00
Tyler Slaton db2fd6539b fix: address merge conflicts and run formatter 2026-06-19 15:51:03 -07:00
Jordan Ritter 43fedfdb55 fix(showcase): fold driver-error/abort + stale dashboard cells to gray (agent + starter axes) 2026-06-19 12:23:23 -07:00
Ran Shem Tov 0e5b2189e7 feat(showcase): add strands-typescript integration with base demos
Add a new node/TypeScript-backed AWS Strands showcase integration at
showcase/integrations/strands-typescript.

Backend: a node/TS agent server (src/agent/) built on @strands-agents/sdk
`Agent`/`tool` wrapped in @ag-ui/aws-strands `StrandsAgent` and served via
@ag-ui/aws-strands/server (`createStrandsApp`/`addStrandsExpressEndpoint`),
modeled on the upstream ag-ui aws-strands TS example server and the
langgraph-typescript infra. A single shared agent at "/" serves most demos
(tools, shared state via toolBehaviors/stateContextBuilder, HITL,
sub-agents), with tool-free specialized agents mounted at /voice,
/byoc-hashbrown, /byoc-json-render. model-factory targets OpenAI chat
completions and honors OPENAI_API_KEY / OPENAI_BASE_URL so it works behind
the showcase aimock proxy. Node-based Dockerfile + entrypoint run the agent
server (:8000) alongside the Next.js frontend.

Frontend mirrors the strands (Python) sibling's demo set and the
langgraph-typescript conventions, with HttpAgent routes proxying to the TS
agent server.

Scope: base integration + standard demos only. A2UI / declarative-gen-ui /
a2ui-fixed-schema is intentionally excluded (no A2UI agents, routes, demos,
or deps) and layered on later.

Platform wiring (mirrors langgraph-typescript): docker-compose local/dev
services on host port 3119, local-ports.json, packages.json, slug-map.ts
(born-in-showcase), showcase_build.yml matrix + path filter + metadata,
shell-docs/dashboard registries, and a logo asset. The python strands
integration is untouched.
2026-06-19 17:38:02 +02:00
Jordan Ritter c786bf8846 fix(showcase): un-fence ms-agent-harness-dotnet probing
The ms-agent-harness-dotnet slug was excluded from per-cell D6/BE/smoke
probe enumeration by a placeholder fence added 2026-06-07, before the
real column existed. The column shipped in PR #5569 and its d6/d4 aimock
fixtures landed on main today (e10df0b4), so the fence is now stale.
Remove the slug from all 8 exclude SSOT sites so the column populates.
2026-06-19 08:04:54 -07:00
Jordan Ritter 4ef08bb112 refactor(showcase): revert label-derived var names to mirror Status enum
The internal counter vars pctBeAgent and totalBeAgent were introduced in
PR #5498 when the display label happened to be "BE (Agent)". The label
has since changed (#5505 made it "API (HTTP)"). Coupling internal variable
identifiers to display labels is fragile — labels move; the underlying
Status enum string "wired" is a persisted contract that does not.

Revert the var identifiers to mirror the enum:

  pctBeAgent   -> pctWired   (coverage-bar.tsx)
  totalBeAgent -> totalWired (cells-view.tsx, parity-view.tsx)

Display labels ("API (HTTP)", etc.) are unchanged — this is internal
identifiers only.
2026-06-16 15:05:46 -07:00
Jordan Ritter 14451d5c3d refactor(showcase): normalize API/BE long-form labels — API (HTTP) for transport, BE (Agent) for chat round-trip
PR #5498 renamed the L1 row "Wired" → "BE (Agent)" for the D2 agent-liveness
dimension (transport/Railway up). PR #5503 renamed the per-cell D4 badge
"RT" → "BE", whose long form was already "BE (Round Trip)". The result was a
same-label-different-concept collision: "BE (Agent)" referred to D2 in some
places while the D4 per-cell badge used "BE (Round Trip)", and the per-cell
D2 badge separately used "API (Agent)" — three names for two concepts.

Normalize the long-form labels so each user-facing layer has exactly one name:

  D2 (transport / Railway up, HTTP-reachable) = "API (HTTP)"
  D4 (agent chat round-trip, end-to-end)      = "BE (Agent)"

That cleanly distinguishes by layer: HTTP transport vs Agent message handling.

Sites touched (visible labels + matching legend prose / test assertions only):
  - stats-bar.tsx, adaptive-stats-bar.tsx — wired count label
  - filter-chips.tsx                       — chip label (id "wired" preserved)
  - packages-section.tsx + .test.tsx       — UWCT legend mnemonic B → A
  - level-strip.tsx + .test.tsx            — agent-dimension badge label (and
                                             derived first letter B → A)
  - cell-drilldown.tsx + .test.tsx +
    cell-drilldown.lazy-signal.test.tsx    — D4 label and D2 label, plus
                                             testid-derivation drift
  - adaptive-legend.tsx                    — D2 / D4 prose

Stable contracts preserved (NOT changed):
  - keyFor("agent" | "d2" | "d4", …) and the "agent" LiveDimension value
  - Filter-chip id "wired"
  - Variable names (`wired`, etc.) — internal; pure label rename is in scope
  - Status enum values "wired" | "stub" | "unshipped" | "unsupported"
  - Driver names, probe registry keys, harness API contracts

Verified: typecheck clean, 1089 pass / 1 skip / 0 fail, build clean.
2026-06-16 14:19:17 -07:00
Jordan Ritter 1d6d063c76 refactor(showcase): rename cell-badge CV → 1P (clearer scope framing vs D6 all-pills)
The per-cell health badge previously labelled "CV" (for "Conversation") is
renamed to "1P" — Single Pill. The new label tells operators what the badge
covers in scope terms (one pill out of N), which is the actual contrast the
D5/D6 ladder draws: D5 driver is `d5-single-pill.ts` (one canonical scripted
conversation), D6 driver is `d6-all-pills.ts` (the full suite). "CV" was
opaque — operators had to remember what "Conversation" meant and how it
differed from D6's full run. "1P vs D6 all-pills" is self-describing.

Mirrors PR #5503's RT → BE pass: only the badge LABEL changes; every stable
contract is preserved.

Stable contracts preserved:
  - Dimension level identifier  `model.d5` / `cell.d5` / `level={model.d5}`
  - Drilldown dimension key     `keyFor("d5", ...)` / `key: "d5"`
  - Probe registry key          `d5:<slug>/<featureId>`
  - PocketBase row keys         unchanged
  - LiveDimension union / Status enum strings  unchanged
  - Driver file names           `d5-single-pill.ts`, `d6-all-pills.ts`
  - `e2e-deep` producer name    unchanged (separate from CV → 1P label)

Updates:
  - Source: badge label in `unified-cell.tsx`, `cell-pieces.tsx`, drilldown
    label in `cell-drilldown.tsx` ("CV (Conversation)" → "1P (Single Pill)"),
    legend text in `adaptive-legend.tsx`, comment refs in `composed-cell.tsx`,
    `cell-model.ts`, `page-stats.ts`, `depth-utils.ts`, `live-status.ts`.
  - Tests: testid strings (`mock-badge-CV` → `mock-badge-1P`), label-text
    assertions, type unions, and comment refs in the affected component +
    drilldown + integration + lib tests.

Verification: typecheck clean, `npm test` = 1089 pass / 1 skip / 0 fail
(same shape as #5503), `npm run build` clean, no remaining `\bCV\b` in
`showcase/shell-dashboard/src/`.
2026-06-16 14:13:24 -07:00
Jordan Ritter 601e45f0d8 refactor(showcase): rename cell-badge RT → BE (parallel to E2E → UI in #5473)
PR #5498 was mis-scoped — it renamed the L1 row label and catalog status
display from "Wired" to "BE (Agent)", but the dashboard's per-cell badge
code rendered in the grid was still "RT". This commit lands the parallel
flip that mirrors PR #5473's E2E → UI rename for the D4 chat/tools
round-trip badge.

Visible-label flips (RT → BE):
  - unified-cell.tsx: <TestBadge name="RT" level={model.d4} /> → name="BE"
  - cell-drilldown.tsx: { key: "d4", label: "RT (Round Trip)" } → "BE (Round Trip)"
  - adaptive-legend.tsx: legend entry "Round Trip (RT)" → "Round Trip (BE)"
  - Doc-comment taxonomy notes in cell-pieces.test.tsx, composed-cell.tsx,
    cell-model.ts, page-stats.ts, unified-cell.tsx, cell-drilldown.tsx,
    overlay-selector-integration.test.tsx, dashboard-color-matrix.test.tsx
  - Test assertions in dashboard-color-matrix.test.tsx, cell-drilldown.test.tsx,
    unified-cell.test.tsx (mock-badge-RT → mock-badge-BE, drilldown-badge-rt-
    → drilldown-badge-be-, "RT (Round Trip)" → "BE (Round Trip)")

Stable contract identifiers are PRESERVED — only the visible name flips:
  - level={model.d4} unchanged (D4 is still D4)
  - dimensionKey={keyFor("e2e", ...)} unchanged
  - probe registry-key "d4"/"chat"/"tools" unchanged
  - persisted dimension codes unchanged

Two intentional residual "RT" mentions remain in
cell-pieces.signal-degrade.test.tsx as historical commentary documenting
the rename lineage (RT → UI on the e2e badge in #5473, then RT → BE on the
D4 badge here).

Verification (in showcase/shell-dashboard):
  - npm run typecheck: clean
  - npm test: 1089 tests passed
  - npm run build: clean (next build OK)
  - LC_ALL=C grep -rlP '\x00' showcase/shell-dashboard/src: empty
2026-06-16 11:40:50 -07:00
Jordan Ritter 80b526d68b refactor(showcase): slugify stats-bar testids to handle multi-word labels
The testid generator in stats-bar.tsx used `label.toLowerCase()` directly,
producing fragile selectors with spaces and parens for compound labels.
After this PR's rename of "Wired" to "BE (Agent)", the testid became
`stat-be (agent)` — a CSS-hostile selector. A pre-existing
`stat-max depth` (from "Max Depth") had the same problem but predated
this PR.

Fix: replace with a proper slugifier:
  label.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/(^-|-$)/g, "")

Resulting testids:
  "BE (Agent)"   -> stat-be-agent   (was stat-be (agent), broken)
  "Max Depth"    -> stat-max-depth  (was stat-max depth, pre-existing fix)
  "Stub"         -> stat-stub       (unchanged)
  "Unshipped"    -> stat-unshipped  (unchanged)
  "Unsupported"  -> stat-unsupported (unchanged)
  "Regressions"  -> stat-regressions (unchanged)
  "Failures"     -> stat-failures   (unchanged)

Call-site enumeration receipt (mandatory):
  rg 'stat-wired|stat-be|stat-max|stat-stub|stat-unshipped|stat-unsupported|stat-regressions|stat-failures' showcase/shell-dashboard/
    -> no matches (zero hardcoded references anywhere)
  rg 'data-testid=.stat-|"stat-|`stat-' showcase/shell-dashboard/
    -> only one hit: stats-bar.tsx:29 (the generator itself)
  rg 'stat-' showcase/shell-dashboard/tests/
    -> no matches (no Playwright/visual tests depend on these testids)

No test updates required.

Verification:
  npm run typecheck   -> clean
  npm test            -> 1088 pass / 1 pre-existing unrelated failure
                         (useLiveStatus.test.tsx R5 F5.2 — does not
                         reference stats-bar, exists on PR HEAD)
  npm run build       -> clean (Next.js lint inline; no lint script)
  NUL byte sweep      -> empty
2026-06-16 09:40:49 -07:00
Jordan Ritter f316d055fb refactor(showcase): rename internal Wired counters to BeAgent
Renames the internal display-counter variables that fed the
stats-bar / coverage-bar "Wired" labels to match the new "BE (Agent)"
taxonomy: totalWired → totalBeAgent in cells-view.tsx and
parity-view.tsx, pctWired → pctBeAgent in coverage-bar.tsx. These
are local render-time aggregates, not part of any persisted
contract.

The status enum literal "wired" (the .filter((c) => c.status ===
"wired") guard, the coverage-segment-wired test ID, and the
wiredByCategory Map's local name) is intentionally preserved — those
all key directly off the persisted catalog Status enum and changing
them would expand scope into the catalog contract.
2026-06-16 09:23:00 -07:00
Jordan Ritter bfebac6565 refactor(showcase): relabel catalog status Wired display → BE (Agent)
Unifies the catalog integration status display label with the same
"BE (Agent)" taxonomy as the live-probe agent dimension. The
underlying Status enum literal ("wired"), the catalog.metadata.wired
data field, and the filter chip id ("wired" → cell-matrix.tsx:311
status === "wired" filter key) are all PRESERVED — they are
persisted catalog data + filter state contracts that must not move.
Only the user-facing display label on stats-bar, adaptive-stats-bar,
and the filter chip flips.

This collapses the two distinct dashboard "Wired" surfaces (the L1
live-probe dimension and the per-cell build-state count) under one
unified label, matching the user-confirmed Path B.
2026-06-16 09:22:41 -07:00
Jordan Ritter ef40bd7cfb refactor(showcase): relabel live-probe agent dimension Wired → BE (Agent)
Unifies the L1 "agent" live-probe display label with the taxonomy
convention established by #5473 (UI (Frontend), E2E, CV, D6 — layer
descriptor in parentheses). The dimension name stays "agent" in code
(PocketBase row keys agent:<slug>, LiveDimension union, keyFor
lookups are all unchanged stable contracts) — only the visible label
changes.

Updates the level-strip L1 badge label and the packages-section
L1-L4 header legend (W → B, "Wired" → "BE (Agent)") so the
level-strip's ToneChip first-letter abbreviation matches the legend
key. Test assertions covering the rendered letter, the legend text,
and the degraded-tone test's local variable follow suit.
2026-06-16 09:22:31 -07:00
Jordan Ritter 53f021afe8 refactor(showcase): probe taxonomy cleanup — drop Smoke, E2E (Demo) → UI (Frontend)
The smoke probe was the same HTTP contract as /health on the same
service (200-OK JSON body), so every tick paid two HTTP calls for the
same liveness signal. Drop the /smoke GET + the smoke:<slug> primary
ProbeResult; the driver now emits health:<slug> as the primary and
agent:<slug> via writer side-emit (half the per-tick cost).

The driver's registry kind stays "smoke" so existing YAML configs and
orchestrator family wiring keep routing to this driver — the
emission key (health:<slug>) is the taxonomy contract that matters.

Dashboard:
- Drilldown: D3/e2e row labelled "UI (Frontend)" (was "E2E (Demo)");
  Smoke row dropped (CellState.smoke field retained for back-compat).
- Cell badges: short label "UI" (was "E2E") in cell-pieces + unified-cell.
- Legend: D3 = "UI (Frontend): demo page renders in browser (Playwright)"
  plus an explicit Health row at the top.

Tests updated to assert new labels:
- cell-drilldown.test.tsx: 6 dimensions (no Smoke); UI (Frontend) label.
- cell-drilldown.lazy-signal.test.tsx: drilldown-badge-ui--frontend- testid.
- cell-pieces.test.tsx + .signal-degrade.test.tsx: badge name "UI".
- unified-cell.test.tsx: mock-badge-UI.
- overlay-selector-integration.test.tsx: "UI" in place of "E2E".
- dashboard-color-matrix.test.tsx: badge: "UI" case names.
- liveness.test.ts: two-call contract (health + agent), regression guard
  asserting no smoke:<slug> ever emitted.

Underlying probe key (e2e:<slug>/<feature>) preserved on PocketBase so
historical rows render correctly during the rename window.
2026-06-16 01:48:41 -07:00
Jordan Ritter 06f6f2cd6d fix(dashboard): worker-run-detail-panel abort fetches on cleanup + move side-effects out of setState updater 2026-06-11 21:16:38 -07:00