README.md: Bank8:eTeller — Staff Teller Terminal App
Third-party Sentin8:App application. It uses the Sentin8 Flutter SDK (
sdk/flutter/sentin8) as a published package. For wire-level detail readDESIGN.md; for the business view readBUSINESS.md.
1. Summary
eTeller is the staff Flutter app of the Bank8 vertical: a branch teller terminal for customer search/onboarding and delegated customer operations — cash/cheque deposits, withdrawals, transfers, account management, statement viewing, posting-batch review, DLQ and manual-decision handling, and payment approval tasks. Customer-data operations ride a CIBA-delegated, time-boxed customer token; the teller never holds customer credentials.
2. Motivation
A teller does bank work on behalf of a customer standing at the counter —
but authorization systems speak the language of "who is allowed to do what
with whose account". The platform's answer is explicit, auditable delegation:
the teller picks the customer, the customer (or assisted link) approves the
operation remotely, and a short-lived delegated token whose act claim names
the teller executes the posting. eTeller is the counter surface for that
model, plus the operational cockpit (batches, DLQ, decisions, tasks) a branch
needs.
3. Architecture & High-Level Design
Components:
- Bootstrap —
fetchRuntimeContext()(SDK) from own origin; failure is fatal, never degraded. ETellerContext(app_context.dart) — state holder adding theInternalAccountResolver, the client-sidesessionLog, the active delegation setter, and thenavigatecallback.ETellerServices(services.dart) —ledger,paymentsclients plus a dedicatedstaffLedgerclient that is never delegation-stamped, and thedelegatedCustomerfactory that stampsX-BFF-Delegationon every customer-scoped call.InternalAccountResolver(internal_accounts.dart) — staff-scoped cache of internal account ids by nickname (cash, cheque clearing, liquidity, clearing, suspense) used as posting counterparties.- Delegation lifecycle (
eteller_shell.dart) — CIBA poll / assisted-link journeys via BFF/delegate/*, delegation state persisted insessionStorageand revalidated after page reload; live updates ride the BFF WebSocket. - Pages — teller journeys (see
DESIGN.md§4 for the full route map).
4. Key Traits and Data Structures
- Two scoped client registries:
staffLedger(staff token, internal plumbing only) vs the delegated customer registry (every request carriesX-BFF-Delegation: {delegationId}; the BFF attaches the CIBA-delegated token whosesubis the customer andactthe teller). InternalAccountResolver: nickname-keyed internal account lookup with an org-validated env override — internal accounts are counterparty plumbing invisible to holder tokens, so they resolve only via staff scope.sessionLog: a per-shift client-side log of teller operations that feeds the shift summary, the pending-cheque view, and receipts.- Delegation state shape:
eteller.delegation_id(+ companion keys) insessionStorage, revalidated against the BFF status endpoint after any reload; dropped on expiry. - CIBA declaration in the listing manifest (
ciba_defaults: assisted link delivery, poll mode, bounded expiry, binding-message template, and the delegated customer scope subset).
5. Rationale, Trade-offs, and Alternatives
- CIBA decoupled delegation, not password sharing or sub-sessions. The
customer approves on their own device (or via assisted link); the teller
receives a time-boxed,
act-attributed, audited token. Rejected: teller "operates as customer" via shared credentials (unattributable, unauditable, unsafe) and direct teller-token writes to customer accounts (breaks holder attribution). - Separate
staffLedgerclient. Internal accounts cannot be resolved with a delegated (holder) token — they carry nocustomer_id— so mixing them into the delegated registry guarantees failure or, worse, leakage attempts. A structurally separate client makes the invisible-to-holders rule executable. Rejected: one multiplexed registry with per-call flags. - Delegation state in
sessionStorage, revalidated server-side. A teller reloads the terminal mid-operation; the delegation must survive reload but only if the BFF still considers it valid. Client-side storage alone is untrusted; the restore loop revalidates against the BFF before any page uses it. Rejected: in-memory-only state (fragile) — and trust-on-reload (unsafe). - WebSocket (not SSE) for delegation updates. Delegation approval is an interactive, bidirectional wait state on the terminal; the platform reserves WebSocket for the bidirectional tier, and this is one.
- Counterparty model over free-form accounts. Deposits/withdrawals pick internal accounts from the resolver by nickname — the teller cannot typo a GL account; teller-initiated transfers remain explicit.
6. Internal Implementation & Invariants (Private Design)
- Routes (shell switch):
/,/customer-search,/delegation-choice,/onboard-customer,/shift-summary,/cash-deposit,/cheque-deposit,/pending-cheques,/withdrawal,/transfer,/open-account,/account-mgmt,/decisions,/tasks,/dlq,/posting-batches,/customer-detail.url_router.dartre-exports the SDK navigation helpers only. - Delegation restore loop: on load, the shell revalidates the stored
delegation against
GET /delegate/status?delegation_id=…with bounded retries; failure clears the delegation and falls back to staff-only navigation. - Sign-out bridge (
test_bridge.dart): registerswindow.sentin8TestSignOutwired to the sameAuthProvider.signOut()path the user menu uses, so E2E sign-out exercises the identical code path (Flutter-web semantics make the menu unreachable to Playwright otherwise). - Invariants:
- Exactly three operations are staff-token-only: customer search, onboarding, and the shift summary — everything touching customer data requires an active delegation.
- The delegated registry is discarded the moment the delegation expires.
- Internal accounts are never requested through the delegated client.
- Every posting carries a
batch_idand idempotency key; retries in the deposit/withdrawal flows reuse the same key. - No credential, token, or delegated token is ever serialized outside the
BFF session;
sessionStorageholds the delegation id, never tokens.
7. Unresolved Questions / Future Designs
- Cheque clearing settlement (mark cleared/bounced) currently concludes the journey client-side; wiring the settle step to the ledger cheque pipeline is the known functional gap.
- Teller cash-drawer balancing / end-of-day cash reconciliation is not yet
designed (today the shift summary is derived from the client
sessionLogonly).
Design attribution: bank8:eteller and its underlying concepts were designed by Hamid Jahanzad Hamid@Jahanzad.com.