1.2.0-rc.1 / Evidence and duties
Continuing storage
Select and execute supported custody and reporting duties with inherited deadlines.
This 1.2.0-draft.3 capability extends the storage-session wire profile through signed agreements, managed custody, authenticated collection and an inert evidence archive. It is not a claim of general visibility into an agent or LLM. See current validation and release status for this implementation candidate.
Explicit acceptance
profiles/storage_contracts.py defines two explicitly selected variants: FREE and PAID. The offer request, exact offer, signed acceptance and signed receipt each require reporting_profile:"storage_sessions_v1". The report itself uses event_profile:"storage_sessions_v1". The schema is profiles/storage-agreement.schema.json.
The ordinary free/paid validators remain closed and reject the extra field. A version number alone never opts an agreement in. Internal projections reuse unchanged base semantics, but every transmitted digest and signature binds the original selected-profile bytes. A projected offer is never used as its accepted digest. Status, token and introspection envelopes retain their base shapes and correlate against the selected receipt.
StorageFreeService and StoragePaidService require their separate StorageFreeStore and StoragePaidStore classes. Their SQLite format markers are storage-agreement-1 and storage-paid-agreement-1. A new store is required; there is no implicit in-place migration from an ordinary free/paid database. These services require exactly one accepted report_usage destination, equal to the selected service’s events endpoint. An offer with additional reporting destinations is rejected instead of dropping duties. Different deadlines for the same destination have already been combined by the policy evaluator.
Signed client reports require the selected event profile and authenticated agent identity. Gateway-origin reports retain the ordinary source-qualified gateway envelope. The collector derives the retention limit and use expiry from its stored accepted offer/receipt; reports cannot replace them. Reports can describe late or expired activity without granting permission to perform it. The paid variant preserves the separate payer mandate and confirmed-payment access gate. Selecting storage reporting does not add a fee or spending mandate.
storage_metrics(agreement_id, as_of=...) is a trusted in-process owner API over retained authenticated intakes. Its authenticated_client_assertions result is not an unauthenticated public analytics endpoint. Conflicts and absent exact parents remain unresolved.
Node managed executor
clients/storage_executor.mjs exports StorageExecutor and STORAGE_PROFILE. The host supplies an authenticated accepted context, a dedicated private directory, one consistent epoch-millisecond clock, and trusted signing/publication adapters:
const executor = await StorageExecutor.open({
context, // verified accepted identity, scope, reporting and retention terms
storageDir: '/private/managed-agreement-directory',
clock, // { now: () => epochMilliseconds }, also used for acquiredAtMs
signEvent, // exact payload Buffer -> compact ES256 event JWS
publishEvent // verify collector authority/signature/exact intake first
});
const first = await executor.store({ bytes, acquiredAtMs });
const second = await executor.copy(first);
await executor.release(first);
await executor.release(second);
await executor.flush();
await executor.close();
The context has exactly event_profile,binding,reporter_id,agreement_id,resource_url,policy_id,policy_revision,actions,purposes,terms. binding contains the five draft-3 origin/service/issuer/delegation fields. terms is exactly report_deadline_seconds,retention_seconds,use_expires_at; the agreement ID is already top-level. Derive these terms from the verified accepted exchange and its appointed collector, never from an incoming usage claim.
The executor supports reversible retrieve/store scopes. It refuses model-training/fine-tuning purposes and transform operations. store/memoryStore create owned copies of a caller’s Buffer; copy/memoryCopy preserve the exact original acquisition instant and retention deadline. A new child receives a new copy/write/session identity and exact parent-write reference. The maximum evidence lineage is 64 writes. Existing caller buffers and external caches remain the caller’s separate custody.
The journal preserves high-resolution acquisition, write and cleanup observations. Retention uses the earlier of floor-to-second acquisition plus the accepted retention interval and accepted use expiry. This rounds towards earlier cleanup rather than extending retention. A zero/exhausted retention interval refuses new storage. Local managed memory is zeroed before release and disk custody ends after successful removal and directory synchronization. This is not secure physical erasure of filesystem backups, swap, heap remnants or untracked copies.
Whole-second wire timestamps cannot express two ordered observations within one second. If a checkpoint precedes cessation in the same second, this executor reports cessation at the next whole-second boundary and retains the actual earlier cleanup instant separately in its journal. It waits until that wire time before publishing. This conservative upper bound can overstate reported custody by less than one second; it does not delay local cleanup or prove subsecond ordering remotely. Wire reporting deadlines use the reported whole-second trigger; local retention uses the original high-resolution observation and conservative deadline. Implementers must disclose this resolution limit rather than claim millisecond conformance from the wire.
Durability, scheduling and failures
A durable intent precedes a managed write. The completed-write action and separate session-start outbox entries commit in one journal replacement. Exact signed payload/JWS bytes persist for retries. Files and directory entries are synchronized; directory/file permissions must be private and owned, and symlinks/hard links are rejected. One live executor owns the journal. Its checksum detects accidental corruption; this is not protection against a privileged local attacker, rollback of the entire directory or PID reuse.
On restart, a matching disk file resumes the known session. A lost/altered file, unfinished write/delete intent or lost in-memory custody becomes uncertain; no completed write or deletion is invented. Unresolved custody blocks dependent storage/copy operations. Cleanup of known active custody remains possible. Journal and report limits fail closed without dropping evidence: 4,096 copies, 32,768 outbox entries, 16 MiB journal and 16 MiB per copy. A crash while acquiring the recovery guard may require explicit operator recovery; the executor does not guess that a surviving guard is safe to remove.
tick() observes due checkpoints and expired custody; flush() ticks, signs and attempts a bounded outbox batch. The host must schedule them while custody remains active, supervise restarts and drain pending reports. There is no hidden background timer. A missed earlier daily checkpoint stays missing; a late wake emits only its currently observed interval. Required missing/late reports or missing past checkpoints block further dependent storage/copy operations. Operating-system suspension can cause late cleanup, which remains visible through retentionOverdue; no implementation can honestly claim timely deletion while its process did not run.
close() releases owned active memory, signs pending observations, persists and relinquishes its lock. It does not delete active disk copies or guarantee network delivery; reopening with the exact accepted context resumes them. The application must release disk handles and flush if it intends to end custody. The host must retain and honour use/retention terms even after access-token expiry.
signEvent is trusted to use the authenticated reporter key; the executor checks exact payload preservation, not its adapter’s cryptographic honesty. publishEvent receives {payload,jws,eventId,payloadDigest,signal} and must authenticate the selected collector, current origin authority and exact signed intake before returning {payload_digest,reporter_id,received_at,intake_jws}. A timeout, mismatch or lost acknowledgement stays pending with the same exact event/JWS. Honour the abort signal where the transport allows it; a late remote completion cannot become a different report. The reference TLS adapter lives in verification/free-client.mjs; it is a bounded demonstration, not a universal agent networking SDK.
Closed portable free storage evidence
profiles/storage_evidence.py exposes a separate archive profile, odexa-storage-free-evidence-snapshot-1, through build_bundle, verify_bundle, ExternalTrust and StorageEvidenceArchive. It accepts only the selected free store. Ordinary free import/export still rejects it. Selected paid storage uses the explicit paid archive variants, including an asset-aware variant; it cannot be imported through the free entry point.
The ordinary archive’s trust and immutable import rules apply. The selected offer/assent/receipt must validate against exact original bytes. Every session’s own write and every non-null copy_context.parent_write must resolve by reporter ID, event ID and exact payload digest inside the signed inventory. A parent reference is checked even when it is not duplicated in related_events. Missing parents fail export or import. Authenticated but conflicting lifecycle claims remain in the archive and are separately classified as unresolved; verification does not turn them into complete storage coverage.
Metrics derive reporting/retention/use terms from the verified accepted offer/receipt and its historically appointed collector. The result is labelled authenticated_client_claims_with_closed_exact_parent_references; downstream_use_verified and reactivates_access remain false. The source scope is all_retained_storage_free_material_for_one_agreement_at_sqlite_snapshot. Asset/derivation closure remains unsupported and is rejected. No credentials, bearer grants or operational agreement state are imported into the inert archive.
Reproduce the integrated checks
PYTHONPATH=tests:. python -m unittest test_storage_contracts test_storage_service test_storage_evidence test_storage_runtime -v
node --test verification/test_storage_executor.mjs verification/test_storage_session_verifier.mjs
The actual Node/Python TLS flows cover direct operation with no provider calls and explicit two-origin delegation. They verify selected assent, protected retrieval, three actual managed copies, restart, original deadlines, cleanup, nine signed storage acknowledgements, export/import and revocation. Controlled-clock executor tests cover daily checkpoints, subsecond boundaries, actual process death after a write, missing-file recovery, exact retry after lost acknowledgement, managed-memory cleanup and the 64-write bound. The ordinary free/paid/portable suites remain regression requirements.
Use current validation for executed commands, current counts and scope. A separate asset/storage archive variant closes native publication dependencies; the ordinary storage archive remains explicitly asset-free. Paid portability uses separately selected profiles. The handover procedure defines a limited predecessor collector phase that preserves original storage deadlines. The observation module preserves declared boundaries and known expected-report gaps without resetting those deadlines. These capabilities retain distinct contracts and evidence; publication gates are listed in release status.