---
id: ATS-PFM-OPS-008
scope: CAP-PFM-OPS-008
product_area: platform
module: operations
revision: 2
status: proposed
approved_by: null
approved_at: null
source_sha:
  services-platform: ad3f6a6582843008ed0f5a17b131878d15829af1
  react-console: baf6dae382d6f41dce976c7f848667ccc37e076f
  macroservice-libraries: 54536a441935c6e09fdb7f96f0bec71c52ba4e03
scenarios:
  - id: ATS-PFM-OPS-008-01
    title: Reject an unsupported viewed type
    level: api
    proves:
      - BR-CAP-PFM-OPS-008-01
    preconditions: The supplied record type is outside the registry.
    steps: Report a visit.
    expected: The refusal names supported types without creating an event.
    negative: true
    data_needs: Controlled records and caller contexts matching this scenario’s preconditions.
    bound_to:
      - TST-PFM-OPS-060
    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
    evidence_candidates:
      - >-
        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
    category: validation
    binding_evidence:
      - >-
        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
  - id: ATS-PFM-OPS-008-02
    title: Authorize the target before recording
    level: api
    proves:
      - BR-CAP-PFM-OPS-008-02
      - INV-DOM-PFM-OPS-002-06
    preconditions: Reports lack record-read or dataset-query authority or name another workspace’s record.
    steps: Submit the reports.
    expected: Forbidden or missing targets produce no activity row.
    negative: true
    data_needs: Controlled records and caller contexts matching this scenario’s preconditions.
    bound_to:
      - TST-PFM-OPS-060
    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
    evidence_candidates:
      - >-
        services-platform:modules/audit/tests/integration/services/record-views/record-view.integration.spec.js#writes
        nothing at all for any of those refusals
    category: permission
    binding_evidence:
      - >-
        services-platform:modules/audit/tests/integration/services/record-views/record-view.integration.spec.js#writes
        nothing at all for any of those refusals
  - id: ATS-PFM-OPS-008-03
    title: Use organization readership for organization visits
    level: api
    proves:
      - BR-CAP-PFM-OPS-008-03
    preconditions: The caller has only contact readership.
    steps: Report an organization visit and repeat with the required authority.
    expected: The first report fails and the authorized case succeeds.
    negative: true
    data_needs: Controlled records and caller contexts matching this scenario’s preconditions.
    bound_to:
      - TST-PFM-OPS-060
    provenance: test
    source: >-
      services-platform:modules/audit/tests/integration/services/record-views/record-view.integration.spec.js#refuses
      an organization when the caller holds only contact read, because opening one needs organization
      read
    evidence_candidates:
      - >-
        services-platform:modules/audit/tests/integration/services/record-views/record-view.integration.spec.js#refuses
        an organization when the caller holds only contact read, because opening one needs organization
        read
    category: permission
    binding_evidence:
      - >-
        services-platform:modules/audit/tests/integration/services/record-views/record-view.integration.spec.js#refuses
        an organization when the caller holds only contact read, because opening one needs organization
        read
  - id: ATS-PFM-OPS-008-04
    title: Reject client-supplied attribution
    level: unit
    proves:
      - BR-CAP-PFM-OPS-008-04
      - INV-DOM-PFM-OPS-002-05
    preconditions: 'Report bodies supply actor, workspace, event time, event type or identity.'
    steps: Submit every altered body.
    expected: Each forbidden field receives a validation refusal.
    negative: true
    data_needs: Controlled records and caller contexts matching this scenario’s preconditions.
    bound_to:
      - TST-PFM-OPS-062
    provenance: test
    source: >-
      services-platform:modules/audit/tests/unit/aws/api/audit-api-mapper.spec.ts#refuses a body naming
      the actor, which is how a trail would be written in somebody else’s name
    evidence_candidates:
      - >-
        services-platform:modules/audit/tests/unit/aws/api/audit-api-mapper.spec.ts#refuses a body naming
        the actor, which is how a trail would be written in somebody else’s name
    category: validation
    binding_evidence:
      - >-
        services-platform:modules/audit/tests/unit/aws/api/audit-api-mapper.spec.ts#refuses a body naming
        the actor, which is how a trail would be written in somebody else’s name
  - id: ATS-PFM-OPS-008-05
    title: Separate delivery retries from new visits
    level: api
    proves:
      - BR-CAP-PFM-OPS-008-05
      - INV-DOM-PFM-OPS-002-04
    preconditions: A visit is already recorded.
    steps: 'Repeat its delivery, submit a fresh key and reuse the old key for another record.'
    expected: >-
      The repeat resolves to the original, the fresh key creates another event and conflicting reuse
      fails.
    negative: true
    data_needs: Controlled records and caller contexts matching this scenario’s preconditions.
    bound_to:
      - TST-PFM-OPS-060
    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
    evidence_candidates:
      - >-
        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
    category: concurrency
    binding_evidence:
      - >-
        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
  - id: ATS-PFM-OPS-008-06
    title: Normalize keys within person and workspace
    level: api
    proves:
      - BR-CAP-PFM-OPS-008-06
    preconditions: A lower-case visit key is stored.
    steps: Repeat in upper case and submit under another actor and workspace.
    expected: Case changes retain one visit while other actor and workspace scopes admit their own events.
    negative: true
    data_needs: Controlled records and caller contexts matching this scenario’s preconditions.
    bound_to:
      - TST-PFM-OPS-058
    provenance: test
    source: >-
      services-platform:modules/audit/tests/integration/services/persistence/audit-events.integration.spec.js#lower-cases
      the occurrence key, so one visit cannot become two by changing its case
    evidence_candidates:
      - >-
        services-platform:modules/audit/tests/integration/services/persistence/audit-events.integration.spec.js#lower-cases
        the occurrence key, so one visit cannot become two by changing its case
    category: invariant
    binding_evidence:
      - >-
        services-platform:modules/audit/tests/integration/services/persistence/audit-events.integration.spec.js#lower-cases
        the occurrence key, so one visit cannot become two by changing its case
  - id: ATS-PFM-OPS-008-07
    title: Reject unsafe location metadata
    level: api
    proves:
      - BR-CAP-PFM-OPS-008-07
    preconditions: 'Metadata contains unknown keys, excessive length or invalid token spelling.'
    steps: Submit each report.
    expected: Validation names the rejected key and leaves no event.
    negative: true
    data_needs: Controlled records and caller contexts matching this scenario’s preconditions.
    bound_to:
      - TST-PFM-OPS-058
    provenance: test
    source: >-
      services-platform:modules/audit/tests/integration/services/persistence/audit-events.integration.spec.js#refuses
      a metadata value outside the token grammar, naming the key
    evidence_candidates:
      - >-
        services-platform:modules/audit/tests/integration/services/persistence/audit-events.integration.spec.js#refuses
        a metadata value outside the token grammar, naming the key
    category: boundary
    binding_evidence:
      - >-
        services-platform:modules/audit/tests/integration/services/persistence/audit-events.integration.spec.js#refuses
        a metadata value outside the token grammar, naming the key
  - id: ATS-PFM-OPS-008-08
    title: Track navigation rather than rendering
    level: browser
    proves:
      - BR-CAP-PFM-OPS-008-08
    preconditions: A readable contact screen has an established visit.
    steps: 'Change tabs, reload, leave and return, then open an unreadable target.'
    expected: Only new successful navigations add visits.
    negative: false
    data_needs: Controlled records and caller contexts matching this scenario’s preconditions.
    bound_to:
      - TST-PFM-OPS-048
    provenance: test
    source: >-
      react-console:e2e/controlled/record-views.controlled.spec.ts#switching tab on the same record is
      still one visit
    evidence_candidates:
      - >-
        react-console:e2e/controlled/record-views.controlled.spec.ts#switching tab on the same record is
        still one visit
    category: rule
    binding_evidence:
      - >-
        react-console:e2e/controlled/record-views.controlled.spec.ts#switching tab on the same record is
        still one visit
  - id: ATS-PFM-OPS-008-09
    title: Hide reporting outages
    level: browser
    proves:
      - BR-CAP-PFM-OPS-008-09
    preconditions: Visit reporting encounters retryable network errors.
    steps: Navigate to a record and allow the retry budget to run.
    expected: The record remains usable without an audit error and retries retain one key.
    negative: true
    data_needs: Controlled records and caller contexts matching this scenario’s preconditions.
    bound_to:
      - TST-PFM-OPS-048
    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
    evidence_candidates:
      - >-
        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
    category: failure
    binding_evidence:
      - >-
        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
  - id: ATS-PFM-OPS-008-10
    title: Record custom-record reads on the server
    level: api
    proves:
      - BR-CAP-PFM-OPS-008-10
    preconditions: A readable custom record exists.
    steps: Fetch it through the custom-record endpoint.
    expected: An attributed event carries the server’s key and location metadata.
    negative: false
    data_needs: Controlled records and caller contexts matching this scenario’s preconditions.
    bound_to: []
    provenance: proposed
    source: 'services-platform:modules/custom-objects/aws/api/custom-object-records.routes.ts:recordViewEvent'
    category: rule
  - id: ATS-PFM-OPS-008-11
    title: Keep Recent Items personal across real services
    level: browser
    proves:
      - AS-JNY-PFM-OPS-003-01
      - AS-JNY-PFM-OPS-003-02
    preconditions: Two people and test contacts exist in the development workspace.
    steps: One person visits the contacts and both inspect Recent Items.
    expected: Only that person receives the visited records in recent order.
    negative: false
    data_needs: Controlled records and caller contexts matching this scenario’s preconditions.
    bound_to:
      - TST-PFM-OPS-052
    provenance: test
    source: >-
      react-console:e2e/dev/record-views.dev.spec.ts#the records a person opens are recorded in the
      Platform and read back by the header
    evidence_candidates:
      - >-
        react-console:e2e/dev/record-views.dev.spec.ts#the records a person opens are recorded in the
        Platform and read back by the header
      - >-
        react-console:e2e/dev/record-views.dev.spec.ts#somebody else's Recent Items holds none of this
        person's records
    category: interaction
    binding_evidence:
      - >-
        react-console:e2e/dev/record-views.dev.spec.ts#the records a person opens are recorded in the
        Platform and read back by the header
      - >-
        react-console:e2e/dev/record-views.dev.spec.ts#somebody else's Recent Items holds none of this
        person's records
  - id: ATS-PFM-OPS-008-12
    title: Read a retried visit only once
    category: interaction
    level: api
    proves:
      - BR-CAP-PFM-OPS-008-05
      - BR-CAP-PFM-OPS-009-02
    exercises:
      - CAP-PFM-OPS-008
      - CAP-PFM-OPS-009
    preconditions: A person has successfully opened a readable record and retains its visit key.
    steps: Replay the visit report and then read that person’s raw history.
    expected: The duplicate response and history refer to one stored visit.
    negative: false
    data_needs: Isolated workspace records and caller contexts for both sides of the interaction.
    bound_to: []
    provenance: proposed
    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;
      services-platform:modules/audit/tests/integration/services/record-views/view-history.integration.spec.js#still
      holds the view after the contact is gone, because a trail is not a list of records;
      CAP-PFM-OPS-009; WF-PFM-OPS-008
  - id: ATS-PFM-OPS-008-13
    title: Write a visit into the protected activity trail
    category: interaction
    level: api
    proves:
      - BR-CAP-PFM-OPS-008-04
      - BR-CAP-PFM-OPS-010-03
    exercises:
      - CAP-PFM-OPS-008
      - CAP-PFM-OPS-010
    preconditions: A person opens a readable record using verified execution context.
    steps: >-
      Report the visit, attempt to rewrite its stored activity with ordinary application privileges, and
      read the retained event.
    expected: The retained event identifies the original visit and caller despite the refused rewrite.
    negative: false
    data_needs: Isolated workspace records and caller contexts for both sides of the interaction.
    bound_to: []
    provenance: proposed
    source: >-
      services-platform:modules/audit/tests/unit/aws/api/audit-api-mapper.spec.ts#refuses a body naming
      the actor, which is how a trail would be written in somebody else’s name;
      services-platform:modules/audit/tests/integration/db/audit-events.table.integration.spec.js#refuses
      an UPDATE of the event by the application role, so a trail cannot be rewritten; CAP-PFM-OPS-010;
      WF-PFM-OPS-008
