Skip to content

Device capability flags: restrict POS devices to sales and/or topup - #187

Open
daedeloth wants to merge 15 commits into
mainfrom
device-capability-flags
Open

Device capability flags: restrict POS devices to sales and/or topup#187
daedeloth wants to merge 15 commits into
mainfrom
device-capability-flags

Conversation

@daedeloth

Copy link
Copy Markdown
Member

Summary

Implements the feature request to assign POS devices a limited function — sales-only, topup-only, or both — so bar volunteers cannot top up or reset NFC cards, while dedicated topup stations can be locked out of sales.

  • Two admin-controlled flags on Device: allow_sales / allow_topup (default true, existing fleets unaffected). Editable only via the Management API (Manage → Devices → Edit, with capability badges in the list); the Device API exposes them read-only, so a device cannot re-enable its own capabilities.
  • POS gates its UI and routes by the flags (router guard covers deep links; nav items hidden; order toggles hidden; topup/reset/rebuild/discount/alias controls in the shared Card component gated; a "no functions enabled" screen when both flags are off, with Settings always reachable).
  • Because card balances are written offline with the device's signing key, rogue topups cannot be rejected server-side. Instead the transaction merger records uploaded_by_device_id on every device upload and marks unauthorized = true for topup/reset/refund uploads — and any positive-value non-reversal upload — from non-topup devices. Flagged rows get a red badge in the transactions overview. Residual risks are documented in the spec's Known limitations section.
  • Sales-disabled devices are excluded from remote-order assignment (mirrors allow_remote_orders), and pending orders are reassigned when an admin disables sales.
  • Includes the dockerized PHPUnit runner (docker compose run --rm phpunit, test profile) mirroring CI.

Design spec: docs/superpowers/specs/2026-07-08-device-capability-flags-design.md

Test plan

  • PHPUnit: 208 passing (12 new: capability defaults, Device API read-only boundary, Management write path, merger flagging incl. positive-value rule and endpoint-level HTTP test, order-assignment exclusion)
  • Vitest: 184 passing (16 new: capability redirect matrix, boot/guard wiring, Card topup-UI gating)
  • Jest (NFC): 32 passing — card format and signing untouched
  • npm run production builds clean; i18n keys verified identical across all 5 locales
  • Not covered automatically: end-to-end check with a physical paired device + NFC hardware (manual checklist in the plan, step 10.4) — the server-side upload path is covered by an HTTP-level test with a real device token

🤖 Generated with Claude Code

https://claude.ai/code/session_01RQjdQhQM3q7MZCWCv3AdmD

daedeloth and others added 15 commits July 8, 2026 16:59
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RQjdQhQM3q7MZCWCv3AdmD
docker compose run --rm phpunit mirrors the CI MySQL service
(catlab_drinks_test, test/test) so feature tests run locally without
host PHP extensions or a manually provisioned database. Both services
sit behind the 'test' profile so docker compose up doesn't start them.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The repo gained a feature-test suite and dockerized runner since CLAUDE.md
was written; backend tasks now carry real failing-test steps instead of
tinker smoke checks.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RQjdQhQM3q7MZCWCv3AdmD
Implement Task 1: Add database columns and model support for device capability flags (allow_sales and allow_topup). Both flags default to true. Updated Device model to include fillable fields, boolean casts, and reassignment logic when allow_sales is disabled.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RQjdQhQM3q7MZCWCv3AdmD
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RQjdQhQM3q7MZCWCv3AdmD
- Management API: allow_sales/allow_topup fields writeable (admin can change)
- Device API: same fields visible but read-only (POS cannot change own capabilities)
- TransactionResourceDefinition: expose unauthorized field for both APIs

Implements Task 3: TDD approach with three tests ensuring proper security boundary.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RQjdQhQM3q7MZCWCv3AdmD
TransactionMerger now accepts the uploading Device and records
uploaded_by_device_id on each merged transaction. Topup/reset/refund
transactions uploaded by a device with allow_topup=false are flagged
unauthorized=true, giving admins an audit trail for offline NFC card
writes that can't be blocked server-side.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RQjdQhQM3q7MZCWCv3AdmD
- Add 'allow_sales' check to findBestDevice() query alongside allow_remote_orders
- Update reevaluateAssignments() to reassign when either flag is disabled
- Add testAssignOrderExcludesSalesDisabledDevices and testReevaluateReassignsWhenSalesDisabled tests

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RQjdQhQM3q7MZCWCv3AdmD
Adds a pure deviceCapabilities.js module (TDD, vitest-covered) that maps
allow_sales/allow_topup to route redirects, a Disabled.vue dead-end screen,
and app.js wiring (window.DEVICE_ALLOW_SALES/TOPUP boot flags, a
router.beforeEach guard, and the /disabled route). Missing flags on stale
cached device responses are treated as enabled so existing devices keep
working.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RQjdQhQM3q7MZCWCv3AdmD
Gate the Events and Cards navbar items in App.vue, and the order
toggles in Settings.vue based on window.DEVICE_ALLOW_SALES and
window.DEVICE_ALLOW_TOPUP capability flags set in Task 7.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RQjdQhQM3q7MZCWCv3AdmD
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RQjdQhQM3q7MZCWCv3AdmD
…ads, document risks

- Card.vue: gate the topup/reset/rebuild/alias/discount UI behind an
  allowTopup flag (window.DEVICE_ALLOW_TOPUP !== false) so a sales-only POS
  device can no longer mutate cards via the CheckIn route, which mounts
  Card.vue without any capability check.
- TransactionMerger: also flag a transaction as unauthorized when a
  non-topup device uploads any positive-value, non-reversal transaction
  (not just topup/reset/refund), closing the gap where a mistyped
  transaction_type could bypass the type-list check.
- Design spec: document known residual risks (offline balance inflation
  without a typed upload, unflagged positive reversals, detection vs.
  prevention) in a new Known limitations section.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RQjdQhQM3q7MZCWCv3AdmD
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant