Assistant: codex Assistant-Model: gpt-6-astra Assistant-Session: 01a10780-6d41-7952-9718-dfbc1f41236f
106 lines
6.5 KiB
Markdown
106 lines
6.5 KiB
Markdown
# Lifecycle foundation: first implementation contract
|
||
|
||
2026-10-04; `INFD-WP-0007-T02/T03`. This is the internal persistence slice of
|
||
`DecisionLifecycleService.md`. The protected application interface built on this
|
||
slice is documented in `LifecycleServiceContract.md`; the deployed private HTTP
|
||
adapter is specified in `LifecycleHTTPContract.md`.
|
||
|
||
## Implemented boundary
|
||
|
||
`LifecycleRepository` depends on the existing `Store`, memo and disposition
|
||
types. It imports no UI, HTTP runtime or provider client. It registers complete,
|
||
unbound version-one memos and creates immutable successor revisions. The existing
|
||
Decision Memo is reused without schema changes. Incomplete authoring drafts and
|
||
conversion of legacy/approval-bound records remain future work.
|
||
|
||
Each call carries `Attribution(tenant, Actor(subject, kind))` from its trusted
|
||
caller. This is audit attribution, not an authentication assertion. Like `Store`,
|
||
the repository is an internal persistence interface: the application/API
|
||
layer must authenticate callers and enforce current access-engine authorization
|
||
before invoking it. Supplying a tenant string is not an access grant. The deployed application adapters authenticate and authorize these operations;
|
||
this internal persistence interface is never exposed directly.
|
||
|
||
## Commands and reads
|
||
|
||
| Method | Contract |
|
||
| --- | --- |
|
||
| `register(context, memo, key=..., documents=...)` | Version 1, no approval binding or seal; rejects ids already present in the memo store; returns durable event id |
|
||
| `revise(context, memo, expected_version=..., summary=..., key=..., documents=...)` | Exact current revision precondition and next version; nonempty change summary; returns durable event id |
|
||
| `annotate(context, memo_id, version, kind=..., body=..., key=...)` | Append source/evaluation/comment to an existing exact revision; returns durable event id |
|
||
| `retrieve(tenant, memo_id, version=None)` | Retrieve current or historical memo only within its registered tenant |
|
||
| `events(tenant, memo_id, after=0, limit=100)` | Ordered cursor page of attributed immutable events, maximum 100 |
|
||
|
||
Registration returns the memo's existing caller-supplied id through retrieval;
|
||
generated public ids and canonical URLs are deferred to the API adapter. Memo ids
|
||
remain globally unique because existing review URLs and storage use that key.
|
||
An existing imported memo cannot be taken over by registration, even if its bytes
|
||
match. A tenant mismatch produces the same unavailable error as a missing record.
|
||
This is partition enforcement, not a replacement for per-record authorization.
|
||
|
||
Idempotency scope is tenant + actor kind/subject + operation + key. A SHA-256 of
|
||
canonical command JSON detects content conflicts, including changed revision
|
||
preconditions. Exact retries return the original event id even after subsequent
|
||
revisions. Different authors must use concurrency preconditions; matching content
|
||
alone never makes a stale edit succeed. The command bound is 256 KiB.
|
||
|
||
All writes use one SQLite transaction for memo content, immutable lifecycle event,
|
||
pending-delivery row and idempotency receipt. An event queue failure rolls back
|
||
the content mutation. Events contain actor, tenant, server timestamp, record and
|
||
revision; they do not record an authorization verdict or approval validity.
|
||
|
||
## Companion records
|
||
|
||
* Comment: `text`, optional `locator`. This is discussion, not a disposition.
|
||
* Source: `reference`, timezone-qualified `retrieved_at`, SHA-256 `digest`,
|
||
`summary`. These are supplied research provenance, not a claim that the service
|
||
fetched or verified the source. This slice performs no network fetching.
|
||
* Evaluation: `assessment`, `source_ids`, `uncertainty`. Referenced source event
|
||
ids must belong to this record, tenant and revision. Up to 100 references are
|
||
accepted. Empty references are allowed for an explicitly uncited assessment.
|
||
|
||
Fields are closed and text is bounded at 32,768 characters per field. Annotations
|
||
cannot silently enter `view_hash`. Promoting evidence into the commitment requires
|
||
a new memo revision using the existing packet/binding rules. Research comments
|
||
from agents never go through or weaken the human disposition guards.
|
||
|
||
## Migration, evidence and rollout
|
||
|
||
Store schema 4 adds `lifecycle_records`, `lifecycle_events`,
|
||
`lifecycle_operations` and `lifecycle_pending`; migrations from versions 1–3
|
||
preserve existing memo, presentation, disposition and evidence rows. Historic
|
||
records are not automatically registered or assigned a tenant. Existing store
|
||
backup includes lifecycle content and pending rows. Old code refuses schema 4;
|
||
rollback therefore needs restoration of a pre-migration backup and explicit
|
||
handling of intervening writes, not simply an old image against the new database.
|
||
|
||
Memo persistence is extracted into a shared transaction helper without changing
|
||
legacy `save_memo` behavior. Existing stale-presentation guards see the same memo
|
||
versions. Approval-bound/sealed revision handling requires the later review
|
||
integration; this repository refuses those lifecycle revision commands.
|
||
|
||
Pending lifecycle events are durable local obligations. The opt-in runtime audit
|
||
adapter stages immutable `informed-decision.lifecycle` commitments into the
|
||
existing transactional outbox and clears pending obligations only after a receiver
|
||
receipt. Content remains in local custody; the independent commitment contains
|
||
its digest and existence assertion. Readiness refuses while lifecycle delivery
|
||
is pending. The adapter is deployed for the initial audience. See
|
||
`LifecycleWorkspaceDelivery.md` for the platform tenant boundary, retries and
|
||
heartbeats.
|
||
|
||
## Delivery boundary
|
||
|
||
T02/T03/T04/T05 are complete for the initial production audience. The contracts
|
||
are specified in `LifecycleAdmissionBoundary.md`; foundation, HTTP and browser
|
||
adapters are deployed. Native intake admission is completed in INFD-WP-0005.
|
||
Expansion beyond these explicit limits is tracked in INFD-WP-0008. Legacy imported
|
||
records keep their existing review route and cannot be taken over by registration.
|
||
|
||
Packet command extension: optional `documents` carries digest/media_type/base64
|
||
content, verified and written in the same transaction as the memo/event. New
|
||
registration requires packet bytes; revisions may retain their current packet's
|
||
digests. The details and limits are in `LifecycleHTTPContract.md`. Idempotency
|
||
digests for prior commands with no documents remain compatible.
|
||
|
||
Schema 5 adds unique review-handoff indexes and prevents older code without
|
||
source-revision guards from opening the store. Migration preserves schema-4
|
||
records. See LifecycleReviewHandoff.md for the rollback boundary.
|