Skip to main content

POS Audit-Log Coverage Inventory

Date: 2026-04-17 Status: Discovery complete — no decision Tracking: #1001 Charter reference: Gateway Integration Charter Section 11 ("Audit log unification") — docs/superpowers/specs/2026-04-13-gateway-integration-charter-design.md

Purpose

Charter Section 11 flagged audit-log unification as a deferred item because gateway has comprehensive audit logging but POS coverage was unverified. This document inventories what POS actually audits today, highlights gaps, and surfaces three candidate paths forward. It is a discovery artifact: no path is chosen here. A follow-up implementation ticket is filed after review.

TL;DR

Scope of scan: the three POS microservices that accept merchant-facing traffic (merchant-api, management-api, terminal-api), the terminal-onboarding service, the Android terminal app, the customer portal, and shared libraries under libs/.

Headline finding: POS has an activity_logs Spanner table with a fully-wired read path (service, repository, two controllers, IAM-gated endpoints, SDK clients in TypeScript and Kotlin) — but the write path is dead code. ActivityLogRepository.insert(...) is defined and exists as a SqlDelight query (insertMerchant in ActivityLog.sq), but no production code ever calls it. Only test fixtures instantiate ActivityLog objects. Merchants can query the UI for an activity log that is always empty.

Count across the inventory below:

Coverage stateActions
Logged to a durable, structured sink0
Logged partially (stdout / INFO / ledger-only)13
Not logged at all24
Total in-scope actions audited37

Section 11 of the charter was written assuming POS had some audit coverage that needed mapping. The reality is substantially less coverage than implied: beyond the deny-side IAM decision logs and the per-domain side-effect tables (inventory ledger, point transactions, cash movements, invoice_event) that exist for operational reasons, POS has no audit surface. The activity_logs table is scaffolding, not a working system.

Full detail in Section 3 and Section 5.


