@plurnk/plurnk-service 1.5.0 → 1.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.defaults +22 -27
- package/INSTALL.md +22 -17
- package/README.md +1 -1
- package/SPEC.md +505 -362
- package/dist/build-info.json +1 -1
- package/dist/content/edit-collision.d.ts +5 -0
- package/dist/content/edit-collision.d.ts.map +1 -0
- package/dist/content/edit-collision.js +14 -0
- package/dist/content/edit-collision.js.map +1 -0
- package/dist/content/edit-receipt.d.ts +3 -2
- package/dist/content/edit-receipt.d.ts.map +1 -1
- package/dist/content/edit-receipt.js +58 -13
- package/dist/content/edit-receipt.js.map +1 -1
- package/dist/content/index.d.ts +4 -1
- package/dist/content/index.d.ts.map +1 -1
- package/dist/content/index.js +3 -1
- package/dist/content/index.js.map +1 -1
- package/dist/content/line-anchors.d.ts +41 -0
- package/dist/content/line-anchors.d.ts.map +1 -0
- package/dist/content/line-anchors.js +191 -0
- package/dist/content/line-anchors.js.map +1 -0
- package/dist/content/read-projector.d.ts +7 -1
- package/dist/content/read-projector.d.ts.map +1 -1
- package/dist/content/read-projector.js +46 -10
- package/dist/content/read-projector.js.map +1 -1
- package/dist/content/read-resolve.js +3 -3
- package/dist/content/read-resolve.js.map +1 -1
- package/dist/core/BudgetReadout.d.ts.map +1 -1
- package/dist/core/BudgetReadout.js +9 -2
- package/dist/core/BudgetReadout.js.map +1 -1
- package/dist/core/ChannelWrite.js +1 -1
- package/dist/core/ChannelWrite.js.map +1 -1
- package/dist/core/ChannelWrite.sql +3 -3
- package/dist/core/CoreSchemeServices.d.ts +1 -1
- package/dist/core/CoreSchemeServices.d.ts.map +1 -1
- package/dist/core/CoreSchemeServices.js +1 -1
- package/dist/core/CoreSchemeServices.js.map +1 -1
- package/dist/core/Dispatcher.d.ts +15 -5
- package/dist/core/Dispatcher.d.ts.map +1 -1
- package/dist/core/Dispatcher.js +220 -978
- package/dist/core/Dispatcher.js.map +1 -1
- package/dist/core/DurableStatement.d.ts.map +1 -1
- package/dist/core/DurableStatement.js +2 -0
- package/dist/core/DurableStatement.js.map +1 -1
- package/dist/core/Engine.d.ts +18 -43
- package/dist/core/Engine.d.ts.map +1 -1
- package/dist/core/Engine.js +90 -1554
- package/dist/core/Engine.js.map +1 -1
- package/dist/core/Engine.sql +192 -112
- package/dist/core/ExecutableTools.d.ts +10 -0
- package/dist/core/ExecutableTools.d.ts.map +1 -0
- package/dist/core/ExecutableTools.js +41 -0
- package/dist/core/ExecutableTools.js.map +1 -0
- package/dist/core/ExecutorRegistry.d.ts +3 -3
- package/dist/core/ExecutorRegistry.d.ts.map +1 -1
- package/dist/core/ExecutorRegistry.js +2 -2
- package/dist/core/ExecutorRegistry.js.map +1 -1
- package/dist/core/LogBody.d.ts +3 -0
- package/dist/core/LogBody.d.ts.map +1 -1
- package/dist/core/LogBody.js +21 -5
- package/dist/core/LogBody.js.map +1 -1
- package/dist/core/LogEntryProjection.d.ts +14 -0
- package/dist/core/LogEntryProjection.d.ts.map +1 -0
- package/dist/core/LogEntryProjection.js +38 -0
- package/dist/core/LogEntryProjection.js.map +1 -0
- package/dist/core/ModelCall.d.ts +27 -0
- package/dist/core/ModelCall.d.ts.map +1 -0
- package/dist/core/ModelCall.js +127 -0
- package/dist/core/ModelCall.js.map +1 -0
- package/dist/core/PacketBuilder.d.ts +14 -25
- package/dist/core/PacketBuilder.d.ts.map +1 -1
- package/dist/core/PacketBuilder.js +170 -244
- package/dist/core/PacketBuilder.js.map +1 -1
- package/dist/core/ProblemLog.d.ts +1 -1
- package/dist/core/ProblemLog.d.ts.map +1 -1
- package/dist/core/ProblemLog.js +15 -3
- package/dist/core/ProblemLog.js.map +1 -1
- package/dist/core/ProposalLifecycle.d.ts +2 -2
- package/dist/core/ProposalLifecycle.d.ts.map +1 -1
- package/dist/core/ProposalLifecycle.js +17 -9
- package/dist/core/ProposalLifecycle.js.map +1 -1
- package/dist/core/ProviderInstantiate.d.ts.map +1 -1
- package/dist/core/ProviderInstantiate.js +25 -8
- package/dist/core/ProviderInstantiate.js.map +1 -1
- package/dist/core/ResourceMutations.d.ts +73 -0
- package/dist/core/ResourceMutations.d.ts.map +1 -0
- package/dist/core/ResourceMutations.js +1142 -0
- package/dist/core/ResourceMutations.js.map +1 -0
- package/dist/core/SchemeRegistry.js +10 -10
- package/dist/core/SchemeRegistry.js.map +1 -1
- package/dist/core/StoredPacket.d.ts +2 -2
- package/dist/core/StoredPacket.js +7 -7
- package/dist/core/StrikeRail.d.ts +0 -1
- package/dist/core/StrikeRail.d.ts.map +1 -1
- package/dist/core/StrikeRail.js +6 -4
- package/dist/core/StrikeRail.js.map +1 -1
- package/dist/core/TurnRunner.d.ts +72 -0
- package/dist/core/TurnRunner.d.ts.map +1 -0
- package/dist/core/TurnRunner.js +1730 -0
- package/dist/core/TurnRunner.js.map +1 -0
- package/dist/core/caps/CapsResolve.js +1 -1
- package/dist/core/caps/CapsResolve.js.map +1 -1
- package/dist/core/caps/DbChannelCaps.js +4 -4
- package/dist/core/caps/DbChannelCaps.js.map +1 -1
- package/dist/core/caps/DbEntryCaps.d.ts +2 -1
- package/dist/core/caps/DbEntryCaps.d.ts.map +1 -1
- package/dist/core/caps/DbEntryCaps.js +8 -5
- package/dist/core/caps/DbEntryCaps.js.map +1 -1
- package/dist/core/caps/DbProjectionCaps.d.ts +1 -0
- package/dist/core/caps/DbProjectionCaps.d.ts.map +1 -1
- package/dist/core/caps/DbProjectionCaps.js +29 -0
- package/dist/core/caps/DbProjectionCaps.js.map +1 -1
- package/dist/core/caps/SchemeCtxImpl.d.ts +5 -2
- package/dist/core/caps/SchemeCtxImpl.d.ts.map +1 -1
- package/dist/core/caps/SchemeCtxImpl.js +6 -4
- package/dist/core/caps/SchemeCtxImpl.js.map +1 -1
- package/dist/core/content-hash.js +2 -2
- package/dist/core/content-hash.js.map +1 -1
- package/dist/core/content-weight.d.ts +2 -0
- package/dist/core/content-weight.d.ts.map +1 -0
- package/dist/core/content-weight.js +4 -0
- package/dist/core/content-weight.js.map +1 -0
- package/dist/core/content_weight.d.ts +4 -0
- package/dist/core/content_weight.d.ts.map +1 -0
- package/dist/core/content_weight.js +6 -0
- package/dist/core/content_weight.js.map +1 -0
- package/dist/core/fork.d.ts.map +1 -1
- package/dist/core/fork.js +23 -7
- package/dist/core/fork.js.map +1 -1
- package/dist/core/fork.sql +38 -17
- package/dist/core/git-membership.d.ts.map +1 -1
- package/dist/core/git-membership.js +1 -2
- package/dist/core/git-membership.js.map +1 -1
- package/dist/core/git-state.js +1 -1
- package/dist/core/git-state.js.map +1 -1
- package/dist/core/packet-wire.d.ts +6 -3
- package/dist/core/packet-wire.d.ts.map +1 -1
- package/dist/core/packet-wire.js +257 -99
- package/dist/core/packet-wire.js.map +1 -1
- package/dist/core/provider-accounting.d.ts +24 -0
- package/dist/core/provider-accounting.d.ts.map +1 -0
- package/dist/core/provider-accounting.js +76 -0
- package/dist/core/provider-accounting.js.map +1 -0
- package/dist/core/scheme-types.d.ts +1 -1
- package/dist/core/scheme-types.d.ts.map +1 -1
- package/dist/core/search-gate.d.ts +2 -2
- package/dist/core/search-gate.d.ts.map +1 -1
- package/dist/core/search-gate.js +8 -8
- package/dist/core/search-gate.js.map +1 -1
- package/dist/core/turn-scheduler.js +1 -1
- package/dist/core/turn-scheduler.js.map +1 -1
- package/dist/digest/Digest.d.ts.map +1 -1
- package/dist/digest/Digest.js +221 -110
- package/dist/digest/Digest.js.map +1 -1
- package/dist/digest/digest.sql +40 -18
- package/dist/schemes/Exec.d.ts.map +1 -1
- package/dist/schemes/Exec.js +136 -110
- package/dist/schemes/Exec.js.map +1 -1
- package/dist/schemes/ExecOutputScheme.js +1 -1
- package/dist/schemes/ExecOutputScheme.js.map +1 -1
- package/dist/schemes/File.d.ts +5 -4
- package/dist/schemes/File.d.ts.map +1 -1
- package/dist/schemes/File.js +25 -23
- package/dist/schemes/File.js.map +1 -1
- package/dist/schemes/Log.d.ts +11 -0
- package/dist/schemes/Log.d.ts.map +1 -1
- package/dist/schemes/Log.js +172 -107
- package/dist/schemes/Log.js.map +1 -1
- package/dist/schemes/Log.sql +48 -32
- package/dist/schemes/Prompt.d.ts +3 -3
- package/dist/schemes/Prompt.d.ts.map +1 -1
- package/dist/schemes/Prompt.js +4 -1
- package/dist/schemes/Prompt.js.map +1 -1
- package/dist/schemes/Skill.d.ts +4 -4
- package/dist/schemes/Skill.d.ts.map +1 -1
- package/dist/schemes/Skill.js +3 -0
- package/dist/schemes/Skill.js.map +1 -1
- package/dist/schemes/Worker.d.ts +4 -4
- package/dist/schemes/Worker.d.ts.map +1 -1
- package/dist/schemes/Worker.js +11 -7
- package/dist/schemes/Worker.js.map +1 -1
- package/dist/schemes/_entry-crud.d.ts +0 -1
- package/dist/schemes/_entry-crud.d.ts.map +1 -1
- package/dist/schemes/_entry-crud.js +19 -13
- package/dist/schemes/_entry-crud.js.map +1 -1
- package/dist/schemes/_entry-crud.sql +4 -16
- package/dist/schemes/_entry-find.d.ts +17 -16
- package/dist/schemes/_entry-find.d.ts.map +1 -1
- package/dist/schemes/_entry-find.js +58 -52
- package/dist/schemes/_entry-find.js.map +1 -1
- package/dist/schemes/_entry-find.sql +2 -21
- package/dist/schemes/_entry-graph.sql +1 -1
- package/dist/schemes/_entry-manifest.d.ts +5 -17
- package/dist/schemes/_entry-manifest.d.ts.map +1 -1
- package/dist/schemes/_entry-manifest.js +32 -28
- package/dist/schemes/_entry-manifest.js.map +1 -1
- package/dist/schemes/_entry-ops.d.ts +5 -4
- package/dist/schemes/_entry-ops.d.ts.map +1 -1
- package/dist/schemes/_entry-ops.js +89 -49
- package/dist/schemes/_entry-ops.js.map +1 -1
- package/dist/schemes/_entry-ops.sql +30 -4
- package/dist/schemes/_entry-send.js +4 -4
- package/dist/schemes/_entry-send.js.map +1 -1
- package/dist/schemes/_search-index.d.ts.map +1 -1
- package/dist/schemes/_search-index.js +10 -2
- package/dist/schemes/_search-index.js.map +1 -1
- package/dist/schemes/exec-abort.js +2 -2
- package/dist/schemes/exec-abort.js.map +1 -1
- package/dist/server/Daemon.d.ts +7 -42
- package/dist/server/Daemon.d.ts.map +1 -1
- package/dist/server/Daemon.js +139 -900
- package/dist/server/Daemon.js.map +1 -1
- package/dist/server/DrainSupervisor.d.ts +117 -0
- package/dist/server/DrainSupervisor.d.ts.map +1 -0
- package/dist/server/DrainSupervisor.js +802 -0
- package/dist/server/DrainSupervisor.js.map +1 -0
- package/dist/server/drain.sql +2 -2
- package/dist/server/envelope.d.ts +0 -2
- package/dist/server/envelope.d.ts.map +1 -1
- package/dist/server/envelope.js.map +1 -1
- package/dist/server/envelope.sql +2 -2
- package/dist/server/lifecycle-recovery.sql +28 -15
- package/dist/server/logEntry.d.ts +2 -1
- package/dist/server/logEntry.d.ts.map +1 -1
- package/dist/server/logEntry.js +14 -2
- package/dist/server/logEntry.js.map +1 -1
- package/dist/server/logEntry.sql +5 -1
- package/dist/server/seam-entry-read.sql +2 -2
- package/dist/service.js +1 -1
- package/dist/service.js.map +1 -1
- package/migrations/001_schema.sql +622 -216
- package/package.json +39 -34
- package/dist/core/BudgetOverflow.d.ts +0 -20
- package/dist/core/BudgetOverflow.d.ts.map +0 -1
- package/dist/core/BudgetOverflow.js +0 -49
- package/dist/core/BudgetOverflow.js.map +0 -1
- package/dist/core/caps/DbTagCaps.d.ts +0 -10
- package/dist/core/caps/DbTagCaps.d.ts.map +0 -1
- package/dist/core/caps/DbTagCaps.js +0 -46
- package/dist/core/caps/DbTagCaps.js.map +0 -1
- package/dist/core/ruler_count.d.ts +0 -4
- package/dist/core/ruler_count.d.ts.map +0 -1
- package/dist/core/ruler_count.js +0 -7
- package/dist/core/ruler_count.js.map +0 -1
- package/dist/core/token-ruler.d.ts +0 -2
- package/dist/core/token-ruler.d.ts.map +0 -1
- package/dist/core/token-ruler.js +0 -5
- package/dist/core/token-ruler.js.map +0 -1
|
@@ -11,7 +11,6 @@ CREATE TABLE IF NOT EXISTS workspaces (
|
|
|
11
11
|
version INTEGER NOT NULL DEFAULT 0 CHECK (version >= 0),
|
|
12
12
|
name TEXT NOT NULL UNIQUE CHECK (length(name) > 0),
|
|
13
13
|
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
|
|
14
|
-
cost_usd REAL DEFAULT 0 CHECK (cost_usd IS NULL OR cost_usd >= 0),
|
|
15
14
|
project_root TEXT,
|
|
16
15
|
-- {§operator-config} validated client workspace settings; each field composes
|
|
17
16
|
-- with operator configuration at its owning use site.
|
|
@@ -27,9 +26,12 @@ CREATE TABLE IF NOT EXISTS workers (
|
|
|
27
26
|
workspace_id INTEGER NOT NULL,
|
|
28
27
|
name TEXT NOT NULL CHECK (length(name) > 0),
|
|
29
28
|
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
|
|
29
|
+
-- {§worker-provider-identity}: provider affinity must not collide when
|
|
30
|
+
-- independent databases reuse local integer ids.
|
|
31
|
+
provider_identity TEXT NOT NULL DEFAULT (lower(hex(randomblob(16))))
|
|
32
|
+
CHECK (length(provider_identity) = 32 AND provider_identity NOT GLOB '*[^0-9a-f]*'),
|
|
30
33
|
-- workers fork via parent_worker_id; workspaces carry no parent — {§machine-processes-no-fork-workspace}
|
|
31
34
|
parent_worker_id INTEGER CHECK (parent_worker_id IS NULL OR parent_worker_id != id),
|
|
32
|
-
cost_usd REAL DEFAULT 0 CHECK (cost_usd IS NULL OR cost_usd >= 0),
|
|
33
35
|
origin TEXT NOT NULL DEFAULT 'client' CHECK (origin IN ('model', 'client', 'plurnk')),
|
|
34
36
|
-- {§methods-model-worker}: durable identity for the workspace's stable
|
|
35
37
|
-- default conversation; unrelated to its human-facing, reclaimable name.
|
|
@@ -44,6 +46,7 @@ CREATE TABLE IF NOT EXISTS workers (
|
|
|
44
46
|
|
|
45
47
|
CREATE INDEX IF NOT EXISTS workers_workspace_id_created_at ON workers (workspace_id, created_at);
|
|
46
48
|
CREATE INDEX IF NOT EXISTS workers_parent_worker_id ON workers (parent_worker_id);
|
|
49
|
+
CREATE UNIQUE INDEX IF NOT EXISTS workers_provider_identity ON workers (provider_identity);
|
|
47
50
|
CREATE UNIQUE INDEX IF NOT EXISTS workers_workspace_default_conversation
|
|
48
51
|
ON workers (workspace_id) WHERE default_conversation = 1;
|
|
49
52
|
-- NOT unique: a name is frozen per worker ({§machine-processes-worker-origin}) but RECLAIMABLE across
|
|
@@ -52,6 +55,13 @@ CREATE UNIQUE INDEX IF NOT EXISTS workers_workspace_default_conversation
|
|
|
52
55
|
-- → worker_live_by_name → 409), never by this index. Indexed for the by-name resolve/spawn lookup.
|
|
53
56
|
CREATE INDEX IF NOT EXISTS workers_workspace_name ON workers (workspace_id, name);
|
|
54
57
|
|
|
58
|
+
CREATE TRIGGER IF NOT EXISTS workers_provider_identity_immutable
|
|
59
|
+
BEFORE UPDATE OF provider_identity ON workers
|
|
60
|
+
WHEN NEW.provider_identity != OLD.provider_identity
|
|
61
|
+
BEGIN
|
|
62
|
+
SELECT RAISE(ABORT, 'workers.provider_identity is immutable');
|
|
63
|
+
END;
|
|
64
|
+
|
|
55
65
|
-- {§env-delta-log-pull}: one append-only occurrence journal gives every ambient
|
|
56
66
|
-- producer a shared monotonic order. It snapshots only what the observer row
|
|
57
67
|
-- needs; shared-world contents remain owned by entries/files, never copied here.
|
|
@@ -70,6 +80,7 @@ CREATE TABLE IF NOT EXISTS ambient_events (
|
|
|
70
80
|
pathname TEXT,
|
|
71
81
|
rx TEXT,
|
|
72
82
|
attrs TEXT NOT NULL DEFAULT '{}' CHECK (json_valid(attrs)),
|
|
83
|
+
tags TEXT NOT NULL DEFAULT '[]' CHECK (json_valid(tags) AND json_type(tags) = 'array'),
|
|
73
84
|
status_rx INTEGER NOT NULL CHECK (status_rx BETWEEN 100 AND 599),
|
|
74
85
|
terminated_by TEXT CHECK (terminated_by IS NULL OR terminated_by = 'cancel'),
|
|
75
86
|
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
|
|
@@ -198,9 +209,9 @@ BEGIN
|
|
|
198
209
|
END;
|
|
199
210
|
|
|
200
211
|
-- turns
|
|
201
|
-
-- finish_reason / model: provider-call metadata from the provider
|
|
202
|
-
--
|
|
203
|
-
--
|
|
212
|
+
-- finish_reason / model: accepted provider-call metadata from the provider
|
|
213
|
+
-- response contract. Physical request accounting is normalized beneath the
|
|
214
|
+
-- logical emission attempt that caused it.
|
|
204
215
|
CREATE TABLE IF NOT EXISTS turns (
|
|
205
216
|
id INTEGER NOT NULL PRIMARY KEY,
|
|
206
217
|
version INTEGER NOT NULL DEFAULT 0 CHECK (version >= 0),
|
|
@@ -208,15 +219,9 @@ CREATE TABLE IF NOT EXISTS turns (
|
|
|
208
219
|
sequence INTEGER NOT NULL CHECK (sequence >= 1),
|
|
209
220
|
timestamp TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
|
|
210
221
|
status INTEGER NOT NULL CHECK (status BETWEEN 100 AND 599),
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
usage_cached INTEGER NOT NULL DEFAULT 0 CHECK (usage_cached >= 0),
|
|
215
|
-
usage_cost TEXT NOT NULL DEFAULT '[]' CHECK (json_valid(usage_cost) AND json_type(usage_cost) = 'array'),
|
|
216
|
-
usage_cost_usd REAL DEFAULT 0 CHECK (usage_cost_usd IS NULL OR usage_cost_usd >= 0),
|
|
217
|
-
-- Effective packet allowance for this turn's provider and policy; NULL is
|
|
218
|
-
-- uncapped or unknown. {§tokenomics-client-gauge}
|
|
219
|
-
usage_prompt_budget INTEGER CHECK (usage_prompt_budget IS NULL OR usage_prompt_budget >= 1),
|
|
222
|
+
-- Provider-derived curation calibration for this turn; NULL when input
|
|
223
|
+
-- capacity is unknown. {§tokenomics-client-gauge}
|
|
224
|
+
usage_curation_budget INTEGER CHECK (usage_curation_budget IS NULL OR usage_curation_budget >= 1),
|
|
220
225
|
-- {§packet-stored-shape}: NULL means no model request was assembled. A
|
|
221
226
|
-- present packet is either the measured request or that request extended
|
|
222
227
|
-- by the paired admitted-response fields.
|
|
@@ -226,8 +231,8 @@ CREATE TABLE IF NOT EXISTS turns (
|
|
|
226
231
|
WHEN json_valid(packet) = 0 THEN 0
|
|
227
232
|
ELSE COALESCE(
|
|
228
233
|
json_type(packet) = 'object'
|
|
229
|
-
AND json_type(packet, '$.
|
|
230
|
-
AND json_extract(packet, '$.
|
|
234
|
+
AND json_type(packet, '$.weight') = 'integer'
|
|
235
|
+
AND json_extract(packet, '$.weight') >= 0
|
|
231
236
|
AND json_type(packet, '$.sections') = 'array'
|
|
232
237
|
AND json_type(packet, '$.attributions') = 'array'
|
|
233
238
|
AND (
|
|
@@ -258,148 +263,261 @@ CREATE TABLE IF NOT EXISTS turns (
|
|
|
258
263
|
CREATE UNIQUE INDEX IF NOT EXISTS turns_loop_id_sequence ON turns (loop_id, sequence);
|
|
259
264
|
CREATE INDEX IF NOT EXISTS turns_timestamp ON turns (timestamp);
|
|
260
265
|
|
|
261
|
-
--
|
|
262
|
-
-- response evidence
|
|
263
|
-
--
|
|
264
|
-
CREATE TABLE IF NOT EXISTS
|
|
266
|
+
-- One logical provider.generate call. Emission attempts and BARE inferences
|
|
267
|
+
-- share response/failure evidence and cardinal physical request accounting;
|
|
268
|
+
-- operation-specific semantics live in their specializing relations.
|
|
269
|
+
CREATE TABLE IF NOT EXISTS model_calls (
|
|
265
270
|
id INTEGER NOT NULL PRIMARY KEY,
|
|
266
271
|
turn_id INTEGER NOT NULL,
|
|
267
272
|
sequence INTEGER NOT NULL CHECK (sequence >= 1),
|
|
273
|
+
kind TEXT NOT NULL CHECK (kind IN ('emission', 'bare')),
|
|
268
274
|
state TEXT NOT NULL DEFAULT 'pending' CHECK (state IN ('pending', 'response', 'error')),
|
|
269
|
-
accepted INTEGER CHECK (accepted IS NULL OR accepted IN (0, 1)),
|
|
270
275
|
response TEXT CHECK (response IS NULL OR json_valid(response)),
|
|
271
276
|
failure TEXT CHECK (failure IS NULL OR json_valid(failure)),
|
|
272
|
-
|
|
277
|
+
-- Request-shaped provider capacity evidence. A completed response always
|
|
278
|
+
-- has it; a pre-I/O or transport failure may retain it without fabricating
|
|
279
|
+
-- physical usage.
|
|
280
|
+
capacity TEXT CHECK (
|
|
281
|
+
capacity IS NULL OR (json_valid(capacity) AND json_type(capacity) = 'object')
|
|
282
|
+
),
|
|
273
283
|
-- Exact opaque tag set forwarded with this provider call.
|
|
274
284
|
-- {§attribution}
|
|
275
285
|
attributions TEXT NOT NULL DEFAULT '[]' CHECK (
|
|
276
286
|
json_valid(attributions) AND json_type(attributions) = 'array'
|
|
277
287
|
),
|
|
278
|
-
usage_prompt INTEGER CHECK (usage_prompt IS NULL OR usage_prompt >= 0),
|
|
279
|
-
usage_completion INTEGER CHECK (usage_completion IS NULL OR usage_completion >= 0),
|
|
280
|
-
usage_reasoning INTEGER CHECK (usage_reasoning IS NULL OR usage_reasoning >= 0),
|
|
281
|
-
usage_cached INTEGER CHECK (usage_cached IS NULL OR usage_cached >= 0),
|
|
282
|
-
usage_cost TEXT CHECK (usage_cost IS NULL OR (json_valid(usage_cost) AND json_type(usage_cost) = 'object')),
|
|
283
|
-
usage_cost_usd REAL CHECK (usage_cost_usd IS NULL OR usage_cost_usd >= 0),
|
|
284
288
|
finish_reason TEXT,
|
|
285
289
|
model TEXT NOT NULL CHECK (length(model) >= 1),
|
|
286
290
|
timestamp TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
|
|
287
291
|
completed_at TEXT,
|
|
288
292
|
CHECK (
|
|
289
293
|
(state = 'pending'
|
|
290
|
-
AND
|
|
291
|
-
AND
|
|
292
|
-
AND usage_reasoning IS NULL AND usage_cached IS NULL
|
|
293
|
-
AND usage_cost IS NULL AND usage_cost_usd IS NULL
|
|
294
|
+
AND response IS NULL AND failure IS NULL
|
|
295
|
+
AND capacity IS NULL
|
|
294
296
|
AND finish_reason IS NULL AND completed_at IS NULL)
|
|
295
297
|
OR
|
|
296
298
|
(state = 'response'
|
|
297
299
|
AND response IS NOT NULL
|
|
298
|
-
AND
|
|
299
|
-
AND
|
|
300
|
-
AND usage_cost IS NOT NULL AND completed_at IS NOT NULL)
|
|
300
|
+
AND capacity IS NOT NULL
|
|
301
|
+
AND completed_at IS NOT NULL)
|
|
301
302
|
OR
|
|
302
303
|
(state = 'error'
|
|
303
|
-
AND
|
|
304
|
-
AND
|
|
305
|
-
AND usage_reasoning = 0 AND usage_cached = 0
|
|
306
|
-
AND usage_cost IS NOT NULL AND completed_at IS NOT NULL)
|
|
304
|
+
AND response IS NULL AND failure IS NOT NULL
|
|
305
|
+
AND completed_at IS NOT NULL)
|
|
307
306
|
),
|
|
308
307
|
UNIQUE (turn_id, sequence),
|
|
309
308
|
FOREIGN KEY (turn_id) REFERENCES turns(id) ON DELETE CASCADE
|
|
310
309
|
) STRICT;
|
|
311
310
|
|
|
312
|
-
CREATE INDEX IF NOT EXISTS
|
|
313
|
-
CREATE UNIQUE INDEX IF NOT EXISTS turn_attempts_one_accepted_per_turn
|
|
314
|
-
ON turn_attempts (turn_id)
|
|
315
|
-
WHERE accepted = 1;
|
|
311
|
+
CREATE INDEX IF NOT EXISTS model_calls_turn_id ON model_calls (turn_id, sequence);
|
|
316
312
|
|
|
317
|
-
CREATE TRIGGER IF NOT EXISTS
|
|
318
|
-
BEFORE UPDATE OF turn_id, sequence, attributions, timestamp ON
|
|
313
|
+
CREATE TRIGGER IF NOT EXISTS model_calls_request_identity_immutable
|
|
314
|
+
BEFORE UPDATE OF turn_id, sequence, kind, attributions, timestamp ON model_calls
|
|
319
315
|
BEGIN
|
|
320
|
-
SELECT RAISE(ABORT, '
|
|
316
|
+
SELECT RAISE(ABORT, 'model call request identity is immutable');
|
|
321
317
|
END;
|
|
322
318
|
|
|
323
|
-
CREATE TRIGGER IF NOT EXISTS
|
|
324
|
-
BEFORE UPDATE OF state ON
|
|
319
|
+
CREATE TRIGGER IF NOT EXISTS model_calls_state_forward_only
|
|
320
|
+
BEFORE UPDATE OF state ON model_calls
|
|
325
321
|
WHEN NOT (
|
|
326
322
|
NEW.state = OLD.state
|
|
327
323
|
OR (OLD.state = 'pending' AND NEW.state IN ('response', 'error'))
|
|
328
324
|
)
|
|
329
325
|
BEGIN
|
|
330
|
-
SELECT RAISE(ABORT, '
|
|
326
|
+
SELECT RAISE(ABORT, 'model call state may only close once');
|
|
331
327
|
END;
|
|
332
328
|
|
|
333
|
-
CREATE TRIGGER IF NOT EXISTS
|
|
334
|
-
BEFORE UPDATE OF response,
|
|
335
|
-
|
|
336
|
-
ON turn_attempts
|
|
329
|
+
CREATE TRIGGER IF NOT EXISTS model_calls_observation_immutable
|
|
330
|
+
BEFORE UPDATE OF response, failure, capacity, finish_reason, model, completed_at
|
|
331
|
+
ON model_calls
|
|
337
332
|
WHEN OLD.state != 'pending'
|
|
338
333
|
BEGIN
|
|
339
|
-
SELECT RAISE(ABORT, '
|
|
334
|
+
SELECT RAISE(ABORT, 'model call observation is immutable');
|
|
335
|
+
END;
|
|
336
|
+
|
|
337
|
+
-- Emission admission specializes one model call without re-owning its response,
|
|
338
|
+
-- provider identity, ordering, or accounting evidence.
|
|
339
|
+
CREATE TABLE IF NOT EXISTS turn_attempts (
|
|
340
|
+
id INTEGER NOT NULL PRIMARY KEY,
|
|
341
|
+
model_call_id INTEGER NOT NULL UNIQUE,
|
|
342
|
+
accepted INTEGER CHECK (accepted IS NULL OR accepted IN (0, 1)),
|
|
343
|
+
parse_errors TEXT NOT NULL DEFAULT '[]' CHECK (
|
|
344
|
+
json_valid(parse_errors) AND json_type(parse_errors) = 'array'
|
|
345
|
+
),
|
|
346
|
+
FOREIGN KEY (model_call_id) REFERENCES model_calls(id) ON DELETE CASCADE
|
|
347
|
+
) STRICT;
|
|
348
|
+
|
|
349
|
+
CREATE TRIGGER IF NOT EXISTS turn_attempts_request_identity_immutable
|
|
350
|
+
BEFORE UPDATE OF model_call_id ON turn_attempts
|
|
351
|
+
BEGIN
|
|
352
|
+
SELECT RAISE(ABORT, 'emission attempt identity is immutable');
|
|
353
|
+
END;
|
|
354
|
+
|
|
355
|
+
CREATE TRIGGER IF NOT EXISTS turn_attempts_emission_call_only
|
|
356
|
+
BEFORE INSERT ON turn_attempts
|
|
357
|
+
WHEN COALESCE((SELECT kind = 'emission' FROM model_calls WHERE id = NEW.model_call_id), 0) != 1
|
|
358
|
+
BEGIN
|
|
359
|
+
SELECT RAISE(ABORT, 'turn attempt requires an emission model call');
|
|
360
|
+
END;
|
|
361
|
+
|
|
362
|
+
CREATE TRIGGER IF NOT EXISTS turn_attempts_classification_after_response
|
|
363
|
+
BEFORE UPDATE OF accepted, parse_errors ON turn_attempts
|
|
364
|
+
WHEN COALESCE((SELECT state = 'response' FROM model_calls WHERE id = OLD.model_call_id), 0) != 1
|
|
365
|
+
BEGIN
|
|
366
|
+
SELECT RAISE(ABORT, 'emission classification requires model response evidence');
|
|
340
367
|
END;
|
|
341
368
|
|
|
342
369
|
CREATE TRIGGER IF NOT EXISTS turn_attempts_classification_once
|
|
343
|
-
BEFORE UPDATE OF accepted, parse_errors
|
|
370
|
+
BEFORE UPDATE OF accepted, parse_errors
|
|
344
371
|
ON turn_attempts
|
|
345
|
-
WHEN OLD.
|
|
372
|
+
WHEN OLD.accepted IS NOT NULL
|
|
346
373
|
BEGIN
|
|
347
|
-
SELECT RAISE(ABORT, '
|
|
374
|
+
SELECT RAISE(ABORT, 'emission attempt classification is immutable');
|
|
348
375
|
END;
|
|
349
376
|
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
COALESCE((
|
|
360
|
-
SELECT json_group_array(json(ordered.usage_cost))
|
|
361
|
-
FROM (
|
|
362
|
-
SELECT observed.usage_cost
|
|
363
|
-
FROM turn_attempts observed
|
|
364
|
-
WHERE observed.turn_id = t.id AND observed.usage_cost IS NOT NULL
|
|
365
|
-
ORDER BY observed.sequence
|
|
366
|
-
) ordered
|
|
367
|
-
), '[]') AS usage_cost,
|
|
368
|
-
CASE
|
|
369
|
-
WHEN COUNT(a.id) = 0 THEN 0
|
|
370
|
-
WHEN COUNT(a.id) FILTER (WHERE a.usage_cost_usd IS NULL) > 0 THEN NULL
|
|
371
|
-
ELSE COALESCE(SUM(a.usage_cost_usd), 0)
|
|
372
|
-
END AS usage_cost_usd
|
|
373
|
-
FROM turns t
|
|
374
|
-
LEFT JOIN turn_attempts a ON a.turn_id = t.id
|
|
375
|
-
GROUP BY t.id;
|
|
376
|
-
|
|
377
|
-
CREATE TRIGGER IF NOT EXISTS turn_attempts_rollup_insert
|
|
378
|
-
AFTER INSERT ON turn_attempts
|
|
377
|
+
CREATE TRIGGER IF NOT EXISTS turn_attempts_one_accepted_insert
|
|
378
|
+
BEFORE INSERT ON turn_attempts
|
|
379
|
+
WHEN NEW.accepted = 1 AND EXISTS (
|
|
380
|
+
SELECT 1
|
|
381
|
+
FROM turn_attempts existing
|
|
382
|
+
JOIN model_calls old_call ON old_call.id = existing.model_call_id
|
|
383
|
+
JOIN model_calls new_call ON new_call.id = NEW.model_call_id
|
|
384
|
+
WHERE old_call.turn_id = new_call.turn_id AND existing.accepted = 1
|
|
385
|
+
)
|
|
379
386
|
BEGIN
|
|
380
|
-
|
|
381
|
-
usage_prompt = (SELECT usage_prompt FROM turn_attempt_rollups WHERE turn_id = NEW.turn_id),
|
|
382
|
-
usage_completion = (SELECT usage_completion FROM turn_attempt_rollups WHERE turn_id = NEW.turn_id),
|
|
383
|
-
usage_reasoning = (SELECT usage_reasoning FROM turn_attempt_rollups WHERE turn_id = NEW.turn_id),
|
|
384
|
-
usage_cached = (SELECT usage_cached FROM turn_attempt_rollups WHERE turn_id = NEW.turn_id),
|
|
385
|
-
usage_cost = (SELECT usage_cost FROM turn_attempt_rollups WHERE turn_id = NEW.turn_id),
|
|
386
|
-
usage_cost_usd = (SELECT usage_cost_usd FROM turn_attempt_rollups WHERE turn_id = NEW.turn_id)
|
|
387
|
-
WHERE id = NEW.turn_id;
|
|
387
|
+
SELECT RAISE(ABORT, 'one emission may be accepted per turn');
|
|
388
388
|
END;
|
|
389
389
|
|
|
390
|
-
CREATE TRIGGER IF NOT EXISTS
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
390
|
+
CREATE TRIGGER IF NOT EXISTS turn_attempts_one_accepted_update
|
|
391
|
+
BEFORE UPDATE OF accepted ON turn_attempts
|
|
392
|
+
WHEN NEW.accepted = 1 AND EXISTS (
|
|
393
|
+
SELECT 1
|
|
394
|
+
FROM turn_attempts existing
|
|
395
|
+
JOIN model_calls old_call ON old_call.id = existing.model_call_id
|
|
396
|
+
JOIN model_calls new_call ON new_call.id = NEW.model_call_id
|
|
397
|
+
WHERE old_call.turn_id = new_call.turn_id
|
|
398
|
+
AND existing.id != OLD.id
|
|
399
|
+
AND existing.accepted = 1
|
|
400
|
+
)
|
|
401
|
+
BEGIN
|
|
402
|
+
SELECT RAISE(ABORT, 'one emission may be accepted per turn');
|
|
403
|
+
END;
|
|
404
|
+
|
|
405
|
+
-- {§provider-request-accounting}: one row is opened before one physical
|
|
406
|
+
-- provider request and settled exactly once with the evidence from that request.
|
|
407
|
+
-- Monetary values remain canonical decimal strings; SQLite REAL is never an
|
|
408
|
+
-- accounting representation.
|
|
409
|
+
CREATE TABLE IF NOT EXISTS provider_requests (
|
|
410
|
+
id INTEGER NOT NULL PRIMARY KEY,
|
|
411
|
+
model_call_id INTEGER NOT NULL,
|
|
412
|
+
sequence INTEGER NOT NULL CHECK (sequence >= 1),
|
|
413
|
+
provider TEXT NOT NULL CHECK (length(provider) > 0),
|
|
414
|
+
model TEXT NOT NULL CHECK (length(model) > 0),
|
|
415
|
+
state TEXT NOT NULL DEFAULT 'pending' CHECK (state IN ('pending', 'settled')),
|
|
416
|
+
outcome TEXT CHECK (outcome IN ('response', 'error')),
|
|
417
|
+
status INTEGER CHECK (status IS NULL OR status BETWEEN 100 AND 599),
|
|
418
|
+
usage_input INTEGER CHECK (usage_input IS NULL OR usage_input >= 0),
|
|
419
|
+
usage_output INTEGER CHECK (usage_output IS NULL OR usage_output >= 0),
|
|
420
|
+
usage_total INTEGER CHECK (usage_total IS NULL OR usage_total >= 0),
|
|
421
|
+
usage_input_no_cache INTEGER CHECK (usage_input_no_cache IS NULL OR usage_input_no_cache >= 0),
|
|
422
|
+
usage_input_cache_read INTEGER CHECK (usage_input_cache_read IS NULL OR usage_input_cache_read >= 0),
|
|
423
|
+
usage_input_cache_write INTEGER CHECK (usage_input_cache_write IS NULL OR usage_input_cache_write >= 0),
|
|
424
|
+
usage_output_text INTEGER CHECK (usage_output_text IS NULL OR usage_output_text >= 0),
|
|
425
|
+
usage_output_reasoning INTEGER CHECK (usage_output_reasoning IS NULL OR usage_output_reasoning >= 0),
|
|
426
|
+
cost_kind TEXT CHECK (cost_kind IN ('charged', 'estimated', 'unknown')),
|
|
427
|
+
cost_amount TEXT,
|
|
428
|
+
cost_currency TEXT,
|
|
429
|
+
cost_usd_equivalent TEXT,
|
|
430
|
+
cost_source TEXT,
|
|
431
|
+
cost_reason TEXT,
|
|
432
|
+
started_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
|
|
433
|
+
completed_at TEXT,
|
|
434
|
+
CHECK (
|
|
435
|
+
(state = 'pending'
|
|
436
|
+
AND outcome IS NULL AND status IS NULL
|
|
437
|
+
AND usage_input IS NULL AND usage_output IS NULL AND usage_total IS NULL
|
|
438
|
+
AND usage_input_no_cache IS NULL AND usage_input_cache_read IS NULL
|
|
439
|
+
AND usage_input_cache_write IS NULL AND usage_output_text IS NULL
|
|
440
|
+
AND usage_output_reasoning IS NULL
|
|
441
|
+
AND cost_kind IS NULL AND cost_amount IS NULL AND cost_currency IS NULL
|
|
442
|
+
AND cost_usd_equivalent IS NULL AND cost_source IS NULL AND cost_reason IS NULL
|
|
443
|
+
AND completed_at IS NULL)
|
|
444
|
+
OR
|
|
445
|
+
(state = 'settled' AND outcome IS NOT NULL AND cost_kind IS NOT NULL AND completed_at IS NOT NULL)
|
|
446
|
+
),
|
|
447
|
+
CHECK (
|
|
448
|
+
cost_amount IS NULL OR (
|
|
449
|
+
length(cost_amount) > 0
|
|
450
|
+
AND cost_amount NOT GLOB '*[^0-9.]*'
|
|
451
|
+
AND cost_amount NOT LIKE '.%'
|
|
452
|
+
AND cost_amount NOT LIKE '%.'
|
|
453
|
+
AND cost_amount NOT LIKE '%.%.%'
|
|
454
|
+
AND (cost_amount = '0' OR cost_amount NOT GLOB '0[0-9]*')
|
|
455
|
+
)
|
|
456
|
+
),
|
|
457
|
+
CHECK (
|
|
458
|
+
cost_usd_equivalent IS NULL OR (
|
|
459
|
+
length(cost_usd_equivalent) > 0
|
|
460
|
+
AND cost_usd_equivalent NOT GLOB '*[^0-9.]*'
|
|
461
|
+
AND cost_usd_equivalent NOT LIKE '.%'
|
|
462
|
+
AND cost_usd_equivalent NOT LIKE '%.'
|
|
463
|
+
AND cost_usd_equivalent NOT LIKE '%.%.%'
|
|
464
|
+
AND (cost_usd_equivalent = '0' OR cost_usd_equivalent NOT GLOB '0[0-9]*')
|
|
465
|
+
)
|
|
466
|
+
),
|
|
467
|
+
CHECK (
|
|
468
|
+
cost_currency IS NULL OR (
|
|
469
|
+
length(cost_currency) BETWEEN 3 AND 12
|
|
470
|
+
AND substr(cost_currency, 1, 1) GLOB '[A-Z]'
|
|
471
|
+
AND cost_currency NOT GLOB '*[^A-Z0-9]*'
|
|
472
|
+
)
|
|
473
|
+
),
|
|
474
|
+
CHECK (
|
|
475
|
+
cost_kind IS NULL
|
|
476
|
+
OR (cost_kind = 'charged'
|
|
477
|
+
AND cost_amount IS NOT NULL AND cost_currency IS NOT NULL
|
|
478
|
+
AND cost_source IS NOT NULL AND length(cost_source) > 0
|
|
479
|
+
AND cost_reason IS NULL)
|
|
480
|
+
OR (cost_kind = 'estimated'
|
|
481
|
+
AND cost_amount IS NOT NULL AND cost_currency IS NOT NULL
|
|
482
|
+
AND cost_usd_equivalent IS NULL
|
|
483
|
+
AND cost_source IS NOT NULL AND length(cost_source) > 0
|
|
484
|
+
AND cost_reason IS NULL)
|
|
485
|
+
OR (cost_kind = 'unknown'
|
|
486
|
+
AND cost_amount IS NULL AND cost_currency IS NULL
|
|
487
|
+
AND cost_usd_equivalent IS NULL AND cost_source IS NULL
|
|
488
|
+
AND cost_reason IS NOT NULL AND length(cost_reason) > 0)
|
|
489
|
+
),
|
|
490
|
+
UNIQUE (model_call_id, sequence),
|
|
491
|
+
FOREIGN KEY (model_call_id) REFERENCES model_calls(id) ON DELETE CASCADE
|
|
492
|
+
) STRICT;
|
|
493
|
+
|
|
494
|
+
CREATE INDEX IF NOT EXISTS provider_requests_model_call_id
|
|
495
|
+
ON provider_requests (model_call_id, sequence);
|
|
496
|
+
|
|
497
|
+
CREATE TRIGGER IF NOT EXISTS provider_requests_identity_immutable
|
|
498
|
+
BEFORE UPDATE OF model_call_id, sequence, provider, model, started_at ON provider_requests
|
|
499
|
+
BEGIN
|
|
500
|
+
SELECT RAISE(ABORT, 'provider request identity is immutable');
|
|
501
|
+
END;
|
|
502
|
+
|
|
503
|
+
CREATE TRIGGER IF NOT EXISTS provider_requests_state_forward_only
|
|
504
|
+
BEFORE UPDATE OF state ON provider_requests
|
|
505
|
+
WHEN NOT (NEW.state = OLD.state OR (OLD.state = 'pending' AND NEW.state = 'settled'))
|
|
394
506
|
BEGIN
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
507
|
+
SELECT RAISE(ABORT, 'provider request may only settle once');
|
|
508
|
+
END;
|
|
509
|
+
|
|
510
|
+
CREATE TRIGGER IF NOT EXISTS provider_requests_settlement_immutable
|
|
511
|
+
BEFORE UPDATE OF outcome, status,
|
|
512
|
+
usage_input, usage_output, usage_total,
|
|
513
|
+
usage_input_no_cache, usage_input_cache_read, usage_input_cache_write,
|
|
514
|
+
usage_output_text, usage_output_reasoning,
|
|
515
|
+
cost_kind, cost_amount, cost_currency, cost_usd_equivalent,
|
|
516
|
+
cost_source, cost_reason, completed_at
|
|
517
|
+
ON provider_requests
|
|
518
|
+
WHEN OLD.state != 'pending'
|
|
519
|
+
BEGIN
|
|
520
|
+
SELECT RAISE(ABORT, 'provider request settlement is immutable');
|
|
403
521
|
END;
|
|
404
522
|
|
|
405
523
|
-- derivations
|
|
@@ -413,6 +531,7 @@ CREATE TABLE IF NOT EXISTS derivations (
|
|
|
413
531
|
state TEXT NOT NULL DEFAULT 'building' CHECK (state IN ('building', 'complete')),
|
|
414
532
|
disposition TEXT CHECK (disposition IN ('vector', 'lexical', 'excluded', 'nonsemantic', 'failed')),
|
|
415
533
|
reason TEXT,
|
|
534
|
+
parse_issues INTEGER CHECK (parse_issues IS NULL OR parse_issues > 0),
|
|
416
535
|
CHECK ((state = 'building' AND disposition IS NULL) OR (state = 'complete' AND disposition IS NOT NULL))
|
|
417
536
|
) STRICT;
|
|
418
537
|
|
|
@@ -476,9 +595,9 @@ CREATE TABLE IF NOT EXISTS entry_channels (
|
|
|
476
595
|
name TEXT NOT NULL CHECK (length(name) > 0),
|
|
477
596
|
content TEXT NOT NULL CHECK (length(content) <= 104857600),
|
|
478
597
|
mimetype TEXT NOT NULL CHECK (length(mimetype) > 0),
|
|
479
|
-
|
|
480
|
-
--
|
|
481
|
-
--
|
|
598
|
+
weight INTEGER NOT NULL DEFAULT 0 CHECK (weight >= 0),
|
|
599
|
+
-- Content identity: sha256 of content, stamped at static writes; streamed
|
|
600
|
+
-- appends leave it NULL. Curation weight remains model-independent.
|
|
482
601
|
content_hash TEXT,
|
|
483
602
|
state TEXT NOT NULL DEFAULT 'static' CHECK (state IN ('static', 'active', 'closed', 'errored')),
|
|
484
603
|
-- Exact terminal producer evidence for this representation channel. NULL is
|
|
@@ -524,14 +643,11 @@ BEGIN
|
|
|
524
643
|
UPDATE entries SET updated_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now') WHERE id = NEW.entry_id;
|
|
525
644
|
END;
|
|
526
645
|
|
|
527
|
-
CREATE
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
) STRICT, WITHOUT ROWID;
|
|
533
|
-
|
|
534
|
-
CREATE INDEX IF NOT EXISTS entry_tags_tag ON entry_tags (tag);
|
|
646
|
+
CREATE TRIGGER IF NOT EXISTS entries_touch_on_channel_update
|
|
647
|
+
AFTER UPDATE ON entry_channels
|
|
648
|
+
BEGIN
|
|
649
|
+
UPDATE entries SET updated_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now') WHERE id = NEW.entry_id;
|
|
650
|
+
END;
|
|
535
651
|
|
|
536
652
|
-- symbol_defs
|
|
537
653
|
-- @graph NODES ({§relation-indexed-dialects}). Code symbol definitions, populated
|
|
@@ -623,12 +739,15 @@ CREATE TABLE IF NOT EXISTS log_entries (
|
|
|
623
739
|
ambient_event_id INTEGER REFERENCES ambient_events(id),
|
|
624
740
|
-- Search derivation attached to this durable log result, when available.
|
|
625
741
|
deep_hash TEXT REFERENCES derivations(deep_hash),
|
|
742
|
+
-- Exact logical provider call represented by a BARE result or model-emission
|
|
743
|
+
-- mirror. Other operation and ambient rows carry no model-call identity.
|
|
744
|
+
model_call_id INTEGER REFERENCES model_calls(id),
|
|
626
745
|
|
|
627
746
|
-- 'error' is an ACTIONLESS row ({§operation-results} — errors are log items): a parse failure that
|
|
628
747
|
-- produced no op still records a log entry (op='error', status_rx≥400, no target) so the model
|
|
629
748
|
-- can fold/kill/recall its own mistakes like any other log row — one budget surface, the log.
|
|
630
|
-
--
|
|
631
|
-
--
|
|
749
|
+
-- Actionless artifacts carry NULL here: kernel-authored worker initialization
|
|
750
|
+
-- ({§worker-initialization-entry}) or a model emission ({§model-entry}).
|
|
632
751
|
-- No op enum here: the grammar op set is grammar's contract (PlurnkOp), and this column is written
|
|
633
752
|
-- only by the PlurnkOp-typed engine (grammar ops), service row selectors, or NULL for no op.
|
|
634
753
|
-- A SQL enum would be a hand-copy of grammar's op list that silently goes stale on every new verb
|
|
@@ -656,7 +775,9 @@ CREATE TABLE IF NOT EXISTS log_entries (
|
|
|
656
775
|
mimetype_rx TEXT NOT NULL CHECK (length(mimetype_rx) > 0),
|
|
657
776
|
status_rx INTEGER NOT NULL CHECK (status_rx BETWEEN 100 AND 599),
|
|
658
777
|
|
|
659
|
-
|
|
778
|
+
-- Complete canonical LogBody content before coordinate/presentation
|
|
779
|
+
-- projection; persistence envelopes do not contribute. {§tokenomics-weight-stored-at-write}
|
|
780
|
+
weight INTEGER NOT NULL DEFAULT 0 CHECK (weight >= 0),
|
|
660
781
|
|
|
661
782
|
state TEXT NOT NULL DEFAULT 'resolved'
|
|
662
783
|
CHECK (state IN ('proposed', 'resolved', 'failed', 'cancelled')),
|
|
@@ -665,7 +786,9 @@ CREATE TABLE IF NOT EXISTS log_entries (
|
|
|
665
786
|
|
|
666
787
|
expanded INTEGER NOT NULL DEFAULT 1 CHECK (expanded IN (0, 1)),
|
|
667
788
|
|
|
668
|
-
CHECK ((op IS NULL) = COALESCE(json_extract(attrs, '$.kind')
|
|
789
|
+
CHECK ((op IS NULL) = COALESCE(json_extract(attrs, '$.kind') IN ('initialization', 'model_emission'), 0)),
|
|
790
|
+
CHECK (json_extract(attrs, '$.kind') != 'initialization' OR origin = 'plurnk'),
|
|
791
|
+
CHECK (json_extract(attrs, '$.kind') != 'model_emission' OR origin = 'model'),
|
|
669
792
|
|
|
670
793
|
FOREIGN KEY (worker_id) REFERENCES workers(id) ON DELETE CASCADE,
|
|
671
794
|
FOREIGN KEY (loop_id) REFERENCES loops(id) ON DELETE CASCADE,
|
|
@@ -676,37 +799,388 @@ CREATE UNIQUE INDEX IF NOT EXISTS log_entries_turn_id_sequence ON log_entries (t
|
|
|
676
799
|
CREATE INDEX IF NOT EXISTS log_entries_worker_id ON log_entries (worker_id);
|
|
677
800
|
CREATE INDEX IF NOT EXISTS log_entries_loop_id ON log_entries (loop_id);
|
|
678
801
|
CREATE INDEX IF NOT EXISTS log_entries_at ON log_entries (at);
|
|
802
|
+
CREATE UNIQUE INDEX IF NOT EXISTS log_entries_model_call_id
|
|
803
|
+
ON log_entries (model_call_id)
|
|
804
|
+
WHERE model_call_id IS NOT NULL;
|
|
679
805
|
CREATE UNIQUE INDEX IF NOT EXISTS log_entries_worker_ambient_event
|
|
680
806
|
ON log_entries (worker_id, ambient_event_id)
|
|
681
807
|
WHERE ambient_event_id IS NOT NULL;
|
|
682
808
|
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
809
|
+
CREATE TRIGGER IF NOT EXISTS log_entries_model_call_valid
|
|
810
|
+
BEFORE INSERT ON log_entries
|
|
811
|
+
WHEN NEW.model_call_id IS NOT NULL
|
|
812
|
+
AND NOT EXISTS (
|
|
813
|
+
SELECT 1
|
|
814
|
+
FROM model_calls call
|
|
815
|
+
WHERE call.id = NEW.model_call_id
|
|
816
|
+
AND call.turn_id = NEW.turn_id
|
|
817
|
+
AND call.state != 'pending'
|
|
818
|
+
AND (
|
|
819
|
+
(call.kind = 'bare' AND NEW.op = 'BARE')
|
|
820
|
+
OR (
|
|
821
|
+
call.kind = 'emission'
|
|
822
|
+
AND NEW.op IS NULL
|
|
823
|
+
AND json_extract(NEW.attrs, '$.kind') = 'model_emission'
|
|
824
|
+
)
|
|
825
|
+
)
|
|
826
|
+
)
|
|
827
|
+
BEGIN
|
|
828
|
+
SELECT RAISE(ABORT, 'log entry model call does not match its represented result');
|
|
829
|
+
END;
|
|
830
|
+
|
|
831
|
+
-- {§log-item-tags} — log classification plus OPEN/FOLD selection and mutation.
|
|
832
|
+
-- CASCADE erases the classification with its row.
|
|
686
833
|
CREATE TABLE IF NOT EXISTS log_tags (
|
|
687
834
|
log_entry_id INTEGER NOT NULL,
|
|
688
|
-
tag TEXT NOT NULL
|
|
835
|
+
tag TEXT NOT NULL,
|
|
689
836
|
PRIMARY KEY (log_entry_id, tag),
|
|
690
837
|
FOREIGN KEY (log_entry_id) REFERENCES log_entries(id) ON DELETE CASCADE
|
|
691
838
|
) STRICT, WITHOUT ROWID;
|
|
692
839
|
|
|
840
|
+
-- The relational table owns stored tag identity. Producers and curation
|
|
841
|
+
-- signals carry signs, but the stored name never does; delimiters, whitespace,
|
|
842
|
+
-- and control characters cannot become durable classifications through a
|
|
843
|
+
-- private SQL caller either.
|
|
844
|
+
CREATE TRIGGER IF NOT EXISTS log_tags_name_valid
|
|
845
|
+
BEFORE INSERT ON log_tags
|
|
846
|
+
WHEN length(NEW.tag) = 0
|
|
847
|
+
OR substr(NEW.tag, 1, 1) IN ('+', '-')
|
|
848
|
+
OR instr(NEW.tag, '[') > 0
|
|
849
|
+
OR instr(NEW.tag, ']') > 0
|
|
850
|
+
OR instr(NEW.tag, ',') > 0
|
|
851
|
+
OR instr(NEW.tag, char(0)) > 0
|
|
852
|
+
OR EXISTS (
|
|
853
|
+
WITH RECURSIVE offsets(position) AS (
|
|
854
|
+
SELECT 1
|
|
855
|
+
UNION ALL
|
|
856
|
+
SELECT position + 1 FROM offsets WHERE position < length(NEW.tag)
|
|
857
|
+
)
|
|
858
|
+
SELECT 1
|
|
859
|
+
FROM offsets
|
|
860
|
+
WHERE unicode(substr(NEW.tag, position, 1)) BETWEEN 0 AND 32
|
|
861
|
+
OR unicode(substr(NEW.tag, position, 1)) BETWEEN 127 AND 159
|
|
862
|
+
OR unicode(substr(NEW.tag, position, 1)) IN (160, 5760, 8232, 8233, 8239, 8287, 12288, 65279)
|
|
863
|
+
OR unicode(substr(NEW.tag, position, 1)) BETWEEN 8192 AND 8202
|
|
864
|
+
)
|
|
865
|
+
BEGIN
|
|
866
|
+
SELECT RAISE(ABORT, 'log tag name is invalid');
|
|
867
|
+
END;
|
|
868
|
+
|
|
693
869
|
CREATE INDEX IF NOT EXISTS log_tags_tag ON log_tags (tag);
|
|
694
870
|
|
|
695
|
-
--
|
|
696
|
-
--
|
|
697
|
-
--
|
|
871
|
+
-- Signal-derived classification is part of the log-row insert, not a later JS
|
|
872
|
+
-- write. This keeps the operation, its ambient occurrence, and its complete
|
|
873
|
+
-- initial folksonomy inside one SQLite commit.
|
|
874
|
+
CREATE TRIGGER IF NOT EXISTS log_entries_classify_signal
|
|
875
|
+
AFTER INSERT ON log_entries
|
|
876
|
+
WHEN NEW.op IN ('FIND', 'READ', 'EDIT', 'COPY', 'MOVE', 'BARE')
|
|
877
|
+
AND NEW.signal IS NOT NULL
|
|
878
|
+
BEGIN
|
|
879
|
+
SELECT CASE
|
|
880
|
+
WHEN json_type(NEW.signal) != 'array'
|
|
881
|
+
OR EXISTS (
|
|
882
|
+
SELECT 1 FROM json_each(NEW.signal)
|
|
883
|
+
WHERE type != 'text'
|
|
884
|
+
OR length(value) = 0
|
|
885
|
+
OR substr(value, 1, 1) = '-'
|
|
886
|
+
OR (
|
|
887
|
+
substr(value, 1, 1) = '+'
|
|
888
|
+
AND (length(value) < 2 OR substr(value, 2, 1) IN ('+', '-'))
|
|
889
|
+
)
|
|
890
|
+
OR instr(value, '[') > 0
|
|
891
|
+
OR instr(value, ']') > 0
|
|
892
|
+
OR instr(value, ',') > 0
|
|
893
|
+
OR instr(value, char(0)) > 0
|
|
894
|
+
OR EXISTS (
|
|
895
|
+
WITH RECURSIVE offsets(position) AS (
|
|
896
|
+
SELECT 1
|
|
897
|
+
UNION ALL
|
|
898
|
+
SELECT position + 1 FROM offsets WHERE position < length(value)
|
|
899
|
+
)
|
|
900
|
+
SELECT 1
|
|
901
|
+
FROM offsets
|
|
902
|
+
WHERE unicode(substr(value, position, 1)) BETWEEN 0 AND 32
|
|
903
|
+
OR unicode(substr(value, position, 1)) BETWEEN 127 AND 159
|
|
904
|
+
OR unicode(substr(value, position, 1)) IN (160, 5760, 8232, 8233, 8239, 8287, 12288, 65279)
|
|
905
|
+
OR unicode(substr(value, position, 1)) BETWEEN 8192 AND 8202
|
|
906
|
+
)
|
|
907
|
+
)
|
|
908
|
+
THEN RAISE(ABORT, 'classifying log operation signal accepts only tag or +tag additions')
|
|
909
|
+
END;
|
|
910
|
+
INSERT OR IGNORE INTO log_tags (log_entry_id, tag)
|
|
911
|
+
SELECT NEW.id, CASE WHEN substr(value, 1, 1) = '+' THEN substr(value, 2) ELSE value END
|
|
912
|
+
FROM json_each(NEW.signal);
|
|
913
|
+
|
|
914
|
+
-- The ambient-event and classification AFTER INSERT triggers may run in
|
|
915
|
+
-- either order. If the event already exists, finish its initial
|
|
916
|
+
-- snapshot here; otherwise its own trigger aggregates these rows later.
|
|
917
|
+
UPDATE ambient_events
|
|
918
|
+
SET tags = COALESCE((
|
|
919
|
+
SELECT json_group_array(tag)
|
|
920
|
+
FROM (SELECT tag FROM log_tags WHERE log_entry_id = NEW.id ORDER BY tag)
|
|
921
|
+
), '[]')
|
|
922
|
+
WHERE kind = 'edit'
|
|
923
|
+
AND producer_worker_id = NEW.worker_id
|
|
924
|
+
AND source_record_id = NEW.id;
|
|
925
|
+
END;
|
|
926
|
+
|
|
927
|
+
-- Successful OPEN/FOLD rows are durable curation events even though their
|
|
928
|
+
-- ordinary packet projection is suppressed ({§fold-open-meta-operations}).
|
|
929
|
+
-- Preserve the exact selected set and each target's pre-event visibility so a
|
|
930
|
+
-- broad selector never collapses into the lossy fact `matched: N`.
|
|
931
|
+
CREATE TABLE IF NOT EXISTS log_curation_effects (
|
|
932
|
+
operation_log_entry_id INTEGER NOT NULL,
|
|
933
|
+
target_log_entry_id INTEGER NOT NULL,
|
|
934
|
+
expanded_before INTEGER NOT NULL CHECK (expanded_before IN (0, 1)),
|
|
935
|
+
tags_added TEXT NOT NULL DEFAULT '[]'
|
|
936
|
+
CHECK (json_valid(tags_added) AND json_type(tags_added) = 'array'),
|
|
937
|
+
tags_removed TEXT NOT NULL DEFAULT '[]'
|
|
938
|
+
CHECK (json_valid(tags_removed) AND json_type(tags_removed) = 'array'),
|
|
939
|
+
PRIMARY KEY (operation_log_entry_id, target_log_entry_id),
|
|
940
|
+
CHECK (operation_log_entry_id != target_log_entry_id),
|
|
941
|
+
FOREIGN KEY (operation_log_entry_id) REFERENCES log_entries(id) ON DELETE CASCADE,
|
|
942
|
+
FOREIGN KEY (target_log_entry_id) REFERENCES log_entries(id) ON DELETE CASCADE
|
|
943
|
+
) STRICT, WITHOUT ROWID;
|
|
944
|
+
|
|
945
|
+
CREATE INDEX IF NOT EXISTS log_curation_effects_target
|
|
946
|
+
ON log_curation_effects (target_log_entry_id);
|
|
947
|
+
|
|
948
|
+
-- Make an invalid curation record structurally unavailable: the event must be
|
|
949
|
+
-- one successful OPEN/FOLD row, both rows must belong to the same worker, and
|
|
950
|
+
-- both exact delta sets must contain only unique canonical tag identities.
|
|
951
|
+
CREATE TRIGGER IF NOT EXISTS log_curation_effects_valid
|
|
952
|
+
BEFORE INSERT ON log_curation_effects
|
|
953
|
+
BEGIN
|
|
954
|
+
SELECT CASE WHEN
|
|
955
|
+
NOT EXISTS (
|
|
956
|
+
SELECT 1
|
|
957
|
+
FROM log_entries operation
|
|
958
|
+
JOIN log_entries target ON target.id = NEW.target_log_entry_id
|
|
959
|
+
WHERE operation.id = NEW.operation_log_entry_id
|
|
960
|
+
AND operation.op IN ('OPEN', 'FOLD')
|
|
961
|
+
AND operation.status_rx < 400
|
|
962
|
+
AND operation.worker_id = target.worker_id
|
|
963
|
+
)
|
|
964
|
+
OR EXISTS (
|
|
965
|
+
SELECT 1
|
|
966
|
+
FROM (
|
|
967
|
+
SELECT value, type FROM json_each(NEW.tags_added)
|
|
968
|
+
UNION ALL
|
|
969
|
+
SELECT value, type FROM json_each(NEW.tags_removed)
|
|
970
|
+
) tag
|
|
971
|
+
WHERE tag.type != 'text'
|
|
972
|
+
OR length(tag.value) = 0
|
|
973
|
+
OR substr(tag.value, 1, 1) IN ('+', '-')
|
|
974
|
+
OR instr(tag.value, '[') > 0
|
|
975
|
+
OR instr(tag.value, ']') > 0
|
|
976
|
+
OR instr(tag.value, ',') > 0
|
|
977
|
+
OR instr(tag.value, char(0)) > 0
|
|
978
|
+
OR EXISTS (
|
|
979
|
+
WITH RECURSIVE offsets(position) AS (
|
|
980
|
+
SELECT 1
|
|
981
|
+
UNION ALL
|
|
982
|
+
SELECT position + 1 FROM offsets WHERE position < length(tag.value)
|
|
983
|
+
)
|
|
984
|
+
SELECT 1
|
|
985
|
+
FROM offsets
|
|
986
|
+
WHERE unicode(substr(tag.value, position, 1)) BETWEEN 0 AND 32
|
|
987
|
+
OR unicode(substr(tag.value, position, 1)) BETWEEN 127 AND 159
|
|
988
|
+
OR unicode(substr(tag.value, position, 1)) IN (160, 5760, 8232, 8233, 8239, 8287, 12288, 65279)
|
|
989
|
+
OR unicode(substr(tag.value, position, 1)) BETWEEN 8192 AND 8202
|
|
990
|
+
)
|
|
991
|
+
)
|
|
992
|
+
OR (SELECT COUNT(*) FROM json_each(NEW.tags_added))
|
|
993
|
+
!= (SELECT COUNT(DISTINCT value) FROM json_each(NEW.tags_added))
|
|
994
|
+
OR (SELECT COUNT(*) FROM json_each(NEW.tags_removed))
|
|
995
|
+
!= (SELECT COUNT(DISTINCT value) FROM json_each(NEW.tags_removed))
|
|
996
|
+
OR EXISTS (
|
|
997
|
+
SELECT 1
|
|
998
|
+
FROM json_each(NEW.tags_added) addition
|
|
999
|
+
JOIN json_each(NEW.tags_removed) removal ON removal.value = addition.value
|
|
1000
|
+
)
|
|
1001
|
+
THEN RAISE(ABORT, 'invalid log curation effect') END;
|
|
1002
|
+
END;
|
|
1003
|
+
|
|
1004
|
+
-- The dispatcher binds an exact, transient curation plan into attrs on the
|
|
1005
|
+
-- successful OPEN/FOLD row. No other row may carry that private payload.
|
|
1006
|
+
CREATE TRIGGER IF NOT EXISTS log_entries_curation_payload_valid
|
|
1007
|
+
BEFORE INSERT ON log_entries
|
|
1008
|
+
WHEN json_type(NEW.attrs, '$.__plurnk_curation') IS NOT NULL
|
|
1009
|
+
AND NOT (
|
|
1010
|
+
NEW.op IN ('OPEN', 'FOLD')
|
|
1011
|
+
AND NEW.status_rx < 400
|
|
1012
|
+
AND json_type(NEW.attrs, '$.__plurnk_curation') = 'object'
|
|
1013
|
+
)
|
|
1014
|
+
BEGIN
|
|
1015
|
+
SELECT RAISE(ABORT, 'private log curation payload requires a successful OPEN/FOLD row');
|
|
1016
|
+
END;
|
|
1017
|
+
|
|
1018
|
+
-- One outer INSERT owns the whole landed curation event: exact selected rows,
|
|
1019
|
+
-- their pre-event visibility and tag deltas, the requested visibility, and the
|
|
1020
|
+
-- resulting classifications. Trigger failure rolls the operation row and all
|
|
1021
|
+
-- effects back together. The private plan is erased before INSERT returns.
|
|
1022
|
+
CREATE TRIGGER IF NOT EXISTS log_entries_apply_curation
|
|
1023
|
+
AFTER INSERT ON log_entries
|
|
1024
|
+
WHEN NEW.op IN ('OPEN', 'FOLD')
|
|
1025
|
+
AND NEW.status_rx < 400
|
|
1026
|
+
AND json_type(NEW.attrs, '$.__plurnk_curation') = 'object'
|
|
1027
|
+
BEGIN
|
|
1028
|
+
SELECT CASE WHEN
|
|
1029
|
+
COALESCE(json_type(NEW.attrs, '$.__plurnk_curation.ids'), '') != 'array'
|
|
1030
|
+
OR COALESCE(json_type(NEW.attrs, '$.__plurnk_curation.add'), '') != 'array'
|
|
1031
|
+
OR COALESCE(json_type(NEW.attrs, '$.__plurnk_curation.remove'), '') != 'array'
|
|
1032
|
+
OR COALESCE(json_type(NEW.attrs, '$.__plurnk_curation.expanded'), '') != 'integer'
|
|
1033
|
+
OR json_extract(NEW.attrs, '$.__plurnk_curation.expanded') NOT IN (0, 1)
|
|
1034
|
+
OR json_array_length(NEW.attrs, '$.__plurnk_curation.ids') = 0
|
|
1035
|
+
OR EXISTS (
|
|
1036
|
+
SELECT 1 FROM json_each(NEW.attrs, '$.__plurnk_curation.ids')
|
|
1037
|
+
WHERE type != 'integer' OR value <= 0
|
|
1038
|
+
)
|
|
1039
|
+
OR (
|
|
1040
|
+
SELECT COUNT(*) FROM json_each(NEW.attrs, '$.__plurnk_curation.ids')
|
|
1041
|
+
) != (
|
|
1042
|
+
SELECT COUNT(DISTINCT value) FROM json_each(NEW.attrs, '$.__plurnk_curation.ids')
|
|
1043
|
+
)
|
|
1044
|
+
OR EXISTS (
|
|
1045
|
+
SELECT 1
|
|
1046
|
+
FROM json_each(NEW.attrs, '$.__plurnk_curation.ids') selected
|
|
1047
|
+
LEFT JOIN log_entries target ON target.id = selected.value
|
|
1048
|
+
WHERE target.id IS NULL OR target.worker_id != NEW.worker_id OR target.id = NEW.id
|
|
1049
|
+
)
|
|
1050
|
+
OR EXISTS (
|
|
1051
|
+
SELECT 1
|
|
1052
|
+
FROM (
|
|
1053
|
+
SELECT value, type FROM json_each(NEW.attrs, '$.__plurnk_curation.add')
|
|
1054
|
+
UNION ALL
|
|
1055
|
+
SELECT value, type FROM json_each(NEW.attrs, '$.__plurnk_curation.remove')
|
|
1056
|
+
) tag
|
|
1057
|
+
WHERE tag.type != 'text'
|
|
1058
|
+
OR length(tag.value) = 0
|
|
1059
|
+
OR substr(tag.value, 1, 1) IN ('+', '-')
|
|
1060
|
+
OR instr(tag.value, '[') > 0
|
|
1061
|
+
OR instr(tag.value, ']') > 0
|
|
1062
|
+
OR instr(tag.value, ',') > 0
|
|
1063
|
+
OR instr(tag.value, char(0)) > 0
|
|
1064
|
+
OR EXISTS (
|
|
1065
|
+
WITH RECURSIVE offsets(position) AS (
|
|
1066
|
+
SELECT 1
|
|
1067
|
+
UNION ALL
|
|
1068
|
+
SELECT position + 1 FROM offsets WHERE position < length(tag.value)
|
|
1069
|
+
)
|
|
1070
|
+
SELECT 1
|
|
1071
|
+
FROM offsets
|
|
1072
|
+
WHERE unicode(substr(tag.value, position, 1)) BETWEEN 0 AND 32
|
|
1073
|
+
OR unicode(substr(tag.value, position, 1)) BETWEEN 127 AND 159
|
|
1074
|
+
OR unicode(substr(tag.value, position, 1)) IN (160, 5760, 8232, 8233, 8239, 8287, 12288, 65279)
|
|
1075
|
+
OR unicode(substr(tag.value, position, 1)) BETWEEN 8192 AND 8202
|
|
1076
|
+
)
|
|
1077
|
+
)
|
|
1078
|
+
OR (
|
|
1079
|
+
SELECT COUNT(*) FROM json_each(NEW.attrs, '$.__plurnk_curation.add')
|
|
1080
|
+
) != (
|
|
1081
|
+
SELECT COUNT(DISTINCT value) FROM json_each(NEW.attrs, '$.__plurnk_curation.add')
|
|
1082
|
+
)
|
|
1083
|
+
OR (
|
|
1084
|
+
SELECT COUNT(*) FROM json_each(NEW.attrs, '$.__plurnk_curation.remove')
|
|
1085
|
+
) != (
|
|
1086
|
+
SELECT COUNT(DISTINCT value) FROM json_each(NEW.attrs, '$.__plurnk_curation.remove')
|
|
1087
|
+
)
|
|
1088
|
+
OR EXISTS (
|
|
1089
|
+
SELECT 1
|
|
1090
|
+
FROM json_each(NEW.attrs, '$.__plurnk_curation.add') addition
|
|
1091
|
+
JOIN json_each(NEW.attrs, '$.__plurnk_curation.remove') removal
|
|
1092
|
+
ON removal.value = addition.value
|
|
1093
|
+
)
|
|
1094
|
+
THEN RAISE(ABORT, 'invalid private log curation payload') END;
|
|
1095
|
+
|
|
1096
|
+
INSERT INTO log_curation_effects (
|
|
1097
|
+
operation_log_entry_id,
|
|
1098
|
+
target_log_entry_id,
|
|
1099
|
+
expanded_before,
|
|
1100
|
+
tags_added,
|
|
1101
|
+
tags_removed
|
|
1102
|
+
)
|
|
1103
|
+
SELECT
|
|
1104
|
+
NEW.id,
|
|
1105
|
+
target.id,
|
|
1106
|
+
target.expanded,
|
|
1107
|
+
COALESCE((
|
|
1108
|
+
SELECT json_group_array(ordered.tag)
|
|
1109
|
+
FROM (
|
|
1110
|
+
SELECT addition.value AS tag
|
|
1111
|
+
FROM json_each(NEW.attrs, '$.__plurnk_curation.add') addition
|
|
1112
|
+
WHERE NOT EXISTS (
|
|
1113
|
+
SELECT 1 FROM log_tags
|
|
1114
|
+
WHERE log_entry_id = target.id AND tag = addition.value
|
|
1115
|
+
)
|
|
1116
|
+
ORDER BY addition.value
|
|
1117
|
+
) ordered
|
|
1118
|
+
), '[]'),
|
|
1119
|
+
COALESCE((
|
|
1120
|
+
SELECT json_group_array(ordered.tag)
|
|
1121
|
+
FROM (
|
|
1122
|
+
SELECT removal.value AS tag
|
|
1123
|
+
FROM json_each(NEW.attrs, '$.__plurnk_curation.remove') removal
|
|
1124
|
+
WHERE EXISTS (
|
|
1125
|
+
SELECT 1 FROM log_tags
|
|
1126
|
+
WHERE log_entry_id = target.id AND tag = removal.value
|
|
1127
|
+
)
|
|
1128
|
+
ORDER BY removal.value
|
|
1129
|
+
) ordered
|
|
1130
|
+
), '[]')
|
|
1131
|
+
FROM json_each(NEW.attrs, '$.__plurnk_curation.ids') selected
|
|
1132
|
+
JOIN log_entries target ON target.id = selected.value;
|
|
1133
|
+
|
|
1134
|
+
UPDATE log_entries
|
|
1135
|
+
SET expanded = json_extract(NEW.attrs, '$.__plurnk_curation.expanded')
|
|
1136
|
+
WHERE id IN (
|
|
1137
|
+
SELECT value FROM json_each(NEW.attrs, '$.__plurnk_curation.ids')
|
|
1138
|
+
);
|
|
1139
|
+
|
|
1140
|
+
DELETE FROM log_tags
|
|
1141
|
+
WHERE log_entry_id IN (
|
|
1142
|
+
SELECT value FROM json_each(NEW.attrs, '$.__plurnk_curation.ids')
|
|
1143
|
+
)
|
|
1144
|
+
AND tag IN (
|
|
1145
|
+
SELECT value FROM json_each(NEW.attrs, '$.__plurnk_curation.remove')
|
|
1146
|
+
);
|
|
1147
|
+
|
|
1148
|
+
INSERT OR IGNORE INTO log_tags (log_entry_id, tag)
|
|
1149
|
+
SELECT selected.value, addition.value
|
|
1150
|
+
FROM json_each(NEW.attrs, '$.__plurnk_curation.ids') selected
|
|
1151
|
+
CROSS JOIN json_each(NEW.attrs, '$.__plurnk_curation.add') addition;
|
|
1152
|
+
|
|
1153
|
+
UPDATE log_entries
|
|
1154
|
+
SET attrs = json_remove(attrs, '$.__plurnk_curation')
|
|
1155
|
+
WHERE id = NEW.id;
|
|
1156
|
+
END;
|
|
1157
|
+
|
|
1158
|
+
-- Column-scoped immutability: the original action's identity and target never
|
|
1159
|
+
-- change; the proposal lifecycle is allowed to mutate state, outcome,
|
|
1160
|
+
-- status_rx, rx, expanded. Keep attrs separate so the curation trigger's one
|
|
1161
|
+
-- private-payload removal cannot exempt changes to any other core column.
|
|
698
1162
|
CREATE TRIGGER IF NOT EXISTS log_entries_immutable_core
|
|
699
1163
|
BEFORE UPDATE OF
|
|
700
|
-
worker_id, loop_id, turn_id, sequence, at, origin, source,
|
|
1164
|
+
worker_id, loop_id, turn_id, sequence, at, origin, source, model_call_id,
|
|
701
1165
|
op, suffix, signal,
|
|
702
1166
|
scheme, username, password, hostname,
|
|
703
1167
|
port, pathname, query, fragment,
|
|
704
|
-
lineMarker, tx, mimetype_tx, mimetype_rx
|
|
1168
|
+
lineMarker, tx, mimetype_tx, mimetype_rx
|
|
705
1169
|
ON log_entries
|
|
706
1170
|
BEGIN
|
|
707
1171
|
SELECT RAISE(ABORT, 'log_entries core fields are immutable; only state/outcome/status_rx/rx/expanded may change');
|
|
708
1172
|
END;
|
|
709
1173
|
|
|
1174
|
+
CREATE TRIGGER IF NOT EXISTS log_entries_immutable_attrs
|
|
1175
|
+
BEFORE UPDATE OF attrs ON log_entries
|
|
1176
|
+
WHEN COALESCE((
|
|
1177
|
+
json_type(OLD.attrs, '$.__plurnk_curation') = 'object'
|
|
1178
|
+
AND NEW.attrs = json_remove(OLD.attrs, '$.__plurnk_curation')
|
|
1179
|
+
), 0) = 0
|
|
1180
|
+
BEGIN
|
|
1181
|
+
SELECT RAISE(ABORT, 'log_entries attrs are immutable outside curation payload removal');
|
|
1182
|
+
END;
|
|
1183
|
+
|
|
710
1184
|
-- The engine may stamp an originating row exactly once. No later reassignment
|
|
711
1185
|
-- can sever or counterfeit the occurrence identity.
|
|
712
1186
|
CREATE TRIGGER IF NOT EXISTS log_entries_ambient_event_once
|
|
@@ -734,10 +1208,17 @@ WHEN NEW.ambient_event_id IS NULL
|
|
|
734
1208
|
BEGIN
|
|
735
1209
|
INSERT INTO ambient_events (
|
|
736
1210
|
workspace_id, producer_worker_id, kind, source_record_id, source,
|
|
737
|
-
op, scheme, hostname, pathname, rx, attrs, status_rx
|
|
1211
|
+
op, scheme, hostname, pathname, rx, attrs, tags, status_rx
|
|
738
1212
|
)
|
|
739
1213
|
SELECT w.workspace_id, NEW.worker_id, 'edit', NEW.id, NEW.source,
|
|
740
|
-
'EDIT', NEW.scheme, NEW.hostname, NEW.pathname, NEW.rx, NEW.attrs,
|
|
1214
|
+
'EDIT', NEW.scheme, NEW.hostname, NEW.pathname, NEW.rx, NEW.attrs,
|
|
1215
|
+
COALESCE((
|
|
1216
|
+
SELECT json_group_array(ordered.tag)
|
|
1217
|
+
FROM (
|
|
1218
|
+
SELECT tag FROM log_tags WHERE log_entry_id = NEW.id ORDER BY tag
|
|
1219
|
+
) ordered
|
|
1220
|
+
), '[]'),
|
|
1221
|
+
NEW.status_rx
|
|
741
1222
|
FROM workers w
|
|
742
1223
|
WHERE w.id = NEW.worker_id;
|
|
743
1224
|
UPDATE log_entries SET ambient_event_id = last_insert_rowid() WHERE id = NEW.id;
|
|
@@ -761,97 +1242,22 @@ WHEN OLD.state = 'proposed'
|
|
|
761
1242
|
BEGIN
|
|
762
1243
|
INSERT INTO ambient_events (
|
|
763
1244
|
workspace_id, producer_worker_id, kind, source_record_id, source,
|
|
764
|
-
op, scheme, hostname, pathname, rx, attrs, status_rx
|
|
1245
|
+
op, scheme, hostname, pathname, rx, attrs, tags, status_rx
|
|
765
1246
|
)
|
|
766
1247
|
SELECT w.workspace_id, NEW.worker_id, 'edit', NEW.id, NEW.source,
|
|
767
|
-
'EDIT', NEW.scheme, NEW.hostname, NEW.pathname, NEW.rx, NEW.attrs,
|
|
1248
|
+
'EDIT', NEW.scheme, NEW.hostname, NEW.pathname, NEW.rx, NEW.attrs,
|
|
1249
|
+
COALESCE((
|
|
1250
|
+
SELECT json_group_array(ordered.tag)
|
|
1251
|
+
FROM (
|
|
1252
|
+
SELECT tag FROM log_tags WHERE log_entry_id = NEW.id ORDER BY tag
|
|
1253
|
+
) ordered
|
|
1254
|
+
), '[]'),
|
|
1255
|
+
NEW.status_rx
|
|
768
1256
|
FROM workers w
|
|
769
1257
|
WHERE w.id = NEW.worker_id;
|
|
770
1258
|
UPDATE log_entries SET ambient_event_id = last_insert_rowid() WHERE id = NEW.id;
|
|
771
1259
|
END;
|
|
772
1260
|
|
|
773
|
-
-- One loop's cost is the complete sum of its provider attempts. One unknown
|
|
774
|
-
-- contributing turn makes the aggregate unknown rather than silently zero.
|
|
775
|
-
CREATE VIEW IF NOT EXISTS loop_costs AS
|
|
776
|
-
SELECT l.id, l.worker_id,
|
|
777
|
-
CASE
|
|
778
|
-
WHEN COUNT(t.id) FILTER (WHERE t.usage_cost_usd IS NULL) > 0 THEN NULL
|
|
779
|
-
ELSE COALESCE(SUM(t.usage_cost_usd), 0)
|
|
780
|
-
END AS cost_usd
|
|
781
|
-
FROM loops l
|
|
782
|
-
LEFT JOIN turns t ON t.loop_id = l.id
|
|
783
|
-
GROUP BY l.id;
|
|
784
|
-
|
|
785
|
-
-- cost_rollups
|
|
786
|
-
-- Triggers maintaining denormalized USD totals on workers and workspaces
|
|
787
|
-
-- as turns insert/update. Pure denormalization (textbook trigger use);
|
|
788
|
-
-- no branching state-machine logic lives here.
|
|
789
|
-
CREATE TRIGGER IF NOT EXISTS turns_cost_rollup_insert_worker
|
|
790
|
-
AFTER INSERT ON turns
|
|
791
|
-
BEGIN
|
|
792
|
-
UPDATE workers
|
|
793
|
-
SET cost_usd = (
|
|
794
|
-
SELECT CASE WHEN COUNT(*) FILTER (WHERE lc.cost_usd IS NULL) > 0
|
|
795
|
-
THEN NULL ELSE COALESCE(SUM(lc.cost_usd), 0) END
|
|
796
|
-
FROM loop_costs lc
|
|
797
|
-
WHERE lc.worker_id = workers.id
|
|
798
|
-
)
|
|
799
|
-
WHERE id = (SELECT worker_id FROM loops WHERE id = NEW.loop_id);
|
|
800
|
-
END;
|
|
801
|
-
|
|
802
|
-
CREATE TRIGGER IF NOT EXISTS turns_cost_rollup_insert_workspace
|
|
803
|
-
AFTER INSERT ON turns
|
|
804
|
-
BEGIN
|
|
805
|
-
UPDATE workspaces
|
|
806
|
-
SET cost_usd = (
|
|
807
|
-
SELECT CASE WHEN COUNT(*) FILTER (WHERE lc.cost_usd IS NULL) > 0
|
|
808
|
-
THEN NULL ELSE COALESCE(SUM(lc.cost_usd), 0) END
|
|
809
|
-
FROM loop_costs lc
|
|
810
|
-
JOIN workers r ON r.id = lc.worker_id
|
|
811
|
-
WHERE r.workspace_id = workspaces.id
|
|
812
|
-
)
|
|
813
|
-
WHERE id = (
|
|
814
|
-
SELECT r.workspace_id
|
|
815
|
-
FROM workers r
|
|
816
|
-
JOIN loops l ON l.worker_id = r.id
|
|
817
|
-
WHERE l.id = NEW.loop_id
|
|
818
|
-
);
|
|
819
|
-
END;
|
|
820
|
-
|
|
821
|
-
CREATE TRIGGER IF NOT EXISTS turns_cost_rollup_update_worker
|
|
822
|
-
AFTER UPDATE OF usage_cost_usd ON turns
|
|
823
|
-
WHEN NEW.usage_cost_usd IS NOT OLD.usage_cost_usd
|
|
824
|
-
BEGIN
|
|
825
|
-
UPDATE workers
|
|
826
|
-
SET cost_usd = (
|
|
827
|
-
SELECT CASE WHEN COUNT(*) FILTER (WHERE lc.cost_usd IS NULL) > 0
|
|
828
|
-
THEN NULL ELSE COALESCE(SUM(lc.cost_usd), 0) END
|
|
829
|
-
FROM loop_costs lc
|
|
830
|
-
WHERE lc.worker_id = workers.id
|
|
831
|
-
)
|
|
832
|
-
WHERE id = (SELECT worker_id FROM loops WHERE id = NEW.loop_id);
|
|
833
|
-
END;
|
|
834
|
-
|
|
835
|
-
CREATE TRIGGER IF NOT EXISTS turns_cost_rollup_update_workspace
|
|
836
|
-
AFTER UPDATE OF usage_cost_usd ON turns
|
|
837
|
-
WHEN NEW.usage_cost_usd IS NOT OLD.usage_cost_usd
|
|
838
|
-
BEGIN
|
|
839
|
-
UPDATE workspaces
|
|
840
|
-
SET cost_usd = (
|
|
841
|
-
SELECT CASE WHEN COUNT(*) FILTER (WHERE lc.cost_usd IS NULL) > 0
|
|
842
|
-
THEN NULL ELSE COALESCE(SUM(lc.cost_usd), 0) END
|
|
843
|
-
FROM loop_costs lc
|
|
844
|
-
JOIN workers r ON r.id = lc.worker_id
|
|
845
|
-
WHERE r.workspace_id = workspaces.id
|
|
846
|
-
)
|
|
847
|
-
WHERE id = (
|
|
848
|
-
SELECT r.workspace_id
|
|
849
|
-
FROM workers r
|
|
850
|
-
JOIN loops l ON l.worker_id = r.id
|
|
851
|
-
WHERE l.id = NEW.loop_id
|
|
852
|
-
);
|
|
853
|
-
END;
|
|
854
|
-
|
|
855
1261
|
-- subscriptions
|
|
856
1262
|
-- Durable subscription lifecycle per SPEC {§subscriptions}. The row records what
|
|
857
1263
|
-- the worker holds and routes cancellation to a separate process-local callable;
|