The prompt, template and knowledge editors all stopped at the same boundary: the
suite proved a document loads and survives an in-memory round trip, but no spec
ever let Save issue its mutation. That is the one operation that can destroy a
user's Go template, and the form→mutation hop — dirty tracking, raw/rich mode,
which field is sent — was covered by nothing.
Each spec now edits, saves, and asserts the request: updatePrompt/createPrompt
carries the edit plus every template atom (heading, {{.Target}}, the bash fence,
the table row), updateFlowTemplate the same for {{TARGET}}, and
updateKnowledgeDocument the edited content with the document id.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The provider editor's whole write path had no oracle: createProvider,
updateProvider, deleteProvider and the two test mutations were absent from every
spec. The failure this guards is not hypothetical — the same class (a zero-ish
agent field silently lost between the form and the wire) was found and fixed in
the converter one round ago, with nothing left behind to keep it fixed.
The cassette's defaults now carry `json: true`, `n: 1` and a real maxTokens, and
the spec asserts the createProvider variables still hold them after the form
round trip, plus that all thirteen agents are serialised rather than only the
one the user touched. Dropping `n` from transformFormToGraphQL turns it red.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
mkdir was the only write the suite drove; move, copy, delete and download had no
oracle at all. That matters more here than the usual coverage argument: these
paths also serve the SPA, so a wrong method answers 200 with HTML instead of
404 — the failure mode looks like success. Each new test pins the method and the
payload: rename → PUT /resources/move with the typed destination, copy → POST
/resources/copy, delete → DELETE with the row path in paths[], download → the
href the row builds.
The fifth case covers the guard that has to stand between a user and data loss:
copying onto an existing name must raise "Replace existing item?" and send
nothing at all until the second decision — asserted by counting requests, not by
reading the dialog.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
data-testid existed in exactly one place in product code — the message-id badge
this suite added — while every other primitive in the app is addressed through
the shadcn `data-slot` attribute. Switch it to `data-slot="flow-message-id"` and
move the five specs and the helper onto a selector constant. The remaining
data-testid occurrences are test-local stubs inside *.test.tsx files, not
product surface.
130 mock-tier tests still pass.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
langchaingo v0.1.14-update.6 is published, so the local scaffolding is gone: the
path `replace` to the working copy, the Dockerfile's vendored-build guard and
the 187 MB backend/vendor tree. The branch already pins the released version and
carries the go.mod it needs (ollama v0.23, sergi/go-diff, jsonschema), so the
backend builds straight from the module proxy.
Merged clean — no conflicts. The two sides barely overlap: 180 files, all
backend/cmd/examples, against this branch's frontend, e2e and CI work. The four
agent-config fields added here (minP/n/json/responseMimeType) and their converter
mapping survive intact, and schema.graphqls is untouched by the merge, so the
generated frontend types stay fresh.
Verified on the merge result: backend builds against the published module,
go test passes everywhere except cmd/installer, which fails the same three
cases on feature/next-release alone (pre-existing, not from this merge).
TestConvertModels_OffCapability, which failed while the local fork was in
place, now passes. Frontend: tsc -b, eslint, prettier, 1104 vitest, 130
mock-tier e2e.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The Record<string, string> index type made every sha string | undefined under
noUncheckedIndexedAccess, which vitest and eslint did not see but tsc -b did.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The converter maps 13 keys of pconfig.AgentConfig; min_p, n, json and
response_mime_type were never among them, and CreateProvider/UpdateProvider
replace the whole row, so saving a provider through the UI persisted a config
with those keys gone. The one that bites out of the box is `json`: openai's
shipped simple_json default carries it, pconfig turns key-presence into
llms.WithJSONMode(), and a user-defined provider built from that same default
therefore called the LLM without JSON mode while the built-in one did not.
Expose the four on AgentConfig/AgentConfigInput and map them both ways. From
GraphQL a zero must be written as an absent key, not as a zero value —
BuildOptions gates on presence, so `json: false` would otherwise switch JSON
mode ON and `n: 0` would emit an invalid request parameter. The form carries
them through untouched; none is user-editable yet.
The behavioural test takes openai's shipped default through the round trip and
asserts JSON mode still reaches the call — it fails on the old converter.
Note: TestConvertModels_OffCapability fails in this working tree both before and
after this change; it depends on the local langchaingo checkout, not on this.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
e2e.md promised "unmatched calls fail the test" without qualification, but only
HTTP is gated: an unmatched subscription is deliberately answered with silence
and merely attached as a diagnostic, because a flow page opens ~15 of them and
erroring on the unmocked ones would poison the client. A typo'd subscription key
therefore surfaces as a UI timeout, which is the opposite of what the doc led a
contributor to expect. Say which half is gated and where the missed operations
show up.
list_detail_pages.md named /settings/api-tokens as the sole exception to the
list/detail pattern, so a reader looking to extend /settings/providers/:id would
grep for a use-provider-detail-navigation.ts that never existed — as would one
looking at /settings/prompts/:name. Both are single-entity forms; name them, and
add the two phantom hooks to the doc's own do-not-reintroduce table.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The cassette pins the mutation on `input.name` only, because ttl is derived from
the clock and cannot be matched exactly — and the subset matcher then accepts any
ttl at all. Every post-submit assertion reads cassette-fixed data, so a
calculateTTL regressed to its 60-second floor (or off by a factor of 1000) still
passed. Assert the value on the request instead, with the band derived from
CASSETTE_EPOCH and the date the picker clicks rather than hardcoded. Clamping
calculateTTL to 60 now fails the spec with 60 vs >387800.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
After a socket drop the GraphQL sweep skips the resources slot — it is
cache-only, and refetchObservableQueries filters that policy out — so the whole
re-hydration rides on the `ws:reconnected` event. The spec asserted the GraphQL
delta and nothing else, and deleting the production dispatch left the entire
suite green. Count the GETs to /api/v1/resources across the drop and require
exactly one more; counting rather than arming a one-shot expectation matters,
because the mount-time hydration would otherwise satisfy it on its own. With the
dispatch removed the poll now fails 1 vs 2.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Three holes in the route manifest, one class: a gate that reports green on a
surface it never mounts.
- FLOW_DETAIL_TABS listed 8 of the flow detail's 10 desktop panels. Terminal is
the default and gets scanned anyway, but Automation was mounted by nothing —
no axe scan, no palette scan — so a regression confined to it stayed invisible.
- /dashboard declared no tabs at all although it is a two-tab Radix Tabs, and
Radix unmounts the inactive panel: Overview, four Cards and two usage tables,
was never scanned. The same regression in Analytics would have been caught.
- Its `ready` was a ChartCard heading rendered above the component's own loading
branch, which is exactly what the tabs contract forbids: the sweep could start
against spinners. Gate on a data row instead.
- templates-provider.tsx was owned by /templates alone while flow-form.tsx
consumes it from /flows/5, so affected-routes scoped a provider change away
from the route that renders the consumer.
All four panels pass axe and palette in both themes as-is. The ownership case is
pinned against the real manifest, not the fake one, and fails before the fix.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The REST cursor was moved off the raw request body because a per-call volatile
field fragmented the sequence into a fresh cursor every time, so step one was
re-served forever. The GraphQL half kept keying on the raw variables and has the
same hole — an operation like createAPIToken, whose ttl is computed from the
clock, cannot be sequenced today. Key on the selected entries' own pins instead;
variable-pinned sequences (the dashboard's week/month stats) stay separated
because their entries pin different variables. The first new case fails at HEAD
with step one served twice; the second pins the property the key must not break.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The Tier-2 runner builds with the repo root as context, and the same script
rewrites e2e/.auth/user.json and playwright-report/ on every run, so `COPY
frontend/ .` missed cache every time and re-ran pnpm install, license-checker
and the frontend build. frontend/.gitignore already enumerates the four output
directories; mirror them, plus the tsbuildinfo files a local typecheck leaves.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Playwright writes a -diff.png when a snapshot differs, but a missing baseline
takes handleMissing, which writes only -actual.png. The detection step looked
for -diff.png alone, so a PR that adds a manifest route without regenerating
baselines uploaded no artifact and the sticky comment carried no advisory at
all. Match -actual.png too — both names are written exclusively by the snapshot
matcher, so a non-snapshot failure (which leaves test-failed-N.png) still
reports nothing — and say in the comment that the missing case uploads actual
only.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
`head.repo.fork == false` is not null-safe: a PR whose head fork was deleted
arrives with `head.repo: null`, and GitHub coerces null and false alike to 0, so
the guard passed and a labelled fork PR would enter the protected environment,
hold a reviewer, then fail on the secrets GitHub withholds from fork runs — the
exact outcome the guard's own comment says it prevents. Comparing head.repo
full_name to github.repository yields false for a live fork and for a deleted
one, and it also survives this repo ever becoming a fork itself.
Evaluated both expressions with @actions/expressions against the real workflow
file: the only row that moves is the deleted fork, true -> false.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
keepDirtyValues covers reset(), and the create form seeds its agents with
setValue, which ignores it. So on /settings/providers/new?type=… every field
under agents.* snapped back to the type's defaults whenever a settingsProviders
result landed that differed from the rendered one — a reconnect refetch, another
session touching a provider, a changed model catalogue — while the dirty name
and type survived, making the loss look arbitrary. Skip the re-seed when the
type has not changed and the agents subtree is already dirty; a real type switch
still re-seeds, since the previous type's models and defaults are wrong for it.
The create-path twin of the existing edit-path test fails on the old code with
the edited temperature back at the default; a second case pins the type switch,
which a plain dirty-guard would have broken.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
--primary is a fill paired with white --primary-foreground, and as text on dark
it measures 3.12:1 on --card (2.89:1 through the upload overlay's tint) — below
AA. There is no lightness that clears AA in both roles: text needs L>=0.59 and
white-on-fill needs L<=0.545, so the window is empty. Add --link, measured live
at 6.34:1 on --card in dark and 15.59:1 in light, and point the link button
variant, the table/file-manager/prompt hover links and the two drag overlays at
it. The icon at flow-assistant-messages.tsx keeps --primary: it is a graphic and
clears the 3:1 bar.
The gate probed exactly one button variant, so the failure had been living under
a waiver instead: probe every variant off the union the way the badge half
already does, and drop the color-contrast waiver on the Screenshots source URL —
that node is the link variant, and axe passes it now. The dark link row fails at
3.12 against the previous colour.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
--with-deps hardlinks node_modules in, and a hardlink cannot cross filesystems,
so on a host whose $TMPDIR is its own mount the flag was unusable: `cp -al`
fails and the ERR trap tears the half-built worktree down, leaving the caller
with an empty $SANDBOX. Reproduced on a real second filesystem (attached image,
st_dev 16777238 vs 16777234): the old script dies with "Cross-device link" at
rc=1, the new one probes with a real link first, reports the fallback on stderr
and builds the sandbox beside the repo. Same-filesystem hosts keep using
$TMPDIR untouched, and PENTAGI_SANDBOX_ROOT overrides both.
`clean` accepts a path under either root, so a sandbox built through the
fallback can still be removed by the containment check.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
`clean <path>` ran `git worktree remove --force || rm -rf` on whatever it was
handed. git exits 128 both for the main working tree and for any directory that
is not a worktree, so the rm fallback is what runs for every wrong argument, and
sitting left of `||` it never trips `set -e` — `clean <repo-root>` erased the
checkout including .git, and `clean <unrelated dir>` erased it silently at rc=0
because the git error was redirected away. Resolve the target and refuse
anything that is not a child of the sandbox root, and let git's refusal print.
The no-argument form was the other half: it wiped the whole root, which every
agent on the host shares, so the documented copy-paste flow with an unset
$SANDBOX took out concurrent live sandboxes. The sweep is now the explicit
`clean --all` and only removes what has been untouched for two hours; a bare
`clean` is an error.
Four of the six new cases fail against the old script.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
On pull_request, `github.event.before` is the PR's previous head, so the second
push to a PR diffs a range that no longer contains the commit that edited the
schema: the detector emits changed=false, the freshness step is skipped by its
`if:`, and lint-and-test goes green on a stale src/graphql/types.ts. Every
later push that does not itself touch one of the five inputs repeats it, forks
included — which is the case the trigger was added for. Nothing else covers the
gap: tsc reads the committed file as truth, eslint ignores it, and prettier
passes on codegen's own output.
Diff from pull_request.base.sha on PR events and keep `before` for push. The
range selection moves out of the YAML into .github/scripts so it can be driven
over real git fixtures: on the three-commit fixture the old range yields
changed=false and the new one changed=true.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
CodeBlockLowlight had no defaultLanguage, so a fence with no info string fell
to highlightAuto — a scan of all 37 `common` grammars — and the plugin redoes
every code block in the document whenever the doc changes with the caret inside
one. Measured on the stand, per keystroke: 12 ms at 20 lines, 33 ms at 60,
62 ms at 120, and 33 ms for three 20-line blocks (it is the document total that
counts, not the block). With the plaintext grammar the same edits cost 0.6-0.9 ms.
The trade is that an undeclared fence now renders monochrome instead of being
coloured by a guess, which matches how the language caption already behaves.
Declared fences are untouched.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Syntax highlighting handed the code-block surface to atom-one-dark, which is
#282c34 in both themes, but the `{{.Var}}` / `<tag>` view decorations kept
their theme tokens — dark ink meant for a light ground. Measured on the stand:
a tag inside a fence sits at 1.16:1 in light theme (a variable at 1.72), and
dark had slipped to 4.05, below AA. Outside a fence both are unaffected.
Re-point the two tokens at the dark ground inside `pre`, where they measure
5.77 / 5.30 in both themes.
The contrast gate could not see this: it mounts probes on a synthetic --card
surface and composited only the probe's immediate parent, so a span inside a
transparent `code` measured against nothing. Walk to the first opaque ancestor
instead, and pin the real rendered spans in the prompt-detail editor — the
cassette's bash fence now carries a tag as well as a variable. Both new rows
fail on the previous CSS with exactly the numbers above.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Replaced all instances of `Qwen3-Coder-Next-FP8` with `MiniMax-M2.7` in the hcnsec provider configuration, adjusting model settings and pricing accordingly.
- Updated the test report to reflect the new model configurations, including success rates and average latencies for various agents using `MiniMax-M2.7`.
- Enhanced the overall structure of the configuration file for clarity and consistency.
- Introduced `callWithSetupRetries` function to enhance error resilience during LLM prompt calls, allowing for retries on transient errors with backoff.
- Updated `NewFlowProvider` and `NewAssistantProvider` methods to utilize `callWithSetupRetries` instead of direct calls to `prv.Call`, improving stability in flow and assistant creation.
- Added comprehensive unit tests for `callWithSetupRetries`, covering immediate success, transient error handling, and context cancellation scenarios.
- Enhanced error messages in Graphiti search tools to provide actionable feedback for missing or malformed parameters.
- Updated templates to clarify search type requirements and taxonomy references.
An audit of the comments added since the feature base flagged a batch of
justification openers ("Not a copy-paste", "Live entry not dead weight"),
"there's a test for it" cross-refs, and how-we-found-it war-stories. Cut the
narration and keep the load-bearing fact in each. One was actively wrong:
playwright.config.ts still described the `maxDiffPixelRatio` anti-aliasing
budget that 5cec81b replaced with an absolute `maxDiffPixels` — removed.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Updated the `langchaingo` dependency in `go.mod` from version `v0.1.15-0.20260723091023-35da5c0f0620` to `v0.1.14-update.6`.
- Modified the model configurations in `config.yml` and `models.yml` to replace `claude-opus-4-8` with `claude-opus-5` for both the generator and refiner sections.
- Updated the model descriptions and pricing in `models.yml` to reflect the new `claude-opus-5` model and added new models `claude-sonnet-4-6` and `claude-haiku-4-5`.
- Adjusted the test report in `anthropic-report.md` to reflect the updated model names and improved average latencies across various tests.
- Added configuration options for an optional internal analytics engine in `.env.example`, `docker-compose.yml`, and related files.
- Updated documentation to include details about the new internal analytics engine, which scrapes and summarizes pages as a fallback for the web_search tool.
- Enhanced the `ftester` to support testing the new internal engine alongside existing search engines.
- Adjusted various components to integrate the new engine, including configuration forms and validation logic.
This feature allows users to enable a browser-based fallback for analytic queries, enhancing the flexibility of the web search capabilities.
280d7b9 filtered the fork-fallback PR lookup by head identity but left the
primary same-repo path taking `run.pull_requests[0]` — one commit can belong to
several same-repo PRs, so the first listed could collect another branch's
report. Apply one identity filter to both sources. workflow_run.pull_requests
carries head.repo.name but no full_name, so the repo is enforced only when
present, keeping the primary path matching on head sha + ref.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The doc told readers to dispatch the workflow "with tier: stand", an input
fc15ec8 removed — workflow_dispatch takes none — and claimed the protected
Environment is what stops a fork or mislabeled run from reaching secrets. It is
not: the job's `if` (label name plus a non-fork head, added in 7b1ecfd) skips
before the Environment is ever evaluated. Describe the real two-gate model: the
`if` first, then the Environment for a run that clears it.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The sequencing cursor embedded the whole request body, so an endpoint whose
body carries a per-call volatile field — an idempotency key, a timestamp — got
a different cursor key on every call and re-served entry one forever, never
advancing the sequence. Key on a signature of the matched entries' pins instead:
two genuinely different sequences still separate (their entries pin different
subsets), but a volatile field no longer fragments a single sequence. Unit test
covers it; it fails on the old body-keyed cursor.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The "shows the upload call to action" test asserted only the "No resources yet"
title — the sole empty-state surface in the suite — so dropping the drop-zone's
actionLabel kept it green. Assert the CTA button, scoped to the drop zone (its
size hint is unique) so the toolbar's Upload button is not mistaken for it.
Removing actionLabel now fails the test.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The target-size waiver matched a bare `aria-label="Select `, so any control on
the page labelled "Select …" — not just the file-manager row-select buttons —
was waived. axe emits those buttons as `button[aria-label="Select <name>"]` with
no parent path, so the label is the only anchor available; requiring the button
tag at least stops a future non-button "Select …" control from slipping the
rule. /resources a11y stays green; a div/anchor "Select" is now caught.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Removes a stale "revisit with the design pass" comment left above the rewritten
scoped-waiver block in routes.ts, and applies the formatting the repo's own
`pnpm run prettier` wants across the a11y, palette and provider-form files —
per-file prettier checks were missing a plugin the package script loads, so a
few of this session's commits landed formatting the gate would flag.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
`create --dirty` piped `git diff HEAD` without --binary and copied no untracked
files, so a regenerated PNG baseline aborted the patch (leaving a half-built
worktree) and a new spec never reached the sandbox — and because the failure
printed onto stdout, the caller captured an error string as the sandbox path.
Use --binary, copy untracked non-ignored files separately, and trap ERR to tear
the worktree down and report on stderr. Verified with a modified baseline plus a
new untracked spec: both now arrive in the sandbox.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The gate compared `[...new Set(offenders)]` against ACCEPTED, so a waiver that
names one node absorbed any number of nodes carrying the same token — a second
element reusing an already-waived off-palette class stayed green, against the
"waive one node, not a rule" contract. Compare the sorted offender list
directly: two nodes with a waived token now fail where one is accepted. All 18
scans stay green (each waived token appears exactly once today).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
PALETTE_UTILITY matched only numbered hues and arbitrary values, so text-white,
text-black and bg-transparent were invisible to it: an overlay like
`cn(badgeVariants({variant:'blue'}), 'text-white')` on a badge went unmeasured.
Add white/black/transparent (with optional opacity) to the colour alternation.
No badge or button variant uses them, so all 18 scans stay green; injecting a
text-white overlay on a rendered badge now fails the gate as an off-palette node.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The only data assertion on the stand tier ran on Playwright's 5s default expect
timeout — the config overrides only the screenshot timeout — so a healthy but
slow stand backend serving the list a little late would fail the smoke falsely.
Give it a 30s budget, matching the real-backend nature of the tier.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
ci.yml triggered only on push, but a fork PR's commits never push to this repo,
so its lint-and-test job (eslint, vitest, and the GraphQL codegen-freshness
check) never ran for forks. e2e.yml, the only workflow a fork PR did trigger,
builds with tsc — which reads the committed types.ts and cannot see it go stale.
So a fork editing schema.graphqls without regenerating, or shipping a lint/test
failure, could merge green.
Add a pull_request trigger (lint-and-test uses no secrets and is fork-safe) and
narrow push to main + tags, which is all docker-build — gated to those refs
already — needs. A concurrency group drops superseded PR-sync runs. The
codegen-detect step already treats an empty `github.event.before` as "check
anyway", so pull_request events always run the freshness check.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The settings-providers cassettes left nine of eleven default configs null and
every model catalog empty. Verified against the live backend that this is a
shape it never sends: gemini/deepseek/ollama all return a non-null default and a
populated model catalog even with zero keys configured. The empty catalogs made
the create-form's model-dropdown seeding path structurally unreachable — no spec
could reach it.
Give every type a default config and a two-entry catalog, and add a spec that
picks a type and opens an agent's model dropdown: it asserts a catalog-only
model appears there. That option is gone when the catalog is empty, so the spec
fails on the old backend-impossible shape — the coverage that was missing.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The waivers were passed to all nine /flows/5 scans (base + 8 tabs), so a
Files-tab waiver silenced its rule on every other tab: a new violation of the
same shape on Assistant would stay green. Add an optional `tabs` to A11yWaiver —
omitted means route-wide (the by-design message metadata), a list means only
those tabs — and filter per scan.
The gate itself supplied the real ownership once scoped: the progressbar-name
waiver belongs to Tasks, the truncate-contrast one to Screenshots, and
table-container scrollable to Dashboard as well as Files — all previously masked.
Proven by injecting a Files-shaped table-container into the Assistant panel: it
now fails the Assistant scan where the route-wide waiver used to absorb it.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The sticky comment computed `mockPassed` by falling back to results.json alone
whenever the jobs API was unreachable. A run killed by the global timeout writes
its results.json before the abort is observed — the un-run tests land in
`skipped`, so `unexpected` stays 0 — so that fallback posted a ✅ on a run that
never finished.
Model three states instead of a boolean: pass (zero failures AND the job
confirmed success), fail, and unknown (jobs API unreachable, so the job cannot
be confirmed). Unknown renders ⚠️ with a note to check the run, never ✅.
Verified by driving the extracted script over a timeout-shaped results.json with
a rejecting jobs API: it now yields ⚠️ unknown where it used to yield ✅.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The provider detail form was the only one of the three detail forms created
without `resetOptions: { keepDirtyValues: true }`. Its seeding effect keys on
`data`, and settingsProviders is cache-and-network + replaceWithIncoming, so a
background refetch (list→detail race, or a provider* subscription) delivers a
fresh `data` reference mid-edit and the plain reset() silently wipes whatever
the user was typing. settings-prompt and template already guard this, the
latter with a comment naming the exact hazard.
Live-reproduced on the docker branch image: an edit typed during a delayed
refetch reverted to the server value. Regression test delivers a fresh data
reference after an edit and asserts it survives — it fails without the option.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
`pnpm run prettier` (which CI gates on) failed on these two files — line
wrapping only, in edits from the M5 pager-switch work. The branch tip was red
until this. No behaviour change; `prettier --check` over the {src,e2e} glob is
clean now.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The fork fallback took `prs[0]` from the commit's PR associations without
checking any of them is still this run's head, so a stale association could
collect another branch's report. Match on head sha, ref and repository, and skip
when nothing matches. Verified against a synthesised association list where the
stale PR is listed first: the old lookup picks it, the new one does not.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The REST gate matched on method and path alone, so an endpoint that carries its
payload in the query and sends no body could not be pinned — `bodySubset` has
nothing to bite on, and the path hit answered success whatever the request
named. Unmatched-call diagnostics dropped the query too, so a miss printed a
path that looked right.
Add `querySubset`, the counterpart to `bodySubset`, thread the query into the
matcher and the sequencing cursor, and report it on a miss. No spec exercises a
query-carrying endpoint yet; the unit test pins the behaviour and fails when the
new filter is removed.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
`paint()` clears to transparent and the label was painted alone, so a
translucent text token kept its opaque base: 30% black on white measured 21:1
instead of 2.12:1 — a failing token reported as perfect contrast. Paint the
label on the same layer stack as the surface.
No probed token is translucent today, so every current reading is unchanged
(7/7 still pass); this stops the helper lying about the next one.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The cassette gave entry 503 a Terminal body under type Report — a combination
`getMessageType` never produces, since the terminal tool's output carries the
terminal type. It mattered: only a report auto-expands, so the spec's two
xterm assertions passed on a panel that was open for the wrong reason.
With the realistic pairing the message starts collapsed, so the spec now walks
the path a user does — assert one xterm, open the details, assert the second
and its content.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Verifying a claim about a gate means breaking something on purpose, and review
runs kept doing that in the working tree: probe specs left behind under src/ and
e2e/, tracked files edited mid-run, and once an `rm -rf` of a directory the run
had not created. Nothing survived to the commits, but only because each run
happened to clean up after itself.
`review-sandbox.sh` hands out a detached `git worktree` under $TMPDIR, optionally
carrying the uncommitted diff and a hardlinked node_modules so pnpm, vitest and
playwright run there. Verified by doing the damage on purpose inside one — an
edited world.ts, a stray probe spec, a deleted frontend/docs — and confirming
the real tree came back untouched.
node_modules is hardlinked rather than symlinked because pnpm treats a symlinked
modules dir as foreign and tries to purge and reinstall it, which would reach
through the link into the real tree; it aborted here only for want of a TTY.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Deleting `eligible`'s second line — the one that makes a newly-enabled flagged
entry outrank the unflagged candidates — left the whole unit tier green. The
existing cases use two entries, where the consumed-index cursor lands on the
flagged entry anyway, so sequencing alone reproduced every asserted progression.
Add a three-candidate case where the two disagree: it passes as written and
fails with that line removed.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
`pageErrorLog` was opt-in with nothing asserting it on teardown, so a spec that
never destructured the fixture collected nothing and an uncaught exception on
that surface failed nothing. Make it auto and assert on teardown. Verified by
throwing from an init script in the populated-providers a11y test, which does
not take the fixture: green before, red after. Console errors stay opt-in —
several specs drive genuine 4xx paths.
The remaining flow-detail waivers keyed on bare utility classes: `bg-primary`
matched every Progress root through `bg-primary/20`, and the other two were
similarly unanchored. Pin all three to the offending nodes, taken from a run
with the waivers disabled.
/templates and /knowledges claimed whole page dirs, so a diff touching only the
unswept detail page scoped to the list route instead of the run-everything
fallback. Name the list files; leave `src/features/knowledges` unowned, since
its only importer is that unswept route.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>