v3.29.0: the batch kinds (delete / reinstall) + the delete-package pre-step

The batch plan is a list of named steps ({name, apdu, delete}) now and
_ram_install_run generalized into _ram_plan_run; POST /api/ram-batch/start
dispatches on kind:
- install-cap (default; delete_package prepends a cascade DELETE on the
  package AID - a leftover package blocks INSTALL [for load]),
- reinstall-app (_ram_reinstall_plan: DELETE the applet instance + INSTALL
  [for install] from the loaded package - no .cap, no LOAD),
- delete (_ram_delete_plan: one GP DELETE, aid + target app/package ->
  P2=00/P2=80).

A delete step tolerates 'not found' (6A88/6A82 - the object is already
gone, the goal is met); any other delete failure stops the chain with the
delete's reason.  The policy serves all three kinds and the single
/api/ram-install-app, which now deletes the instance first (make
selectable stays one step); /api/ram-install takes delete_package too.

The status/summary carry the run's kind (the panel labels follow:
installed/deleted/reinstalled) and each attempt carries the last three
steps (steps + steps_total) for the panel's per-step detail.  The PWA's
three batch entries are grouped at the end of the operation selector (an
optgroup); ramIsBatchOp() is the one source for "per-card batch".

Docs: help EN/RU, api.md, the repo AGENTS.  Tests: the plan builders per
kind, the validations, the delete-package prepend, the not-found
tolerance, the kind dispatch/status/notice, the steps tail, the endpoint
steps, the PWA wiring.  925 frontend + 943 Python; sw cache simple-v447.
This commit is contained in:
2026-10-15 00:27:52 +03:00
parent 5ae8b1fdd1
commit cf7ee77a9e
11 changed files with 766 additions and 228 deletions
+12 -1
View File
@@ -33,7 +33,7 @@ const FNS = ['berLenStr', 'buildApdu', 'escHtml', 'esc', 'chainInit', 'chainRamB
'spTarOptionsHtml', 'spTarRender', 'spSpi1Seed', 'spTarKeysetGuard', 'cardsTarKeysetCell', 'cardsTarKeysetFromForm', 'cardsTarKeysetRefresh', 'cardsTarRowsRender', 'cardsTarRowHtml', 'cardsTarsFromTable', 'cardsFreeTarsFromForm', 'spPresetIdx', 'tarPresetIdx',
'cardsKeysetsFromForm', 'spKeysetSync', 'spKeysetApply',
'ramPresetIdx', 'spKeysetGuard', 'ramKeysetGuard',
'ramCardIdxAfterRemove', 'ramClearResults', 'ramHideProgress', 'ramOpChanged', 'ramRender', 'ramApplyCard', 'ramExecute', 'decodePrivileges', 'ramActionBtn', 'ramCapToolkitMode', 'ramOpProgressText',
'ramCardIdxAfterRemove', 'ramClearResults', 'ramHideProgress', 'ramIsBatchOp', 'ramOpChanged', 'ramRender', 'ramApplyCard', 'ramExecute', 'decodePrivileges', 'ramActionBtn', 'ramCapToolkitMode', 'ramOpProgressText',
'jcAidNorm', 'jcAidName', 'jcAidSuffix', 'jcAidHtml'];
let code = '';
for (const f of FNS) {
@@ -419,6 +419,17 @@ test('ramStepLine shows the PoR verdict and the remote status word', () => {
assert.ok(noPor.startsWith('\u2705') && noPor.includes('no PoR'), noPor);
});
test('ramStepLine marks a tolerated not-found delete step (v3.29.0)', () => {
globalThis.t = s => s;
// a delete step's 'not found' (6A88/6A82) is not a failure: the object was
// already gone, the chain continued - the line says so instead of showing
// the failure style
const line = ramStepLine({ name: 'DELETE (application)', por_sw: '6A88',
por_error: 'remote SW 6A88', delete_not_found: true }, 0);
assert.strictEqual(line,
'\u2796 Step 1: DELETE (application) \u2014 6A88 (not found)');
});
test('ramStepComponents maps a LOAD step to the CAP components it covers', () => {
// RemMobileID-like layout: sizes include each component's 3-byte header
const comps = [