1. Scan methodology

  • Code search: grep for audit, Audit, AUDIT_LOG, ActivityLog, @RequiresPermission, @RequireRole, @PreAuthorize across apps/microservices/**, apps/android/**, apps/websites/**, libs/**.
  • Schema scan: every .sq file under apps/specifications/schema/src/main/sqldelight/com/myriad/schema/ (60 tables) read for audit-shaped columns (actor_id, user_id, created_by, before, after, old_*, new_*).
  • Infra scan: infra/terraform/*.tf for google_logging_project_sink, google_logging_bucket_config, audit-log retention, access-log sinks.
  • Call-site check: every occurrence of insert-capable repositories and services to verify write paths are actually exercised.
  • Charter cross-reference: charter Section 3 source-of-truth table used to scope "mutates merchant-facing state or elevates privileges" actions.

2. Infrastructure state

2.1 Spanner tables that could be audit sinks

TablePurposeUsed as audit today?
activity_logsGeneric action log (actor, org, store, entity, details JSONB, IP)No — insert path is dead code
inventory_ledgerPer-product quantity deltas with user_id and notesYes (operational, side-effect of inventory mutations)
point_transactionsLoyalty point deltas with idempotency_key and typePartial — no actor/cashier id captured
cash_movementsCash drawer movements with user_id + override_user_id (T9)Yes (designed with audit in mind — per T9 spec)
cash_drawer_snapshotsOpening/closing counts with counted_by_user_id + approved_by_user_id (T9)Yes (T9)
invoice_eventCurrent invoice lifecycle/event log: event_type, status transitions, correlation id, payloadPartial — no actor id; scope is invoice lifecycle only
revoked_terminal_certsTerminal mTLS revocations, revoked_by VARCHARPartial (free-text actor, no UUID FK)
terminal_certificate_renewalsCert renewal audit trail with status + error_messageYes (operational)
webhook_eventsInbound webhook delivery recordYes (but inbound-only, not POS action audit)
onboarding_pipelinesMulti-step onboarding with per-step completed_byYes (implicit, per-step)
waste_logProduct waste with store + org scopePartial — operational, not security

All tables are append-mostly, not append-only. None have DB-level REVOKE UPDATE, DELETE grants (charter flags Spanner access control via DDL GRANTs but none are configured for these tables). No row-signing, no hash-chaining, no tamper-evidence beyond the default Spanner write-timestamp columns.

2.2 Structured log sinks

  • Logback JSON encoder wired into every Cloud Run microservice via libs/microservices/security/src/main/resources/com/myriad/shared/logging/cloud-run-json-console.xml. Every log line becomes a JSON object with severity, logger, message, trace.id, plus MDC fields for firebase.uid, firebase.email, firebase.tenant, http.request.method, url.path.
  • Cloud Logging is the default sink (stdout → Cloud Logging ingest via Cloud Run).
  • Exclusion filters in infra/terraform/logging.tf: health checks and staging logs excluded from the default sink. No include-only sink for audit-tagged logs.
  • No dedicated audit log bucket. No google_logging_project_sink directed at a long-term retention bucket. No BigQuery export, no Pub/Sub fanout. Everything relies on Cloud Logging's default 30-day retention (Cloud Run application logs). GCS bucket-access logs are sunk into pinpointpos-access-logs-<project> with a 90/365-day lifecycle (Nearline/Coldline), but these are object-access logs, not application audit.
  • Spanner backup retention: 7 days (infra/terraform/spanner.tf:28). Point-in-time recovery window is Spanner default (1h–7d configurable, not customized).

2.3 IAM decision log (the one genuine structured audit surface)

libs/microservices/iam-engine/src/main/java/com/myriad/shared/iam/IamService.kt emits an INFO line on every checkAccess call with fields {decision, principal(email+orgId), resource, action, reason, context}. This is the only structured log line that meets a standard audit-log schema today.

Caveat: decisions from @RequiresPermission-annotated controllers are logged, but the audit document specified for IAM (docs/private-docs/docs/api/iam-spec.md Section 12) is written as a design intent, not a verified coverage claim. The log message format exists; retention and tamper-evidence claims do not.

2.4 Cloud Audit Logs (GCP-native)

Not configured. No google_project_iam_audit_config resource in Terraform. GCP Data Access audit logs are at the default level (admin-activity only, 400-day retention, free tier). This covers IAM role changes on the GCP project, NOT application-level actions inside our services.

3. Summary inventory table

Grouping is by domain (identity, catalog, payments, retail ops, platform). "Sink" column uses: ACT = activity_logs (dead), IAM = IAM decision log, LG = domain side-effect table (ledger/event log), LOG = free-text INFO log, NONE = no audit.

#ActionSurfaceLogged?SinkFields capturedTamper-evidentGap vs. gateway standard
Identity / membership
1Create organizationmanagement-api OrgController.create via OrgServiceNoLOG (warn only on failure)noneNoNo actor/before/after; not captured at all on success
2Update organizationmanagement-api OrgController.updateNoNONEnoneNoNo actor/before/after
3Delete organizationmanagement-api OrgController.delete; demo via DemoOrgControllerNoNONEnoneNoIrreversible; no record of who deleted or when beyond deleted_at-ish signals
4Create storemanagement-api StoreController; merchant-api StoreControllerNoNONEnoneNoNo actor
5Update storemerchant-api StoreController.updateNoNONEnoneNoNo actor/before/after
6Delete storemanagement-api StoreControllerNoNONEnoneNoNo actor
7Invite org membermerchant-api OrgInviteControllerInviteServicePartialLG (invites.created_by) + LOG (warn on email failure)invite row has created_by FKNoNo record on revoke/resend; no details JSON
8Accept invite (identity promotion)merchant-api InviteService.acceptInvitePartialLG (invites.accepted_at)invite row mutationNoNo record of IP / user-agent / elevation resulting policy
9Change org member rolemerchant-api OrgMemberControllerNoNONEnoneNoElevates privileges; nothing logged
10Remove org membermerchant-api OrgMemberControllerNoNONEnoneNoNo actor
11Invite store membermerchant-api StoreInviteControllerPartialLG (invites.created_by) + LOGinvite rowNoSame as #7
12Change store member rolemerchant-api StoreMemberControllerNoNONEnoneNoElevates privileges; nothing logged
13Remove store membermerchant-api StoreMemberControllerNoNONEnoneNoNo actor
14IAM role assignment (user-to-role)merchant-api IamRoleController -> iam_user_rolesNoNONEnoneNoRow is the only evidence; no change history
15IAM policy / group / role mutationmerchant-api IamRoleController / IamGroupController / IamCatalogControllerPartialLG (tables themselves; IAM DENY/PERMIT lines on check)IAM decision log on readNoNo mutation log — only access-decision log on reads
16Staff invite / deactivation (support tenant)management-api SupportController, SupportUserInviteServicePartialLOG (staff invite, welcome email failures)free-text emailNoNo actor id, no structured record
17Update PIN (cashier PIN reset)merchant-api UserPinControllerNoNONEnoneNoSecurity-sensitive change, no record
18Firebase user create / resetmerchant-api FirebaseAdminService, LocalFirebaseAdminServiceNoLOG (fire-and-forget debug)noneNoNo actor on reset; Firebase's own audit is separate
Customer PII
19Create customer (portal)merchant-api CustomerControllerCustomerService.createNoNONEnoneNoNo record of who entered PII
20Update customer (portal, PII edit)merchant-api CustomerController.updatecustomers.updateNoNONEnoneNoNo before/after; no actor
21Update customer license / DOB (age verification)terminal-api CustomerController (terminal) + compliance flowNoNONEnoneNoHigh-compliance PII; nothing logged
22Delete customermerchant-api CustomerController.deleteNoNONEnoneNoIrreversible; no record
23Loyalty points adjustment (cashier override)merchant-api LoyaltyController / terminal-api LoyaltyRedemptionServicePartialLG (point_transactions)delta, customer_id, type, idempotency_keyNoNo actor id on point_transactions (only org_id)
24Consent change (SMS / marketing)merchant-api ConsentControllerPartialLG (sms_consents row)consent row has stateNoNo actor id
Catalog / pricing
25Create / update / delete productmerchant-api ProductControllerProductServiceNoNONEnoneNoNo actor/before/after
26Bulk import productsmerchant-api BulkImportControllerBulkImportServiceNoNONEnoneNoHigh-impact mass change; nothing logged
27Inventory mutationmerchant-api InventoryController → ledger insertYes (ops)LG (inventory_ledger)store, org, product, Δqty, user_id, notesNoClosest thing to a working audit; per-product only
28Create/update suppliermerchant-api SupplierControllerNoNONEnoneNoNo actor
29Create/update promotion / discountmerchant-api PromotionController / DiscountControllerNoNONEnoneNoNo actor; charter T3 will move this to gateway
30Tax config mutationmerchant-api TaxConfigController / TaxConfigServiceNoNONEnoneNoNo actor; charter T2/T2b moves this to gateway
Payments / retail ops
31Refund issuance (POS terminal origin)terminal-api RefundControllerRefundServicePartialLOG (gateway card refund log line)originalTxnId, gatewayTxnId, amountCentsNoMissing: cashier id, manager-override id, reason, before/after of original row
32Return / credit note (portal origin)merchant-api ReturnControllerReturnServicePartialLG (returns row) + LOGreturns.user_id, notes, restockedNoNo activity_logs entry; no manager-override capture
33Shift close / variance acknowledgmentmerchant-api ShiftController / ShiftServicePartialLG (shifts_actual rows)user_id, clock_in/out, break_minutesNoVariance acknowledgment depends on T9 cash_drawer_snapshots.approved_by_user_id
34Cash movement (deposit / drop / manager override)T9 — cash_movements tableYes (ops)LG (cash_movements)drawer, shift, movement_type, amount, reason, user_id, override_user_idNoBest coverage of any action; still not append-only
Platform / gateway binding / terminal
35Gateway store auth binding (create/update/delete)management-api GatewayStoreAuthController + GatewayWebhookControllerPartialLOG (INFO on StoreAuthorized/StoreRevoked)gatewayMerchantId, oauthClientId, scopesNoNo actor on portal-originated binding change; webhook path logs only
36Terminal provisioning / mTLS cert issuanceterminal-onboarding OnboardingServicePartialLG (terminals, terminal_onboarding_tokens, terminal_certificate_renewals) + LOGcert metadataNoNo actor on renewal trigger
37mTLS cert revocationmanagement-api RevokedCertControllerrevoked_terminal_certsPartialLG (revoked_terminal_certs.revoked_by VARCHAR) + LOGrevoked_by free-textNorevoked_by is a string, not a FK to merchant_users; nothing verifies it

3.1 Actions considered but not counted

  • Gateway transaction sync (POS → gateway) — charter-side concept. Sync itself isn't a merchant-facing mutation; the underlying in-store sale is captured in transactions but transactions have no audit-shaped columns (the cashier id lives on the child transaction_items.user_id for line-level granularity, not on the transaction).
  • Webhook delivery (gateway → POS)webhook_events is captured but it's ingest, not POS mutation.
  • Reads — the ActivityLog list/read endpoints, report endpoints, and @RequiresPermission(action="read") paths are out of scope per the ticket ("mutates merchant-facing state or elevates privileges").

4. By-domain commentary

4.1 Identity / membership

Covered: invite rows carry created_by and accepted_at. The IAM decision log emits an INFO line per access check (PERMIT + DENY), which at least tells you who tried to do what even when the action itself isn't audited.

Gaps: role assignments (iam_user_roles), role/policy/group mutations, org and store creation, and member role changes have no audit trail whatsoever — the row is the only evidence the change happened, and the row has no actor. Deletes are irreversible and leave nothing behind.

This is the highest-severity cluster. Actions #9, #10, #12, #13, and #14 all elevate privileges and none produce any audit record.

4.2 Customer PII

Covered: nothing usefully. customers.updated_at is touched on each mutation but no actor is captured.

Gaps: create, update, and delete of customer records — including license number, date of birth, phone, email — are completely unaudited. For compliance purposes (age verification / tobacco / alcohol) this is a material hole: a changed license_number or date_of_birth cannot be attributed to a specific cashier or admin.

Loyalty points have the best coverage of the customer-domain actions via point_transactions, but point_transactions captures org_id — not the cashier who performed the adjustment. Manual cashier overrides of loyalty balances are indistinguishable from routine accruals at the audit layer.

4.3 Catalog / pricing

Covered: inventory mutations via inventory_ledger (user_id + notes).

Gaps: product create / update / delete, price changes, promotion / discount create, and tax config mutation have no audit. Bulk import is the most alarming — it is the single biggest-blast-radius mutation in the system and produces no record of who ran it or what changed.

Note that charter Section 3 targets much of this moving to the gateway (T2 / T2b tax, T3 discounts, T6 products). Once those migrations land, the audit question for those concepts transfers to the gateway. The POS-owned residue is inventory (ledger exists, adequate) and suppliers (no audit).

4.4 Payments / retail ops

Covered: refunds log an INFO line with amounts and gateway txn ids; cash movements (T9) capture actor + override actor; shift actuals capture user_id.

Gaps: refunds miss the cashier and any manager-override identity — the INFO log line is stdout, not queryable, and not tamper-evident. Shift close variance acknowledgment is captured iff T9 fully lands (cash_drawer_snapshots.approved_by_user_id). Manager override on any flow other than cash is not captured at all — there is no generic managerOverrideUserId or overrideReason column on transactions, returns, or any loyalty path.

4.5 Platform / gateway binding / terminal

Covered: gateway store auth transitions log INFO lines on webhook receipt (StoreAuthorized, StoreRevoked). Cert renewals live in terminal_certificate_renewals with status + error_message. Cert revocations live in revoked_terminal_certs.

Gaps: revoked_terminal_certs.revoked_by is VARCHAR(255) — free-text with no FK to merchant_users or support_users, so "who revoked this cert" is whatever string the caller chose to pass. No actor is captured on the portal-originated gateway-binding mutation path (only the webhook-side is logged, and even then not as a structured audit event).

5. Gap summary

Ranked by severity (compliance + forensics + operational impact):

RankGapSeverityWhy
1Role / permission changes (IAM user-role, group, policy mutations) have no audit trailCriticalPrivilege escalation is the single attack that most matters; charter Principle 2 makes "POS employees never get gateway access" a load-bearing claim that cannot be enforced post-incident without this
2Customer PII edits (license, DOB, phone) unauditedHighAge-verification compliance (TTB / state tobacco boards) requires attribution to a specific employee for ID-check disputes
3Refund issuance lacks structured actor + manager-override + reasonHighRefund fraud is the standard loss-prevention case at POS; INFO-line in Cloud Logging is not queryable by merchant auditors
4activity_logs table is dead code — write path exists only in testsHighExisting merchants can query for their activity log and see it empty; false sense of coverage; shipped SDK methods return nothing
5Bulk product import unauditedHighHighest-blast-radius mutation; a single call can change thousands of prices
6Gateway store auth mutations on portal side (non-webhook) unauditedMediumGateway-binding changes redirect revenue; merchant needs an auditable record of who authorized it
7revoked_terminal_certs.revoked_by is free-text with no referential integrityMedium"Who revoked this terminal cert" is whatever string a caller passed; cannot be trusted
8Delete actions across the board (org, store, customer, member) produce no recordMediumIrreversible, no attribution; any delete is an instant evidence-wipe
9No tamper-evidence — nothing is append-only, no row-signing, no hash-chainMediumEven where a row is written with actor id, an org admin or rogue operator can edit it directly via Spanner once IAM permits them
10Cloud Logging retention is 30 days (default) for all stdout audit-like logsMediumForensic window too short for typical investigation timeline; no long-term archive
11Product / price / promotion / tax / supplier changes unauditedMediumMost of these migrate to gateway (T2/T2b/T3/T6); exposure is transitional, not permanent
12point_transactions captures no actorMediumCashier override on loyalty cannot be distinguished from routine accrual
13No dedicated audit-log Cloud Logging sink → BigQuery or GCS for long-term retentionLowEasy to add infrastructure-wise; not load-bearing until #4 above is fixed

6. Three candidate paths

The ticket asks for paths, not a decision. All three are presented; trade-offs are surfaced; nothing is chosen.

Path A — Fill POS gaps locally

Shape. Wire the existing activity_logs table into a mandatory write path at every mutation. Add a small AuditLogger component in libs/microservices/audit/ (new library) with a fluent API:

auditLogger.actor(authUser).action("customer.update").entity("customer", customerId)
.before(oldRow).after(newRow).context(ctx).record();

Harden the table: add prev_hash VARCHAR(64) for per-org hash-chaining, signature VARCHAR(512) using an ES256 key from KMS, enforce DDL-level REVOKE UPDATE, DELETE on activity_logs for the microservice roles (Spanner DDL GRANT/REVOKE pattern — already called out in the project CLAUDE.md). Add a dedicated Cloud Logging sink to GCS Coldline with 7-year retention and lifecycle → archive.

Rewire the write side by either (a) ArchUnit-style "every mutating service method must call AuditLogger" enforcement plus an AOP aspect that wraps @RequiresPermission(action="create"|"update"|"delete"), or (b) explicit call-sites per action. Option (a) is lower burden at write time, higher magic; option (b) is verbose but reviewable.

Pros.

  • Self-contained — no coordination with gateway team.
  • Table and SDK clients already ship; only the write path and hardening are new.
  • Schema is flexible (JSONB details), accommodates every action type without per-action tables.
  • Tamper-evidence and retention are under POS control.

Cons.

  • Duplicates capability gateway already has. Charter Principle 1 (gateway-first directionality) argues against it: a gateway-only merchant will ask about audit logs and get them from gateway; now we have two systems.
  • Retroactively reliable — every new mutation in POS has to remember to call the logger, ArchUnit or otherwise. Gateway's audit is instrumented at the controller/request layer, which is more uniform.
  • Row-signing + per-org hash-chain is non-trivial to operate correctly (key rotation, verification tooling). Risk of shipping tamper-evidence theater.
  • Does nothing for actions that cross the boundary (refunds that originate at POS but settle on gateway — which side owns the audit? Currently the charter says split-by-origin, which means two partial records instead of one whole one).

Path B — Gateway-proxy

Shape. Every POS mutation emits an audit event to the gateway's existing audit surface via the gateway SDK. A per-service gateway/audit/ package wraps com.myriad.gateway.audit.* (following Charter Rule A). The audit event carries {origin: "pos", pos_service, action, actor_user_id, actor_tenant, entity_type, entity_id, before, after, context}.

Gateway is the retention root; gateway's existing tamper-evidence (whatever it is) applies. POS retains nothing locally — the activity_logs table is deprecated, and the existing read-side controllers become proxies to gatewayAuditApi.listForOrg(...).

For POS actions on entities that have no gateway counterpart (shifts, cash movements, loyalty), we either (a) still emit to gateway with a POS-only entity type, or (b) fall back to Path A for those specific entities.

Pros.

  • Aligns with Charter Principle 1 — gateway owns mainstream capabilities.
  • Single source of truth — merchants and internal staff look in one place.
  • Gateway's existing tamper-evidence and retention investment is reused.
  • Uniform story for cross-origin events like refunds: one record, on the authoritative side.

Cons.

  • Requires a gateway-side audit API that accepts externally-sourced events and preserves them with attribution — tracking: TBD (no gateway issue filed). Coordination overhead with gateway team, unknown API shape today.
  • Latency / availability coupling: a gateway outage means POS mutations run without audit, or fail-closed. Fail-closed breaks the POS (cashiers can't sell); fail-open defeats the audit purpose. Needs a local durable queue.
  • POS-only entities (shifts, cash drawers, loyalty) look awkward in a gateway-centric audit model.
  • Per Charter Rule C, terminal-onboarding and tx-bundler are not authorized to consume gateway SDK — but terminal-onboarding is the only place cert operations happen. Either we carve an exception (needs charter update) or we relay via management-api.

Path C — Unified surface

Shape. Stand up a new service (or augment management-api) as the audit-log aggregator. Both POS and gateway write to it; neither reads their own local audit table as the source of truth. The aggregator fronts a single query API for merchant-portal, support-portal, and regulators.

Storage is gateway-owned (leveraging gateway's existing infrastructure) OR a new Spanner DB under POS ownership OR an external log analytics platform (BigQuery, Datadog, Splunk). All three sub-options are possible; the audit service abstracts them.

Pros.

  • Future-proof — appointments (Issue #866), storefront (Issue #869), and any new cross-product feature has one audit surface from day one.
  • Clean separation: audit writing is a cross-cutting concern, not a gateway feature or a POS feature.
  • Can host tamper-evidence (hash chain / signed rows / WORM bucket) in one hardened place.

Cons.

  • Largest scope by far; takes the audit question from "ship in a quarter" to "ship in a year".
  • New service to operate; another piece of infra to run 24×7.
  • Does not solve the POS-today problem — POS still has nothing while the unified surface is being built.
  • Violates Charter Principle 1 (gateway-first) in a subtle way: if gateway already has audit and we're building a separate aggregator, we're splitting a capability gateway owns.
  • Risk of becoming yet another half-built table: the activity_logs lesson is that scaffolding is cheap and write-paths are expensive.

Comparison at a glance

FactorA — Local fillB — Gateway-proxyC — Unified surface
Time to first valueWeeksQuarters (gateway API needed)Year+
Charter alignmentAgainst (duplicates gateway)With (Principle 1)Neutral
Coordination with gateway teamNoneHeavyHeavy
Handles cross-origin events (refunds)PoorlyNaturallyNaturally
Handles POS-only events (shifts, cash, loyalty)NaturallyAwkwardlyNaturally
Tamper-evidence surfacePOS buildsGateway reusesUnified builds once
Retention costPOS budgetsGateway budgetsShared budget, new line item
Risk of scaffolding-without-writes (the current failure mode)Low — explicit call-sitesLow — SDK typedMedium — more moving parts

Not a decision — the ticket explicitly defers it. But the information the decision needs:

  1. What does gateway's audit surface actually look like today? The charter says "comprehensive" — we need the actual API shape, tamper-evidence implementation, retention policy, and query interface. Path B and C both depend on this.
  2. How much of the POS mutation surface is migrating to gateway anyway? Per charter T2/T2b/T3/T5/T6/T12, a large fraction of the "not logged" actions in Section 3 above transfers to gateway regardless. The POS residue after charter Part 2 lands is smaller than it looks today.
  3. Regulatory baseline. PCI, age-verification (TTB / state ABC), and any state-level POS transparency rules — do any of them set a hard retention floor or tamper-evidence requirement? This dictates whether Path A's GCS Coldline 7-year approach is overkill or insufficient.
  4. Tolerable outage semantics. Fail-open vs fail-closed for audit in the hot path — only relevant for Path B.

8. Out of scope for this ticket

  • Gateway's own audit implementation review — out of repo.
  • Retroactive audit of historical data — this inventory is current-state.
  • Non-merchant-facing mutations (internal ops, batch jobs, scheduled cleanups) — not in scope per ticket.
  • Android app cashier-override UI audit — no evidence any manager-override UI exists today beyond cash-drawer and T9.
  • Redaction / right-to-be-forgotten on the audit log — separate concern.

9. References

  • Gateway Integration Charter, Section 11 — docs/superpowers/specs/2026-04-13-gateway-integration-charter-design.md
  • IAM Spec, Section 12 (Audit Logging)
  • Security Audit — Phases 1 & 2
  • Security Remediation Report
  • Schema: apps/specifications/schema/src/main/sqldelight/com/myriad/schema/ActivityLog.sq
  • Service: apps/microservices/merchant-api/src/main/java/com/myriad/merchant_api/service/ActivityLogService.kt
  • Repository: apps/microservices/merchant-api/src/main/java/com/myriad/merchant_api/repository/ActivityLogRepository.kt
  • Logging config: libs/microservices/security/src/main/resources/com/myriad/shared/logging/cloud-run-json-console.xml
  • Terraform (what's missing): infra/terraform/logging.tf