Adapting Legacy Cases
Many TestRail cases still describe the legacy CPE Admin portal. The Adaptation tab rewrites them for the Angular Management Portal and checks every rewritten case. An administrator writes a passed case into its source case in TestRail project 15. See Writing In Place.
|
At a glance
|
Why
TestRail project Angular Management Portal (id 15) was filled by copying the cases
of the CPE Admin project. The portal name in the title and in the login step was
replaced, but most step text still describes the legacy UI — Go to "Device Update"
tab, Select "LWM2M" from the left menu. The converter grounds a navigation step
only when it follows the grammar in
Writing TestRail Cases; every legacy
phrasing it cannot match falls through to a slower, less reliable LLM interact
step.
The adaptation changes UI wording only. Expected results, numbers, IP addresses,
TR-181 paths and quoted values are carried over verbatim, a case keeps the template
and the number of scenarios of its source. The result is written into the source case.
While ADAPT_COPIES_RETIRED is set, the tab never creates, deletes, moves or copies a case in TestRail.
A write into a source case never does it either.
Writing In Place
The adapted text goes into the project 15 case itself. The case keeps its id, section, history and run results. The adaptation job only reads TestRail. Only the in-place routes write.
-
Preview shows what a write would send. Every role can preview with
POST /api/adapt/inplace/preview. -
Apply and restore write and undo. Only an administrator can use
/api/adapt/inplace/applyand/api/adapt/inplace/restore. -
A request names at most 10 cases.
INPLACE_MAX_PER_REQUESTsets the limit. -
The worker saves the original text before the first write. It reads every write back. Restore returns the original text.
-
The write sends only the five text fields: title, preconditions, steps, expected results and separated steps.
-
A body field changes only when its words change.
-
A title never changes when only the letter case or the spacing differs.
-
Written fields are HTML.
-
Writes run only on the worker that has
ADAPT_INPLACE_ENABLEDon. That worker is live. -
The row must have a clean verdict:
readyorautomation_blocked. A storedpublishedorpublished_blockedrow is clean too. The current adapter version must have made the row. -
Rows with the status
needs_attention,errororpendingare never written. -
A held row has a
hold_reason. An administrator sets it withPOST /api/adapt/holds. -
Preview, apply and format upgrade refuse a held row, even with
force. -
A job returns a written row as a cache hit.
-
To adapt a written case again, an administrator presses Re-adapt from the original in the case detail. The route is
POST /api/adapt/jobs/from-legacy, at most 200 cases. -
Write to TestRail in the bulk bar sends 10 cases per request, one request after another. See Bulk Actions.
Flow
Copies in Project 20 (retired)
The tab no longer copies cases into project 20 and shows no project 20 control.
While ADAPT_COPIES_RETIRED is set, the publish and target routes answer 409.
|
A stored error row with the text publish outcome unknown stays as it is. A job does
not adapt it again, because a new adaptation could write the case twice. Re-gate or
Re-run with engine clears it.
Start a Job
The job form sits under the header (write roles only):
| Field | Meaning |
|---|---|
First pass |
The engine that rewrites a case the rules could not finish. Starts at the Settings
default ( |
Repair |
The engine of a repair pass. Starts at the Settings default ( |
Max repairs |
Repair passes allowed per case, |
Source section |
A section of the source project: the job takes the cases TestRail lists for it. The job reads the cases only. |
Case IDs |
Case ids, comma or space separated, with or without the |
Limit |
Take only the first N cases of the selection; |
Press Start. A job takes at most 1000 cases, and a case listed twice runs once.
The job is refused when another adaptation job holds the slot (see
One Job at a Time), when an engine is unknown, or when an ollama: engine is picked
while no Ollama URL is set.
Refresh to the Current Version
A change of a rule or of the vocabulary creates a new adapter version. Rows of an older version are stale. A refresh brings them to the current version with the rules only.
-
Wait until no run and no adaptation job is in progress.
-
In Run adaptation, press Refresh to current version.
-
Press Start refresh in the dialog.
The worker picks the rows, at most 1000 per job. A toast names the rows left for the next refresh. When no row is stale, a toast says
Nothing to refresh.
The refresh never calls a model:
-
A case the rules finish gets the current version.
-
The refresh marks a case the rules cannot finish. The mark carries the current version. The case keeps its row and shows Waiting for model.
-
The refresh also takes old-version cases in the statuses Error and No Angular equivalent.
-
A mark from an older version shows Refresh pending. The next refresh checks the case again.
-
The refresh never takes a Written, Restored, No change needed or Held case.
-
The refresh never takes a case with a written text in TestRail. For such a case, use Re-adapt from the original in the case detail.
-
The refresh does not adapt a case again after you edited it with Re-gate. It runs the gate again on the edited text. Then it stamps the current version. Your edit stays.
The owner decides on the model spend for Waiting for model cases. Then select them and
press Re-adapt with model. The route of the refresh is POST /api/adapt/jobs/refresh.
What Happens to Each Case
-
Cache. When the case has not changed in TestRail since its last adaptation (title, preconditions, steps, expected results and template), the stored row was made by the current adapter version and it is not
error— or is theerrorof a publish whose outcome is unknown (see Copies in Project 20 (retired)), whatever its version — the stored row is reused: the job counts it under its stored status, adds no tokens and raises its cache hits. The adapter version changes with a release that changes the rules, the gate or the prompts, and whenever the curated vocabulary or the vendored portal phrases change; a row of another version — every row stored before versions existed included — is adapted again by the next job that selects it, never deleted. The Refresh pending and Waiting for model tiles count the old-version cases still to do. Re-gate stamps the current version on the row it stores, so an edited case is reused rather than adapted over. A refresh gates an edited case again and never adapts it again. -
Rules. Deterministic rewrites: the title prefix (
CPE Admin >becomesAngular Management Portal >), the login line (Log into the Angular Management Portal), the portal name everywhere else, a step that equals a known legacy menu step or opens with one, a quoted label that has a known Angular name, and — on a step, for the navigation target itself, never for a button, a value, a precondition or an expected result — a label of two or more words that differs from exactly one Angular label only in case, punctuation or a plurals:Navigate to the "Filter (optional)" sectionbecomes… "Filters (optional)" …, keeping the case the step wrote. A curated rename wins over it, an ambiguous label (two Angular labels differ from it the same way) is left alone, and a label of one word is never renamed —"File"is not the Files page when the section means Download file, so such a step goes to the model.A step that only opens with a legacy menu step has that clause rewritten and the rest carried as written:
Go to "Settings" tab and make sure that any alert is configuredbecomesSelect "Settings" from the left menu and make sure that any alert is configured. The clause must end atand, a comma or a semicolon, and its Angular side must be one step with a quoted label — never aSettings > …menu path (the rest of the sentence would read as the path’s last segment), never a step Angular drops, never one the screen makes unmappable. A legacy tab later in the sentence (Open the mentioned device from the "Device Update" tab) is no menu step and is left for the model. The gate judges such a step by its whole ruled text, so a page the rest of the sentence names still has to survive a model pass.A portal case that logs in nowhere gets
Log into the Angular Management Portalas the first step of its first scenario (ruleR-loginin the row’s rule changes). The step carries no source line, the published numbering moves by one and astep Nreference moves with it; a model pass that drops the step gets it back, and a case that should carry it and does not is blocked (login-missing). The rule applies only when all of these hold:-
no field — title, preconditions, steps, expected results — mentions a login (
log in,logged in,logging in,log on,login,sign in,signed in,sign-in, andlogs inafter someone who logs in — "the user logs in", not "the logs in the tab"): C89092 checks that the login screen is shown, and C89037 / C89039 what a user who is not logged in sees; -
the first action — the first step that is not a
Make sure …setup line — opens something on the portal: a page, a tab, a menu item, a form,Go to "X"; -
no step sends a REST / SOAP request or an upper-case
GET/POST/PUT/DELETErequest, resets or restarts IIS or a service, or opens a URL in any browser: such a case works on the server, not in the portal ("let the device rest" is no REST call); -
a case of several scenarios does not refer to step numbers — the renumbering covers one scenario, so such a case is left alone and gets the MAJOR
login-not-added.
A case that logs in later (a
Make sure …setup line first, then the login) is left alone, and so is a case with no step at all. Over the 3738 source cases this adds the step to 46 cases.The rules also add the steps an Angular form needs and the legacy case never wrote (
UF-<fact>) — see Missing Intermediate Steps.When no legacy wording is left and every navigation target is a known Angular label, the case goes straight to the gate. The case detail then shows
engine rules, and no model is called.The model does not change an expected result. A case can have legacy labels left only in an expected result. If every navigation target is also a known Angular label, this case goes straight to the gate. The gate blocks the case. The case then shows Needs attention.
-
-
First pass. Otherwise the First pass engine rewrites the case. It receives the grammar, the vocabulary entries that occur in the case and portal UI-map snippets for up to three of its navigation targets.
-
Gate. The result is checked, see The Gate.
-
Repair. While the gate fails on something in the text and fewer than Max repairs repair passes have run, the Repair engine gets the adapted case, the source and the findings, and the gate runs again. When every blocking finding is a dry-conversion finding (see The Gate) no repair runs: a model cannot change what the converter covers or what the stand provides.
-
Store. The copy statuses in this step apply only to old rows. The row is saved as
ready,automation_blocked,needs_attentionorerror— see Statuses. A case that already has a TestRail copy keeps showing it: anautomation_blockedverdict is stored aspublished_blocked, and when the text would publish exactly what the copy holds (the same payload as the last publish sent), areadyverdict is stored aspublished— there is nothing to publish again. Areadyverdict with a changed text staysready, and the copy keeps its old text. -
Write. A clean row waits for an administrator to write it in place. See Writing In Place.
A model call gets 240 seconds and is not retried by the job. A call that fails, an
answer that is not the expected JSON, and a model refusal each make the case an
error row without a repair pass.
Missing Intermediate Steps
An Angular form shows some tabs and enables some buttons only after another action: the Automation events and Automation parameters tabs appear once the chosen model has a template, Add tasks stays disabled until its row has a condition, the Device profile rule tabs need a name and a model, and Send on Custom RPC or on a file upload asks a confirmation pop-up. A legacy case never says so — it was written for a portal that needed fewer actions — so the adapted case looked complete in TestRail and failed on the stand.
scripts/uiflow/registry.yaml lists those gates and the facts that open them. A
fact carries the one sentence to perform it, for example Fill in the "Name" field
with any value. It also carries the wordings already stating the fact and the
converter action for the sentence. For seven of the eight facts, an inserted step runs
and does not fall to the agent. The eighth fact, confirm_answered, becomes a noop
on purpose. The runner already answers the confirmation pop-up as part of the click.
The sentence stays for the reader of the report.
The devices_picked fact becomes a pick_devices step with the placeholder
$serial. The runner replaces the placeholder with the first device from this list:
-
the serial of the case
-
the device from the
MOVE_DEVICE_TO_DOMAINpreflight of the case -
the stand’s own device
If the case has a serial, the converter already writes the serial into the step. The model on the form comes from the same device.
The stand’s own device uses the TR-069 protocol. A case can name another protocol, such as the User Services Platform (USP) or Message Queuing Telemetry Transport (MQTT). If such a case has no device of its own, the step fails. The step does not take the stand’s TR-069 device instead.
The step also fails if the dialog does not list the device. For example, a domain user sees only the devices of the domain. If no preflight moves a device into that domain, the dialog has no device for the step. The step never picks the first device in the dialog.
A gate names its screens, its mode (add, edit or any), the control, the facts
it requires before that control, the ones that follow it (then — the two
confirmations) and the Angular line that proves it. The file is data, not
code: a malformed entry is a load error, and the worker then mounts no /api/adapt
route at all rather than adapt cases with half a registry.
The rules walk each adapted scenario with state — where the tester stands, whether a
form is open, which facts the case has already performed — and insert the missing
sentence immediately before the step that reaches the gated control. An inserted step
carries no source line and a UF-<fact> change record, which is what exempts it from
the invented step check of the gate; the published numbering moves and every step N
reference moves with it (R8). A step that hides the work behind an umbrella (Perform
all required actions to …) keeps its own line and gets the spelled-out sentences in
front of it — the umbrella often names where it goes next, which a replacement would
drop. A step that opens a tab and uses that tab’s button in one breath is split in two
(UF-split), with the facts between the halves. Over the 3738 source cases this adds
744 sentences to 447 cases and splits 45 steps. A case that already states a fact gets
nothing for it, and a second pass over an adapted case adds nothing more.
What the walk cannot settle by itself is a BLOCKER, so the case waits in
needs_attention:
| Finding | What a human does |
|---|---|
|
The case performs the enabling action after the step that needs it, so inserting would do it twice. Move that action before that step, or delete the later one. |
|
One step opens the tab and uses its control together, and its first clause is not a clean navigation, so nothing can be split off. Split the step by hand: the navigation, then the enabling action, then the control. |
|
The gate’s Angular evidence no longer matches the census, so what the portal does there is no longer known. Check the control on the portal and correct the registry entry (below). |
|
The case has several scenarios and refers to step numbers, so no step may be inserted at all — R8 renumbers one scenario only. Insert the step by hand and renumber the references. |
Adding a gate. Find the condition in the oneiot_angular checkout. Add the fact —
its sentence, the recognisers that spot a case which already does it, and the
converter: action the sentence maps to — or reuse an existing one; then add the gate
with its screens (keys of generated/portal_screens.json), control, requires /
then and angular.evidence as path:line. Then run python
scripts/sync_ui_gates.py: it rewrites generated/ui_gates.json, the census of every
form-state condition in the checkout, and the new entry’s fingerprint goes into the
gate. A condition held in TypeScript is not censused and keeps fingerprint: null —
nothing checks such a gate automatically, so it is verified by hand on the portal.
python scripts/sync_ui_gates.py --check exits 1 when the vendored census
is stale and is due on every frontend bump: a gate whose fingerprint the census no
longer carries loads unverified, and the cases that reach it are reported instead of
adapted.
The registry, its loader and the census belong to the adapter version, exactly as the curated vocabulary does. Editing any of them makes every stored row stale and the next job over them adapts them again — so batch the registry work and run once. Only a case whose adapted text changes becomes Ready to write, and an administrator writes it.
The Gate
A case is ready only when no check below finds a problem. The gate never runs the
case on a stand.
| Check | Blocks the case when |
|---|---|
Legacy wording |
The title, a precondition, a step or an expected result still names the legacy
portal ( |
Unmappable |
The adapted text names a legacy screen — or a legacy control or location — that has no Angular equivalent (Hazelcast Statistics, Unclosed connection, SQL query, Reactivated Reports, Save encryption, Next step), or the model listed an element it could not map. An element the model listed does not block when every label it quotes is a label of the Angular Management Portal — as written, or by the same two-word near-match the rules use — and an adapted step still names that label: the finding stays as a MAJOR naming the label that exists, so the case can still be written. Only the steps count, because only a step performs an element: a label that survives in the title, a precondition or an expected result while the step naming it was folded away blocks, as does an entry that quotes no label, one whose label is known only from the legacy portal or exists nowhere, and one that names a removed screen. |
Navigation |
A navigation target is not a label of the Angular Management Portal. A label known
only from the legacy portal does not count. A target the step quotes as a menu path
( |
Values |
A value of the source — a number, IP address, TR-181 path ( |
Source coverage |
A source line is claimed by no adapted step and does not appear in a precondition, or the number of scenarios differs from the source. |
Dry conversion |
The adapted case, converted with the LLM off, fails to convert, or its coverage map is not audited by design: a sentence nothing performs, a sentence that needs infrastructure the stand lacks, or an expected block that is neither measured nor judged. |
Every finding lowers the Score — 100, minus 40 per BLOCKER, 10 per MAJOR and 3 per
MINOR — and a problem found by a check above is a BLOCKER. The gate also reports
findings that do not block, for example a number that the adapted title,
preconditions or steps carry and the source does not (in an expected result it
blocks), a share of catch-all interact steps in the dry conversion above Fallback
warn, a button caption the vocabulary does not know, or a scenario that lost its
expected result.
When every BLOCKER comes from the coverage map of the dry conversion — a sentence
nothing performs (coverage-uncovered-…), a sentence that needs infrastructure the
stand lacks (coverage-blocked-…, infra-blocked-<category>), a map that is not
audited (coverage-not-audited) or a step the conversion lost (step-lost-…) — the
adapted text itself is clean: the case is automation_blocked, no repair runs, and an
administrator can write it. Any other BLOCKER — wording, navigation, values, source coverage, an
unmappable element, a conversion that crashed, and a coverage map the gate could not
build at all (coverage-unavailable, a failure of the gate itself) — keeps the case in
needs_attention.
Some infrastructure is deferred rather than blocking. The converter stamps one
category, usp_device, for a USP, MQTT or LwM2M device case, and it is the
evidence that names the protocol (USP device, MQTT device, LWM2M device). The
IoT emulator every worker carries (mcp-iot-emul) serves USP and MQTT, so for those
the only thing missing is the converter’s route to it — and not LwM2M, which the
stand does not run at all, so an LwM2M case keeps every BLOCKER and waits in
automation_blocked exactly as before. A marker whose evidence names no protocol at all
is treated the same way LwM2M is — as hard.
For a USP or MQTT device the infra finding becomes a MAJOR with the recommendation
Automation coverage deferred: needs the IoT emulator route in the converter
(mcp-iot-emul is on the stand), and so does every coverage finding that the same device
is what blocks — the coverage-blocked-… of each sentence it stops and the
coverage-uncovered-… that follow. The ids and the refs are unchanged, so the row
panel still points at the same sentences; only the severity moves, and a case blocked
by nothing else is therefore ready. The decision is per sentence wherever the coverage
map records which reason blocked it, and per case otherwise: one reason the stand really
lacks (file_server, license, oracle_db, an LwM2M device, …) keeps that sentence’s
BLOCKER and leaves the case in automation_blocked. A wording, navigation, value or
source-coverage BLOCKER is never deferred, so a case carrying one is still
needs_attention.
|
A deferred case can be written, and a run still cannot execute it. The runner keeps
its own list of waived infra categories and |
When the dry conversion cannot reach a service it needs, there is no verdict: the row
is stored as error with that reason, no repair runs, and the next job tries the case
again.
A legacy control the gate reports as unknown wording is cleared by curating it in
scripts/adapter/vocab/legacy_management.yaml, in one of two shapes. When the Angular
portal has the control under another name, a labels: entry gives the name — always
scoped to the element word the cases actually write (button, link, section,
drop-down, checkbox, field, tab, left menu), because the value check trusts a
curated rename only when its entry names an element kind: a rename of kind any on a
step, a precondition or an expected result merely turns the wording finding into a
Values one. A rename never contradicts the element word the source wrote either: where
the only kind that could fire would publish a picker as a link or a screen as a page, the
label is left uncurated and its cases wait for a human, because nothing in the table can
rewrite that word. When the control is genuinely gone, a removed: entry with a one-sentence
note naming the Angular source that proves it sends the case to a human with the
evidence instead of a bare "no Angular name for it" — but the note’s phrase is matched
over the whole source text, prose included, so a phrase also found in a case with a clean verdict today would block that case too and belongs in a report to QA rather than in
the file. Either entry changes the vocabulary digest, hence the adapter version, so
every stored row is adapted again by the next job: batch the vocabulary work and run
once.
Following a Job
The last job line of the Status panel counts each case a refresh left for the model
as needs model. The progress row of the job shows the same count as a needs model pill.
Under the form the tab shows the job’s status pill, <done> / <total> · now C<id>, a
pill per row status, tokens <in> / <out> · billed <n> · cache hits <n>, a progress
bar and the job’s error lines. The Cases list refreshes as cases finish, and the
case in progress carries a blue running pill. When the job ends, a toast gives its
status and counts.
| Pill | Job status | Meaning |
|---|---|---|
|
|
Cases are being adapted. |
|
|
The model refused a call. No further case starts; the cases already in flight finish, and the job slot stays taken until they have. |
|
|
The job ended after such a refusal —
the Claude usage limit, or a Claude authentication failure (its error lines say
which). The refused case is stored as |
|
|
Every case of the selection was processed. |
|
|
Stop was pressed, or the job lost the shared job slot and stopped itself (an error line says so). The cases in flight finished; no other case started. |
|
|
The job failed outside any case; its error line says why. |
|
— |
The worker that serves your session does not know the job, so this page cannot follow it. The job may still be running on another worker. |
Stop is shown to write roles while the job runs. It answers
The job is not running on this worker when the job belongs to another worker.
To carry on after a pause or a stop, start a job over the same selection. Unchanged
cases that already have a row are reused at no token cost; error rows and the cases
the earlier job never reached are adapted. A case TestRail could not return leaves no
row at all: the job counts it as an error and names it in its error lines.
One Job at a Time
Only one adaptation job runs at a time across all workers, and Re-run with engine
counts as a job. The slot is taken in the database the workers share, so a second
Start, on any worker, is refused with a message that names the holder:
an adaptation job is already running: job <id> on worker <host>:<pid>.
A job’s live progress is known only to the worker that runs it. When a job holds the
slot and the worker serving your session is not running it, the tab shows the slot
instead: an amber job slot held pill and Held by <worker> (job <id>) since <time>,
last heartbeat <time>. A running job refreshes its heartbeat every minute. The tab
reads the slot when it opens, on Reload and after its own actions — not
continuously.
A worker that crashed or was redeployed in the middle of a job leaves the slot taken; the slot frees itself 30 minutes after the last heartbeat. To free it sooner, a write role presses Clear stuck claim and confirms.
| Clear the slot only when its holder is gone — a heartbeat that has stopped moving is the sign. If that job is still running, a second job can start and pay for the same cases again; the cleared job stops itself once it notices. The clear is refused when the slot changed hands after the tab read it: press Reload and look again. |
Statuses
Every case of the source suite has exactly one status. The first matching rule wins. The counts add up to the cases of the source suite plus the orphans.
| Status | Rule | Next action |
|---|---|---|
Not adapted |
No adaptation row for the source case. |
Adapt |
In progress |
A job has the case, no verdict yet. |
Wait |
Written |
Adapted text live in TestRail, whatever the latest verdict or error. |
Done |
Error |
The last adaptation had no verdict. |
Adapt again |
Restored |
Original text restored by an administrator. |
Done |
No change needed |
Nothing to write; gate not failed. |
Done |
Held |
Held by an administrator, with a reason. |
QA or owner decides |
No Angular equivalent |
A legacy screen or control without an Angular equivalent. |
QA rewrites or retires |
Waiting for model |
Marked by the current refresh; the rules alone cannot finish it. |
Owner decides |
Refresh pending |
Made by an older adapter version; not refreshed yet. |
Runs after a deploy |
Needs attention |
The current adapter version still fails the gate. |
Edit and re-gate |
Ready to write |
Clean verdict; the writer has something to send. |
Write to TestRail |
The To do row of the panel holds every status except Written, No change needed and Restored. These three form the Done row.
Notes on the rules:
-
In progress comes first. Written, Error, Restored and No change needed come next. A hold or a version does not change these cases. Error is a to-do status.
-
A live write wins over the latest verdict and over an error. A later re-adaptation can fail. The case stays Written. Its reason line then says
a later re-adaptation needs attention; TestRail holds the earlier written version. After an error it saysa later re-adaptation failed: <error>; TestRail holds the earlier written version. -
A newer adaptation can wait for a write. The case stays Written. Its reason line then says
a newer adaptation waits for a write in the Project 15 panel. -
The Title column of a Written case shows the title that TestRail holds. A newer adaptation does not change it.
-
The refresh and the bulk action Adapt / Re-adapt never take a Written case. An administrator can re-adapt it from the original.
-
Held comes before the version rules. A released case moves to the next matching rule.
-
Refresh pending also covers a row with an unmeasured change.
-
A stored error row with the text
publish outcome unknownis not adapted again. See Copies in Project 20 (retired). -
A formatting-only fix is a change to write. Ready to write covers it too.
The row keeps its stored status and the gate verdict. Verdict and Change are columns
of Cases and filters under More filters. A stored published row reads as verdict
ready. A stored published_blocked row reads as automation_blocked. The stored
statuses published and published_blocked belong to the retired copies in project 20.
No new row gets them.
Status Panel
The header line names the source project, its case count and the time of the last read. The Reload button of the header reads the tab again. The Status panel sits under the header.
-
Each status has a tile with its count and its next action.
-
The To do row comes first, the Done row second.
-
A tile with no case stays in place, dimmed.
-
Click a tile number. Cases then shows exactly that status.
-
The bar splits all cases into the statuses. The legend gives each count and share.
-
The last line names the last job of this worker.
The worker reads the source suite once an hour. Before the first read, Not adapted shows
—. The header then says case list not read yet.
An orphan is an adaptation row without a case in the source suite. The summary names the orphans when there are any.
Sections
The Sections table lists every source section in the TestRail tree order. Each line shows the total, one column per to-do status and the Done count. The to-do columns include In progress. The All sections line gives the totals. Click a section to filter Cases. Click it again to show every section.
Cases
Cases lists every case of the source suite, one row per case. The worker filters and sorts the list. Every filter covers the whole suite.
| Column | Content |
|---|---|
Case |
The case id, linked to TestRail. |
Section |
The source section path. |
Title |
The adapted title, as written or as the writer will write it. Hover for the original title. |
Status |
The status pill and one reason line: the hold reason, the error or the first BLOCKER finding. |
Verdict |
The gate verdict. Empty for a case without an adaptation. |
Change |
|
Score |
The gate score. |
Updated |
The time of the last adaptation. |
Filters:
-
The Status pills. Each pill shows its count under the other filters. A click adds or removes the status.
-
The section list and the search box. The search matches a case id prefix or a title substring.
-
This job only keeps the rows of the job this page follows.
-
More filters opens Verdict, Change and Deferred. These filters skip cases without an adaptation.
-
Clear filters resets all filters.
Case, Section, Status, Verdict, Score and Updated sort on a click. A second click reverses the order. A page holds 50, 100 or 200 rows. The browser keeps the filters, the sort and the page.
Click a row to open the case detail. A Not adapted case has no detail. Its id links to TestRail. The links use the TestRail URL set on the worker of your session.
Export CSV downloads every row of the current filter. The columns are id, section, title, status, reason, verdict, change, project 15 state, hold reason, updated and stored status.
Bulk Actions
A checkbox selects a row. The header checkbox selects the page. Select all N cases matching the filter selects every page. A selection survives a page change and a filter change.
The bulk bar shows only the actions allowed for the selected statuses. Each button counts the cases it takes. Every action reads the selection again first.
| Action | Statuses | Role | Rules |
|---|---|---|---|
Adapt / Re-adapt |
Not adapted, Refresh pending, Error |
editor |
One job, at most 1000 cases. A case with a written text in TestRail is left out. A case with a hold reason is left out too. |
Re-adapt with model |
Waiting for model |
editor |
The owner’s decision. The dialog states the model calls. |
Write to TestRail |
Ready to write |
admin |
10 cases per request, one request after another. Stops at the first failed request. Never writes a held case. |
Hold… |
Any to-do status except Not adapted and Held |
admin |
A reason is required. |
Release hold |
Held, or any case with a hold reason |
admin |
Clears the hold. |
Before a job starts, a dialog states the case count, the engines and the upper bound of model calls. The upper bound is the case count multiplied by one more than Max repairs. The dialog also counts the cases with a hand edit. A new adaptation replaces that edit.
Adapt / Re-adapt, Re-adapt with model and Write to TestRail wait while an adaptation job runs. Write to TestRail also waits while in-place writes are off. The button tooltip says why. Hold… and Release hold work during a job.
After a bulk write, the bar lists each case with its outcome. When a request fails, the toast names the number of cases sent before it.
Reviewing a Case
The detail header shows the status pill and its reason line. Small text gives the verdict, the change, the engines and the tokens.
The detail shows the source case as TestRail has it now (Source
(TestRail)), the adaptation as JSON (Adapted (editable JSON), read-only for
viewers), Findings (N) with severity, id, message and recommendation, a line
Dry conversion: <n> steps, <m> fallback (<p>%), step types: …, and the number of
rule changes. Closing it with an unsaved edit asks Discard the edited adaptation?.
The JSON has this shape:
| Key | Holds |
|---|---|
|
The adapted title. |
|
A list of lines. A source line moved here counts as covered when its text (or its rule-rewritten text) appears in one of them. |
|
One entry per source scenario: |
|
Legacy elements with no Angular equivalent. An entry blocks the case unless every label it quotes is a label of the Angular portal and an adapted step still names it (see The Gate), and then it is only a MAJOR. |
|
Free text. |
Re-gate (write roles) stores the edit and runs the gate again without a model: the
row becomes ready, automation_blocked, needs_attention or error, and a toast
shows the status and the score. The edit must be valid JSON in the shape above, checked the way a model’s
answer is: a source_scenario outside the source’s scenarios, source_lines that are
not a list of line numbers, a step without text, or preconditions / unmappable /
notes that are not lists is refused with the reason, and nothing is stored.
Re-gate marks the case as edited by hand. A job that adapts the case again clears the mark. A new adaptation replaces your edit. Re-run with engine asks you to confirm before it replaces an edited case.
Re-run with engine (write roles) adapts the case again as a one-case job, with the engine picked beside the button as its first pass; its repairs use the Repair engine and Max repairs the worker has from Settings. The stored row changes only when the re-run finishes, and it is refused while an adaptation job holds the slot. A case the rules adapt completely never reaches a model, so its re-run applies the rules and the gate again, whatever engine is picked.
Hold and Release hold sit in the case detail for an administrator. Hold needs a reason and applies to cases still to do. Release hold shows for any held case. The writer refuses a held case.
A Written case stays Written after a later re-adaptation. When the new text is clean, the detail says a newer adaptation waits. In the Project 15 (in place) panel of the detail, press Preview in project 15, then Write to TestRail. When the re-adaptation fails, the detail shows the reason line and a note. A plain job would adapt the text now in TestRail. That text is the written text. The detail hides Re-run with engine for a Written case, and the server refuses it. An administrator presses Re-adapt from the original instead. The job adapts the saved original text. The button waits while a job runs. A restored case can still use Re-run with engine.
An administrator can name at most 200 cases in one Re-adapt from the original request. The bulk bar has no such action.
Engines and Cost
| Engine | Notes |
|---|---|
|
Claude Sonnet through the Claude CLI. Default First pass. |
|
Claude Opus through the Claude CLI. Default Repair. |
|
A local model over the Ollama API at Ollama URL. The engine
selects offer |
Only cases the rules cannot finish spend tokens.
The case detail and the job line show input and output tokens followed by
billed <n>: input, output and prompt-cache tokens (cache creation and cache reads)
together. The Claude engines report cache tokens and Ollama does not, so compare
engines by the billed figure. A case reused from the cache adds nothing to the job: it
counts under cache hits, while its row still shows what it cost when it was adapted.
Pilot report (every role) opens a table with one line per first-pass engine —
rules for cases no model touched:
| Column | Meaning |
|---|---|
Cases |
Rows whose current adaptation used that first-pass engine. |
Ready 1st pass / Ready after repair |
|
Automation blocked |
|
Needs attention / Errors |
Rows in those statuses. |
Mean tokens in / out, Mean billed, Mean s |
Per row. |
Project tokens in / out, Project billed, Hours |
The means multiplied by the number of cases in the source project. |
With This job only ticked, the report covers the rows last adapted by the job this page follows; otherwise every row. It reads the rows as they are now, hand edits included. When TestRail cannot be reached, the extrapolation uses the number of stored rows instead of the source project’s case count.
|
Comparing engines on the same cases
A second job over the same unchanged cases reuses the stored rows and calls no model, so it measures nothing new. Re-run with engine ignores the cache, but it does not add a row: each case has exactly one, and a re-run replaces it — engine, tokens, repairs, time, findings and status — whatever its outcome. The Pilot report then counts the case under the new engine in place of the old one; it never shows two engines for the same case. Read and note the first engine’s Pilot report before re-running its cases with another engine. A re-run also gives a stored |
Settings
Settings → Adaptation (legacy cases) (admin only, see Settings):
| Field | Default | Environment variable | Meaning |
|---|---|---|---|
Target TestRail project name |
|
|
Retired. The Adaptation tab does not use it. |
Source project id |
|
|
The project cases are read from, and the project the in-place write changes. |
First-pass engine |
|
|
Default of First pass: |
Repair engine |
|
|
Default of Repair, and the repair engine of Re-run with engine. |
Max repairs |
|
|
Default of Max repairs, and the value Re-run with engine uses. A value outside
|
Workers |
|
|
How many cases one job adapts at the same time — not the worker stacks. A re-run
uses one. A value outside |
Ollama URL |
|
Where |
|
Fallback warn |
|
|
The share of catch-all |
First-pass thinking |
|
|
Whether a Claude first pass uses extended thinking. Off, a first pass cost about a seventh of the output tokens and time in the measurement it was chosen on, and still answered in the expected form. |
Repair thinking |
|
|
Whether a Claude repair uses extended thinking. When it differs from First-pass thinking and both passes use the same model, the repair gets a Claude process of its own instead of continuing in the first pass’s. |
Short system prompt |
|
|
On, every Claude call gets a one-line system prompt (rewrite the case, answer with the JSON object only) in place of Claude Code’s own, which roughly halves a first pass’s output again. Off keeps Claude Code’s system prompt. |
Whatever these three say, a Claude call never gets tools and never reads Claude Code’s auto memory: the case is answered from its prompt alone. Ollama engines ignore all three.
|
When a change takes effect
Saving writes the environment profile; it changes no worker by itself. The profile’s values are written when a run of that env is dispatched, into the settings file the pooled workers share: a dispatch to any worker rewrites it for all of them, each worker picks the values up the next time it reloads its settings, and a run of another env replaces them the same way. The Adaptation tab talks to the worker that serves your session (see
Which worker answers), so the values that worker uses are
the ones the tab shows: the source project in the header, and the First pass, Repair and Max repairs defaults the job form
starts from — read once per page load, so reload the page to pick up new ones.
Workers, Ollama URL, Fallback warn and the three thinking and system prompt
settings are not shown in the tab; |
Who Can Do What
| Action | admin | editor | viewer |
|---|---|---|---|
Open the tab; read the header, the Status panel, the job slot, a job’s progress, Cases and the Pilot report; open a case; follow the TestRail links |
✓ |
✓ |
✓ |
Start, Stop and Refresh to current version; Clear stuck claim |
✓ |
✓ |
— |
Adapt / Re-adapt and Re-adapt with model in the bulk bar |
✓ |
✓ |
— |
In-place preview |
✓ |
✓ |
✓ |
In-place apply and restore; Write to TestRail; Hold… and Release hold; Re-adapt from the original |
✓ |
— |
— |
Edit the adapted JSON, Re-gate, Re-run with engine |
✓ |
✓ |
— |
Settings → Adaptation (legacy cases) |
✓ |
— |
— |
The write actions are enforced by the server as well; a viewer’s page simply does not show them. See Roles & Capabilities.
After Writing
Import the written cases through the TestRail modal. See Importing Tests. Run a small batch before you import everything. The gate converted each case with the LLM off. An import with the LLM on adds its enrichment. A green run is not proof. Audit the steps against the source case, as for any other import.
A deferred case is worth importing but not worth queueing yet. A batch skips it with
needs infra: usp_device. The skip lasts until the converter routes its USP or MQTT
device to the IoT emulator. See The Gate.
|
All four worker stacks must run the same image
Worker stacks 3, 25, 34 and 40 and the gateway must run the same image version. Only then is the Adaptation tab consistent. The adapter store is on the live worker only. On another stand the tab lists every source case as Not adapted. Select the live stand to see the adaptations. |