Skip to content

Cortex — Investigation Lifecycle

This spec adds lifecycle semantics and implementation guidance to the Investigation (InsightRoot) object: status semantics and transitions, entry-context requirements per mode, evidence lifecycle within an investigation, edition creation and sealing, accountability enforcement points, and session management. The Investigation, Block, Edition, and Event schemas are authoritative in component.cortex.decision-evidence.

What an Investigation Is

An Investigation IS a git-like repository of evidence-gathering activity; anchored to a subject entity (subject_ref required); the container for all blocks, events, editions, and tasks related to a decision; tracked via an append-only DAG event ledger; the parent object for editions (sealed decisions).

An Investigation IS NOT a workflow engine (no automatic state progression), a decision (editions are decisions), a signal, or deletable (investigations are append-only records; they can only be archived).

Cortex stores additional fields via additionalProperties (see component.cortex.decision-evidence#investigation.fields): linked_signal_ids, rationales, narrative (executive_summary, methodology, etc.), and ai_usage (token rollup auto-accumulated from events).

Investigation Lifecycle States

DRAFT ──review──> IN_REVIEW ──approve──> APPROVED ──publish──> PUBLISHED
  │                   │                                            │
  │                   └──reject──> DRAFT (re-opened)              │
  └───────────────── archive ──────────────────────────────── ARCHIVED
Status Meaning Typical Duration
draft Active — evidence being gathered, narrative written Hours to days
in_review Edition submitted for review — awaiting reviewer Hours
approved Reviewer approved — ready for attestation or publication Minutes to hours
published Complete, decision sealed and attested Terminal (permanent record)
archived Closed (completed, superseded, or abandoned) Terminal

Transition Rules

From Valid Targets Who Gate
draft in_review, archived Author Edition must exist for in_review
in_review approved, draft, archived Reviewer Review decision
approved published, in_review, archived Attester or author Attestation for published
published archived Admin/system Superseded by new investigation
archived — (terminal)

Implementation gap (#REQ.transition-validation): set_insight_status() does not validate transitions today (see §status). Intended:

VALID_INSIGHT_TRANSITIONS = {
    "draft": {"in_review", "archived"},
    "in_review": {"approved", "draft", "archived"},
    "approved": {"published", "in_review", "archived"},
    "published": {"archived"},
    "archived": set(),   # terminal
}
if new_status not in VALID_INSIGHT_TRANSITIONS.get(old_status, set()):
    return 409 INVALID_INVESTIGATION_TRANSITION

Status Drivers

Investigation status is driven by edition and signal lifecycle, not by direct user action on the status field.

Action Status Effect
User creates investigation draft
User submits edition for review (review_requested) in_review
Reviewer approves edition (review_closed, approved) approved
Reviewer rejects edition (review_closed, rejected) draft (re-opened)
Attester attests edition (attested) May → published
All linked signals resolved May → archived

Key principle: Tasks do NOT change investigation status. Tasks support decisions; they don't define them (see component.cortex.task-and-effect#investigation). Auto-advance must NOT be automatic — it is a suggestion or one-click UI action; automatic state changes violate "AI assists, humans decide."

Closure

An investigation reaches terminal state when all linked signals are resolved or dismissed, at least one edition is attested (or a no_action decision), and no open tasks remain. Closure is NOT automatic — it requires explicit human action.

Entry Modes

The four entry modes are defined in component.cortex.decision-evidence#investigation.entry-modes. Trigger and signal requirements:

Mode Required Trigger Type Requires Signal?
signal_driven signal Yes
curiosity_driven home, direct, api No
task_driven task No
decision_driven decision No

Entry Context

Every investigation has a required entry_context (schema in component.cortex.decision-evidence#investigation.entry-context). Before creation, Cortex checks the accountability pack via check_entry_mode(pack, mode) → 403 ACCOUNTABILITY_ENTRY_MODE_DENIED. Entry modes are controlled per role:

Role Allowed Entry Modes
RM signal_driven, curiosity_driven
Risk Manager signal_driven, curiosity_driven, task_driven
Treasury signal_driven, curiosity_driven
Auditor none (read-only)

INVARIANT: Every investigation MUST have a subject_ref with type and id. For signal-driven investigations the subject is copied from the signal's subject; for curiosity-driven the user specifies it.

Event Ledger

Events form a DAG via parent_event_id, with branch heads in heads (schema and append-only invariant in component.cortex.decision-evidence#event). All events are created via _append_event(), which generates a DES-compliant evt_ id, sets parent_event_id to the current branch head, writes to ES, and returns the event_id for head advancement. The main branch head always points to the latest event. Actor types (user, agent, system) and their legality are defined in component.cortex.decision-evidence#event.actor-legality.

Evidence Lifecycle Within an Investigation

Block lifecycle (transient → curated → frozen) is defined in component.cortex.decision-evidence#block.lifecycle. Within an investigation:

Auto-Journal

When an investigation is active (via start_investigation()), every MCP tool call that produces a block automatically creates the block with insight_id set to the active investigation, appends a block_created event, and starts the block as transient (user must explicitly pin to advance to curated). State lives in the Redis session as investigation_context.insight_id and the in-process ContextVar session_context.

Pinning

Pinning is a deliberate human action advancing a block to curated: pin_block(insight_id, block_id, rationale=...). It requires a rationale, validates the forward-only lifecycle transition, updates lifecycle_stage to curated, sets the block's insight_id, and appends block_pinned.

Freezing

Freezing happens at edition creation. When create_edition() is called: all blocks associated with the investigation (pinned + auto-journaled) are gathered; each non-frozen block is frozen (materialization_mode: frozen, lifecycle_stage: frozen); captured_at is set; result_hash (SHA-256) is computed from structural content; the block is written back; the evidence manifest is assembled with {block_id, title, digest, mode: frozen}. INVARIANT: a frozen block's content can NEVER change; result_hash is proof.

Edition Creation and Sealing

An investigation produces one or more editions; each is a sealed snapshot. Edition status lifecycle and content-hash rules are in component.cortex.decision-evidence#edition.

Creation Flow

create_edition(insight_id, decision_outcome, executive_summary, decision_statement, ...) steps: accountability pack validation (minimum evidence, decision type, decision template); gather all evidence blocks (pinned + auto-journaled by insight_id); freeze all blocks, compute digests → evidence_manifest; build narrative_snapshot; create EditionSnapshot in ES; backfill pinned_block_ids with auto-journaled blocks; append edition_created.

Freezing the Edition

freeze_edition_for_attestation(edition_id): verify all evidence blocks frozen; accountability validation (decision type, require rationale); compute content_hash = SHA256({insight_id, edition_number, evidence_manifest, narrative_snapshot, decision_metadata}); set frozen_at and frozen_by; append revision_committed.

Attestation

attest_edition(edition_id, attestation_type): verify the edition has a content_hash (must be frozen first); enforce separation of duties (attester ≠ author); accountability validation (attester role); build attestation record with content_hash_attested, confirmations, signature; set status to attested; append attested. INVARIANT: only actors of type user may attest; agents MUST NOT.

Accountability Pack Enforcement Points

The accountability pack is checked at multiple points (constraints in component.cortex.decision-evidence#accountability, pack schema in component.cortex.pack-reference#accountability):

Action Check Error Code
Create investigation check_entry_mode(pack, mode) ACCOUNTABILITY_ENTRY_MODE_DENIED
Create edition check_minimum_evidence(pack, evidence_count) ACCOUNTABILITY_EVIDENCE_INSUFFICIENT
Create edition check_decision_template(pack, template_id) ACCOUNTABILITY_TEMPLATE_NOT_ALLOWED
Freeze edition check_decision_type(pack, decision_type) ACCOUNTABILITY_DECISION_TYPE_DENIED
Freeze edition check_require_rationale(pack) ACCOUNTABILITY_RATIONALE_REQUIRED
Attest edition check_attester_role(pack, roles) ACCOUNTABILITY_ATTESTER_ROLE_DENIED

Fail-Closed Pattern

If a profile references an accountability pack but it can't be loaded → BLOCK (ACCOUNTABILITY_PACK_NOT_FOUND). If the pack exists, enforcement is mandatory. If no profile is configured → ALLOW (no accountability enforcement) — this enables development/testing without packs.

TheBank Enforcement Matrix

Role Entry Modes Min Evidence Decision Types Attester?
RM signal_driven, curiosity_driven 1 (default) action, no_action, deferred No (routes to Risk)
Risk signal_driven, curiosity_driven, task_driven 2 action, no_action, deferred, escalation Yes
Treasury signal_driven, curiosity_driven 1 (default) action, no_action, deferred No (routes to Risk)
Auditor none (read-only) No

Session Management

Investigation Session Binding

start_investigation() binds the investigation to the current MCP session: the Redis session store (store.update(sid, {"investigation_context": {"insight_id": insight_id}}), persists across requests) and the in-process ContextVar session_context (for same-request tool calls).

Auto-Journal Activation

With an active session, every subsequent tool call that creates a block checks session_context for investigation_context.insight_id, sets block.insight_id, and appends block_created — a complete audit trail without manual logging.

Idempotent Guard

start_investigation() has an idempotent guard: a session check (if an investigation is already active in the session, return it) and ES dedup (if a non-archived investigation already exists for the same trigger signal, return it). Both are bypassed with force_new=True.

Signal Linkage

Signals and investigations maintain bidirectional references: signal linked_insight_ids[] and investigation linked_signal_ids[].

Auto-link on creation: in start_investigation(), when trigger.type == "signal", _auto_link_signal() reads the signal and adds insight_id to linked_insight_ids; reads the insight and adds signal_id to linked_signal_ids; appends signal_linked with auto_linked: true; advances the insight head; writes back.

Manual link: link_signal(insight_id, signal_id, rationale) lets users link additional signals to an existing investigation — useful when a second signal fires for the same subject, an analyst discovers a related signal from a different policy, or cross-subject signals share a common root cause.

Implementation Status

What exists today: create_insight() (full entry_context validation); create_insight_from_signal() (auto-links); start_investigation() (session binding + idempotent guard + ES dedup); get_insight(), list_insights() (with dedup), count_insights(), get_insight_events() (filterable); set_insight_status() (NO transition validation); pin_block() (forward-only validation); update_insight_text() (5 narrative fields); link_signal() (bidirectional); create_edition(), freeze_edition_for_attestation() (computes content_hash), attest_edition() (separation of duties); event ledger DAG; actor types; auto-journal; accountability enforcement at all 6 points.

What's missing (priority order):

ID Gap Priority
I1 Investigation status transition validation P1 — correctness
I2 Auto-status suggestion from edition events (attested → published) P2 — UX
I3 Closure validation (all signals resolved, tasks complete) P2 — governance
I4 prompt_submitted event type (user chat messages during investigation) P2 — audit trail
I5 Branching support (multiple investigation threads) P4
I6 Investigation merge (combine parallel branch findings) P4
I7 Investigation handoff (transfer to another user/role) P3 — production

I4: the Beacon sidebar shows prompt_submitted events but Cortex doesn't capture them; every user chat message during an investigation should be a ledger event for complete auditability.


Depends on: component.cortex.decision-evidence, component.cortex.pack-reference

Realizes: product.block, product.edition, product.investigation