---
id: WF-PFM-OPS-008
title: Record that a person opened a record
product_area: platform
module: operations
revision: 1
status: draft
design_intent: confirmed
implementation_evidence: observed
inference: []
origin: 'services-platform:docs/audit-events.md#2026-10-12 — Final acceptance (accepted by Andrew)'
trigger: A tracked screen successfully displays the record selected by navigation.
source_sha:
  services-platform: ad3f6a6582843008ed0f5a17b131878d15829af1
  react-console: baf6dae382d6f41dce976c7f848667ccc37e076f
implements:
  - CAP-PFM-OPS-008
  - CAP-PFM-OPS-010
moves: []
crosses:
  - SVC-PFM-OPS-audit
idempotency: >-
  The visit tracker reuses one UUID occurrence key across delivery retries. Store uniqueness returns the
  earlier same-target visit and rejects reuse for another target.
concurrency: Concurrent same-key deliveries compete on occurrence uniqueness; the losing insert reads the existing event.
hops:
  - 'n': 1
    actor_or_service: Console record screen
    action: >-
      Start an occurrence when a screen first displays a record or changes to another; preserve it through tab
      changes.
    anchor: 'react-console:lib/record-views/tracking/visit.ts:visitIdentityFor'
    seam: sync
    on_failure: Do not report a record that never resolves for display.
    left_inconsistent: No visit is retained for an unresolved navigation.
    observer: None.
    provenance: test
    source: >-
      react-console:e2e/controlled/record-views.controlled.spec.ts#switching tab on the same record is still
      one visit
  - 'n': 2
    actor_or_service: Console tracker
    action: 'Report type, target and occurrence key; retry transport, throttle and server errors using that same key.'
    anchor: 'react-console:lib/record-views/tracking/visit.ts'
    seam: sync
    on_failure: >-
      Suppress errors from the user and discard the visit when attempts are exhausted or a client refusal
      occurs.
    left_inconsistent: The activity account can omit a real navigation.
    observer: Browser log only is the observation surface.
    provenance: test
    source: >-
      react-console:e2e/controlled/record-views.controlled.spec.ts#a failure to report a visit never reaches
      the person, and the retries are one visit
  - 'n': 3
    actor_or_service: Audit API
    action: >-
      Require reporting authority and reject attribution fields, unsupported keys or an unregistered target
      type.
    anchor: 'services-platform:modules/audit/aws/api/audit.routes.ts:recordRecordView'
    seam: sync
    on_failure: Return validation or authority refusal without inserting activity.
    left_inconsistent: The refused navigation report has no stored event.
    observer: Response is the observation surface.
    provenance: test
    source: >-
      services-platform:modules/audit/tests/integration/services/record-views/record-view.integration.spec.js#refuses
      a record type the registry does not hold, naming the types it does
  - 'n': 4
    actor_or_service: Audit service
    action: Resolve the target using the caller’s current read and dataset-query authority.
    anchor: 'services-platform:modules/audit/services/record-views/target-records.ts:requireViewableRecord'
    seam: sync
    on_failure: Record nothing when resolution is refused or the target is absent.
    left_inconsistent: The visit is omitted without changing the target.
    observer: Response is the observation surface.
    provenance: test
    source: >-
      services-platform:modules/audit/tests/integration/services/record-views/record-view.integration.spec.js#writes
      nothing at all for any of those refusals
  - 'n': 5
    actor_or_service: Audit service
    action: 'Insert with context attribution and server time, or return the same-target occurrence already stored.'
    anchor: 'services-platform:modules/audit/services/persistence/audit-events.ts:recordViewEvent'
    seam: sync
    on_failure: Refuse a previously used key pointing to another record.
    left_inconsistent: No second event is inserted for the reused occurrence.
    observer: Response and the record-view metric is the observation surface.
    provenance: test
    source: >-
      services-platform:modules/audit/tests/integration/services/record-views/record-view.integration.spec.js#reports
      the retry as a duplicate rather than refusing it, because the client has nothing to fix
  - 'n': 6
    actor_or_service: Query layer
    action: The query layer independently joins latest visits to currently readable records for Recently Viewed.
    anchor: 'services-platform:docs/audit-events.md#Where a Recently Viewed list comes from'
    seam: sync
    on_failure: Treat query errors as failures of that read rather than delivery of this visit.
    left_inconsistent: No visit mutation is performed by the recent-record read.
    observer: Recent Items menu is the observation surface.
    provenance: documented
    source: 'services-platform:docs/audit-events.md#Where a Recently Viewed list comes from'
branches:
  - condition: A custom record is read through its API
    path: >-
      Custom-record reads append their own visit with a server-generated key, independently of console
      tracking.
waits: []
evidence:
  - >-
    react-console at baf6dae: lib/record-views/tracking (visit.ts, client.ts, use-record-view-tracking.ts),
    lib/api/audit.ts; e2e/controlled/record-views.controlled.spec.ts.
  - >-
    services-platform at ad3f6a6: modules/audit (aws/api/audit.routes.ts, audit-api-mapper.ts,
    services/record-views, services/persistence/audit-events.ts, registry/viewable-record-types.ts);
    docs/audit-events.md.
  - >-
    codex examination 2026-10-23: checked the pinned source anchors, planning references and enforcement
    boundaries; unresolved limits are recorded in module findings.
controls: [TC-AVL-004, TC-AVL-005, TC-AZ-001, TC-AZ-002, TC-AZ-003, TC-AZ-004, TC-DAT-001, TC-DAT-002, TC-DAT-003, TC-DAT-004, TC-IAM-001, TC-IAM-002, TC-IAM-004, TC-IAM-005, TC-LOG-002, TC-LOG-003, TC-LOG-004, TC-LOG-005, TC-MON-001, TC-PI-001, TC-PI-002, TC-PI-003, TC-PI-004, TC-PI-005, TC-PRV-001, TC-PRV-002, TC-TEN-001, TC-TEN-002, TC-TEN-003, TC-VEN-001, TC-VEN-002, TC-VEN-003]
---

# Record that a person opened a record

## What It Does

The console sends an attributed visit after a tracked record becomes visible. The audit writer verifies current target authority, then stores a compact occurrence for later history and recent-record queries. Other record types can have their own reporting paths; custom-record API reads report server-generated visits separately.

## The Flow

```mermaid
flowchart TD
  H0["Start an occurrence when a screen first displays a record or changes to another; preserve it through tab changes."]
  H1["Report type, target and occurrence key; retry transport, throttle and server errors using that same key."]
  H0 --> H1
  H2["Require reporting authority and reject attribution fields, unsupported keys or an unregistered target type."]
  H1 --> H2
  H3["Resolve the target using the caller’s current read and dataset-query authority."]
  H2 --> H3
  H4["Insert with context attribution and server time, or return the same-target occurrence already stored."]
  H3 --> H4
  H5["The query layer independently joins latest visits to currently readable records for Recently Viewed."]
  H4 --> H5
```

## When Something Fails

Delivery failure is invisible to the person. Retryable failures retain the key, avoiding another stored visit after an uncertain response. Client refusals and exhausted retries lose the report. The browser fallback identity is not a UUID and is therefore rejected. Target resolution precedes the insert transaction, so no atomic lock spans authorization and activity insertion.

## Open Design Questions

Retention remains undecided, and failed delivery has no repair mechanism.
