# Additive storage-session event profile

17 September 2026. Contract and accounting profile for the unpublished `1.2.0-draft.3` workbench. Profile identifier: **`storage_sessions_v1`**. This document supersedes the field suggestions in the earlier continuous-storage proposal for this scoped implementation. [Selected agreements, signed collection, managed execution and portable free evidence](STORAGE-INTEGRATION.md) now implement the bounded runtime path separately from these pure accounting rules.

## Explicit selection and exact fields

Both emitter and collector must explicitly select this profile. The top-level `event_profile` property is required and covered by the event signature. A draft.3 version alone does not select it. Existing draft.2 events and ordinary draft.3 free events retain their closed contracts and must reject these additional fields. The selected agreement integration binds `reporting_profile:"storage_sessions_v1"` in request, exact offer, signed acceptance and signed receipt; it never infers selection from a permissive parser or downgrades failed session reports to completed actions.

The event contains all ordinary draft.3 report fields plus `event_profile:"storage_sessions_v1"`. This scoped profile accepts only `event_type:"use.reported"`, `source:"client_reported"`, one action, and null `http`, `quantity` and `unit`. It adds no automatic duration charge or payable quantity. Asset references remain explicit and may be null; a URL alone is not an immutable asset/version claim.

The closed `operation` object is exactly:

```text
id, started_at, ended_at, state, kind, copy_context, storage
```

`kind` is `action` or `storage_session`. An action retains the existing in-progress/completed/failed semantics and has `storage:null`. A non-store action has `copy_context:null`. Every store action and storage session carries:

```json
{
  "copy_id": "<UUIDv4>",
  "acquired_at": "<whole-second UTC>",
  "retain_until": "<whole-second UTC or null>",
  "parent_write": null
}
```

When this is a copy, `parent_write` is a qualified reference `{reporter_id,event_id,payload_digest}` to the exact authenticated completed parent write. Initial acquisition time is still a client assertion, not an independently observed fact. A verified chain establishes consistency of that assertion, not the existence or absence of undisclosed copies.

A storage session has exactly `actions:["store"]`, no `derived_from` entries, and one `related_events` reference to its own completed write. It has a different operation ID from that write. Its `copy_context`, publisher/service/issuer/delegation, reporter, policy, agreement, resource, asset reference and purposes remain fixed. The write completion time equals the session's `started_at`. One reporter/copy ID cannot name multiple write operations or multiple sessions.

The session's closed `storage` object is exactly `{trigger,checkpoint_index,scheduled_at}`:

| Trigger | State and timing |
| --- | --- |
| `start` | `in_progress`, null end, index `0`; scheduled, occurred and started times equal the observed write completion timestamp. |
| `checkpoint` | `in_progress`, null end, positive index n; scheduled time is original session start + n × 86,400 seconds. Actual occurrence is at or after that schedule, within its corresponding daily interval. |
| `cessation` | `completed`, null index; scheduled, occurred and ended times equal the reported observed cessation time. |

There is no failed terminal session that implies deletion. Failed or uncertain cleanup leaves custody open/unresolved. A checkpoint cannot occur at or after cessation. A completed write is never reopened as a session.

## Accepted terms and retained-copy inheritance

The pure reducer takes one externally authenticated duty context:

```python
terms = {
    "agreement_id": "<UUIDv4 or None for a static-policy duty>",
    "report_deadline_seconds": 300,
    "retention_seconds": 600,        # None when absent
    "use_expires_at": "<UTC>"       # None when absent
}
```

The adapter must derive these values from the applicable accepted agreement/policy and selected reporting endpoint; it must not accept a reporter's substitute terms. For each copy, `retain_until` must equal the earlier of acquisition + retention seconds and accepted use expiry. Null is valid only when both limits are absent. Parent and child copy references must resolve by exact bytes and authenticated reporter identity, with matching scope, original acquisition time and deadline. Missing, altered, cyclic or overlong parent evidence remains unresolved. The bounded chain limit is 64 writes; batches contain at most 4,096 intake records.

An expired or zero-retention custody assertion can remain evidence of a client's claim. The reducer exposes overdue custody; it grants no permission to perform that storage. It does not convert an overdue deadline into an automatic deletion event. Different agreement contexts must be reduced separately; cross-agreement copying is not inferred by this profile.

