Two plans land: - 2026-06-07-step-up-implementation.md (v1): original 6-phase plan written before the Path X session. Captured the four optimizations (opt 1: reuse AliroGcm; opt 2: structural CBOR only; opt 3: stream-encrypt during ENVELOPE emit; opt 4: cache IssuerAuth verify at personalization) and the spec citations for each. Superseded by v2 but retained for the planning-history record. - 2026-06-11-step-up-implementation-v2.md: revised after Path X resolved the three crypto bugs. Splits into Milestone 1 (~400 LOC, 4-6 hours, gets DOOR OPERATION SUCCEEDED on stock X-CUBE-ALIRO with decrypt-and-discard stubs) and Milestone 2 (~1500 LOC, 1-2 weeks, real Access Document retrieval via mdoc DeviceResponse + CBOR + COSE). The four optimizations are preserved into M2 where they matter; M1 ships without them since the demo doesn't need real document payload yet. Inline TODO comments mark the four optimization sites in source: - CredentialStore.finalizeAccessDocument -- opt 4a (verify-flag in a JCSystem.beginTransaction block) + opt 4b (Credential Issuer key rotation assumption documented). - StepUpApplet class javadoc -- all four optimizations laid out so the implementer (subagent or human) lands them as they build the body. (The AliroApplet INS_EXCHANGE comment that references the plans is in the previous fix commit; it'll move/retire when M1 lands.) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
22 KiB
Step-Up Phase Implementation Plan (v2 — supersedes 2026-06-07)
For Claude: REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
Goal: Implement Aliro v1.0 §8.4 Step-Up Phase end-to-end against stock X-CUBE-ALIRO firmware so the j3r452 returns DOOR OPERATION SUCCEEDED on a real NFC reader, then incrementally extend to full Access Document retrieval over the same code path.
Architecture: Step-Up handlers live on StepUpApplet (A000000909ACCE5502). The StepUpSK 32-byte session secret is already derived during AUTH1 by AliroApplet and parked in the shared SessionContext (armStepUp()); StepUpApplet reads it via copyStepUpSK() after the vendor firmware issues the spec-mandated step-up AID SELECT. The same AliroGcm instance gets re-keyed for the Step-Up phase (one shared object across both phases — saves ~368 B transient). CBOR parsing is structural-only (major-type + length boundaries). IssuerAuth COSE_Sign1 verification is cached at personalization. The plan ships in two demoable milestones: Milestone 1 = minimum to make X-CUBE-ALIRO say "DOOR SUCCEEDED" (no real document retrieval, just spec-shaped acks), Milestone 2 = real Access Document retrieval.
Tech Stack: Java Card 3.0.5 (target J3R452), userland AES-256-GCM with 4-bit GHASH (existing), native SHA-256 + ECDSA-P256 verify, structural CBOR codec (RFC 8949 §4.2.1 deterministic encoding for emit), COSE_Sign1 (RFC 9052), mdoc DeviceRequest/DeviceResponse shapes (ISO/IEC 18013-5 §8 with Aliro-remapped integer keys per spec Tables 8-21/8-22).
What today's Path X session locked in (no longer in scope to investigate):
-
Crypto baseline is interop-correct. Three bugs found and fixed:
salt_volatilehad an extra 32-bytex(credential_long_term_pub)(commit pending; line 720-723 ofAliroApplet.buildSaltVolatile)Kdhderived via HKDF instead of X9.63 KDF per §8.3.1.4 (commit pending;AliroCrypto.deriveKdh)salt_volatileused AUTH0'scommand_parametersinstead of AUTH1's (commit pending; samebuildSaltVolatile)
Vendor library's
ACWG_processAUTH1ResponsePayloadnow returnsACWG_OKon a freshly personalized j3r452 — confirmed in/tmp/nucleo-three-fixes.logagainst UID04565E4A0B2190. -
X-CUBE-ALIRO firmware DOES honor §10.2 step-up AID SELECT. Once
processAUTH1ResponsePayloadsucceeds andsignaling_bitmapbit 2 is set, the firmware correctly issues00 A4 04 00 09 A0 00 00 09 09 AC CE 55 02 00. The "vendor SHALL violation" I was planning to file with ST was actually downstream of the GCM decrypt failure — one less vendor bug to file. Our existingStepUpApplet.sendStepUpFcialready responds 9000 with a minimal FCI. -
Vendor firmware sends TWO post-AUTH1 commands on Step-Up:
00 C3 00 00 <len> ENVELOPE— encrypted mdoc DeviceRequest (Table 8-21 keys)80 C9 00 00 <len> EXCHANGE— encrypted Reader Status sub-event (§8.3.3.5.x)
Both arrive after the SELECT to
ACCE5502. Both need to land onStepUpApplet(notAliroApplet). ENVELOPE's CLA is0x00— unusual for a proprietary command but matches the spec table. EXCHANGE's CLA is0x80. -
StepUpSK derivation is wired.
AliroApplet.processAuth1extracts bytes 64..96 ofderived_keys_volatileand callsSessionContext.armStepUp(stepUpSK, 0). The consumer side (StepUpApplet) just hasn't been written yet.
Out of scope for this plan:
- BLE Step-Up (separate transport)
- mdoc field-level interpretation beyond locating IssuerAuth + extracting nameSpaces (we transport bytes; the reader interprets)
- Revocation Document handling (same shape as Access Document; just a different blob — covered implicitly by the same emit path)
Reference material:
- Aliro 1.0 spec §8.3.1.5 (HKDF), §8.3.1.13 (key material), §8.4 (Step-up phase), §10.2 (Transaction), Tables 8-14 / 8-21 / 8-22
- RFC 8949 (CBOR core deterministic encoding)
- RFC 9052 (COSE_Sign1)
- ISO/IEC 18013-5 §8 (mdoc DeviceRequest/DeviceResponse; Aliro Tables 8-21/8-22 remap the integer keys)
- Existing code:
AliroApplet.java,AliroGcm.java,AliroCrypto.java,SessionContext.java,StepUpApplet.java(scaffold with TODOs that name each optimization) - Today's interop trace:
/tmp/nucleo-three-fixes.log(shows the exact ENVELOPE + EXCHANGE bytes the firmware sends) - Original Step-Up plan:
docs/plans/2026-06-07-step-up-implementation.md(this plan supersedes it; reuses the four optimization names + spec citations)
Milestone 1 — "DOOR OPERATION SUCCEEDED" on X-CUBE-ALIRO (~400 LOC, 4-6 hours)
Result: The Nucleo prints DOOR OPERATION SUCCEEDED against our card. No real Access Document is served — we acknowledge the ENVELOPE and EXCHANGE with spec-shape empty responses. Enough for fundraising demo, leaves the real protocol body for Milestone 2.
Phase M1A — Wire StepUpSKDevice/StepUpSKReader into StepUpApplet
Per §8.4.3, the Step-Up phase uses fresh GCM session keys derived from StepUpSK. Spec text:
Compute
StepUpSKDevice = HKDF(IKM=StepUpSK, salt=∅, info="SKDevice", L=32)ComputeStepUpSKReader = HKDF(IKM=StepUpSK, salt=∅, info="SKReader", L=32)
The HKDF helpers already exist on AliroCrypto (Extract/Expand are public-package-visible). The info strings are 8 ASCII bytes each.
Task M1A.1: Add a deriveStepUpSessionKeys helper to AliroCrypto
Files:
- Modify:
applet/src/main/java/com/dangerousthings/aliro/AliroCrypto.java - Modify:
applet/src/test/java/com/dangerousthings/aliro/AliroCryptoTest.java
Step 1: Write a failing test for the device/reader derivations.
@Test
void deriveStepUpSessionKeysMatchesRfc5869WithSpecInfoStrings() throws Exception {
AliroCrypto crypto = new AliroCrypto();
byte[] stepUpSK = new byte[32];
for (byte i = 0; i < 32; i++) stepUpSK[i] = (byte) (0x10 + i);
byte[] skDevice = new byte[32];
byte[] skReader = new byte[32];
crypto.deriveStepUpSessionKeys(stepUpSK, (short) 0, skDevice, (short) 0, skReader, (short) 0);
// Reference: RFC 5869 HKDF-SHA-256, salt=∅, info=ASCII("SKDevice")/("SKReader")
javax.crypto.Mac mac = javax.crypto.Mac.getInstance("HmacSHA256");
mac.init(new javax.crypto.spec.SecretKeySpec(new byte[32], "HmacSHA256")); // empty salt
byte[] prk = mac.doFinal(stepUpSK);
byte[] expectedDevice = hkdfExpandOnce(prk, "SKDevice".getBytes("ASCII"), 32);
byte[] expectedReader = hkdfExpandOnce(prk, "SKReader".getBytes("ASCII"), 32);
assertArrayEquals(expectedDevice, skDevice);
assertArrayEquals(expectedReader, skReader);
}
(hkdfExpandOnce is a tiny BouncyCastle-free helper at the bottom of the test class.)
Step 2: Run, expect FAIL with "method not defined."
./scripts/dt-mvn.sh test -Dtest=AliroCryptoTest#deriveStepUpSessionKeysMatchesRfc5869WithSpecInfoStrings
Step 3: Implement AliroCrypto.deriveStepUpSessionKeys.
Uses existing hkdfExtract + hkdfExpand. Info strings are 8-byte static byte[] fields. The "empty salt" passes a zero-length range to hkdfExtract, which already falls back to emptySaltZeros.
private static final byte[] STEP_UP_INFO_SK_DEVICE = {
'S','K','D','e','v','i','c','e'
};
private static final byte[] STEP_UP_INFO_SK_READER = {
'S','K','R','e','a','d','e','r'
};
short deriveStepUpSessionKeys(
byte[] stepUpSK, short stepUpSKOff,
byte[] skDeviceOut, short skDeviceOff,
byte[] skReaderOut, short skReaderOff) {
// PRK = HKDF-Extract(salt=∅, IKM=StepUpSK)
hkdfExtract(
emptySaltZeros, (short) 0, (short) emptySaltZeros.length,
stepUpSK, stepUpSKOff, HASH_LEN,
hkdfPrevT, (short) 0);
// SKDevice = HKDF-Expand(PRK, info="SKDevice", L=32)
hkdfExpand(
hkdfPrevT, (short) 0, HASH_LEN,
STEP_UP_INFO_SK_DEVICE, (short) 0, (short) STEP_UP_INFO_SK_DEVICE.length,
HASH_LEN,
skDeviceOut, skDeviceOff);
// SKReader = HKDF-Expand(PRK, info="SKReader", L=32)
hkdfExpand(
hkdfPrevT, (short) 0, HASH_LEN,
STEP_UP_INFO_SK_READER, (short) 0, (short) STEP_UP_INFO_SK_READER.length,
HASH_LEN,
skReaderOut, skReaderOff);
return HASH_LEN;
}
Step 4: Run test, expect PASS.
Step 5: Run full suite, expect 81/81 PASS (one new test).
Step 6: Commit.
git add applet/src/main/java/com/dangerousthings/aliro/AliroCrypto.java \
applet/src/test/java/com/dangerousthings/aliro/AliroCryptoTest.java
git commit -m "feat: AliroCrypto.deriveStepUpSessionKeys per §8.4.3"
Task M1A.2: Have StepUpApplet derive its session keys on demand
Files:
- Modify:
applet/src/main/java/com/dangerousthings/aliro/StepUpApplet.java - Modify:
applet/src/main/java/com/dangerousthings/aliro/SessionContext.java(exposearmStepUpAlreadyArmed()if needed; currentisStepUpArmed()works) - Test:
applet/src/test/java/com/dangerousthings/aliro/StepUpAppletTest.java
Step 1: Write failing test — StepUpApplet, freshly selected after an armed SessionContext, can derive its SKDevice/SKReader.
Re-uses existing test plumbing in AliroAppletAuth1Test.auth1SuccessArmsStepUpContextWithExpectedStepUpSK.
Step 2-5: Add private fields stepUpSKDevice[32], stepUpSKReader[32] to StepUpApplet (transient, CLEAR_ON_DESELECT). On select(): if SessionContext.isStepUpArmed(), copy StepUpSK via copyStepUpSK() then call AliroCrypto.deriveStepUpSessionKeys(...). Re-use the singleton AliroCrypto already used by AliroApplet (move it to a package-private static getter on a CryptoSingletons class so both applets share the same instance — opt 1 prelude).
Step 6: Commit.
git commit -m "feat: StepUpApplet derives SKDevice/SKReader on select when StepUpSK armed"
Phase M1B — Decrypt-and-discard EXCHANGE (INS 0xC9) on StepUpApplet
X-CUBE-ALIRO sends a "Reader Status sub-event" via EXCHANGE after the Step-Up SELECT. From today's trace: 20 bytes of payload (4-byte plaintext after AES-GCM strip-tag-16). Per §8.3.3.5 the response is a Reader Status response sub-event — for our purposes an empty acknowledgment works.
Task M1B.1: Decrypt the inbound EXCHANGE payload with StepUpSKReader
Files:
- Modify:
applet/src/main/java/com/dangerousthings/aliro/StepUpApplet.java - Test:
applet/src/test/java/com/dangerousthings/aliro/StepUpAppletTest.java
Step 1: Write failing test that drives a fake X-CUBE-ALIRO-shape EXCHANGE.
Set up: AUTH0+AUTH1 via existing test harness. Use ReaderSide to derive StepUpSKReader, encrypt a 4-byte payload with expedited_reader_counter=1 per §8.3.1.8, frame as 80 C9 00 00 <len> <ct||tag> 00, send to StepUpApplet via JCardSim. Assert SW=9000.
Step 2-5: Implement INS_EXCHANGE handler that:
- Reads encrypted_payload via
apdu.setIncomingAndReceive() - Builds IV =
0x0000000000000000 || expedited_reader_counter(4B BE) per §8.3.1.8 - Calls
gcm.decrypt(StepUpSKReader, IV, ct, tag, plaintext_scratch) - Discards plaintext (we don't care what the Reader Status said — log/ack only)
- Increments
expedited_reader_counter - Returns
apdu.setOutgoingAndSend((short) 0, (short) 0)— 9000 with no payload
But wait: spec says Reader needs the EXCHANGE response in same secure channel. Vendor may or may not accept empty. Test first whether 9000-empty is enough; if not, encrypt-and-return an empty CBOR map (0xA0). Note apdu.setOutgoingAndSend returns after sending — extend with a one-shot encrypt that produces 17 bytes (1B 0xA0 + 16B tag) if needed.
Step 6: Commit.
git commit -m "feat: StepUpApplet INS_EXCHANGE decrypt/discard/ack (minimal Reader Status)"
Phase M1C — Decrypt-and-discard ENVELOPE (INS 0xC3) on StepUpApplet
X-CUBE-ALIRO sends the mdoc DeviceRequest via ENVELOPE with CLA=0x00, INS=0xC3. From today's trace: 75 bytes of payload (the encrypted DeviceRequest CBOR with expedited_device_counter-derived IV).
Task M1C.1: Accept CLA=0x00 on StepUpApplet.process
Current code rejects anything not CLA_PROPRIETARY (0x80). ENVELOPE is 0x00 C3 ... per spec Table 10-? (see app_aliro.c ENVELOPE_POS for the byte values we just observed).
Step 1-5: Add INS dispatch handling for CLA=0x00 INS=0xC3. Same decrypt-and-discard pattern as EXCHANGE but with the device-side key + the device counter starting from 1 + §8.3.1.6 IV format (0x0000000000000001 || expedited_device_counter). Return empty-encrypted DeviceResponse (0xA0 CBOR empty map encrypted with StepUpSKDevice).
Step 6: Commit.
git commit -m "feat: StepUpApplet INS_ENVELOPE (CLA=00) decrypt/discard, return empty CBOR map ack"
Phase M1D — Retire the AliroApplet.INS_EXCHANGE stub
Now that StepUpApplet handles EXCHANGE on its own AID, the bitmap=0x0005 + AliroApplet EXCHANGE stub from June 7 is obsolete. The vendor firmware will route post-AUTH1 commands to ACCE5502 correctly.
Task M1D.1: Remove the stub
Files:
- Modify:
applet/src/main/java/com/dangerousthings/aliro/AliroApplet.java(delete thecase INS_EXCHANGE:block) - Modify:
applet/src/test/java/com/dangerousthings/aliro/AliroAppletTest.java(assertAliroAppletnow returnsSW_INS_NOT_SUPPORTEDfor INS 0xC9)
Step 1-5: TDD as usual. Spec-conformant readers route EXCHANGE to ACCE5502 after step-up SELECT; the stub on AliroApplet was vendor-firmware-bug-specific compat and is now strictly wrong (would let stale expedited_device_counter confuse the vendor).
Step 6: Commit.
git commit -m "refactor: retire AliroApplet INS_EXCHANGE stub now that StepUpApplet handles it"
Phase M1E — End-to-end Nucleo retest
Task M1E.1: Rebuild, reinstall, retest on real hardware
- Rebuild j3r452 CAP:
./scripts/dt-mvn.sh -Pj3r452 clean package -DskipTests - Verify the card on PC/SC:
aliro-bench-test --trust-dir ~/aliro-trust(AUTH1=9000, ~3.2s) - Move to Nucleo + capture via
python3 -c "import serial,time; ..."(pyserial — reliable; bashcatis flaky after USB re-enumeration) - Expected verdict:
NFCA Passive ISO-DEP device found. UID: 04565E4A0B2190
SELECT (ACCE5501) → 9000
AUTH0 → 9000
AUTH1 → 9000
SELECT STEPUP (ACCE5502) → 9000
ENVELOPE (00 C3 ...) → 9000 + encrypted empty CBOR map
EXCHANGE (80 C9 ...) → 9000 + (empty or encrypted-empty)
DOOR OPERATION SUCCEEDED ← THE MILESTONE
- If different failure mode: capture full UART, identify the next vendor error class (
retval=ACWG_Error_*), iterate.
Task M1E.2: Update memory + commit milestone
- Update
nucleo_nfc10a1_bringup_state.mdwith the Milestone 1 success and note: real Access Document retrieval is deferred to Milestone 2. - Commit any docs changes.
git commit -m "feat: Step-Up Milestone 1 - DOOR OPERATION SUCCEEDED on X-CUBE-ALIRO"
Milestone 2 — Real Access Document retrieval (~1500 LOC, 1-2 weeks)
Result: Spec-conformant Step-Up phase. The Reader receives our actual provisioned Access Document via the mdoc DeviceResponse over ENVELOPE/GET RESPONSE chaining. IssuerAuth verification is cached at personalization time so per-transaction latency stays at ~3.2 s AUTH1 + ~500ms-1s Step-Up. Bitmap goes back to fully honest about capability.
Phase M2A — Foundation: cache IssuerAuth verification at personalization (opt 4a + 4b)
(Tasks A1-A3 from the original 2026-06-07 plan, mostly unchanged.)
- Task M2A.1: Add
accessDocumentVerifiedpersistent boolean toCredentialStore+ getter (~30 lines + test) - Task M2A.2: Add
CoseVerifier.verifyCoseSign1(RFC 9052, ECDSA-P256) (~150 lines + test vectors) - Task M2A.3: Wire IssuerAuth verification into
PersonalizationApplet.finalizeAccessDocumentinsideJCSystem.beginTransaction()(~50 lines + tests for valid/tampered)
Phase M2B — Structural CBOR codec (opt 2)
(Tasks B1-B4 from the original plan, unchanged.)
- Task M2B.1:
StructuralCbor.decodeHeader— RFC 8949 §3 major-type + argument decoder (~80 lines + 8 tests) - Task M2B.2:
StructuralCbor.elementSpan— recursive span walker (~50 lines + 5 tests) - Task M2B.3:
StructuralCbor.locateIssuerAuth— walk mdoc IssuerSigned wrapper (~30 lines + test) - Task M2B.4: Canonical CBOR encoder for uint/bstr/tstr only (the primitives we emit) (~50 lines + tests)
Phase M2C — GCM session reuse for Step-Up (opt 1)
- Task M2C.1: Add
AliroGcm.setKeyAndIvrekey method (~30 lines) - Task M2C.2:
StepUpSessionIV builder for both directions (~50 lines)
(Note: deriving keys + decrypting the inbound EXCHANGE and ENVELOPE payloads is already done at Milestone 1; M2C just consolidates the GCM instance to share with AliroApplet.)
Phase M2D — Real ENVELOPE handler with DeviceResponse + chaining (opt 3)
- Task M2D.1: mdoc DeviceRequest structural parse (don't interpret fields, just confirm shape + extract docRequest namespace IDs) (~80 lines + tests)
- Task M2D.2: Build DeviceResponse with cached-verified Access Document bytes (~120 lines + tests)
- Top-level CBOR map per spec Table 8-22
- Embed cached
accessDocumentbytes verbatim (already CBOR-encoded by issuer)
- Task M2D.3: Stream-encrypt during ENVELOPE emit (opt 3) (~80 lines + tests)
- Task M2D.4: GET RESPONSE chaining when DeviceResponse exceeds APDU size (~60 lines + tests)
Phase M2E — Real EXCHANGE Reader Status response
- Task M2E.1: Decrypt Reader Status sub-event request (~30 lines + tests)
- Task M2E.2: Build Reader Status response sub-event (status=OK, no payload) per §8.3.3.5 Table 8-20 (~50 lines)
Phase M2F — Bitmap honesty + cleanup
- Task M2F.1: Switch bitmap from hardcoded 0x0005 back to a computed value reflecting actual capability (bit 0 set iff Access Document provisioned, bit 2 set per spec since we use split AID) (~5 lines + test)
- Task M2F.2: Delete the Milestone 1 stub responses, replace with real ones (~50 lines net)
- Task M2F.3: Strip diagnostic INSes (
INS_DIAG_*) — flipDIAGNOSTICS_ENABLED=false, run prod build, verify CAP shrinks ~3 KB (M2F.3 is a quick win — Phase 6 of the older RAM-diet/cleanup todo list)
Phase M2G — Cross-reader interop
- Task M2G.1: Run end-to-end on stock X-CUBE-ALIRO: DOOR SUCCEEDED + Access Document bytes round-tripped (verified by adding a print hook to the firmware that dumps the received DeviceResponse, comparing to what we provisioned)
- Task M2G.2: Run end-to-end on
aliro-bench-testPython reader (after extending the harness withaliro-bench-stepupCLI that drives ENVELOPE/GET RESPONSE) - Task M2G.3: File the WTX-cap bug + one or both remaining vendor issues with ST and/or CSA (see "vendor bug reports" backlog item below)
Phase M2H — Documentation + memory
- Update
MEMORY.mdwith Milestone 2 completion - Mark this plan complete; add
step_up_implementation_notes.mdmemory capturing the four optimizations' final form + spec citations - README pass on the Step-Up architecture decisions
Risk register (revised based on today's learnings)
| Risk | Likelihood | Impact | Mitigation |
|---|---|---|---|
| j3r452 transient RAM doesn't fit additional Step-Up buffers | Low (revised down) | High | Today's measurements show ~1.7 KB used / 3.1 KB available; Milestone 1 adds ~150 B for SK keys; opt 3 streaming caps Milestone 2 at +400 B max. Comfortable headroom. |
| CBOR canonical encoding subtleties | Low | Medium | We only emit uint/bstr/tstr — fully unambiguous per RFC 8949 §4.2.1. No floats, no special cases. |
| Vendor library rejects "empty" Reader Status response | Medium | Low | If 9000-empty doesn't work in Milestone 1, fall back to a 17-byte encrypted-empty-CBOR-map. Adds ~30 lines if needed. |
| Vendor library rejects "empty" DeviceResponse map | Medium | Low | Same fallback: encrypt 0xA0 (empty map) instead of returning empty payload. |
| ECDSA-P256 verify on j3r452 quirks | Low | High | We already use it for AUTH0 reader-sig verification (works). No new risk. |
| Spec ambiguity on Reader Status sub-event response shape | Medium | Medium | Try empty first; if vendor errors, capture the new error class + iterate. Same Path-X-style debugging we just demonstrated end-to-end. |
Acceptance criteria
Milestone 1:
- Stock X-CUBE-ALIRO firmware (with only the documented
RFAL_ISODEP_MAX_WTX_RETRYScap bump) printsDOOR OPERATION SUCCEEDEDagainst our card - PC/SC
aliro-bench-teststill passes (≥ regression check) - All existing 80+ unit tests pass + new StepUpApplet tests
- AliroApplet INS_EXCHANGE stub retired
- Transient RAM usage on j3r452 remains ≤ 2,200 B
CLEAR_ON_DESELECT(≥ 900 B headroom on the 3,120 B pool)
Milestone 2 (additional):
- Real Access Document bytes round-trip through ENVELOPE/GET RESPONSE
- All four optimizations land as documented (reuse AliroGcm, structural CBOR only, stream-encrypt, IssuerAuth verify cached at personalization)
signaling_bitmaphonestly reflects capability- Diagnostic INSes stripped for production CAP
- Code-reviewer subagent pass before merge
Vendor bug reports backlog (separate from this plan)
These were genuinely identified during the AUTH1 bring-up; they remain real even after today's progress:
-
WTX retry cap default of 20 is too aggressive. Card running spec-compliant userland GCM on the most permissive ISO 7816 secure element (j3r452) needs ~11 WTX cycles for AUTH1. ST's 20-cap is conservative; a 50-cap default would cover ecosystem. File with ST as middleware enhancement request.
-
(Possible — verify during Milestone 1) X-CUBE-ALIRO may still reject empty-payload Reader Status responses. If observed, file with ST as spec-clarification (RFC the Reader Status response shape with CSA).
Item 1 is real; item 2 we'll know during M1E.
Plan complete and saved to docs/plans/2026-06-11-step-up-implementation-v2.md. Two execution options:
1. Subagent-Driven (this session) — I dispatch a fresh subagent per task, review between tasks, fast iteration.
2. Parallel Session (separate) — Open new session with executing-plans, batch execution with checkpoints.
Which approach?