evidence:
  - >-
    Pinned capability rules, object invariants, state transitions and workflows are reviewed for testing
    obligations.
  - >-
    Executable bindings refer only to existing suites; no module executable suite runs during this
    documentation job.
coverage:
  rule: covered
  transition: not-applicable
  validation: covered
  permission: covered
  edge: 'gap: FND-PFM-OPS-060'
  failure: covered
  boundary: covered
  concurrency: covered
  invariant: covered
  interaction: covered
coverage_notes:
  transition: >-
    Visit reporting appends an immutable occurrence without changing a stored lifecycle state. Workspace
    retirement belongs to the shared activity-trail capability.
  edge: 'Empty, missing and unusual inputs need dedicated proof.'
related_capabilities:
  - CAP-PFM-OPS-009
  - CAP-PFM-OPS-010
workflow_interactions:
  - WF-PFM-OPS-008
coverage_obligations:
  finding: FND-PFM-OPS-060
  uncovered_must_prove: []
  object_invariants_requiring_explicit_trace:
    - INV-DOM-PFM-OPS-002-01
    - INV-DOM-PFM-OPS-002-02
    - INV-DOM-PFM-OPS-002-03
    - INV-DOM-PFM-OPS-002-07
    - INV-DOM-PFM-OPS-002-08
    - INV-DOM-PFM-OPS-002-09
  related_scenarios:
    - capabilities:
        - CAP-PFM-OPS-008
        - CAP-PFM-OPS-009
      scenario: ATS-PFM-OPS-008-12
    - capabilities:
        - CAP-PFM-OPS-008
        - CAP-PFM-OPS-010
      scenario: ATS-PFM-OPS-008-13
  unbound_scenarios:
    - ATS-PFM-OPS-008-10
    - ATS-PFM-OPS-008-12
    - ATS-PFM-OPS-008-13
  transition_review: []
  permission_review:
    - key: platform.record-views.create
      where_checked: api
      provenance: unknown
      source: FND-PFM-OPS-060
      note: >-
        Scenario-level audience checks do not establish every grant combination at each enforcement
        surface.
    - key: crm.contacts.read
      where_checked: service
      provenance: unknown
      source: FND-PFM-OPS-060
      note: >-
        Scenario-level audience checks do not establish every grant combination at each enforcement
        surface.
    - key: crm.organizations.read
      where_checked: service
      provenance: unknown
      source: FND-PFM-OPS-060
      note: >-
        Scenario-level audience checks do not establish every grant combination at each enforcement
        surface.
    - key: committees.committees.read
      where_checked: service
      provenance: unknown
      source: FND-PFM-OPS-060
      note: >-
        Scenario-level audience checks do not establish every grant combination at each enforcement
        surface.
  gap_detail: >-
    Navigation around a removed or newly unreadable target needs additional edge proof; public reporting
    and immutable storage have separate enforcement boundaries.