## Reporting deadlines and coverage

Start and cessation reporting deadlines use their trigger times. Checkpoint deadlines use the **original scheduled** time, never a late observation or retry time. A verified intake received exactly at its deadline is timely. A report received afterward is late. An expected report with no qualifying intake is pending through its deadline and missing after it.

A late wake can report its current index with its actual observation time. It cannot manufacture reports for earlier intervals. If actual cessation occurs exactly at a checkpoint boundary, that boundary's checkpoint is discharged; earlier gaps remain. When cleanup fails and the copy remains, the checkpoint still becomes due. Custody beyond the retention deadline remains visible even when access has expired.

Every known session with consistent, resolved write context contributes expected checkpoints through `as_of` or its reported cessation. A missing start report is exposed separately and does not remove its known schedule from the denominator. Contradictory session identity or unresolved write/parent evidence is counted in an unresolved cohort, excluded from resolved coverage and never labelled complete. Reports received after `as_of` cannot repair the historical denominator. Exact retries preserve one event; different bytes under one reporter/event ID conflict. All distinct conflicting variants remain in quarantine accounting, deduplicated per exact payload, so arrival order cannot hide a session or change the retry count. No conflicting variant can resolve a write reference. A different event ID for an occupied session trigger/index also conflicts.

The public metrics distinguish completed **action claims**, sessions known/started/closed, unresolved sessions and expected/timely/late/missing/pending checkpoints. Session cessation does not add a completed action. Missing/pending indices use compressed inclusive ranges, avoiding allocation of one row per day for arbitrarily old claims. An unusually large reporting deadline beyond the supported four-digit-year calendar is represented in analytical output as `deadline_at:null` with `deadline_after_supported_calendar:true`; integer duration comparisons still determine timeliness. Claimed session duration is not a measurement of continuous activity, bytes consumed, model use, or billable time.

Wire timestamps are whole-second UTC. The managed executor preserves original high-resolution acquisition/cleanup observations and uses conservative retention rounding. Its [integration guide](STORAGE-INTEGRATION.md) specifies the less-than-one-second conservative cessation bound when two ordered observations share a wire second. Passing whole-second semantic tests is not proof of timer precision or physical erasure.

## Implemented APIs and verification

`profiles/storage_sessions.py` provides `validate_event(value)` and:

```python
reduce_records(records, as_of=utc_time, terms=terms, expected_binding=B)
# record = {payload: exact_bytes, received_at: verified_collector_time,
#           authenticated_reporter_id: verified_reporter_identity}
```

`expected_binding`, when supplied, is exactly the ordinary five-field draft.3 binding. The trusted adapter must verify reporter signature, collector signature/time, current or retained authority as appropriate, and exact intake payload before supplying records. Merely labelling an input `authenticated_reporter_id` does not authenticate it. Do not mix intakes from different reporting endpoints to manufacture the earliest successful delivery. The reducer validates the profile and then reuses unchanged ordinary report semantics on a deliberately normalized internal copy; it never accepts legacy wire bytes as this profile.

`verification/storage-session-verifier.mjs` independently implements event semantics and `verifyStorageSession(records,{asOf,terms,expectedBinding})` for one session plus exact write/parent evidence. It uses native Node and the separately implemented strict JSON primitives; it imports no Python validator. Unlike the batch reducer's quarantine output, it rejects contradictory or unresolved evidence. Neither API performs physical observation or cryptographic authentication itself.

Executed checks: **26 Python tests**, **14 independent Node tests**, and **11 shared wire cases** (5 positive, 6 negative) accepted/rejected consistently by strict AJV and the Node semantic validator. Commands:

```sh
PYTHONPATH=tests:. python3 -m unittest test_storage_sessions -v
node --test verification/test_storage_session_verifier.mjs
node verification/storage-session-verifier.mjs profiles/fixtures/storage-session-cases.json
node verification/check_profiles.cjs /absolute/path/to/ajv profiles/fixtures/storage-session-cases.json
```

Original structural results are retained in `verification/storage-schema-validation.json`. The current [verification report](../verification/REPORT.md) records the integrated agreement, collector, executor and selected free archive checks. Native asset closure, paid storage portability, operational provider migration and final integrated review remain open; these accounting tests alone never establish those capabilities.
