Second review round on the unpushed v3.20.0-v3.22.0 batch:
- install parameters: a zero memory quota (nv_quota: 0 - the vendor
reference form's C7 02 0000 C8 02 0000) is composed instead of being
dropped as "unset" (only None/blank leaves the TLV out); a truncated EF
length in stk_params is refused instead of read as an empty EF.
- test suites: a suite whose member script is gone is repairable - the
update endpoint accepts pruning a dangling reference (a removal of a
live script stays refused; the PWA's row Delete prunes it), and
reconcile_scripts_to_suites() lists an orphan even when its suite_id
write is refused by the validator, so a stored script that no longer
validates stays visible in the editor and fails at run start with the
message instead of disappearing.
- engine/PWA: the proactive action refuses a check.data (its result data
is the drained-command list - the check was silently ignored); the
vestigial _testScriptDraft is gone; berLenStr emits the 2/3-byte BER
long form (a >255-byte C9 composes correctly).
- docs: api.md (the stop cleanup answers ok, never an automatic cancel;
the server persists the counters - the PWA does not write them back; the
suite bundle import always merges the scripts; the quota-0 note) and
test-scripts.md (the event action covers all modelled events; the
direct-create path needs a suite_id; the format history); help EN/RU
counter wording.
- tests: the zero quota / truncated EF, the prune path, the invalid-script
listing, the proactive data-check refusal, berLenStr and the >255-byte
C9. Tests: 839 frontend + 804 Python; sw cache simple-v403.
The suite/script ownership invariant could break silently: a suites import
with mode "replace" wiped the suites while their scripts stayed behind
(suite_id pointing at gone suites - invisible in the UI), a scripts import
with mode "replace" left every suite's member list dangling, and a script
whose suite_id disagreed with its listing suite stayed inconsistent.
- server: reconcile_scripts_to_suites() repairs the invariant - a script a
suite lists adopts that suite (the member list is authoritative), a script
no suite lists joins "Imported scripts"; migrate_scripts_to_suites()
delegates to it (the startup migration is the same pass) and the suites
import runs it after loading, so a "replace" adopts the wiped suites'
scripts instead of losing them.
- the scripts import refuses mode "replace" while any suite still lists
scripts (400 naming the dangling references; merge is the normal path).
- nits from the review: a legacy numeric `event` is no longer parsed as hex
in the step summary/error check (0x0A displayed as 0x10), and an integral
float memory quota (a JSON client's 200.0) is accepted.
- tests: the reconciliation (missing suite, listed-but-mismatched,
idempotent), the scripts-replace guard (refused/allowed), the
suites-replace adoption, the float quota, the numeric-event summary.
Tests: 838 frontend + 800 Python; sw cache simple-v402.
A suite is the root object: every script belongs to exactly one suite, the
suite holds the ordered member list by reference - the optional setup first,
the members, the optional teardown last - and runs them in one card session,
without resets and without resume.
- test_suites.py: the suite store (test_suites.json, --test-suites), validated
and atomic like the script store: roles normalised (setup first, teardown
last, at most one of each, a script appears once per suite), per-member
on_fail stop/continue and the suite-level require_adm.
- test_scripts.py schema v2: suite_id/require_adm; list_by_suite()/orphans();
the import reports the id_map a bundle import remaps with. v3.21.0 scripts
without a suite are attached once to an auto-created "Imported scripts"
suite (migrate_scripts_to_suites, called from __main__).
- API: /api/test/suites (+ update|delete|import - delete refused while
non-empty; the bundle import imports the scripts first, remaps the suite
references and skips unresolvable ones); the scripts endpoints gain
suite_id/require_adm plus move|copy (delete detaches from the suite first;
a suite update refuses a membership change).
- runner: /api/test/run {suite_id} resolves the members and the preset, runs
the ADM gate, then setup -> members (per-member on_fail) -> teardown. The
teardown always runs on a stop or a failure (skipped only when the card is
gone; it clears the stop flag for its duration); a card reset stops the
suite, skips the remaining members and is flagged. Members' live steps land
in the shared snapshot, their records (status/steps/note/log excerpt/
counters) in _TEST_RUN['suite']; the summary carries the counts, wall time,
preset, the SCP80 counters before/after per keyset and the session flags.
_test_log buffers the semantic lines (bounded) for the report's per-member
excerpts.
- ADM gate (_ensure_adm): a suite/script require_adm verifies the matched
preset's key once per card session (the /api/verify-adm badge sets the same
latch); a failure refuses the start, stamps the session (no automatic
retry) and needs a manual verify.
- PWA: the Test script pill becomes four views - the suite list (default),
the suite editor (the script list: role badges, on_fail, up/down, per-script
Run/Edit/Move/Copy/Delete, Add script with the templates), the script editor
(the step list; a single run shows its result under the steps and is not
preserved) and the suite report (per-member blocks with the step table,
notes and run log; summary; export JSON/Markdown). Suite bundles export/
import; the legacy script export imports into "Imported scripts". EN/RU.
- tests: the suite store/migration, the runner orchestration (order, on_fail,
setup failure, stop, card reset, teardown, per-member logs) and the ADM gate
(Python); the suite HTTP paths; the PWA report/Markdown/wiring (Node).
Tests: 833 frontend + 794 Python; sw cache simple-v397.