| /* | |
| * Licensed to the Apache Software Foundation (ASF) under one | |
| * or more contributor license agreements. See the NOTICE file | |
| * distributed with this work for additional information | |
| * regarding copyright ownership. The ASF licenses this file | |
| * to you under the Apache License, Version 2.0 (the | |
| * "License"); you may not use this file except in compliance | |
| * with the License. You may obtain a copy of the License at | |
| * | |
| * http://www.apache.org/licenses/LICENSE-2.0 | |
| * | |
| * Unless required by applicable law or agreed to in writing, | |
| * software distributed under the License is distributed on an | |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | |
| * KIND, either express or implied. See the License for the | |
| * specific language governing permissions and limitations | |
| * under the License. | |
| */ | |
| -- Canonical facts and durable control state. Disposable projections are built at startup. | |
| CREATE TABLE event_log ( | |
| sequence INTEGER PRIMARY KEY AUTOINCREMENT CHECK(sequence > 0), | |
| event_id TEXT NOT NULL UNIQUE, | |
| invocation_id TEXT, | |
| kind TEXT NOT NULL, | |
| operation_id TEXT, | |
| -- Keep compact accounting fields before potentially large JSON overflow | |
| -- pages. SQLite derives them from canonical bytes; they cannot be edited. | |
| accounting_at REAL GENERATED ALWAYS AS ( | |
| CASE WHEN kind IN ('model_requested', 'model_completed', 'model_interrupted', | |
| 'invocation_ended', 'auxiliary_model_started', 'auxiliary_model_settled', | |
| 'tool_dispatched', 'tool_settled', 'tool_rejected') | |
| THEN COALESCE(json_extract(COALESCE(event_json, retained_json), '$.recorded_at.secs_since_epoch'), | |
| json_extract(COALESCE(event_json, retained_json), '$.started_at.secs_since_epoch'), | |
| json_extract(COALESCE(event_json, retained_json), '$.completed_at.secs_since_epoch')) * 1000.0 | |
| + COALESCE(json_extract(COALESCE(event_json, retained_json), '$.recorded_at.nanos_since_epoch'), | |
| json_extract(COALESCE(event_json, retained_json), '$.started_at.nanos_since_epoch'), | |
| json_extract(COALESCE(event_json, retained_json), '$.completed_at.nanos_since_epoch')) / 1000000.0 | |
| END | |
| ) STORED, | |
| accounting_usage TEXT GENERATED ALWAYS AS ( | |
| CASE | |
| WHEN kind = 'model_completed' THEN json_extract(COALESCE(event_json, retained_json), '$.fact.output.usage') | |
| WHEN kind = 'model_observed' AND json_extract(COALESCE(event_json, retained_json), '$.fact.event.kind') = 'finished' | |
| THEN json_extract(COALESCE(event_json, retained_json), '$.fact.event.data.usage') | |
| WHEN kind = 'auxiliary_model_usage' THEN json_extract(COALESCE(event_json, retained_json), '$.usage') | |
| END | |
| ) STORED, | |
| invocation_model TEXT GENERATED ALWAYS AS ( | |
| CASE WHEN kind = 'invocation_opened' THEN json_extract(COALESCE(event_json, retained_json), '$.fact.configuration.model') END | |
| ) STORED, | |
| accounting_tool TEXT GENERATED ALWAYS AS ( | |
| CASE | |
| WHEN kind IN ('tool_dispatched', 'tool_rejected') THEN json_object( | |
| 'invocation', json_extract(COALESCE(event_json, retained_json), '$.invocation'), | |
| 'call', json_extract(COALESCE(event_json, retained_json), '$.fact.call'), | |
| 'name', json_extract(COALESCE(event_json, retained_json), '$.fact.name'), | |
| 'rejection', json_extract(COALESCE(event_json, retained_json), '$.fact.reason.kind')) | |
| WHEN kind = 'tool_settled' THEN json_object( | |
| 'outcome', json_extract(COALESCE(event_json, retained_json), '$.fact.outcome.kind')) | |
| END | |
| ) STORED, | |
| event_json TEXT, | |
| -- A collected body is absent, never a synthetic replayable RuntimeEvent. | |
| retained_json TEXT CHECK(retained_json IS NULL OR json_valid(retained_json)), | |
| body_digest TEXT, | |
| event_session TEXT GENERATED ALWAYS AS ( | |
| json_extract(COALESCE(event_json, retained_json), '$.invocation.session_id') | |
| ) STORED, | |
| CHECK((event_json IS NOT NULL AND retained_json IS NULL AND body_digest IS NULL) | |
| OR (event_json IS NULL AND retained_json IS NOT NULL AND body_digest IS NOT NULL)), | |
| CHECK(invocation_id IS NOT NULL OR operation_id IS NULL) | |
| ); | |
| CREATE TABLE session_control ( | |
| id TEXT PRIMARY KEY, | |
| fingerprint TEXT NOT NULL, | |
| revision INTEGER NOT NULL CHECK(revision > 0), | |
| created_at INTEGER NOT NULL, | |
| updated_at INTEGER NOT NULL, | |
| archived INTEGER NOT NULL CHECK(archived IN (0, 1)), | |
| configuration TEXT NOT NULL | |
| ); | |
| CREATE TABLE session_catalog_revision ( | |
| singleton INTEGER PRIMARY KEY CHECK(singleton = 1), | |
| revision INTEGER NOT NULL CHECK(revision >= 0) | |
| ); | |
| -- Canonical import admission, distinct from executable Session/Run admission. | |
| -- Record bodies already have immutable ledger positions while this is collecting. | |
| CREATE TABLE session_imports ( | |
| session_id TEXT PRIMARY KEY, | |
| source_json TEXT NOT NULL CHECK(json_valid(source_json)), | |
| configuration TEXT NOT NULL CHECK(json_valid(configuration)), | |
| created_at INTEGER NOT NULL, | |
| state TEXT NOT NULL CHECK(state IN ('collecting','published','abandoned')), | |
| records INTEGER NOT NULL CHECK(records >= 0), | |
| bytes INTEGER NOT NULL CHECK(bytes >= 0), | |
| conversation INTEGER NOT NULL CHECK(conversation BETWEEN 0 AND records) | |
| ); | |
| -- Retirement fences new admissions before asynchronous resource cleanup. Keep | |
| -- its identity after cleanup so a lost reply cannot resurrect the Session. | |
| CREATE TABLE session_retirements ( | |
| session_id TEXT PRIMARY KEY, | |
| remove_session INTEGER NOT NULL DEFAULT 1 CHECK(remove_session IN (0, 1)), | |
| completed INTEGER NOT NULL DEFAULT 0 CHECK(completed IN (0, 1)) | |
| ); | |
| CREATE TABLE session_removal_receipts ( | |
| session_id TEXT PRIMARY KEY, | |
| plan_json TEXT NOT NULL | |
| ); | |
| -- A Host crash loses native handles, not proof that a process may still write. | |
| CREATE TABLE session_processes ( | |
| id TEXT PRIMARY KEY, | |
| session_id TEXT NOT NULL, | |
| cleaned INTEGER NOT NULL DEFAULT 0 CHECK(cleaned IN (0, 1)) | |
| ); | |
| CREATE INDEX session_processes_pending ON session_processes(session_id) WHERE cleaned=0; | |
| CREATE TABLE session_read_state ( | |
| session_id TEXT PRIMARY KEY REFERENCES session_control(id), | |
| has_unread INTEGER NOT NULL CHECK(has_unread IN (0, 1)), | |
| last_read_message_id TEXT | |
| ); | |
| CREATE TABLE interaction_requests ( | |
| request_id TEXT PRIMARY KEY, | |
| session_id TEXT NOT NULL, | |
| created_at INTEGER NOT NULL, | |
| record_json TEXT NOT NULL CHECK(length(CAST(record_json AS BLOB)) <= 20480) | |
| ); | |
| CREATE INDEX interaction_session_pending ON interaction_requests(session_id, created_at, request_id); | |
| CREATE INDEX interaction_permission_basis ON interaction_requests( | |
| session_id, json_extract(record_json, '$.request.baseRevision'), created_at, request_id | |
| ) WHERE json_extract(record_json, '$.request.kind') = 'permissions'; | |
| CREATE TABLE interaction_outcomes ( | |
| request_id TEXT PRIMARY KEY REFERENCES interaction_requests(request_id), | |
| outcome_json TEXT NOT NULL CHECK(length(CAST(outcome_json AS BLOB)) <= 8192) | |
| ); | |
| CREATE TABLE client_capability_session_grants ( | |
| authority_key TEXT PRIMARY KEY, | |
| record_json TEXT NOT NULL CHECK(length(CAST(record_json AS BLOB)) <= 12288) | |
| ); | |
| CREATE TABLE artifacts ( | |
| session_id TEXT NOT NULL REFERENCES session_control(id), | |
| id TEXT NOT NULL, | |
| created_at INTEGER NOT NULL CHECK(created_at >= 0), | |
| record_json TEXT NOT NULL CHECK(length(CAST(record_json AS BLOB)) <= 16384), | |
| content_sha256 TEXT NOT NULL, | |
| payload BLOB NOT NULL CHECK(length(payload) <= 52428800), | |
| PRIMARY KEY (session_id, id), | |
| CHECK(json_extract(record_json, '$.sessionId') = session_id), | |
| CHECK(json_extract(record_json, '$.id') = id), | |
| CHECK(json_extract(record_json, '$.createdAt') = created_at), | |
| CHECK(json_extract(record_json, '$.sizeBytes') = length(payload)) | |
| ); | |
| CREATE INDEX artifacts_session_order ON artifacts(session_id, created_at DESC, id); | |
| CREATE TABLE artifact_catalog ( | |
| session_id TEXT PRIMARY KEY REFERENCES session_control(id), | |
| revision INTEGER NOT NULL CHECK(revision > 0 AND revision <= 9007199254740991) | |
| ); | |
| CREATE TABLE shell_runs ( | |
| session_id TEXT NOT NULL REFERENCES session_control(id), | |
| id TEXT NOT NULL, | |
| started_at INTEGER NOT NULL CHECK(started_at >= 0), | |
| active INTEGER NOT NULL CHECK(active IN (0, 1)), | |
| record_json TEXT NOT NULL CHECK(length(CAST(record_json AS BLOB)) <= 4194304), | |
| PRIMARY KEY (session_id, id), | |
| CHECK(json_extract(record_json, '$.sessionId') IS session_id), | |
| CHECK(json_extract(record_json, '$.id') IS id), | |
| CHECK(json_extract(record_json, '$.startedAt') IS started_at), | |
| CHECK(active IS (json_extract(record_json, '$.state.kind') IN ('starting', 'running'))) | |
| ); | |
| CREATE INDEX shell_runs_session_order ON shell_runs(session_id, started_at, id); | |
| CREATE INDEX shell_runs_active ON shell_runs(active) WHERE active = 1; | |
| CREATE TABLE message_admissions ( | |
| session_id TEXT NOT NULL, | |
| message_id TEXT NOT NULL, | |
| position INTEGER NOT NULL, | |
| record_json TEXT NOT NULL, | |
| PRIMARY KEY(session_id, message_id) | |
| ); | |
| CREATE INDEX message_admission_order ON message_admissions(session_id, position, message_id); | |
| CREATE TABLE message_queue_state ( | |
| session_id TEXT PRIMARY KEY NOT NULL, | |
| revision INTEGER NOT NULL CHECK(revision >= 0) | |
| ); | |
| CREATE TABLE message_cancellations ( | |
| session_id TEXT NOT NULL, | |
| message_id TEXT NOT NULL, | |
| cancellation_id TEXT NOT NULL, | |
| PRIMARY KEY(session_id, message_id) | |
| ); | |
| CREATE TABLE queue_command_receipts ( | |
| host_epoch TEXT NOT NULL, | |
| session_id TEXT NOT NULL, | |
| operation TEXT NOT NULL, | |
| command_id TEXT NOT NULL, | |
| fingerprint TEXT NOT NULL, | |
| receipt_json TEXT NOT NULL, | |
| PRIMARY KEY(host_epoch, session_id, operation, command_id) | |
| ); | |
| CREATE TABLE message_submit_receipts ( | |
| host_epoch TEXT NOT NULL, | |
| session_id TEXT NOT NULL, | |
| message_id TEXT NOT NULL, | |
| receipt_json TEXT NOT NULL, | |
| PRIMARY KEY(host_epoch, session_id, message_id) | |
| ); | |
| CREATE TABLE message_interrupt_receipts ( | |
| host_epoch TEXT NOT NULL, | |
| session_id TEXT NOT NULL, | |
| interrupt_id TEXT NOT NULL, | |
| turn_id TEXT NOT NULL, | |
| run_id TEXT NOT NULL, | |
| fence_json TEXT, | |
| PRIMARY KEY(host_epoch, session_id, interrupt_id) | |
| ); | |
| CREATE INDEX message_interrupt_run ON message_interrupt_receipts(session_id, run_id) | |
| WHERE fence_json IS NOT NULL; | |
| CREATE TABLE projects ( | |
| id TEXT PRIMARY KEY NOT NULL, | |
| identity TEXT NOT NULL UNIQUE, | |
| name TEXT NOT NULL CHECK(length(CAST(name AS BLOB)) BETWEEN 1 AND 16384), | |
| last_used_at INTEGER NOT NULL CHECK(last_used_at BETWEEN 0 AND 9007199254740991), | |
| archived_at INTEGER CHECK(archived_at BETWEEN 0 AND 9007199254740991) | |
| ) STRICT; | |
| CREATE TABLE project_identities ( | |
| id TEXT PRIMARY KEY NOT NULL, | |
| project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE | |
| ) STRICT; | |
| CREATE INDEX project_identity_owner ON project_identities(project_id, id); | |
| CREATE TABLE project_locations ( | |
| project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE, | |
| path TEXT NOT NULL CHECK(length(CAST(path AS BLOB)) BETWEEN 1 AND 4096), | |
| is_worktree INTEGER NOT NULL CHECK(is_worktree IN (0, 1)), | |
| last_used_at INTEGER NOT NULL CHECK(last_used_at BETWEEN 0 AND 9007199254740991), | |
| PRIMARY KEY(project_id, path) | |
| ) STRICT; | |
| CREATE UNIQUE INDEX pending_root_invocation ON message_admissions ( | |
| json_extract(record_json, '$.invocation.invocation_id') | |
| ) WHERE json_extract(record_json, '$.source.disposition') = 'turn_started'; | |
| CREATE TABLE "tool_result_payloads" ( | |
| event_id TEXT PRIMARY KEY NOT NULL REFERENCES event_log(event_id), | |
| payload BLOB NOT NULL CHECK(length(payload) <= 67108864) | |
| ); | |
| CREATE VIEW runtime_events AS | |
| SELECT * FROM event_log WHERE invocation_id IS NOT NULL AND event_json IS NOT NULL; | |
| -- Accounting can read compact retained proof, never conversation history. | |
| CREATE VIEW accounting_events AS | |
| SELECT sequence, event_id, invocation_id, kind, operation_id, | |
| accounting_at, accounting_usage, invocation_model, accounting_tool, | |
| COALESCE(event_json, retained_json) AS event_json | |
| FROM event_log; | |
| CREATE INDEX event_material_owner ON event_log(event_session, sequence) | |
| WHERE event_json IS NOT NULL; | |
| CREATE INDEX event_proof_owner ON event_log(event_session, sequence); | |
| -- Copies retain original facts, never execution authority. Flatten membership | |
| -- at creation so descendants do not query mutable parent Session metadata. | |
| CREATE TABLE session_history_copies ( | |
| session_id TEXT PRIMARY KEY, | |
| source_session_id TEXT NOT NULL, | |
| source_revision INTEGER NOT NULL CHECK(source_revision > 0), | |
| through_sequence INTEGER NOT NULL CHECK(through_sequence >= 0), | |
| observed_through INTEGER NOT NULL CHECK(observed_through >= through_sequence), | |
| request_json TEXT NOT NULL, | |
| lineage_json TEXT NOT NULL CHECK(json_valid(lineage_json)), | |
| state TEXT NOT NULL CHECK(state IN ('preparing', 'committed', 'abandoned')), | |
| CHECK(state = 'committed' OR json_extract(lineage_json, '$.kind') = 'revision'), | |
| CHECK(session_id != source_session_id) | |
| ); | |
| CREATE UNIQUE INDEX session_revision_family ON session_history_copies( | |
| CAST(json_extract(lineage_json, '$.root_session_id') AS TEXT), | |
| CAST(json_extract(lineage_json, '$.index') AS INTEGER) | |
| ) WHERE json_extract(lineage_json, '$.kind') = 'revision'; | |
| CREATE TABLE session_history_members ( | |
| session_id TEXT NOT NULL REFERENCES session_history_copies(session_id), | |
| sequence INTEGER NOT NULL REFERENCES event_log(sequence), | |
| archive_sequence INTEGER REFERENCES event_log(sequence), | |
| PRIMARY KEY(session_id, sequence) | |
| ); | |
| -- Revision input is editable evidence, not part of the destination conversation. | |
| CREATE TABLE session_revision_sources ( | |
| session_id TEXT NOT NULL REFERENCES session_history_copies(session_id), | |
| sequence INTEGER NOT NULL REFERENCES event_log(sequence), | |
| PRIMARY KEY(session_id, sequence) | |
| ); | |
| CREATE INDEX history_source_owners ON session_history_members(sequence, session_id); | |
| CREATE INDEX archive_source_owners ON session_history_members(archive_sequence, session_id) | |
| WHERE archive_sequence IS NOT NULL; | |
| CREATE INDEX revision_source_owners ON session_revision_sources(sequence, session_id); | |
| CREATE VIEW session_history_events AS | |
| SELECT json_extract(event_json, '$.invocation.session_id') AS owner_session_id, | |
| 0 AS inherited, NULL AS archive_sequence, e.* FROM runtime_events e | |
| UNION ALL | |
| SELECT h.session_id, 1, h.archive_sequence, e.* | |
| FROM session_history_members h JOIN runtime_events e ON e.sequence = h.sequence; | |
| CREATE TABLE session_history_artifacts ( | |
| session_id TEXT NOT NULL REFERENCES session_history_copies(session_id), | |
| source_session_id TEXT NOT NULL, | |
| source_artifact_id TEXT NOT NULL, | |
| artifact_id TEXT NOT NULL, | |
| PRIMARY KEY(session_id, source_session_id, source_artifact_id), | |
| FOREIGN KEY(session_id, artifact_id) REFERENCES artifacts(session_id, id) | |
| ); | |
| CREATE INDEX invocation_sequence ON event_log(invocation_id, sequence); | |
| CREATE UNIQUE INDEX archived_tool_result ON event_log( | |
| CAST(json_extract(event_json, '$.fact.placeholder.identity.runtime_event_id') AS TEXT), | |
| json_extract(event_json, '$.invocation.session_id') | |
| ) WHERE kind = 'tool_result_archived'; | |
| CREATE INDEX session_event_sequence ON event_log( | |
| json_extract(event_json, '$.invocation.session_id'), sequence | |
| ); | |
| CREATE INDEX turn_opening_lookup ON event_log( | |
| json_extract(event_json, '$.invocation.session_id'), | |
| json_extract(event_json, '$.invocation.turn_id'), sequence DESC | |
| ) WHERE kind = 'invocation_opened'; | |
| CREATE UNIQUE INDEX operation_fact ON event_log(operation_id, kind) | |
| WHERE operation_id IS NOT NULL; | |
| CREATE UNIQUE INDEX invocation_boundary ON event_log(invocation_id, kind) | |
| WHERE kind IN ('invocation_opened', 'invocation_ended'); | |
| CREATE INDEX model_usage_requests ON event_log(sequence) WHERE kind = 'model_requested'; | |
| -- Quote admission and provider-usage valuation share their source fact's commit. | |
| -- These immutable records are canonical accounting, not recalculated projections. | |
| CREATE TABLE model_accounting ( | |
| request_id TEXT PRIMARY KEY REFERENCES event_log(event_id), | |
| quote_json TEXT NOT NULL CHECK(json_valid(quote_json) AND length(CAST(quote_json AS BLOB)) <= 8192) | |
| ); | |
| CREATE TABLE model_valuations ( | |
| request_id TEXT PRIMARY KEY REFERENCES model_accounting(request_id), | |
| usage_json TEXT NOT NULL CHECK(json_valid(usage_json)), | |
| usd REAL CHECK(usd IS NULL OR usd >= 0) | |
| ); | |
| CREATE INDEX model_usage_observation ON event_log( | |
| invocation_id, json_extract(COALESCE(event_json, retained_json), '$.fact.step_id'), sequence DESC | |
| ) WHERE kind = 'model_observed' AND json_extract(COALESCE(event_json, retained_json), '$.fact.event.kind') = 'finished'; | |
| -- Count physical admissions, not history memberships or accepted responses. | |
| -- A rejected response can still have provider-reported usage. No mutable | |
| -- Session metadata or current provider configuration participates in this view. | |
| CREATE VIEW agent_model_usage AS | |
| WITH attempts AS ( | |
| SELECT request.sequence, request.event_id, | |
| json_extract(request.event_json, '$.invocation') AS invocation, | |
| opening.invocation_model AS binding, | |
| json_extract(request.event_json, '$.fact.model_id') AS model_id, | |
| json_extract(request.event_json, '$.fact.purpose') AS purpose, | |
| request.accounting_at AS started_at, | |
| COALESCE(completed.accounting_at, interrupted.accounting_at, terminal.accounting_at) AS completed_at, | |
| COALESCE(completed.sequence, interrupted.sequence, terminal.sequence) AS completed_sequence, | |
| CASE | |
| WHEN completed.sequence IS NOT NULL THEN 'success' | |
| WHEN json_extract(interrupted.event_json, '$.fact.status') = 'cancelled' THEN 'aborted' | |
| WHEN interrupted.sequence IS NOT NULL THEN 'error' | |
| WHEN json_extract(terminal.event_json, '$.fact.outcome.kind') = 'cancelled' THEN 'aborted' | |
| WHEN terminal.sequence IS NOT NULL THEN 'unknown' | |
| END AS outcome, | |
| COALESCE(completed.accounting_usage, ( | |
| SELECT observed.accounting_usage | |
| FROM event_log observed INDEXED BY model_usage_observation | |
| WHERE observed.kind = 'model_observed' | |
| AND json_extract(COALESCE(observed.event_json, observed.retained_json), '$.fact.event.kind') = 'finished' | |
| AND observed.invocation_id = request.invocation_id | |
| AND json_extract(COALESCE(observed.event_json, observed.retained_json), '$.fact.step_id') = request.operation_id | |
| ORDER BY observed.sequence DESC LIMIT 1 | |
| ), '{}') AS usage | |
| FROM accounting_events request | |
| JOIN accounting_events opening ON opening.invocation_id = request.invocation_id | |
| AND opening.kind = 'invocation_opened' | |
| LEFT JOIN accounting_events completed ON completed.operation_id = request.operation_id | |
| AND completed.kind = 'model_completed' | |
| LEFT JOIN accounting_events interrupted ON interrupted.operation_id = request.operation_id | |
| AND interrupted.kind = 'model_interrupted' | |
| LEFT JOIN accounting_events terminal ON terminal.invocation_id = request.invocation_id | |
| AND terminal.kind = 'invocation_ended' | |
| WHERE request.kind = 'model_requested' | |
| ) | |
| SELECT sequence, event_id, | |
| json_object('kind', 'agent', 'invocation', json(invocation), 'purpose', purpose) AS origin, | |
| binding, model_id, outcome, usage, | |
| json_extract(invocation, '$.session_id') AS session_id, | |
| started_at, completed_at, | |
| completed_sequence | |
| FROM attempts; | |
| CREATE UNIQUE INDEX auxiliary_model_source ON event_log(json_extract(event_json, '$.source')) | |
| WHERE kind = 'auxiliary_model_started'; | |
| CREATE UNIQUE INDEX auxiliary_model_outcome ON event_log( | |
| json_extract(event_json, '$.request_id'), kind | |
| ) WHERE kind IN ('auxiliary_model_settled', 'auxiliary_model_usage'); | |
| CREATE VIEW auxiliary_model_usage AS | |
| SELECT started.sequence, started.event_id, | |
| json_object('kind', 'auxiliary', 'source', json_extract(started.event_json, '$.source')) AS origin, | |
| json_extract(started.event_json, '$.binding') AS binding, | |
| json_extract(started.event_json, '$.binding.model') AS model_id, | |
| json_extract(settled.event_json, '$.outcome') AS outcome, | |
| COALESCE(observed.accounting_usage, '{}') AS usage, | |
| json_extract(started.event_json, '$.session_id') AS session_id, | |
| started.accounting_at AS started_at, settled.accounting_at AS completed_at, | |
| settled.sequence AS completed_sequence | |
| FROM event_log started | |
| LEFT JOIN event_log settled ON settled.kind = 'auxiliary_model_settled' | |
| AND json_extract(settled.event_json, '$.request_id') = started.event_id | |
| LEFT JOIN event_log observed ON observed.kind = 'auxiliary_model_usage' | |
| AND json_extract(COALESCE(observed.event_json, observed.retained_json), '$.request_id') = started.event_id | |
| WHERE started.kind = 'auxiliary_model_started'; | |
| CREATE VIEW model_usage AS | |
| SELECT calls.*, accounting.quote_json, valuation.usd | |
| FROM (SELECT * FROM agent_model_usage UNION ALL SELECT * FROM auxiliary_model_usage) calls | |
| LEFT JOIN model_accounting accounting ON accounting.request_id = calls.event_id | |
| LEFT JOIN model_valuations valuation ON valuation.request_id = calls.event_id; | |
| CREATE INDEX tool_usage_requests ON event_log(sequence) | |
| WHERE kind IN ('tool_dispatched', 'tool_rejected'); | |
| -- Refusal is a known non-dispatch, not an executed tool with fabricated duration. | |
| CREATE VIEW tool_usage AS | |
| SELECT request.sequence, request.event_id, | |
| json_extract(request.accounting_tool, '$.invocation') AS invocation, | |
| json_extract(request.accounting_tool, '$.invocation.session_id') AS session_id, | |
| json_extract(request.accounting_tool, '$.call') AS call, | |
| json_extract(request.accounting_tool, '$.name') AS name, | |
| opening.invocation_model AS binding, | |
| CASE WHEN request.kind = 'tool_rejected' THEN request.accounting_at | |
| ELSE COALESCE(settled.accounting_at, terminal.accounting_at) END AS completed_at, | |
| CASE WHEN request.kind = 'tool_rejected' THEN request.sequence | |
| ELSE COALESCE(settled.sequence, terminal.sequence) END AS completed_sequence, | |
| CASE WHEN request.kind = 'tool_rejected' THEN json_object( | |
| 'kind', 'rejected', 'reason', json_extract(request.accounting_tool, '$.rejection')) | |
| ELSE json_object('kind', 'settled', 'startedAt', request.accounting_at, | |
| 'outcome', CASE json_extract(settled.accounting_tool, '$.outcome') | |
| WHEN 'succeeded' THEN 'success' WHEN 'failed' THEN 'error' ELSE 'unknown' END) | |
| END AS result | |
| FROM accounting_events request | |
| JOIN accounting_events opening ON opening.invocation_id = request.invocation_id | |
| AND opening.kind = 'invocation_opened' | |
| LEFT JOIN accounting_events settled ON settled.invocation_id = request.invocation_id | |
| AND settled.operation_id = request.operation_id AND settled.kind = 'tool_settled' | |
| LEFT JOIN accounting_events terminal ON terminal.invocation_id = request.invocation_id | |
| AND terminal.kind = 'invocation_ended' | |
| WHERE request.kind IN ('tool_dispatched', 'tool_rejected'); | |
| -- Common ordering and filtering across kinds; bodies never enter this projection. | |
| CREATE VIEW usage_activity AS | |
| SELECT 'model' AS kind, sequence, event_id, session_id, completed_sequence, | |
| binding, completed_at, origin, model_id, started_at, outcome, usage, quote_json, usd, | |
| NULL AS invocation, NULL AS call, NULL AS name, NULL AS result, | |
| outcome AS status, | |
| COALESCE(session_id, '') || ' ' || model_id || ' ' || | |
| COALESCE(json_extract(binding, '$.connection_slug'), '') || ' ' || | |
| COALESCE(json_extract(quote_json, '$.providerId'), '') AS search | |
| FROM model_usage | |
| UNION ALL | |
| SELECT 'tool', sequence, event_id, session_id, completed_sequence, | |
| binding, completed_at, NULL, NULL, NULL, NULL, NULL, NULL, NULL, | |
| invocation, call, name, result, | |
| CASE WHEN json_extract(result, '$.kind') = 'rejected' THEN 'rejected' | |
| ELSE json_extract(result, '$.outcome') END, | |
| session_id || ' ' || name || ' ' || | |
| COALESCE(json_extract(binding, '$.connection_slug'), '') || ' ' || | |
| COALESCE(json_extract(binding, '$.model'), '') | |
| FROM tool_usage; | |
| CREATE UNIQUE INDEX message_steering_identity ON event_log( | |
| json_extract(event_json, '$.invocation.session_id'), | |
| json_extract(event_json, '$.fact.message.message_id') | |
| ) WHERE kind = 'message_steered'; | |
| CREATE UNIQUE INDEX continuation_claim_id ON event_log( | |
| json_extract(event_json, '$.fact.input.claim.id') | |
| ) WHERE kind = 'invocation_opened' AND json_extract(event_json, '$.fact.input.kind') IN ('continuation','handoff'); | |
| CREATE UNIQUE INDEX continuation_source_boundary ON event_log( | |
| json_extract(event_json, '$.invocation.session_id'), | |
| json_extract(event_json, '$.fact.input.claim.source.invocation.run_id'), | |
| json_extract(event_json, '$.fact.input.claim.source.high_water') | |
| ) WHERE kind = 'invocation_opened' AND json_extract(event_json, '$.fact.input.kind') IN ('continuation','handoff'); | |
| CREATE UNIQUE INDEX handoff_successor_run ON event_log( | |
| json_extract(event_json, '$.fact.outcome.pause.intent.successor_run_id') | |
| ) WHERE kind = 'invocation_ended' AND json_extract(event_json, '$.fact.outcome.kind') = 'handoff_paused'; | |
| CREATE UNIQUE INDEX handoff_successor_invocation ON event_log( | |
| json_extract(event_json, '$.fact.outcome.pause.intent.successor_invocation_id') | |
| ) WHERE kind = 'invocation_ended' AND json_extract(event_json, '$.fact.outcome.kind') = 'handoff_paused'; | |
| CREATE UNIQUE INDEX handoff_claim ON event_log( | |
| json_extract(event_json, '$.fact.outcome.pause.intent.claim_id') | |
| ) WHERE kind = 'invocation_ended' AND json_extract(event_json, '$.fact.outcome.kind') = 'handoff_paused'; | |
| CREATE TABLE plugin_composition ( | |
| singleton INTEGER PRIMARY KEY CHECK (singleton = 1), | |
| ledger_json TEXT NOT NULL CHECK (json_valid(ledger_json)) | |
| ); | |
| CREATE TABLE plugin_package_blobs ( | |
| digest TEXT PRIMARY KEY | |
| ); | |
| CREATE TABLE plugin_package_files ( | |
| digest TEXT NOT NULL REFERENCES plugin_package_blobs(digest) ON DELETE CASCADE, | |
| path TEXT NOT NULL, | |
| payload BLOB NOT NULL, | |
| PRIMARY KEY (digest, path) | |
| ); | |
| CREATE TABLE plugin_packages ( | |
| id TEXT PRIMARY KEY, | |
| digest TEXT NOT NULL REFERENCES plugin_package_blobs(digest) | |
| ); | |
| CREATE TABLE plugin_data ( | |
| package_id TEXT NOT NULL, | |
| scope_id TEXT NOT NULL, | |
| key TEXT NOT NULL, | |
| revision INTEGER NOT NULL CHECK (revision > 0), | |
| value_json TEXT CHECK (value_json IS NULL OR json_valid(value_json)), | |
| PRIMARY KEY (package_id, scope_id, key) | |
| ); | |
| CREATE TABLE plugin_message_receipts ( | |
| package_id TEXT NOT NULL, | |
| scope_id TEXT NOT NULL, | |
| operation_id TEXT NOT NULL, | |
| receipt_json TEXT NOT NULL, | |
| PRIMARY KEY (package_id, scope_id, operation_id) | |
| ); | |
| CREATE TABLE plugin_execution_receipts ( | |
| package_id TEXT NOT NULL, | |
| scope_id TEXT NOT NULL, | |
| operation_id TEXT NOT NULL, | |
| receipt_json TEXT NOT NULL CHECK (json_valid(receipt_json)), | |
| PRIMARY KEY (package_id, scope_id, operation_id) | |
| ); | |
| CREATE TABLE request_compositions ( | |
| digest TEXT PRIMARY KEY NOT NULL, | |
| surface BLOB NOT NULL CHECK(length(surface) <= 4194304) | |
| ); | |
| CREATE TABLE model_request_compositions ( | |
| event_id TEXT PRIMARY KEY NOT NULL REFERENCES event_log(event_id), | |
| digest TEXT NOT NULL REFERENCES request_compositions(digest) | |
| ); | |
| CREATE INDEX model_request_composition_digest ON model_request_compositions(digest); | |
| CREATE TABLE plugin_sessions ( | |
| session_id TEXT PRIMARY KEY NOT NULL, | |
| package_id TEXT NOT NULL, | |
| scope_id TEXT NOT NULL, | |
| fingerprint TEXT NOT NULL, | |
| managed INTEGER NOT NULL CHECK(managed IN (0, 1)), | |
| authority_session_id TEXT, | |
| CHECK(authority_session_id IS NULL OR authority_session_id != session_id) | |
| ); | |
| CREATE TABLE host_effects ( | |
| id TEXT PRIMARY KEY, | |
| package_id TEXT NOT NULL, | |
| scope_id TEXT NOT NULL, | |
| request TEXT NOT NULL CHECK(json_valid(request) AND length(CAST(request AS BLOB)) <= 8388608), | |
| outcome TEXT CHECK(outcome IS NULL OR (json_valid(outcome) AND length(CAST(outcome AS BLOB)) <= 8388608)), | |
| payload BLOB CHECK(payload IS NULL OR length(payload) <= 5242880), | |
| CHECK(outcome IS NOT NULL OR payload IS NULL) | |
| ) STRICT; | |
| CREATE INDEX host_effects_namespace ON host_effects(package_id, scope_id); | |
| -- Query-independent text projection; canonical events remain authoritative. | |
| CREATE TABLE transcript_text ( | |
| session_id TEXT NOT NULL, | |
| sequence INTEGER NOT NULL, | |
| timestamp INTEGER NOT NULL, | |
| role TEXT NOT NULL, | |
| body BLOB NOT NULL, | |
| PRIMARY KEY(session_id, sequence) | |
| ); | |
| CREATE TABLE message_sources ( | |
| session_id TEXT NOT NULL, message_id TEXT NOT NULL, event_id TEXT NOT NULL, | |
| PRIMARY KEY(session_id, message_id) | |
| ); | |
| CREATE INDEX message_source_identity ON message_sources(message_id, event_id); | |
| CREATE INDEX message_openings_by_turn ON event_log( | |
| CAST(json_extract(event_json, '$.invocation.turn_id') AS TEXT), sequence | |
| ) WHERE kind = 'invocation_opened' AND json_extract(event_json, '$.fact.input.kind') = 'message'; | |
| CREATE VIEW session_message_sources AS | |
| SELECT session_id AS owner_session_id, session_id AS source_session_id, message_id, event_id | |
| FROM message_sources | |
| UNION ALL | |
| SELECT h.session_id, s.session_id, s.message_id, s.event_id FROM session_history_members h | |
| JOIN runtime_events e ON e.sequence=h.sequence JOIN message_sources s ON s.event_id=e.event_id | |
| UNION ALL | |
| SELECT r.session_id, s.session_id, s.message_id, s.event_id FROM session_revision_sources r | |
| JOIN runtime_events e ON e.sequence=r.sequence JOIN message_sources s ON s.event_id=e.event_id; | |
| CREATE TABLE transcript_rows ( | |
| sequence INTEGER NOT NULL CHECK(sequence >= 0), | |
| session_id TEXT NOT NULL, | |
| turn_id TEXT NOT NULL, | |
| message_id TEXT NOT NULL, | |
| payload BLOB NOT NULL, | |
| digest TEXT NOT NULL, | |
| total_bytes INTEGER NOT NULL CHECK(total_bytes > 0), | |
| PRIMARY KEY(session_id, sequence), | |
| UNIQUE(session_id, message_id) | |
| ); | |
| CREATE INDEX transcript_session_turn | |
| ON transcript_rows(session_id, turn_id, sequence); | |
| CREATE TABLE transcript_progress ( | |
| session_id TEXT PRIMARY KEY, | |
| through_sequence INTEGER NOT NULL CHECK(through_sequence >= 0) | |
| ); | |
| CREATE INDEX catalog_message_facts ON event_log( | |
| json_extract(event_json, '$.invocation.session_id'), kind, sequence | |
| ) WHERE kind IN ('invocation_opened', 'model_completed'); | |
| CREATE INDEX catalog_part_starts ON event_log( | |
| json_extract(event_json, '$.invocation.session_id'), invocation_id, | |
| json_extract(event_json, '$.fact.step_id'), sequence | |
| ) WHERE kind = 'model_observed' | |
| AND json_extract(event_json, '$.fact.event.kind') = 'part_started'; | |
| CREATE TABLE catalog_messages ( | |
| sequence INTEGER NOT NULL, ordinal INTEGER NOT NULL, session_id TEXT NOT NULL, | |
| message_at INTEGER NOT NULL, preview TEXT, message_id TEXT NOT NULL, | |
| PRIMARY KEY (sequence, ordinal) | |
| ); | |
| CREATE INDEX catalog_visible_tail ON catalog_messages( | |
| session_id, sequence DESC, ordinal DESC | |
| ); | |
| CREATE INDEX catalog_latest_message ON catalog_messages( | |
| session_id, message_at DESC, sequence DESC, ordinal DESC | |
| ); | |
| CREATE INDEX catalog_latest_preview ON catalog_messages( | |
| session_id, message_at DESC, sequence DESC, ordinal DESC | |
| ) WHERE preview IS NOT NULL; | |
| CREATE TABLE catalog_message_watermark( | |
| singleton INTEGER PRIMARY KEY CHECK(singleton = 1), sequence INTEGER NOT NULL | |
| ); | |
| CREATE INDEX catalog_partial_deltas ON event_log( | |
| invocation_id, json_extract(event_json, '$.fact.step_id'), | |
| json_extract(event_json, '$.fact.event.data.id'), sequence | |
| ) WHERE kind = 'model_observed' | |
| AND json_extract(event_json, '$.fact.event.kind') = 'part_delta'; | |
| CREATE INDEX catalog_model_boundaries ON event_log( | |
| invocation_id, kind, sequence | |
| ) WHERE kind IN ('model_requested', 'model_completed', 'model_interrupted'); | |
| CREATE INDEX navigation_boundaries ON event_log( | |
| json_extract(event_json, '$.invocation.session_id'), kind, sequence | |
| ) WHERE kind IN ('invocation_opened', 'invocation_ended'); | |
| INSERT INTO session_catalog_revision VALUES (1, 0); | |
| INSERT INTO plugin_composition VALUES (1, '{"generation":0,"packageLayers":[],"overlays":[]}'); | |
| PRAGMA user_version = 1; | |
| -- Application Input is authoritative; immutable revisions preserve original operation sources. | |
| CREATE TABLE application_sources ( | |
| deployment TEXT NOT NULL, | |
| revision INTEGER NOT NULL CHECK(revision BETWEEN 1 AND 9007199254740991), | |
| input_json TEXT NOT NULL CHECK(json_valid(input_json)), | |
| remote_exports_json TEXT NOT NULL CHECK(json_valid(remote_exports_json)), | |
| PRIMARY KEY(deployment, revision) | |
| ); | |
| CREATE TABLE application_deployments ( | |
| id TEXT PRIMARY KEY NOT NULL, | |
| revision INTEGER NOT NULL, | |
| FOREIGN KEY(id, revision) REFERENCES application_sources(deployment, revision) | |
| ); | |
| CREATE TABLE application_packages ( | |
| id TEXT NOT NULL, | |
| revision TEXT NOT NULL, | |
| PRIMARY KEY(id, revision) | |
| ); | |
| CREATE TABLE application_package_files ( | |
| id TEXT NOT NULL, | |
| revision TEXT NOT NULL, | |
| path TEXT NOT NULL, | |
| payload BLOB NOT NULL, | |
| PRIMARY KEY(id, revision, path), | |
| FOREIGN KEY(id, revision) REFERENCES application_packages(id, revision) ON DELETE CASCADE | |
| ); | |
| CREATE TABLE application_source_packages ( | |
| deployment TEXT NOT NULL, | |
| source_revision INTEGER NOT NULL, | |
| id TEXT NOT NULL, | |
| revision TEXT NOT NULL, | |
| PRIMARY KEY(deployment, source_revision, id, revision), | |
| FOREIGN KEY(deployment, source_revision) REFERENCES application_sources(deployment, revision), | |
| FOREIGN KEY(id, revision) REFERENCES application_packages(id, revision) | |
| ); | |
| CREATE INDEX application_package_references ON application_source_packages(id, revision); | |
| CREATE TABLE application_operations ( | |
| deployment TEXT NOT NULL, | |
| namespace TEXT NOT NULL, | |
| operation TEXT NOT NULL, | |
| source_revision INTEGER NOT NULL, | |
| request_json TEXT NOT NULL CHECK(json_valid(request_json)), | |
| outcome_json TEXT NOT NULL CHECK(json_valid(outcome_json)), | |
| cleanup_json TEXT NOT NULL CHECK(json_valid(cleanup_json)), | |
| PRIMARY KEY(deployment, namespace, operation), | |
| FOREIGN KEY(deployment, source_revision) REFERENCES application_sources(deployment, revision) | |
| ); |