← All tests

PIN view + admin-set member PIN PROD

2026-05-12 on prod (get-coffee). Two features bundled. View existing PIN — owner/manager reveals an existing cashier or member POS PIN without resetting it, via new AES-GCM encrypted column populated on every create/reset, gated by new permission nix_cafe.team.view_pin. Admin set/reset/view member POS PIN — new admin-side flow on /cafe/team Members tab; the existing self-serve flow (POS lock screen + password 2FA) is unchanged. PBKDF2 hash stays authoritative for verification; pin_enc is reversible storage purely for display. Every reveal audit-logged.

7/7 prod checks PASS on get-coffee.nixtech.app via SSO as narongix (owner, has view_pin).
58/58 total prod tests green — no regressions from this push.
test-pin-view-prod.mjs7/7
test-phase1-prod.mjs11/11
test-phase2-sso-outdoor-prod.mjs6/6
test-phase2-cafe-multishop-prod.mjs6/6
test-m1-prod.mjs10/10
test-r7-prod.mjs14/14
test-r8-prod.mjs4/4

Run with NODE_TLS_REJECT_UNAUTHORIZED=0 to work around today's Windows schannel cert-chain issue against api.nixtech.app + cafe/api routes from Node's built-in fetch() — same environment issue that needed --strict-ssl=false on npm install and -c http.sslVerify=false on git push. Browser-driven Playwright tests (own TLS stack) never failed. Not a code regression.

What ships

backend (1 migration + 2 migrate.js entries):
  migrations/20260512100000_pin_enc_columns.ts
  migrate.js:
    20260512100000 — ADD COLUMN commerce.pin_identities.pin_enc + tenant_users.pos_pin_enc
    20260512110000 — INSERT nix_permissions row + grant to owner+manager
                     across all tenants (8/8 prod grants applied)

cafe (4 commits — 1 main + 3 follow-up fixes surfaced by Gate 2):
  7208b40  feat(cafe-team): view existing PIN + admin set/reset/view member POS PIN
           (main bundle: schema + DAOs + actions + UI on Cashiers tab + EditMemberModal)
  f2953c4  fix(cafe-team): EditMemberModal scrolls when tall
           (POS PIN section was pushing footer buttons below viewport
            on default 900px height — added max-h-[90vh] overflow-y-auto)
  8065bd8  fix(cafe-team): router.refresh after create cashier so the
           new row appears immediately (newly-created cashier wasn't
           visible until next page navigation)
  4af72e0  fix(cafe-team): wrap getPinForIdentity + getMemberPosPin in
           db.transaction to skip Hyperdrive cache (view-immediately-
           after-reset was decrypting the previous pin_enc — same
           pattern as listPinIdentities + telegram getters)

Lessons surfaced at Gate 2

(1) Modal sizing — when adding sections to an existing modal, check the
    total height fits a 900px viewport. EditMemberModal grew from ~3
    sections to 4 with POS PIN; the footer Save/Remove/Cancel buttons
    were getting pushed below the viewport on the standard test
    resolution. max-h-[90vh] + overflow-y-auto on the card fixes it.

(2) revalidatePath ≠ refresh — server-side revalidatePath marks /team
    stale for subsequent navigations but does NOT trigger the current
    client tree to refetch. The create-cashier flow needed an explicit
    router.refresh() to make the new row visible immediately.
    Pre-existing latent bug; surfaced because the new test exercises
    the row-just-after-create path more aggressively than before.

(3) Hyperdrive caching strikes again — getPinForIdentity +
    getMemberPosPin were NOT wrapped in db.transaction, so a view
    immediately after a reset would decrypt the previous pin_enc.
    Symptom on prod: reset returns 5535 then view returns 5411 (the
    pre-reset value). Fix: wrap both DAOs in db.transaction — same
    pattern documented in project_hyperdrive_cache_stale_reads.md.
    Local DAO probe missed this because docker postgres has no
    Hyperdrive layer; only surfaces on prod cf-workers + cf-pages.