workflow_scenarios:
  WF-PFM-OPS-008:
    - ATS-PFM-OPS-008-13
---

# Remember a person’s visits to records: Capability Test Specification

## Coverage

The category map distinguishes authored scenarios from unresolved proof obligations. A bound scenario names an existing executable suite; its recorded result remains unrun.

| Rule | Proof status |
|---|---|
| `BR-CAP-PFM-OPS-008-01` | `ATS-PFM-OPS-008-01` |
| `BR-CAP-PFM-OPS-008-02` | `ATS-PFM-OPS-008-02` |
| `BR-CAP-PFM-OPS-008-03` | `ATS-PFM-OPS-008-03` |
| `BR-CAP-PFM-OPS-008-04` | `ATS-PFM-OPS-008-04`, `ATS-PFM-OPS-008-13` |
| `BR-CAP-PFM-OPS-008-05` | `ATS-PFM-OPS-008-05`, `ATS-PFM-OPS-008-12` |
| `BR-CAP-PFM-OPS-008-06` | `ATS-PFM-OPS-008-06` |
| `BR-CAP-PFM-OPS-008-07` | `ATS-PFM-OPS-008-07` |
| `BR-CAP-PFM-OPS-008-08` | `ATS-PFM-OPS-008-08` |
| `BR-CAP-PFM-OPS-008-09` | `ATS-PFM-OPS-008-09` |
| `BR-CAP-PFM-OPS-008-10` | `ATS-PFM-OPS-008-10` |

## Interactions With Related Capabilities

Keep Recent Items personal across real services. Only that person receives the visited records in recent order.

Read a retried visit only once. The duplicate response and history refer to one stored visit.

Write a visit into the protected activity trail. The retained event identifies the original visit and caller despite the refused rewrite.

## Evidence and Boundaries

Pinned assertion sources support the listed executable bindings. Proposed cases retain the proof required beyond those boundaries. Structured obligations name incomplete object, transition and permission trace; row-level concurrency proof does not establish complete workflow serialization.
