POS Audit Coverage
This page describes the audit paths that are implemented in the current POS codebase. It is an operating reference, not a dated security finding. When code and this page disagree, the writers, schema queries, and focused Bazel tests listed below are authoritative.
Ownership
| Surface | Owner | Responsibility |
|---|---|---|
activity_logs | Shared SQLDelight schema in apps/specifications/schema/src/main/sqldelight/com/myriad/schema/ActivityLog.sq | Cross-cutting POS mutation records keyed by actor, organization, store, action, entity, IP address, and timestamp. |
| Merchant activity log | merchant-api | Writes merchant-side mutations through ActivityLogService; serves organization and store activity-log reads through OrgActivityLogController and StoreActivityLogController. Organization reads also merge compatible rows from audit_events. |
| Management activity log | management-api | Writes support/admin mutations locally through its ActivityLogService; it does not own a separate activity-log read API. |
| Terminal certificate activity | terminal-onboarding | Writes certificate issue and renewal events through TerminalOnboardingAuditService as the stable system actor. |
audit_events | Feature owners using AuditEvent.sq | Product-specific event history, currently including auth feature-planning changes and merchant campaign history. tx-bundler exposes the account-reporting query path. |
| Domain histories | Owning domain service | Invoice events, inventory ledger entries, point transactions, cash movements, certificate renewals, webhook receipts, and similar operational histories remain domain records. They complement the cross-cutting activity log; they are not substitutes for an activity-log row where a mutation is wired. |
ActionConstants.kt is the shared catalog for stable activity action and entity
names. Feature-local action names are allowed only where the owning service
writes and tests them explicitly.
Write Contract
merchant-api and management-api construct the actor from AuditContext:
- Firebase-authenticated portal users resolve to the POS user UUID.
- Support, merchant-admin, terminal, and system actor types are distinguished.
- Work without a user principal uses the stable system actor UUID.
- Request IP address is best effort: the first
X-Forwarded-Forhop, then the servlet remote address, ornulloutside a request.
The ordinary helpers are intentionally fail-open. They write after a successful
business mutation, catch any audit failure, and emit the structured
pos.audit.write.failure warning without changing the parent response. A flow
that requires exactly-once evidence uses the required writer with a stable log
ID; that failure propagates so the owning transaction cannot commit without its
audit invariant.
Merchant detail maps remove null fields and pass through
AuditDetailRedactor before JSON serialization. Callers must still keep raw
credentials, tokens, card data, and unnecessary customer PII out of audit
details.
Covered Actions
The table groups implemented write call sites. Use the action string when
querying activity_logs; do not infer coverage from a constant that has no
writer call site.
| Domain | Implemented actions |
|---|---|
| Organization and store lifecycle | org.create, org.update, org.deactivate, org.activate, store.create, store.update, store.deactivate, store.activate |
| Membership and identity | user.invite, user.invite_accept, user.role_change, user.deactivate, org_member.add, org_member.remove, store_member.add, store_member.remove |
| Customer and loyalty | customer.create, customer.pii_edit, customer.delete, customer.points_adjust |
| Catalog and purchasing | product.import, product.bulk_price_update, product.vendor_cost_adjustment, category create/update/move/deactivate/merge/delete/provider-code replacement, and purchase-order draft/approve/receive |
| Cash, returns, and compliance | cash_drawer.register, cash_drawer.deactivate, cash_movement.record, cash_snapshot.record, refund.issue, compliance.check, compliance.decline, compliance.override, and gift-card import |
| Terminal and Gateway administration | terminal provision/update/deactivate/revoke/reassign/delete; terminal certificate issue/renewal; Gateway binding create/relink/delete; and payment-device link revalidate/clear/authorize-relink/revoke |
| Support and security operations | support PIN challenge create/revoke, administrator PIN set, merchant or agent TOTP reset, SAML JIT provisioning, and support-managed billing price changes |
| Advertising | advertisement create/update/archive and advertisement store-group create/update/delete |
Invoice lifecycle events use the dedicated invoice_event path rather than
being represented as generic activity_logs coverage. Campaign and auth
configuration events use audit_events; organization activity-log reads merge
those compatible rows so operators have one chronological view.
Intentional Boundaries
The cross-cutting activity log does not attempt to record everything:
- Read-only requests are excluded.
- Rejected authorization attempts are evidence in the structured IAM decision log, not successful mutation rows.
- A failed business mutation does not create a success activity row. Service logs and request traces remain the failure evidence.
- Inbound webhooks and scheduled processing retain their delivery or domain event records unless they also perform a mutation with an explicit activity writer.
- Detail JSON is selective evidence, not a complete before/after snapshot and never a place to duplicate secrets or regulated payloads.
- POS activity logging does not claim to be the Gateway product's audit store. Cross-product investigations must reconcile each product's authoritative event source.
The activity_logs table is queryable and durable in Spanner, but the current
schema does not claim cryptographic tamper evidence or an application-enforced
append-only database role. Do not describe it as either without a schema and
grant change that proves the property.
Evidence Sources
Review these in order when coverage changes:
libs/microservices/security/src/main/java/com/myriad/shared/security/ActionConstants.ktlibs/microservices/security/src/main/java/com/myriad/shared/security/AuditContext.kt- The three activity writers and their repositories in
merchant-api,management-api, andterminal-onboarding ActivityLog.sqandAuditEvent.sq- The mutation call site that writes the action
- The focused service or wiring test that asserts the action, actor, scope, and detail redaction or failure behavior
A text search for an action constant is discovery only. Coverage is established when the successful mutation path invokes the writer and a focused test observes the expected record.
Validation
Run the focused Bazel tests for the shared writer contract and the original Phase 1 wiring:
bazel test \
//apps/microservices/merchant-api:activity_log_service_test \
//apps/microservices/merchant-api:activity_log_wiring_test \
//apps/microservices/management-api:activity_log_wiring_test \
//apps/microservices/terminal-onboarding:onboarding_service_test \
//apps/microservices/terminal-onboarding:terminal_certificate_lifecycle_qa_test
Then run the focused test for the service whose mutation changed. For example, membership, invite, return, cash-drawer, terminal-lifecycle, campaign, category, or billing changes are asserted in their owning service tests rather than one global source-text inventory.
For an end-to-end local check:
- Start and seed the supported stack with
./scripts/local-validation.sh up. - Perform one covered mutation through the portal or API.
- Read the organization or store activity-log endpoint with an authorized account and confirm the actor, action, scope, entity, redacted details, and timestamp.
- Check service logs for
pos.audit.write.failure; its presence means the business action may have succeeded without ordinary audit evidence. - Stop the stack with
./scripts/local-validation.sh downwhen the check is complete.