@plurnk/plurnk-service 1.4.0 → 1.6.0
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 +37 -31
- package/INSTALL.md +16 -11
- package/README.md +1 -0
- package/SPEC.md +631 -397
- package/dist/Paths.d.ts.map +1 -1
- package/dist/Paths.js +2 -3
- package/dist/Paths.js.map +1 -1
- 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 +5 -1
- package/dist/content/index.d.ts.map +1 -1
- package/dist/content/index.js +4 -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/line-marker.d.ts +7 -3
- package/dist/content/line-marker.d.ts.map +1 -1
- package/dist/content/line-marker.js +2 -1
- package/dist/content/line-marker.js.map +1 -1
- package/dist/content/matcher.d.ts +1 -1
- package/dist/content/matcher.d.ts.map +1 -1
- package/dist/content/matcher.js +2 -2
- package/dist/content/matcher.js.map +1 -1
- package/dist/content/read-projector.d.ts +20 -0
- package/dist/content/read-projector.d.ts.map +1 -0
- package/dist/content/read-projector.js +122 -0
- package/dist/content/read-projector.js.map +1 -0
- package/dist/content/read-resolve.d.ts +2 -7
- package/dist/content/read-resolve.d.ts.map +1 -1
- package/dist/content/read-resolve.js +32 -107
- package/dist/content/read-resolve.js.map +1 -1
- package/dist/core/BranchReceipt.d.ts +0 -1
- package/dist/core/BranchReceipt.d.ts.map +1 -1
- package/dist/core/BranchReceipt.js +0 -3
- package/dist/core/BranchReceipt.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.d.ts +3 -0
- package/dist/core/ChannelWrite.d.ts.map +1 -1
- package/dist/core/ChannelWrite.js +3 -3
- package/dist/core/ChannelWrite.js.map +1 -1
- package/dist/core/ChannelWrite.sql +4 -3
- package/dist/core/CoreSchemeServices.d.ts +10 -2
- package/dist/core/CoreSchemeServices.d.ts.map +1 -1
- package/dist/core/CoreSchemeServices.js.map +1 -1
- package/dist/core/Dispatcher.d.ts +14 -8
- package/dist/core/Dispatcher.d.ts.map +1 -1
- package/dist/core/Dispatcher.js +656 -312
- 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 +24 -18
- package/dist/core/Engine.d.ts.map +1 -1
- package/dist/core/Engine.js +620 -392
- package/dist/core/Engine.js.map +1 -1
- package/dist/core/Engine.sql +188 -119
- 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 +4 -1
- package/dist/core/LogBody.d.ts.map +1 -1
- package/dist/core/LogBody.js +46 -2
- 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/LoopLifecycle.d.ts +1 -2
- package/dist/core/LoopLifecycle.d.ts.map +1 -1
- package/dist/core/LoopLifecycle.js +1 -4
- package/dist/core/LoopLifecycle.js.map +1 -1
- package/dist/core/LoopLifecycle.sql +1 -4
- package/dist/core/ModelCall.d.ts +27 -0
- package/dist/core/ModelCall.d.ts.map +1 -0
- package/dist/core/ModelCall.js +125 -0
- package/dist/core/ModelCall.js.map +1 -0
- package/dist/core/PacketBuilder.d.ts +13 -12
- package/dist/core/PacketBuilder.d.ts.map +1 -1
- package/dist/core/PacketBuilder.js +146 -118
- package/dist/core/PacketBuilder.js.map +1 -1
- package/dist/core/ProblemLog.d.ts.map +1 -1
- package/dist/core/ProblemLog.js +1 -0
- package/dist/core/ProblemLog.js.map +1 -1
- package/dist/core/ProposalLifecycle.d.ts.map +1 -1
- package/dist/core/ProposalLifecycle.js +3 -4
- package/dist/core/ProposalLifecycle.js.map +1 -1
- package/dist/core/ProviderInstantiate.d.ts.map +1 -1
- package/dist/core/ProviderInstantiate.js +23 -6
- package/dist/core/ProviderInstantiate.js.map +1 -1
- package/dist/core/SchemeRegistry.js +7 -7
- package/dist/core/SchemeRegistry.js.map +1 -1
- package/dist/core/StrikeRail.d.ts +5 -2
- package/dist/core/StrikeRail.d.ts.map +1 -1
- package/dist/core/StrikeRail.js +10 -7
- package/dist/core/StrikeRail.js.map +1 -1
- package/dist/core/TerminalResult.d.ts +16 -0
- package/dist/core/TerminalResult.d.ts.map +1 -0
- package/dist/core/TerminalResult.js +49 -0
- package/dist/core/TerminalResult.js.map +1 -0
- package/dist/core/caps/CapsResolve.d.ts +5 -1
- package/dist/core/caps/CapsResolve.d.ts.map +1 -1
- package/dist/core/caps/CapsResolve.js +9 -5
- package/dist/core/caps/CapsResolve.js.map +1 -1
- package/dist/core/caps/DbChannelCaps.d.ts +1 -1
- package/dist/core/caps/DbChannelCaps.d.ts.map +1 -1
- package/dist/core/caps/DbChannelCaps.js +6 -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 +21 -8
- package/dist/core/caps/DbEntryCaps.js.map +1 -1
- package/dist/core/caps/DbNotifyCaps.d.ts +1 -1
- package/dist/core/caps/DbNotifyCaps.d.ts.map +1 -1
- package/dist/core/caps/DbNotifyCaps.js +6 -4
- package/dist/core/caps/DbNotifyCaps.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/DbSubscriptionCaps.d.ts +4 -6
- package/dist/core/caps/DbSubscriptionCaps.d.ts.map +1 -1
- package/dist/core/caps/DbSubscriptionCaps.js +27 -13
- package/dist/core/caps/DbSubscriptionCaps.js.map +1 -1
- package/dist/core/caps/SchemeCtxImpl.d.ts +11 -3
- package/dist/core/caps/SchemeCtxImpl.d.ts.map +1 -1
- package/dist/core/caps/SchemeCtxImpl.js +10 -8
- package/dist/core/caps/SchemeCtxImpl.js.map +1 -1
- 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 +35 -14
- package/dist/core/git-iso.d.ts +3 -2
- package/dist/core/git-iso.d.ts.map +1 -1
- package/dist/core/git-iso.js +27 -12
- package/dist/core/git-iso.js.map +1 -1
- package/dist/core/git-membership.d.ts +1 -0
- package/dist/core/git-membership.d.ts.map +1 -1
- package/dist/core/git-membership.js +87 -26
- package/dist/core/git-membership.js.map +1 -1
- package/dist/core/git-state.d.ts +8 -1
- package/dist/core/git-state.d.ts.map +1 -1
- package/dist/core/git-state.js +36 -17
- package/dist/core/git-state.js.map +1 -1
- package/dist/core/namespace.d.ts +1 -0
- package/dist/core/namespace.d.ts.map +1 -1
- package/dist/core/namespace.js +20 -0
- package/dist/core/namespace.js.map +1 -1
- package/dist/core/optimistic-settlement.d.ts +2 -0
- package/dist/core/optimistic-settlement.d.ts.map +1 -0
- package/dist/core/optimistic-settlement.js +14 -0
- package/dist/core/optimistic-settlement.js.map +1 -0
- package/dist/core/packet-wire.d.ts +5 -2
- package/dist/core/packet-wire.d.ts.map +1 -1
- package/dist/core/packet-wire.js +318 -114
- 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/results.d.ts +4 -2
- package/dist/core/results.d.ts.map +1 -1
- package/dist/core/results.js +6 -0
- package/dist/core/results.js.map +1 -1
- package/dist/core/scheme-types.d.ts +0 -12
- package/dist/core/scheme-types.d.ts.map +1 -1
- package/dist/core/scheme-types.js.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/token-ruler.js +1 -1
- package/dist/core/token-ruler.js.map +1 -1
- package/dist/core/turn-scheduler.js +1 -1
- package/dist/core/turn-scheduler.js.map +1 -1
- package/dist/core/worker-ops.sql +4 -4
- package/dist/core/workspace-settings.js +1 -1
- package/dist/core/workspace-settings.js.map +1 -1
- package/dist/digest/Digest.d.ts.map +1 -1
- package/dist/digest/Digest.js +349 -76
- package/dist/digest/Digest.js.map +1 -1
- package/dist/digest/digest.sql +42 -16
- package/dist/schemes/Exec.d.ts +4 -4
- package/dist/schemes/Exec.d.ts.map +1 -1
- package/dist/schemes/Exec.js +167 -134
- package/dist/schemes/Exec.js.map +1 -1
- package/dist/schemes/ExecOutputScheme.d.ts +4 -5
- package/dist/schemes/ExecOutputScheme.d.ts.map +1 -1
- package/dist/schemes/ExecOutputScheme.js +12 -20
- package/dist/schemes/ExecOutputScheme.js.map +1 -1
- package/dist/schemes/File.d.ts +5 -6
- package/dist/schemes/File.d.ts.map +1 -1
- package/dist/schemes/File.js +35 -41
- package/dist/schemes/File.js.map +1 -1
- package/dist/schemes/Log.d.ts +16 -5
- package/dist/schemes/Log.d.ts.map +1 -1
- package/dist/schemes/Log.js +229 -262
- package/dist/schemes/Log.js.map +1 -1
- package/dist/schemes/Log.sql +50 -27
- package/dist/schemes/Prompt.d.ts +3 -4
- package/dist/schemes/Prompt.d.ts.map +1 -1
- package/dist/schemes/Prompt.js +5 -5
- package/dist/schemes/Prompt.js.map +1 -1
- package/dist/schemes/Skill.d.ts +4 -5
- package/dist/schemes/Skill.d.ts.map +1 -1
- package/dist/schemes/Skill.js +3 -3
- package/dist/schemes/Skill.js.map +1 -1
- package/dist/schemes/Worker.d.ts +7 -7
- package/dist/schemes/Worker.d.ts.map +1 -1
- package/dist/schemes/Worker.js +99 -84
- package/dist/schemes/Worker.js.map +1 -1
- package/dist/schemes/_entry-crud.d.ts +2 -2
- package/dist/schemes/_entry-crud.d.ts.map +1 -1
- package/dist/schemes/_entry-crud.js +29 -10
- package/dist/schemes/_entry-crud.js.map +1 -1
- package/dist/schemes/_entry-crud.sql +11 -17
- package/dist/schemes/_entry-find.d.ts +41 -14
- package/dist/schemes/_entry-find.d.ts.map +1 -1
- package/dist/schemes/_entry-find.js +159 -166
- 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 +28 -24
- package/dist/schemes/_entry-manifest.js.map +1 -1
- package/dist/schemes/_entry-ops.d.ts +13 -8
- package/dist/schemes/_entry-ops.d.ts.map +1 -1
- package/dist/schemes/_entry-ops.js +98 -119
- package/dist/schemes/_entry-ops.js.map +1 -1
- package/dist/schemes/_entry-ops.sql +31 -6
- package/dist/schemes/_entry-semantic.d.ts +5 -7
- package/dist/schemes/_entry-semantic.d.ts.map +1 -1
- package/dist/schemes/_entry-semantic.js +10 -31
- package/dist/schemes/_entry-semantic.js.map +1 -1
- package/dist/schemes/_entry-semantic.sql +2 -2
- 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 +16 -6
- 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/BranchBatches.d.ts +1 -0
- package/dist/server/BranchBatches.d.ts.map +1 -1
- package/dist/server/BranchBatches.js +1 -0
- package/dist/server/BranchBatches.js.map +1 -1
- package/dist/server/Daemon.d.ts +6 -8
- package/dist/server/Daemon.d.ts.map +1 -1
- package/dist/server/Daemon.js +238 -68
- package/dist/server/Daemon.js.map +1 -1
- package/dist/server/DaemonModule.d.ts +3 -3
- package/dist/server/DaemonModule.d.ts.map +1 -1
- package/dist/server/branch-batch.sql +3 -1
- package/dist/server/client-input.d.ts +2 -1
- package/dist/server/client-input.d.ts.map +1 -1
- package/dist/server/client-input.js +7 -1
- package/dist/server/client-input.js.map +1 -1
- package/dist/server/drain.sql +8 -7
- package/dist/server/envelope.d.ts +0 -2
- package/dist/server/envelope.d.ts.map +1 -1
- package/dist/server/envelope.js +0 -1
- package/dist/server/envelope.js.map +1 -1
- package/dist/server/envelope.sql +3 -4
- package/dist/server/lifecycle-recovery.sql +44 -1
- package/dist/server/logEntry.d.ts +2 -1
- package/dist/server/logEntry.d.ts.map +1 -1
- package/dist/server/logEntry.js +15 -2
- package/dist/server/logEntry.js.map +1 -1
- package/dist/server/logEntry.sql +5 -1
- package/dist/service.d.ts.map +1 -1
- package/dist/service.js +13 -1
- package/dist/service.js.map +1 -1
- package/migrations/001_schema.sql +730 -221
- package/package.json +53 -46
- package/dist/core/BudgetOverflow.d.ts +0 -23
- package/dist/core/BudgetOverflow.d.ts.map +0 -1
- package/dist/core/BudgetOverflow.js +0 -61
- 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 -44
- package/dist/core/caps/DbTagCaps.js.map +0 -1
- package/dist/matrix/live-specimen.d.ts +0 -9
- package/dist/matrix/live-specimen.d.ts.map +0 -1
- package/dist/matrix/live-specimen.js +0 -37
- package/dist/matrix/live-specimen.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,14 +80,21 @@ 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
|
-
prompt TEXT,
|
|
75
85
|
terminated_by TEXT CHECK (terminated_by IS NULL OR terminated_by = 'cancel'),
|
|
76
86
|
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
|
|
77
87
|
CHECK (
|
|
78
|
-
(kind = 'edit' AND op = 'EDIT' AND rx IS NOT NULL AND
|
|
88
|
+
(kind = 'edit' AND op = 'EDIT' AND rx IS NOT NULL AND terminated_by IS NULL)
|
|
79
89
|
OR
|
|
80
|
-
(kind = 'loop_termination'
|
|
90
|
+
(kind = 'loop_termination'
|
|
91
|
+
AND op = 'SEND'
|
|
92
|
+
AND scheme = 'worker'
|
|
93
|
+
AND rx IS NOT NULL
|
|
94
|
+
AND json_valid(rx)
|
|
95
|
+
AND json_type(rx) = 'object'
|
|
96
|
+
AND json_type(rx, '$.status') = 'integer'
|
|
97
|
+
AND json_extract(rx, '$.status') = status_rx)
|
|
81
98
|
),
|
|
82
99
|
FOREIGN KEY (workspace_id) REFERENCES workspaces(id) ON DELETE CASCADE,
|
|
83
100
|
FOREIGN KEY (producer_worker_id) REFERENCES workers(id) ON DELETE CASCADE
|
|
@@ -102,6 +119,7 @@ CREATE TABLE IF NOT EXISTS loops (
|
|
|
102
119
|
prompt TEXT NOT NULL,
|
|
103
120
|
flags TEXT NOT NULL DEFAULT '{}' CHECK (json_valid(flags)),
|
|
104
121
|
provider_spec TEXT NOT NULL DEFAULT 'null' CHECK (json_valid(provider_spec)),
|
|
122
|
+
child_provider_spec TEXT NOT NULL DEFAULT 'null' CHECK (json_valid(child_provider_spec)),
|
|
105
123
|
max_turns INTEGER NOT NULL DEFAULT 50 CHECK (max_turns >= -1),
|
|
106
124
|
-- {§methods-loop-run-open-paths}: the initial prompt frame's selected paths,
|
|
107
125
|
-- held here until turn 1 materializes that frame (string[] JSON).
|
|
@@ -110,22 +128,56 @@ CREATE TABLE IF NOT EXISTS loops (
|
|
|
110
128
|
-- complete orphan frame set of one concluded source loop.
|
|
111
129
|
orphan_source_loop_id INTEGER,
|
|
112
130
|
-- {§worker-scheme} loop-termination delta: terminated_at is stamped by the trigger
|
|
113
|
-
-- below when status crosses into terminal (every death-path, uniformly)
|
|
114
|
-
-- terminal_message is the deliverable — the SEND[200] body or the abandonment
|
|
115
|
-
-- reason — set by the guarded terminal lifecycle transition.
|
|
131
|
+
-- below when status crosses into terminal (every death-path, uniformly).
|
|
116
132
|
terminated_at TEXT,
|
|
117
|
-
|
|
118
|
-
terminal_result TEXT CHECK (terminal_result IS NULL OR json_valid(terminal_result)),
|
|
133
|
+
terminal_result TEXT,
|
|
119
134
|
-- {§loop-terminal-authorship}: 'cancel' names an external loop.cancel;
|
|
120
135
|
-- NULL covers model terminals and engine verdicts whose result carries the story.
|
|
121
136
|
terminated_by TEXT CHECK (terminated_by IS NULL OR terminated_by = 'cancel'),
|
|
137
|
+
CONSTRAINT loops_terminal_result_contract CHECK (
|
|
138
|
+
CASE
|
|
139
|
+
WHEN status IN (100, 102, 202) THEN terminal_result IS NULL
|
|
140
|
+
WHEN terminal_result IS NULL OR NOT json_valid(terminal_result) THEN 0
|
|
141
|
+
ELSE
|
|
142
|
+
json_type(terminal_result) IS 'object'
|
|
143
|
+
AND json_type(terminal_result, '$.status') IS 'integer'
|
|
144
|
+
AND (
|
|
145
|
+
json_extract(terminal_result, '$.status') = status
|
|
146
|
+
OR (
|
|
147
|
+
status = 200
|
|
148
|
+
AND json_extract(terminal_result, '$.status') BETWEEN 200 AND 399
|
|
149
|
+
AND json_extract(terminal_result, '$.status') != 202
|
|
150
|
+
)
|
|
151
|
+
OR (
|
|
152
|
+
status = 500
|
|
153
|
+
AND json_extract(terminal_result, '$.status') BETWEEN 400 AND 599
|
|
154
|
+
)
|
|
155
|
+
)
|
|
156
|
+
AND CASE
|
|
157
|
+
WHEN json_extract(terminal_result, '$.status') < 400 THEN
|
|
158
|
+
json_type(terminal_result, '$.problem') IS NULL
|
|
159
|
+
ELSE
|
|
160
|
+
json_type(terminal_result, '$.problem') IS 'object'
|
|
161
|
+
AND json_type(terminal_result, '$.problem.status') IS 'integer'
|
|
162
|
+
AND json_extract(terminal_result, '$.problem.status')
|
|
163
|
+
= json_extract(terminal_result, '$.status')
|
|
164
|
+
AND json_type(terminal_result, '$.problem.type') IS 'text'
|
|
165
|
+
AND length(json_extract(terminal_result, '$.problem.type')) > 0
|
|
166
|
+
AND json_type(terminal_result, '$.problem.title') IS 'text'
|
|
167
|
+
AND length(json_extract(terminal_result, '$.problem.title')) > 0
|
|
168
|
+
AND json_type(terminal_result, '$.problem.detail') IS 'text'
|
|
169
|
+
AND length(json_extract(terminal_result, '$.problem.detail')) > 0
|
|
170
|
+
AND json_type(terminal_result, '$.problem.instance') IS 'text'
|
|
171
|
+
AND length(json_extract(terminal_result, '$.problem.instance')) > 0
|
|
172
|
+
END
|
|
173
|
+
END
|
|
174
|
+
),
|
|
122
175
|
FOREIGN KEY (worker_id) REFERENCES workers(id) ON DELETE CASCADE,
|
|
123
176
|
FOREIGN KEY (orphan_source_loop_id) REFERENCES loops(id)
|
|
124
177
|
) STRICT;
|
|
125
178
|
|
|
126
179
|
CREATE UNIQUE INDEX IF NOT EXISTS loops_worker_id_sequence ON loops (worker_id, sequence);
|
|
127
180
|
CREATE UNIQUE INDEX IF NOT EXISTS loops_orphan_source_loop_id ON loops (orphan_source_loop_id);
|
|
128
|
-
|
|
129
181
|
-- {§worker-scheme}: a loop crossing into a terminal status stamps terminated_at, so sibling
|
|
130
182
|
-- workers pull the termination as a folded ambient delta — caught uniformly across every
|
|
131
183
|
-- death-path (SEND, grinder, max-turns, strike, KILL). The stamp updates terminated_at,
|
|
@@ -147,100 +199,19 @@ WHEN NEW.status IN (200, 413, 429, 499, 500, 504, 508) AND OLD.status NOT IN (20
|
|
|
147
199
|
BEGIN
|
|
148
200
|
INSERT INTO ambient_events (
|
|
149
201
|
workspace_id, producer_worker_id, kind, source_record_id, source,
|
|
150
|
-
op, scheme, pathname, rx, status_rx,
|
|
202
|
+
op, scheme, pathname, rx, status_rx, terminated_by
|
|
151
203
|
)
|
|
152
204
|
SELECT w.workspace_id, NEW.worker_id, 'loop_termination', NEW.id, NULL,
|
|
153
|
-
'SEND', 'worker', '/' || w.name, NEW.
|
|
205
|
+
'SEND', 'worker', '/' || w.name, NEW.terminal_result,
|
|
206
|
+
json_extract(NEW.terminal_result, '$.status'), NEW.terminated_by
|
|
154
207
|
FROM workers w
|
|
155
208
|
WHERE w.id = NEW.worker_id;
|
|
156
209
|
END;
|
|
157
210
|
|
|
158
|
-
CREATE TRIGGER IF NOT EXISTS loops_result_contract_insert
|
|
159
|
-
BEFORE INSERT ON loops
|
|
160
|
-
WHEN NEW.status IN (100, 102, 200, 202, 413, 429, 499, 500, 504, 508)
|
|
161
|
-
AND NOT (
|
|
162
|
-
(NEW.status IN (100, 102, 202) AND NEW.terminal_result IS NULL)
|
|
163
|
-
OR (
|
|
164
|
-
NEW.status IN (200, 413, 429, 499, 500, 504, 508)
|
|
165
|
-
AND NEW.terminal_result IS NOT NULL
|
|
166
|
-
AND json_valid(NEW.terminal_result)
|
|
167
|
-
AND json_type(NEW.terminal_result, '$.status') = 'integer'
|
|
168
|
-
AND (
|
|
169
|
-
json_extract(NEW.terminal_result, '$.status') = NEW.status
|
|
170
|
-
OR (
|
|
171
|
-
NEW.status = 200
|
|
172
|
-
AND json_extract(NEW.terminal_result, '$.status') BETWEEN 200 AND 399
|
|
173
|
-
AND json_extract(NEW.terminal_result, '$.status') != 202
|
|
174
|
-
)
|
|
175
|
-
OR (
|
|
176
|
-
NEW.status = 500
|
|
177
|
-
AND json_extract(NEW.terminal_result, '$.status') BETWEEN 400 AND 599
|
|
178
|
-
)
|
|
179
|
-
)
|
|
180
|
-
AND (
|
|
181
|
-
(NEW.status < 400 AND json_type(NEW.terminal_result, '$.problem') IS NULL)
|
|
182
|
-
OR (
|
|
183
|
-
NEW.status >= 400
|
|
184
|
-
AND json_type(NEW.terminal_result, '$.problem') = 'object'
|
|
185
|
-
AND json_extract(NEW.terminal_result, '$.problem.status')
|
|
186
|
-
= json_extract(NEW.terminal_result, '$.status')
|
|
187
|
-
AND length(json_extract(NEW.terminal_result, '$.problem.type')) > 0
|
|
188
|
-
AND length(json_extract(NEW.terminal_result, '$.problem.title')) > 0
|
|
189
|
-
AND length(json_extract(NEW.terminal_result, '$.problem.detail')) > 0
|
|
190
|
-
AND length(json_extract(NEW.terminal_result, '$.problem.instance')) > 0
|
|
191
|
-
)
|
|
192
|
-
)
|
|
193
|
-
)
|
|
194
|
-
)
|
|
195
|
-
BEGIN
|
|
196
|
-
SELECT RAISE(ABORT, 'loop terminal result violates the operation-result contract');
|
|
197
|
-
END;
|
|
198
|
-
|
|
199
|
-
CREATE TRIGGER IF NOT EXISTS loops_result_contract_update
|
|
200
|
-
BEFORE UPDATE OF status, terminal_result ON loops
|
|
201
|
-
WHEN NEW.status IN (100, 102, 200, 202, 413, 429, 499, 500, 504, 508)
|
|
202
|
-
AND NOT (
|
|
203
|
-
(NEW.status IN (100, 102, 202) AND NEW.terminal_result IS NULL)
|
|
204
|
-
OR (
|
|
205
|
-
NEW.status IN (200, 413, 429, 499, 500, 504, 508)
|
|
206
|
-
AND NEW.terminal_result IS NOT NULL
|
|
207
|
-
AND json_valid(NEW.terminal_result)
|
|
208
|
-
AND json_type(NEW.terminal_result, '$.status') = 'integer'
|
|
209
|
-
AND (
|
|
210
|
-
json_extract(NEW.terminal_result, '$.status') = NEW.status
|
|
211
|
-
OR (
|
|
212
|
-
NEW.status = 200
|
|
213
|
-
AND json_extract(NEW.terminal_result, '$.status') BETWEEN 200 AND 399
|
|
214
|
-
AND json_extract(NEW.terminal_result, '$.status') != 202
|
|
215
|
-
)
|
|
216
|
-
OR (
|
|
217
|
-
NEW.status = 500
|
|
218
|
-
AND json_extract(NEW.terminal_result, '$.status') BETWEEN 400 AND 599
|
|
219
|
-
)
|
|
220
|
-
)
|
|
221
|
-
AND (
|
|
222
|
-
(NEW.status < 400 AND json_type(NEW.terminal_result, '$.problem') IS NULL)
|
|
223
|
-
OR (
|
|
224
|
-
NEW.status >= 400
|
|
225
|
-
AND json_type(NEW.terminal_result, '$.problem') = 'object'
|
|
226
|
-
AND json_extract(NEW.terminal_result, '$.problem.status')
|
|
227
|
-
= json_extract(NEW.terminal_result, '$.status')
|
|
228
|
-
AND length(json_extract(NEW.terminal_result, '$.problem.type')) > 0
|
|
229
|
-
AND length(json_extract(NEW.terminal_result, '$.problem.title')) > 0
|
|
230
|
-
AND length(json_extract(NEW.terminal_result, '$.problem.detail')) > 0
|
|
231
|
-
AND length(json_extract(NEW.terminal_result, '$.problem.instance')) > 0
|
|
232
|
-
)
|
|
233
|
-
)
|
|
234
|
-
)
|
|
235
|
-
)
|
|
236
|
-
BEGIN
|
|
237
|
-
SELECT RAISE(ABORT, 'loop terminal result violates the operation-result contract');
|
|
238
|
-
END;
|
|
239
|
-
|
|
240
211
|
-- turns
|
|
241
|
-
-- finish_reason / model: provider-call metadata from the provider
|
|
242
|
-
--
|
|
243
|
-
--
|
|
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.
|
|
244
215
|
CREATE TABLE IF NOT EXISTS turns (
|
|
245
216
|
id INTEGER NOT NULL PRIMARY KEY,
|
|
246
217
|
version INTEGER NOT NULL DEFAULT 0 CHECK (version >= 0),
|
|
@@ -248,12 +219,6 @@ CREATE TABLE IF NOT EXISTS turns (
|
|
|
248
219
|
sequence INTEGER NOT NULL CHECK (sequence >= 1),
|
|
249
220
|
timestamp TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
|
|
250
221
|
status INTEGER NOT NULL CHECK (status BETWEEN 100 AND 599),
|
|
251
|
-
usage_prompt INTEGER NOT NULL DEFAULT 0 CHECK (usage_prompt >= 0),
|
|
252
|
-
usage_completion INTEGER NOT NULL DEFAULT 0 CHECK (usage_completion >= 0),
|
|
253
|
-
usage_reasoning INTEGER NOT NULL DEFAULT 0 CHECK (usage_reasoning >= 0),
|
|
254
|
-
usage_cached INTEGER NOT NULL DEFAULT 0 CHECK (usage_cached >= 0),
|
|
255
|
-
usage_cost TEXT NOT NULL DEFAULT '[]' CHECK (json_valid(usage_cost) AND json_type(usage_cost) = 'array'),
|
|
256
|
-
usage_cost_usd REAL DEFAULT 0 CHECK (usage_cost_usd IS NULL OR usage_cost_usd >= 0),
|
|
257
222
|
-- Effective packet allowance for this turn's provider and policy; NULL is
|
|
258
223
|
-- uncapped or unknown. {§tokenomics-client-gauge}
|
|
259
224
|
usage_prompt_budget INTEGER CHECK (usage_prompt_budget IS NULL OR usage_prompt_budget >= 1),
|
|
@@ -298,38 +263,254 @@ CREATE TABLE IF NOT EXISTS turns (
|
|
|
298
263
|
CREATE UNIQUE INDEX IF NOT EXISTS turns_loop_id_sequence ON turns (loop_id, sequence);
|
|
299
264
|
CREATE INDEX IF NOT EXISTS turns_timestamp ON turns (timestamp);
|
|
300
265
|
|
|
301
|
-
--
|
|
302
|
-
-- response evidence
|
|
303
|
-
--
|
|
304
|
-
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 (
|
|
305
270
|
id INTEGER NOT NULL PRIMARY KEY,
|
|
306
271
|
turn_id INTEGER NOT NULL,
|
|
307
272
|
sequence INTEGER NOT NULL CHECK (sequence >= 1),
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
273
|
+
kind TEXT NOT NULL CHECK (kind IN ('emission', 'bare')),
|
|
274
|
+
state TEXT NOT NULL DEFAULT 'pending' CHECK (state IN ('pending', 'response', 'error')),
|
|
275
|
+
response TEXT CHECK (response IS NULL OR json_valid(response)),
|
|
276
|
+
failure TEXT CHECK (failure IS NULL OR json_valid(failure)),
|
|
277
|
+
-- Exact opaque tag set forwarded with this provider call.
|
|
312
278
|
-- {§attribution}
|
|
313
279
|
attributions TEXT NOT NULL DEFAULT '[]' CHECK (
|
|
314
280
|
json_valid(attributions) AND json_type(attributions) = 'array'
|
|
315
281
|
),
|
|
316
|
-
usage_prompt INTEGER NOT NULL DEFAULT 0 CHECK (usage_prompt >= 0),
|
|
317
|
-
usage_completion INTEGER NOT NULL DEFAULT 0 CHECK (usage_completion >= 0),
|
|
318
|
-
usage_reasoning INTEGER NOT NULL DEFAULT 0 CHECK (usage_reasoning >= 0),
|
|
319
|
-
usage_cached INTEGER NOT NULL DEFAULT 0 CHECK (usage_cached >= 0),
|
|
320
|
-
usage_cost TEXT NOT NULL CHECK (json_valid(usage_cost) AND json_type(usage_cost) = 'object'),
|
|
321
|
-
usage_cost_usd REAL CHECK (usage_cost_usd IS NULL OR usage_cost_usd >= 0),
|
|
322
282
|
finish_reason TEXT,
|
|
323
283
|
model TEXT NOT NULL CHECK (length(model) >= 1),
|
|
324
284
|
timestamp TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
|
|
285
|
+
completed_at TEXT,
|
|
286
|
+
CHECK (
|
|
287
|
+
(state = 'pending'
|
|
288
|
+
AND response IS NULL AND failure IS NULL
|
|
289
|
+
AND finish_reason IS NULL AND completed_at IS NULL)
|
|
290
|
+
OR
|
|
291
|
+
(state = 'response'
|
|
292
|
+
AND response IS NOT NULL
|
|
293
|
+
AND completed_at IS NOT NULL)
|
|
294
|
+
OR
|
|
295
|
+
(state = 'error'
|
|
296
|
+
AND response IS NULL AND failure IS NOT NULL
|
|
297
|
+
AND completed_at IS NOT NULL)
|
|
298
|
+
),
|
|
325
299
|
UNIQUE (turn_id, sequence),
|
|
326
300
|
FOREIGN KEY (turn_id) REFERENCES turns(id) ON DELETE CASCADE
|
|
327
301
|
) STRICT;
|
|
328
302
|
|
|
329
|
-
CREATE INDEX IF NOT EXISTS
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
303
|
+
CREATE INDEX IF NOT EXISTS model_calls_turn_id ON model_calls (turn_id, sequence);
|
|
304
|
+
|
|
305
|
+
CREATE TRIGGER IF NOT EXISTS model_calls_request_identity_immutable
|
|
306
|
+
BEFORE UPDATE OF turn_id, sequence, kind, attributions, timestamp ON model_calls
|
|
307
|
+
BEGIN
|
|
308
|
+
SELECT RAISE(ABORT, 'model call request identity is immutable');
|
|
309
|
+
END;
|
|
310
|
+
|
|
311
|
+
CREATE TRIGGER IF NOT EXISTS model_calls_state_forward_only
|
|
312
|
+
BEFORE UPDATE OF state ON model_calls
|
|
313
|
+
WHEN NOT (
|
|
314
|
+
NEW.state = OLD.state
|
|
315
|
+
OR (OLD.state = 'pending' AND NEW.state IN ('response', 'error'))
|
|
316
|
+
)
|
|
317
|
+
BEGIN
|
|
318
|
+
SELECT RAISE(ABORT, 'model call state may only close once');
|
|
319
|
+
END;
|
|
320
|
+
|
|
321
|
+
CREATE TRIGGER IF NOT EXISTS model_calls_observation_immutable
|
|
322
|
+
BEFORE UPDATE OF response, failure, finish_reason, model, completed_at
|
|
323
|
+
ON model_calls
|
|
324
|
+
WHEN OLD.state != 'pending'
|
|
325
|
+
BEGIN
|
|
326
|
+
SELECT RAISE(ABORT, 'model call observation is immutable');
|
|
327
|
+
END;
|
|
328
|
+
|
|
329
|
+
-- Emission admission specializes one model call without re-owning its response,
|
|
330
|
+
-- provider identity, ordering, or accounting evidence.
|
|
331
|
+
CREATE TABLE IF NOT EXISTS turn_attempts (
|
|
332
|
+
id INTEGER NOT NULL PRIMARY KEY,
|
|
333
|
+
model_call_id INTEGER NOT NULL UNIQUE,
|
|
334
|
+
accepted INTEGER CHECK (accepted IS NULL OR accepted IN (0, 1)),
|
|
335
|
+
parse_errors TEXT NOT NULL DEFAULT '[]' CHECK (
|
|
336
|
+
json_valid(parse_errors) AND json_type(parse_errors) = 'array'
|
|
337
|
+
),
|
|
338
|
+
FOREIGN KEY (model_call_id) REFERENCES model_calls(id) ON DELETE CASCADE
|
|
339
|
+
) STRICT;
|
|
340
|
+
|
|
341
|
+
CREATE TRIGGER IF NOT EXISTS turn_attempts_request_identity_immutable
|
|
342
|
+
BEFORE UPDATE OF model_call_id ON turn_attempts
|
|
343
|
+
BEGIN
|
|
344
|
+
SELECT RAISE(ABORT, 'emission attempt identity is immutable');
|
|
345
|
+
END;
|
|
346
|
+
|
|
347
|
+
CREATE TRIGGER IF NOT EXISTS turn_attempts_emission_call_only
|
|
348
|
+
BEFORE INSERT ON turn_attempts
|
|
349
|
+
WHEN COALESCE((SELECT kind = 'emission' FROM model_calls WHERE id = NEW.model_call_id), 0) != 1
|
|
350
|
+
BEGIN
|
|
351
|
+
SELECT RAISE(ABORT, 'turn attempt requires an emission model call');
|
|
352
|
+
END;
|
|
353
|
+
|
|
354
|
+
CREATE TRIGGER IF NOT EXISTS turn_attempts_classification_after_response
|
|
355
|
+
BEFORE UPDATE OF accepted, parse_errors ON turn_attempts
|
|
356
|
+
WHEN COALESCE((SELECT state = 'response' FROM model_calls WHERE id = OLD.model_call_id), 0) != 1
|
|
357
|
+
BEGIN
|
|
358
|
+
SELECT RAISE(ABORT, 'emission classification requires model response evidence');
|
|
359
|
+
END;
|
|
360
|
+
|
|
361
|
+
CREATE TRIGGER IF NOT EXISTS turn_attempts_classification_once
|
|
362
|
+
BEFORE UPDATE OF accepted, parse_errors
|
|
363
|
+
ON turn_attempts
|
|
364
|
+
WHEN OLD.accepted IS NOT NULL
|
|
365
|
+
BEGIN
|
|
366
|
+
SELECT RAISE(ABORT, 'emission attempt classification is immutable');
|
|
367
|
+
END;
|
|
368
|
+
|
|
369
|
+
CREATE TRIGGER IF NOT EXISTS turn_attempts_one_accepted_insert
|
|
370
|
+
BEFORE INSERT ON turn_attempts
|
|
371
|
+
WHEN NEW.accepted = 1 AND EXISTS (
|
|
372
|
+
SELECT 1
|
|
373
|
+
FROM turn_attempts existing
|
|
374
|
+
JOIN model_calls old_call ON old_call.id = existing.model_call_id
|
|
375
|
+
JOIN model_calls new_call ON new_call.id = NEW.model_call_id
|
|
376
|
+
WHERE old_call.turn_id = new_call.turn_id AND existing.accepted = 1
|
|
377
|
+
)
|
|
378
|
+
BEGIN
|
|
379
|
+
SELECT RAISE(ABORT, 'one emission may be accepted per turn');
|
|
380
|
+
END;
|
|
381
|
+
|
|
382
|
+
CREATE TRIGGER IF NOT EXISTS turn_attempts_one_accepted_update
|
|
383
|
+
BEFORE UPDATE OF accepted ON turn_attempts
|
|
384
|
+
WHEN NEW.accepted = 1 AND EXISTS (
|
|
385
|
+
SELECT 1
|
|
386
|
+
FROM turn_attempts existing
|
|
387
|
+
JOIN model_calls old_call ON old_call.id = existing.model_call_id
|
|
388
|
+
JOIN model_calls new_call ON new_call.id = NEW.model_call_id
|
|
389
|
+
WHERE old_call.turn_id = new_call.turn_id
|
|
390
|
+
AND existing.id != OLD.id
|
|
391
|
+
AND existing.accepted = 1
|
|
392
|
+
)
|
|
393
|
+
BEGIN
|
|
394
|
+
SELECT RAISE(ABORT, 'one emission may be accepted per turn');
|
|
395
|
+
END;
|
|
396
|
+
|
|
397
|
+
-- {§provider-request-accounting}: one row is opened before one physical
|
|
398
|
+
-- provider request and settled exactly once with the evidence from that request.
|
|
399
|
+
-- Monetary values remain canonical decimal strings; SQLite REAL is never an
|
|
400
|
+
-- accounting representation.
|
|
401
|
+
CREATE TABLE IF NOT EXISTS provider_requests (
|
|
402
|
+
id INTEGER NOT NULL PRIMARY KEY,
|
|
403
|
+
model_call_id INTEGER NOT NULL,
|
|
404
|
+
sequence INTEGER NOT NULL CHECK (sequence >= 1),
|
|
405
|
+
provider TEXT NOT NULL CHECK (length(provider) > 0),
|
|
406
|
+
model TEXT NOT NULL CHECK (length(model) > 0),
|
|
407
|
+
state TEXT NOT NULL DEFAULT 'pending' CHECK (state IN ('pending', 'settled')),
|
|
408
|
+
outcome TEXT CHECK (outcome IN ('response', 'error')),
|
|
409
|
+
status INTEGER CHECK (status IS NULL OR status BETWEEN 100 AND 599),
|
|
410
|
+
usage_input INTEGER CHECK (usage_input IS NULL OR usage_input >= 0),
|
|
411
|
+
usage_output INTEGER CHECK (usage_output IS NULL OR usage_output >= 0),
|
|
412
|
+
usage_total INTEGER CHECK (usage_total IS NULL OR usage_total >= 0),
|
|
413
|
+
usage_input_no_cache INTEGER CHECK (usage_input_no_cache IS NULL OR usage_input_no_cache >= 0),
|
|
414
|
+
usage_input_cache_read INTEGER CHECK (usage_input_cache_read IS NULL OR usage_input_cache_read >= 0),
|
|
415
|
+
usage_input_cache_write INTEGER CHECK (usage_input_cache_write IS NULL OR usage_input_cache_write >= 0),
|
|
416
|
+
usage_output_text INTEGER CHECK (usage_output_text IS NULL OR usage_output_text >= 0),
|
|
417
|
+
usage_output_reasoning INTEGER CHECK (usage_output_reasoning IS NULL OR usage_output_reasoning >= 0),
|
|
418
|
+
cost_kind TEXT CHECK (cost_kind IN ('charged', 'estimated', 'unknown')),
|
|
419
|
+
cost_amount TEXT,
|
|
420
|
+
cost_currency TEXT,
|
|
421
|
+
cost_usd_equivalent TEXT,
|
|
422
|
+
cost_source TEXT,
|
|
423
|
+
cost_reason TEXT,
|
|
424
|
+
started_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
|
|
425
|
+
completed_at TEXT,
|
|
426
|
+
CHECK (
|
|
427
|
+
(state = 'pending'
|
|
428
|
+
AND outcome IS NULL AND status IS NULL
|
|
429
|
+
AND usage_input IS NULL AND usage_output IS NULL AND usage_total IS NULL
|
|
430
|
+
AND usage_input_no_cache IS NULL AND usage_input_cache_read IS NULL
|
|
431
|
+
AND usage_input_cache_write IS NULL AND usage_output_text IS NULL
|
|
432
|
+
AND usage_output_reasoning IS NULL
|
|
433
|
+
AND cost_kind IS NULL AND cost_amount IS NULL AND cost_currency IS NULL
|
|
434
|
+
AND cost_usd_equivalent IS NULL AND cost_source IS NULL AND cost_reason IS NULL
|
|
435
|
+
AND completed_at IS NULL)
|
|
436
|
+
OR
|
|
437
|
+
(state = 'settled' AND outcome IS NOT NULL AND cost_kind IS NOT NULL AND completed_at IS NOT NULL)
|
|
438
|
+
),
|
|
439
|
+
CHECK (
|
|
440
|
+
cost_amount IS NULL OR (
|
|
441
|
+
length(cost_amount) > 0
|
|
442
|
+
AND cost_amount NOT GLOB '*[^0-9.]*'
|
|
443
|
+
AND cost_amount NOT LIKE '.%'
|
|
444
|
+
AND cost_amount NOT LIKE '%.'
|
|
445
|
+
AND cost_amount NOT LIKE '%.%.%'
|
|
446
|
+
AND (cost_amount = '0' OR cost_amount NOT GLOB '0[0-9]*')
|
|
447
|
+
)
|
|
448
|
+
),
|
|
449
|
+
CHECK (
|
|
450
|
+
cost_usd_equivalent IS NULL OR (
|
|
451
|
+
length(cost_usd_equivalent) > 0
|
|
452
|
+
AND cost_usd_equivalent NOT GLOB '*[^0-9.]*'
|
|
453
|
+
AND cost_usd_equivalent NOT LIKE '.%'
|
|
454
|
+
AND cost_usd_equivalent NOT LIKE '%.'
|
|
455
|
+
AND cost_usd_equivalent NOT LIKE '%.%.%'
|
|
456
|
+
AND (cost_usd_equivalent = '0' OR cost_usd_equivalent NOT GLOB '0[0-9]*')
|
|
457
|
+
)
|
|
458
|
+
),
|
|
459
|
+
CHECK (
|
|
460
|
+
cost_currency IS NULL OR (
|
|
461
|
+
length(cost_currency) BETWEEN 3 AND 12
|
|
462
|
+
AND substr(cost_currency, 1, 1) GLOB '[A-Z]'
|
|
463
|
+
AND cost_currency NOT GLOB '*[^A-Z0-9]*'
|
|
464
|
+
)
|
|
465
|
+
),
|
|
466
|
+
CHECK (
|
|
467
|
+
cost_kind IS NULL
|
|
468
|
+
OR (cost_kind = 'charged'
|
|
469
|
+
AND cost_amount IS NOT NULL AND cost_currency IS NOT NULL
|
|
470
|
+
AND cost_source IS NOT NULL AND length(cost_source) > 0
|
|
471
|
+
AND cost_reason IS NULL)
|
|
472
|
+
OR (cost_kind = 'estimated'
|
|
473
|
+
AND cost_amount IS NOT NULL AND cost_currency IS NOT NULL
|
|
474
|
+
AND cost_usd_equivalent IS NULL
|
|
475
|
+
AND cost_source IS NOT NULL AND length(cost_source) > 0
|
|
476
|
+
AND cost_reason IS NULL)
|
|
477
|
+
OR (cost_kind = 'unknown'
|
|
478
|
+
AND cost_amount IS NULL AND cost_currency IS NULL
|
|
479
|
+
AND cost_usd_equivalent IS NULL AND cost_source IS NULL
|
|
480
|
+
AND cost_reason IS NOT NULL AND length(cost_reason) > 0)
|
|
481
|
+
),
|
|
482
|
+
UNIQUE (model_call_id, sequence),
|
|
483
|
+
FOREIGN KEY (model_call_id) REFERENCES model_calls(id) ON DELETE CASCADE
|
|
484
|
+
) STRICT;
|
|
485
|
+
|
|
486
|
+
CREATE INDEX IF NOT EXISTS provider_requests_model_call_id
|
|
487
|
+
ON provider_requests (model_call_id, sequence);
|
|
488
|
+
|
|
489
|
+
CREATE TRIGGER IF NOT EXISTS provider_requests_identity_immutable
|
|
490
|
+
BEFORE UPDATE OF model_call_id, sequence, provider, model, started_at ON provider_requests
|
|
491
|
+
BEGIN
|
|
492
|
+
SELECT RAISE(ABORT, 'provider request identity is immutable');
|
|
493
|
+
END;
|
|
494
|
+
|
|
495
|
+
CREATE TRIGGER IF NOT EXISTS provider_requests_state_forward_only
|
|
496
|
+
BEFORE UPDATE OF state ON provider_requests
|
|
497
|
+
WHEN NOT (NEW.state = OLD.state OR (OLD.state = 'pending' AND NEW.state = 'settled'))
|
|
498
|
+
BEGIN
|
|
499
|
+
SELECT RAISE(ABORT, 'provider request may only settle once');
|
|
500
|
+
END;
|
|
501
|
+
|
|
502
|
+
CREATE TRIGGER IF NOT EXISTS provider_requests_settlement_immutable
|
|
503
|
+
BEFORE UPDATE OF outcome, status,
|
|
504
|
+
usage_input, usage_output, usage_total,
|
|
505
|
+
usage_input_no_cache, usage_input_cache_read, usage_input_cache_write,
|
|
506
|
+
usage_output_text, usage_output_reasoning,
|
|
507
|
+
cost_kind, cost_amount, cost_currency, cost_usd_equivalent,
|
|
508
|
+
cost_source, cost_reason, completed_at
|
|
509
|
+
ON provider_requests
|
|
510
|
+
WHEN OLD.state != 'pending'
|
|
511
|
+
BEGIN
|
|
512
|
+
SELECT RAISE(ABORT, 'provider request settlement is immutable');
|
|
513
|
+
END;
|
|
333
514
|
|
|
334
515
|
-- derivations
|
|
335
516
|
-- Content-addressed deep projections. Entries point at a COMPLETE artifact by
|
|
@@ -342,6 +523,7 @@ CREATE TABLE IF NOT EXISTS derivations (
|
|
|
342
523
|
state TEXT NOT NULL DEFAULT 'building' CHECK (state IN ('building', 'complete')),
|
|
343
524
|
disposition TEXT CHECK (disposition IN ('vector', 'lexical', 'excluded', 'nonsemantic', 'failed')),
|
|
344
525
|
reason TEXT,
|
|
526
|
+
parse_issues INTEGER CHECK (parse_issues IS NULL OR parse_issues > 0),
|
|
345
527
|
CHECK ((state = 'building' AND disposition IS NULL) OR (state = 'complete' AND disposition IS NOT NULL))
|
|
346
528
|
) STRICT;
|
|
347
529
|
|
|
@@ -377,7 +559,8 @@ CREATE TABLE IF NOT EXISTS entries (
|
|
|
377
559
|
-- unchanged entry is skipped, never re-metadatafied every turn.
|
|
378
560
|
deep_hash TEXT,
|
|
379
561
|
-- SPEC {§membership-change-gated-sync} — the per-member sync stat-detect:
|
|
380
|
-
-- "<mtimeMs>:<size>" of the disk file at its last materialization
|
|
562
|
+
-- "<mtimeMs>:<size>" of the disk file at its last materialization, or `absent`
|
|
563
|
+
-- after an observed deletion. The pre-turn
|
|
381
564
|
-- sync stat()s every member but re-reads/re-tokenizes/rewrites only one whose
|
|
382
565
|
-- signature changed; an unchanged member is a no-op. NULL = never synced.
|
|
383
566
|
synced_sig TEXT,
|
|
@@ -409,6 +592,36 @@ CREATE TABLE IF NOT EXISTS entry_channels (
|
|
|
409
592
|
-- NULL). The per-tokenizer token cache it once keyed was retired — {§tokenomics-agnostic-ruler}.
|
|
410
593
|
content_hash TEXT,
|
|
411
594
|
state TEXT NOT NULL DEFAULT 'static' CHECK (state IN ('static', 'active', 'closed', 'errored')),
|
|
595
|
+
-- Exact terminal producer evidence for this representation channel. NULL is
|
|
596
|
+
-- the ordinary implicit {status:200}; selection/projection metadata belongs
|
|
597
|
+
-- to core and is never stored here.
|
|
598
|
+
producer_result TEXT,
|
|
599
|
+
CONSTRAINT entry_channel_producer_result_contract CHECK (
|
|
600
|
+
CASE
|
|
601
|
+
WHEN producer_result IS NULL THEN 1
|
|
602
|
+
WHEN NOT json_valid(producer_result) THEN 0
|
|
603
|
+
ELSE
|
|
604
|
+
json_type(producer_result) IS 'object'
|
|
605
|
+
AND json_type(producer_result, '$.status') IS 'integer'
|
|
606
|
+
AND json_extract(producer_result, '$.status') BETWEEN 200 AND 599
|
|
607
|
+
AND json_extract(producer_result, '$.status') != 202
|
|
608
|
+
AND CASE
|
|
609
|
+
WHEN json_extract(producer_result, '$.status') < 400 THEN
|
|
610
|
+
json_type(producer_result, '$.problem') IS NULL
|
|
611
|
+
ELSE
|
|
612
|
+
json_type(producer_result, '$.problem') IS 'object'
|
|
613
|
+
AND json_type(producer_result, '$.problem.status') IS 'integer'
|
|
614
|
+
AND json_extract(producer_result, '$.problem.status')
|
|
615
|
+
= json_extract(producer_result, '$.status')
|
|
616
|
+
AND json_type(producer_result, '$.problem.type') IS 'text'
|
|
617
|
+
AND length(json_extract(producer_result, '$.problem.type')) > 0
|
|
618
|
+
AND json_type(producer_result, '$.problem.title') IS 'text'
|
|
619
|
+
AND length(json_extract(producer_result, '$.problem.title')) > 0
|
|
620
|
+
AND json_type(producer_result, '$.problem.detail') IS 'text'
|
|
621
|
+
AND length(json_extract(producer_result, '$.problem.detail')) > 0
|
|
622
|
+
END
|
|
623
|
+
END
|
|
624
|
+
),
|
|
412
625
|
PRIMARY KEY (entry_id, name),
|
|
413
626
|
FOREIGN KEY (entry_id) REFERENCES entries(id) ON DELETE CASCADE
|
|
414
627
|
) STRICT, WITHOUT ROWID;
|
|
@@ -422,14 +635,11 @@ BEGIN
|
|
|
422
635
|
UPDATE entries SET updated_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now') WHERE id = NEW.entry_id;
|
|
423
636
|
END;
|
|
424
637
|
|
|
425
|
-
CREATE
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
) STRICT, WITHOUT ROWID;
|
|
431
|
-
|
|
432
|
-
CREATE INDEX IF NOT EXISTS entry_tags_tag ON entry_tags (tag);
|
|
638
|
+
CREATE TRIGGER IF NOT EXISTS entries_touch_on_channel_update
|
|
639
|
+
AFTER UPDATE ON entry_channels
|
|
640
|
+
BEGIN
|
|
641
|
+
UPDATE entries SET updated_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now') WHERE id = NEW.entry_id;
|
|
642
|
+
END;
|
|
433
643
|
|
|
434
644
|
-- symbol_defs
|
|
435
645
|
-- @graph NODES ({§relation-indexed-dialects}). Code symbol definitions, populated
|
|
@@ -521,18 +731,20 @@ CREATE TABLE IF NOT EXISTS log_entries (
|
|
|
521
731
|
ambient_event_id INTEGER REFERENCES ambient_events(id),
|
|
522
732
|
-- Search derivation attached to this durable log result, when available.
|
|
523
733
|
deep_hash TEXT REFERENCES derivations(deep_hash),
|
|
734
|
+
-- Exact logical provider call represented by a BARE result or model-emission
|
|
735
|
+
-- mirror. Other operation and ambient rows carry no model-call identity.
|
|
736
|
+
model_call_id INTEGER REFERENCES model_calls(id),
|
|
524
737
|
|
|
525
738
|
-- 'error' is an ACTIONLESS row ({§operation-results} — errors are log items): a parse failure that
|
|
526
739
|
-- produced no op still records a log entry (op='error', status_rx≥400, no target) so the model
|
|
527
740
|
-- can fold/kill/recall its own mistakes like any other log row — one budget surface, the log.
|
|
528
|
-
--
|
|
529
|
-
--
|
|
530
|
-
-- exemplar is born open). text/vnd.plurnk-typed; OPEN/FOLD/KILL-able like any row.
|
|
741
|
+
-- Actionless artifacts carry NULL here: kernel-authored worker initialization
|
|
742
|
+
-- ({§worker-initialization-entry}) or a model emission ({§model-entry}).
|
|
531
743
|
-- No op enum here: the grammar op set is grammar's contract (PlurnkOp), and this column is written
|
|
532
|
-
-- only by the PlurnkOp-typed engine (grammar ops)
|
|
744
|
+
-- only by the PlurnkOp-typed engine (grammar ops), service row selectors, or NULL for no op.
|
|
533
745
|
-- A SQL enum would be a hand-copy of grammar's op list that silently goes stale on every new verb
|
|
534
746
|
-- (it did — FORK/WORK). Validity lives at the parse + type layer, not duplicated in DDL.
|
|
535
|
-
op TEXT
|
|
747
|
+
op TEXT,
|
|
536
748
|
suffix TEXT NOT NULL DEFAULT '',
|
|
537
749
|
signal TEXT CHECK (signal IS NULL OR json_valid(signal)),
|
|
538
750
|
|
|
@@ -564,6 +776,10 @@ CREATE TABLE IF NOT EXISTS log_entries (
|
|
|
564
776
|
|
|
565
777
|
expanded INTEGER NOT NULL DEFAULT 1 CHECK (expanded IN (0, 1)),
|
|
566
778
|
|
|
779
|
+
CHECK ((op IS NULL) = COALESCE(json_extract(attrs, '$.kind') IN ('initialization', 'model_emission'), 0)),
|
|
780
|
+
CHECK (json_extract(attrs, '$.kind') != 'initialization' OR origin = 'plurnk'),
|
|
781
|
+
CHECK (json_extract(attrs, '$.kind') != 'model_emission' OR origin = 'model'),
|
|
782
|
+
|
|
567
783
|
FOREIGN KEY (worker_id) REFERENCES workers(id) ON DELETE CASCADE,
|
|
568
784
|
FOREIGN KEY (loop_id) REFERENCES loops(id) ON DELETE CASCADE,
|
|
569
785
|
FOREIGN KEY (turn_id) REFERENCES turns(id) ON DELETE CASCADE
|
|
@@ -573,37 +789,388 @@ CREATE UNIQUE INDEX IF NOT EXISTS log_entries_turn_id_sequence ON log_entries (t
|
|
|
573
789
|
CREATE INDEX IF NOT EXISTS log_entries_worker_id ON log_entries (worker_id);
|
|
574
790
|
CREATE INDEX IF NOT EXISTS log_entries_loop_id ON log_entries (loop_id);
|
|
575
791
|
CREATE INDEX IF NOT EXISTS log_entries_at ON log_entries (at);
|
|
792
|
+
CREATE UNIQUE INDEX IF NOT EXISTS log_entries_model_call_id
|
|
793
|
+
ON log_entries (model_call_id)
|
|
794
|
+
WHERE model_call_id IS NOT NULL;
|
|
576
795
|
CREATE UNIQUE INDEX IF NOT EXISTS log_entries_worker_ambient_event
|
|
577
796
|
ON log_entries (worker_id, ambient_event_id)
|
|
578
797
|
WHERE ambient_event_id IS NOT NULL;
|
|
579
798
|
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
799
|
+
CREATE TRIGGER IF NOT EXISTS log_entries_model_call_valid
|
|
800
|
+
BEFORE INSERT ON log_entries
|
|
801
|
+
WHEN NEW.model_call_id IS NOT NULL
|
|
802
|
+
AND NOT EXISTS (
|
|
803
|
+
SELECT 1
|
|
804
|
+
FROM model_calls call
|
|
805
|
+
WHERE call.id = NEW.model_call_id
|
|
806
|
+
AND call.turn_id = NEW.turn_id
|
|
807
|
+
AND call.state != 'pending'
|
|
808
|
+
AND (
|
|
809
|
+
(call.kind = 'bare' AND NEW.op = 'BARE')
|
|
810
|
+
OR (
|
|
811
|
+
call.kind = 'emission'
|
|
812
|
+
AND NEW.op IS NULL
|
|
813
|
+
AND json_extract(NEW.attrs, '$.kind') = 'model_emission'
|
|
814
|
+
)
|
|
815
|
+
)
|
|
816
|
+
)
|
|
817
|
+
BEGIN
|
|
818
|
+
SELECT RAISE(ABORT, 'log entry model call does not match its represented result');
|
|
819
|
+
END;
|
|
820
|
+
|
|
821
|
+
-- {§log-item-tags} — log classification plus OPEN/FOLD selection and mutation.
|
|
822
|
+
-- CASCADE erases the classification with its row.
|
|
583
823
|
CREATE TABLE IF NOT EXISTS log_tags (
|
|
584
824
|
log_entry_id INTEGER NOT NULL,
|
|
585
|
-
tag TEXT NOT NULL
|
|
825
|
+
tag TEXT NOT NULL,
|
|
586
826
|
PRIMARY KEY (log_entry_id, tag),
|
|
587
827
|
FOREIGN KEY (log_entry_id) REFERENCES log_entries(id) ON DELETE CASCADE
|
|
588
828
|
) STRICT, WITHOUT ROWID;
|
|
589
829
|
|
|
830
|
+
-- The relational table owns stored tag identity. Producers and curation
|
|
831
|
+
-- signals carry signs, but the stored name never does; delimiters, whitespace,
|
|
832
|
+
-- and control characters cannot become durable classifications through a
|
|
833
|
+
-- private SQL caller either.
|
|
834
|
+
CREATE TRIGGER IF NOT EXISTS log_tags_name_valid
|
|
835
|
+
BEFORE INSERT ON log_tags
|
|
836
|
+
WHEN length(NEW.tag) = 0
|
|
837
|
+
OR substr(NEW.tag, 1, 1) IN ('+', '-')
|
|
838
|
+
OR instr(NEW.tag, '[') > 0
|
|
839
|
+
OR instr(NEW.tag, ']') > 0
|
|
840
|
+
OR instr(NEW.tag, ',') > 0
|
|
841
|
+
OR instr(NEW.tag, char(0)) > 0
|
|
842
|
+
OR EXISTS (
|
|
843
|
+
WITH RECURSIVE offsets(position) AS (
|
|
844
|
+
SELECT 1
|
|
845
|
+
UNION ALL
|
|
846
|
+
SELECT position + 1 FROM offsets WHERE position < length(NEW.tag)
|
|
847
|
+
)
|
|
848
|
+
SELECT 1
|
|
849
|
+
FROM offsets
|
|
850
|
+
WHERE unicode(substr(NEW.tag, position, 1)) BETWEEN 0 AND 32
|
|
851
|
+
OR unicode(substr(NEW.tag, position, 1)) BETWEEN 127 AND 159
|
|
852
|
+
OR unicode(substr(NEW.tag, position, 1)) IN (160, 5760, 8232, 8233, 8239, 8287, 12288, 65279)
|
|
853
|
+
OR unicode(substr(NEW.tag, position, 1)) BETWEEN 8192 AND 8202
|
|
854
|
+
)
|
|
855
|
+
BEGIN
|
|
856
|
+
SELECT RAISE(ABORT, 'log tag name is invalid');
|
|
857
|
+
END;
|
|
858
|
+
|
|
590
859
|
CREATE INDEX IF NOT EXISTS log_tags_tag ON log_tags (tag);
|
|
591
860
|
|
|
592
|
-
--
|
|
593
|
-
--
|
|
594
|
-
--
|
|
861
|
+
-- Signal-derived classification is part of the log-row insert, not a later JS
|
|
862
|
+
-- write. This keeps the operation, its ambient occurrence, and its complete
|
|
863
|
+
-- initial folksonomy inside one SQLite commit.
|
|
864
|
+
CREATE TRIGGER IF NOT EXISTS log_entries_classify_signal
|
|
865
|
+
AFTER INSERT ON log_entries
|
|
866
|
+
WHEN NEW.op IN ('FIND', 'READ', 'EDIT', 'COPY', 'MOVE', 'BARE')
|
|
867
|
+
AND NEW.signal IS NOT NULL
|
|
868
|
+
BEGIN
|
|
869
|
+
SELECT CASE
|
|
870
|
+
WHEN json_type(NEW.signal) != 'array'
|
|
871
|
+
OR EXISTS (
|
|
872
|
+
SELECT 1 FROM json_each(NEW.signal)
|
|
873
|
+
WHERE type != 'text'
|
|
874
|
+
OR length(value) = 0
|
|
875
|
+
OR substr(value, 1, 1) = '-'
|
|
876
|
+
OR (
|
|
877
|
+
substr(value, 1, 1) = '+'
|
|
878
|
+
AND (length(value) < 2 OR substr(value, 2, 1) IN ('+', '-'))
|
|
879
|
+
)
|
|
880
|
+
OR instr(value, '[') > 0
|
|
881
|
+
OR instr(value, ']') > 0
|
|
882
|
+
OR instr(value, ',') > 0
|
|
883
|
+
OR instr(value, char(0)) > 0
|
|
884
|
+
OR EXISTS (
|
|
885
|
+
WITH RECURSIVE offsets(position) AS (
|
|
886
|
+
SELECT 1
|
|
887
|
+
UNION ALL
|
|
888
|
+
SELECT position + 1 FROM offsets WHERE position < length(value)
|
|
889
|
+
)
|
|
890
|
+
SELECT 1
|
|
891
|
+
FROM offsets
|
|
892
|
+
WHERE unicode(substr(value, position, 1)) BETWEEN 0 AND 32
|
|
893
|
+
OR unicode(substr(value, position, 1)) BETWEEN 127 AND 159
|
|
894
|
+
OR unicode(substr(value, position, 1)) IN (160, 5760, 8232, 8233, 8239, 8287, 12288, 65279)
|
|
895
|
+
OR unicode(substr(value, position, 1)) BETWEEN 8192 AND 8202
|
|
896
|
+
)
|
|
897
|
+
)
|
|
898
|
+
THEN RAISE(ABORT, 'classifying log operation signal accepts only tag or +tag additions')
|
|
899
|
+
END;
|
|
900
|
+
INSERT OR IGNORE INTO log_tags (log_entry_id, tag)
|
|
901
|
+
SELECT NEW.id, CASE WHEN substr(value, 1, 1) = '+' THEN substr(value, 2) ELSE value END
|
|
902
|
+
FROM json_each(NEW.signal);
|
|
903
|
+
|
|
904
|
+
-- The ambient-event and classification AFTER INSERT triggers may run in
|
|
905
|
+
-- either order. If the event already exists, finish its initial
|
|
906
|
+
-- snapshot here; otherwise its own trigger aggregates these rows later.
|
|
907
|
+
UPDATE ambient_events
|
|
908
|
+
SET tags = COALESCE((
|
|
909
|
+
SELECT json_group_array(tag)
|
|
910
|
+
FROM (SELECT tag FROM log_tags WHERE log_entry_id = NEW.id ORDER BY tag)
|
|
911
|
+
), '[]')
|
|
912
|
+
WHERE kind = 'edit'
|
|
913
|
+
AND producer_worker_id = NEW.worker_id
|
|
914
|
+
AND source_record_id = NEW.id;
|
|
915
|
+
END;
|
|
916
|
+
|
|
917
|
+
-- Successful OPEN/FOLD rows are durable curation events even though their
|
|
918
|
+
-- ordinary packet projection is suppressed ({§fold-open-meta-operations}).
|
|
919
|
+
-- Preserve the exact selected set and each target's pre-event visibility so a
|
|
920
|
+
-- broad selector never collapses into the lossy fact `matched: N`.
|
|
921
|
+
CREATE TABLE IF NOT EXISTS log_curation_effects (
|
|
922
|
+
operation_log_entry_id INTEGER NOT NULL,
|
|
923
|
+
target_log_entry_id INTEGER NOT NULL,
|
|
924
|
+
expanded_before INTEGER NOT NULL CHECK (expanded_before IN (0, 1)),
|
|
925
|
+
tags_added TEXT NOT NULL DEFAULT '[]'
|
|
926
|
+
CHECK (json_valid(tags_added) AND json_type(tags_added) = 'array'),
|
|
927
|
+
tags_removed TEXT NOT NULL DEFAULT '[]'
|
|
928
|
+
CHECK (json_valid(tags_removed) AND json_type(tags_removed) = 'array'),
|
|
929
|
+
PRIMARY KEY (operation_log_entry_id, target_log_entry_id),
|
|
930
|
+
CHECK (operation_log_entry_id != target_log_entry_id),
|
|
931
|
+
FOREIGN KEY (operation_log_entry_id) REFERENCES log_entries(id) ON DELETE CASCADE,
|
|
932
|
+
FOREIGN KEY (target_log_entry_id) REFERENCES log_entries(id) ON DELETE CASCADE
|
|
933
|
+
) STRICT, WITHOUT ROWID;
|
|
934
|
+
|
|
935
|
+
CREATE INDEX IF NOT EXISTS log_curation_effects_target
|
|
936
|
+
ON log_curation_effects (target_log_entry_id);
|
|
937
|
+
|
|
938
|
+
-- Make an invalid curation record structurally unavailable: the event must be
|
|
939
|
+
-- one successful OPEN/FOLD row, both rows must belong to the same worker, and
|
|
940
|
+
-- both exact delta sets must contain only unique canonical tag identities.
|
|
941
|
+
CREATE TRIGGER IF NOT EXISTS log_curation_effects_valid
|
|
942
|
+
BEFORE INSERT ON log_curation_effects
|
|
943
|
+
BEGIN
|
|
944
|
+
SELECT CASE WHEN
|
|
945
|
+
NOT EXISTS (
|
|
946
|
+
SELECT 1
|
|
947
|
+
FROM log_entries operation
|
|
948
|
+
JOIN log_entries target ON target.id = NEW.target_log_entry_id
|
|
949
|
+
WHERE operation.id = NEW.operation_log_entry_id
|
|
950
|
+
AND operation.op IN ('OPEN', 'FOLD')
|
|
951
|
+
AND operation.status_rx < 400
|
|
952
|
+
AND operation.worker_id = target.worker_id
|
|
953
|
+
)
|
|
954
|
+
OR EXISTS (
|
|
955
|
+
SELECT 1
|
|
956
|
+
FROM (
|
|
957
|
+
SELECT value, type FROM json_each(NEW.tags_added)
|
|
958
|
+
UNION ALL
|
|
959
|
+
SELECT value, type FROM json_each(NEW.tags_removed)
|
|
960
|
+
) tag
|
|
961
|
+
WHERE tag.type != 'text'
|
|
962
|
+
OR length(tag.value) = 0
|
|
963
|
+
OR substr(tag.value, 1, 1) IN ('+', '-')
|
|
964
|
+
OR instr(tag.value, '[') > 0
|
|
965
|
+
OR instr(tag.value, ']') > 0
|
|
966
|
+
OR instr(tag.value, ',') > 0
|
|
967
|
+
OR instr(tag.value, char(0)) > 0
|
|
968
|
+
OR EXISTS (
|
|
969
|
+
WITH RECURSIVE offsets(position) AS (
|
|
970
|
+
SELECT 1
|
|
971
|
+
UNION ALL
|
|
972
|
+
SELECT position + 1 FROM offsets WHERE position < length(tag.value)
|
|
973
|
+
)
|
|
974
|
+
SELECT 1
|
|
975
|
+
FROM offsets
|
|
976
|
+
WHERE unicode(substr(tag.value, position, 1)) BETWEEN 0 AND 32
|
|
977
|
+
OR unicode(substr(tag.value, position, 1)) BETWEEN 127 AND 159
|
|
978
|
+
OR unicode(substr(tag.value, position, 1)) IN (160, 5760, 8232, 8233, 8239, 8287, 12288, 65279)
|
|
979
|
+
OR unicode(substr(tag.value, position, 1)) BETWEEN 8192 AND 8202
|
|
980
|
+
)
|
|
981
|
+
)
|
|
982
|
+
OR (SELECT COUNT(*) FROM json_each(NEW.tags_added))
|
|
983
|
+
!= (SELECT COUNT(DISTINCT value) FROM json_each(NEW.tags_added))
|
|
984
|
+
OR (SELECT COUNT(*) FROM json_each(NEW.tags_removed))
|
|
985
|
+
!= (SELECT COUNT(DISTINCT value) FROM json_each(NEW.tags_removed))
|
|
986
|
+
OR EXISTS (
|
|
987
|
+
SELECT 1
|
|
988
|
+
FROM json_each(NEW.tags_added) addition
|
|
989
|
+
JOIN json_each(NEW.tags_removed) removal ON removal.value = addition.value
|
|
990
|
+
)
|
|
991
|
+
THEN RAISE(ABORT, 'invalid log curation effect') END;
|
|
992
|
+
END;
|
|
993
|
+
|
|
994
|
+
-- The dispatcher binds an exact, transient curation plan into attrs on the
|
|
995
|
+
-- successful OPEN/FOLD row. No other row may carry that private payload.
|
|
996
|
+
CREATE TRIGGER IF NOT EXISTS log_entries_curation_payload_valid
|
|
997
|
+
BEFORE INSERT ON log_entries
|
|
998
|
+
WHEN json_type(NEW.attrs, '$.__plurnk_curation') IS NOT NULL
|
|
999
|
+
AND NOT (
|
|
1000
|
+
NEW.op IN ('OPEN', 'FOLD')
|
|
1001
|
+
AND NEW.status_rx < 400
|
|
1002
|
+
AND json_type(NEW.attrs, '$.__plurnk_curation') = 'object'
|
|
1003
|
+
)
|
|
1004
|
+
BEGIN
|
|
1005
|
+
SELECT RAISE(ABORT, 'private log curation payload requires a successful OPEN/FOLD row');
|
|
1006
|
+
END;
|
|
1007
|
+
|
|
1008
|
+
-- One outer INSERT owns the whole landed curation event: exact selected rows,
|
|
1009
|
+
-- their pre-event visibility and tag deltas, the requested visibility, and the
|
|
1010
|
+
-- resulting classifications. Trigger failure rolls the operation row and all
|
|
1011
|
+
-- effects back together. The private plan is erased before INSERT returns.
|
|
1012
|
+
CREATE TRIGGER IF NOT EXISTS log_entries_apply_curation
|
|
1013
|
+
AFTER INSERT ON log_entries
|
|
1014
|
+
WHEN NEW.op IN ('OPEN', 'FOLD')
|
|
1015
|
+
AND NEW.status_rx < 400
|
|
1016
|
+
AND json_type(NEW.attrs, '$.__plurnk_curation') = 'object'
|
|
1017
|
+
BEGIN
|
|
1018
|
+
SELECT CASE WHEN
|
|
1019
|
+
COALESCE(json_type(NEW.attrs, '$.__plurnk_curation.ids'), '') != 'array'
|
|
1020
|
+
OR COALESCE(json_type(NEW.attrs, '$.__plurnk_curation.add'), '') != 'array'
|
|
1021
|
+
OR COALESCE(json_type(NEW.attrs, '$.__plurnk_curation.remove'), '') != 'array'
|
|
1022
|
+
OR COALESCE(json_type(NEW.attrs, '$.__plurnk_curation.expanded'), '') != 'integer'
|
|
1023
|
+
OR json_extract(NEW.attrs, '$.__plurnk_curation.expanded') NOT IN (0, 1)
|
|
1024
|
+
OR json_array_length(NEW.attrs, '$.__plurnk_curation.ids') = 0
|
|
1025
|
+
OR EXISTS (
|
|
1026
|
+
SELECT 1 FROM json_each(NEW.attrs, '$.__plurnk_curation.ids')
|
|
1027
|
+
WHERE type != 'integer' OR value <= 0
|
|
1028
|
+
)
|
|
1029
|
+
OR (
|
|
1030
|
+
SELECT COUNT(*) FROM json_each(NEW.attrs, '$.__plurnk_curation.ids')
|
|
1031
|
+
) != (
|
|
1032
|
+
SELECT COUNT(DISTINCT value) FROM json_each(NEW.attrs, '$.__plurnk_curation.ids')
|
|
1033
|
+
)
|
|
1034
|
+
OR EXISTS (
|
|
1035
|
+
SELECT 1
|
|
1036
|
+
FROM json_each(NEW.attrs, '$.__plurnk_curation.ids') selected
|
|
1037
|
+
LEFT JOIN log_entries target ON target.id = selected.value
|
|
1038
|
+
WHERE target.id IS NULL OR target.worker_id != NEW.worker_id OR target.id = NEW.id
|
|
1039
|
+
)
|
|
1040
|
+
OR EXISTS (
|
|
1041
|
+
SELECT 1
|
|
1042
|
+
FROM (
|
|
1043
|
+
SELECT value, type FROM json_each(NEW.attrs, '$.__plurnk_curation.add')
|
|
1044
|
+
UNION ALL
|
|
1045
|
+
SELECT value, type FROM json_each(NEW.attrs, '$.__plurnk_curation.remove')
|
|
1046
|
+
) tag
|
|
1047
|
+
WHERE tag.type != 'text'
|
|
1048
|
+
OR length(tag.value) = 0
|
|
1049
|
+
OR substr(tag.value, 1, 1) IN ('+', '-')
|
|
1050
|
+
OR instr(tag.value, '[') > 0
|
|
1051
|
+
OR instr(tag.value, ']') > 0
|
|
1052
|
+
OR instr(tag.value, ',') > 0
|
|
1053
|
+
OR instr(tag.value, char(0)) > 0
|
|
1054
|
+
OR EXISTS (
|
|
1055
|
+
WITH RECURSIVE offsets(position) AS (
|
|
1056
|
+
SELECT 1
|
|
1057
|
+
UNION ALL
|
|
1058
|
+
SELECT position + 1 FROM offsets WHERE position < length(tag.value)
|
|
1059
|
+
)
|
|
1060
|
+
SELECT 1
|
|
1061
|
+
FROM offsets
|
|
1062
|
+
WHERE unicode(substr(tag.value, position, 1)) BETWEEN 0 AND 32
|
|
1063
|
+
OR unicode(substr(tag.value, position, 1)) BETWEEN 127 AND 159
|
|
1064
|
+
OR unicode(substr(tag.value, position, 1)) IN (160, 5760, 8232, 8233, 8239, 8287, 12288, 65279)
|
|
1065
|
+
OR unicode(substr(tag.value, position, 1)) BETWEEN 8192 AND 8202
|
|
1066
|
+
)
|
|
1067
|
+
)
|
|
1068
|
+
OR (
|
|
1069
|
+
SELECT COUNT(*) FROM json_each(NEW.attrs, '$.__plurnk_curation.add')
|
|
1070
|
+
) != (
|
|
1071
|
+
SELECT COUNT(DISTINCT value) FROM json_each(NEW.attrs, '$.__plurnk_curation.add')
|
|
1072
|
+
)
|
|
1073
|
+
OR (
|
|
1074
|
+
SELECT COUNT(*) FROM json_each(NEW.attrs, '$.__plurnk_curation.remove')
|
|
1075
|
+
) != (
|
|
1076
|
+
SELECT COUNT(DISTINCT value) FROM json_each(NEW.attrs, '$.__plurnk_curation.remove')
|
|
1077
|
+
)
|
|
1078
|
+
OR EXISTS (
|
|
1079
|
+
SELECT 1
|
|
1080
|
+
FROM json_each(NEW.attrs, '$.__plurnk_curation.add') addition
|
|
1081
|
+
JOIN json_each(NEW.attrs, '$.__plurnk_curation.remove') removal
|
|
1082
|
+
ON removal.value = addition.value
|
|
1083
|
+
)
|
|
1084
|
+
THEN RAISE(ABORT, 'invalid private log curation payload') END;
|
|
1085
|
+
|
|
1086
|
+
INSERT INTO log_curation_effects (
|
|
1087
|
+
operation_log_entry_id,
|
|
1088
|
+
target_log_entry_id,
|
|
1089
|
+
expanded_before,
|
|
1090
|
+
tags_added,
|
|
1091
|
+
tags_removed
|
|
1092
|
+
)
|
|
1093
|
+
SELECT
|
|
1094
|
+
NEW.id,
|
|
1095
|
+
target.id,
|
|
1096
|
+
target.expanded,
|
|
1097
|
+
COALESCE((
|
|
1098
|
+
SELECT json_group_array(ordered.tag)
|
|
1099
|
+
FROM (
|
|
1100
|
+
SELECT addition.value AS tag
|
|
1101
|
+
FROM json_each(NEW.attrs, '$.__plurnk_curation.add') addition
|
|
1102
|
+
WHERE NOT EXISTS (
|
|
1103
|
+
SELECT 1 FROM log_tags
|
|
1104
|
+
WHERE log_entry_id = target.id AND tag = addition.value
|
|
1105
|
+
)
|
|
1106
|
+
ORDER BY addition.value
|
|
1107
|
+
) ordered
|
|
1108
|
+
), '[]'),
|
|
1109
|
+
COALESCE((
|
|
1110
|
+
SELECT json_group_array(ordered.tag)
|
|
1111
|
+
FROM (
|
|
1112
|
+
SELECT removal.value AS tag
|
|
1113
|
+
FROM json_each(NEW.attrs, '$.__plurnk_curation.remove') removal
|
|
1114
|
+
WHERE EXISTS (
|
|
1115
|
+
SELECT 1 FROM log_tags
|
|
1116
|
+
WHERE log_entry_id = target.id AND tag = removal.value
|
|
1117
|
+
)
|
|
1118
|
+
ORDER BY removal.value
|
|
1119
|
+
) ordered
|
|
1120
|
+
), '[]')
|
|
1121
|
+
FROM json_each(NEW.attrs, '$.__plurnk_curation.ids') selected
|
|
1122
|
+
JOIN log_entries target ON target.id = selected.value;
|
|
1123
|
+
|
|
1124
|
+
UPDATE log_entries
|
|
1125
|
+
SET expanded = json_extract(NEW.attrs, '$.__plurnk_curation.expanded')
|
|
1126
|
+
WHERE id IN (
|
|
1127
|
+
SELECT value FROM json_each(NEW.attrs, '$.__plurnk_curation.ids')
|
|
1128
|
+
);
|
|
1129
|
+
|
|
1130
|
+
DELETE FROM log_tags
|
|
1131
|
+
WHERE log_entry_id IN (
|
|
1132
|
+
SELECT value FROM json_each(NEW.attrs, '$.__plurnk_curation.ids')
|
|
1133
|
+
)
|
|
1134
|
+
AND tag IN (
|
|
1135
|
+
SELECT value FROM json_each(NEW.attrs, '$.__plurnk_curation.remove')
|
|
1136
|
+
);
|
|
1137
|
+
|
|
1138
|
+
INSERT OR IGNORE INTO log_tags (log_entry_id, tag)
|
|
1139
|
+
SELECT selected.value, addition.value
|
|
1140
|
+
FROM json_each(NEW.attrs, '$.__plurnk_curation.ids') selected
|
|
1141
|
+
CROSS JOIN json_each(NEW.attrs, '$.__plurnk_curation.add') addition;
|
|
1142
|
+
|
|
1143
|
+
UPDATE log_entries
|
|
1144
|
+
SET attrs = json_remove(attrs, '$.__plurnk_curation')
|
|
1145
|
+
WHERE id = NEW.id;
|
|
1146
|
+
END;
|
|
1147
|
+
|
|
1148
|
+
-- Column-scoped immutability: the original action's identity and target never
|
|
1149
|
+
-- change; the proposal lifecycle is allowed to mutate state, outcome,
|
|
1150
|
+
-- status_rx, rx, expanded. Keep attrs separate so the curation trigger's one
|
|
1151
|
+
-- private-payload removal cannot exempt changes to any other core column.
|
|
595
1152
|
CREATE TRIGGER IF NOT EXISTS log_entries_immutable_core
|
|
596
1153
|
BEFORE UPDATE OF
|
|
597
|
-
worker_id, loop_id, turn_id, sequence, at, origin, source,
|
|
1154
|
+
worker_id, loop_id, turn_id, sequence, at, origin, source, model_call_id,
|
|
598
1155
|
op, suffix, signal,
|
|
599
1156
|
scheme, username, password, hostname,
|
|
600
1157
|
port, pathname, query, fragment,
|
|
601
|
-
lineMarker, tx, mimetype_tx, mimetype_rx
|
|
1158
|
+
lineMarker, tx, mimetype_tx, mimetype_rx
|
|
602
1159
|
ON log_entries
|
|
603
1160
|
BEGIN
|
|
604
1161
|
SELECT RAISE(ABORT, 'log_entries core fields are immutable; only state/outcome/status_rx/rx/expanded may change');
|
|
605
1162
|
END;
|
|
606
1163
|
|
|
1164
|
+
CREATE TRIGGER IF NOT EXISTS log_entries_immutable_attrs
|
|
1165
|
+
BEFORE UPDATE OF attrs ON log_entries
|
|
1166
|
+
WHEN COALESCE((
|
|
1167
|
+
json_type(OLD.attrs, '$.__plurnk_curation') = 'object'
|
|
1168
|
+
AND NEW.attrs = json_remove(OLD.attrs, '$.__plurnk_curation')
|
|
1169
|
+
), 0) = 0
|
|
1170
|
+
BEGIN
|
|
1171
|
+
SELECT RAISE(ABORT, 'log_entries attrs are immutable outside curation payload removal');
|
|
1172
|
+
END;
|
|
1173
|
+
|
|
607
1174
|
-- The engine may stamp an originating row exactly once. No later reassignment
|
|
608
1175
|
-- can sever or counterfeit the occurrence identity.
|
|
609
1176
|
CREATE TRIGGER IF NOT EXISTS log_entries_ambient_event_once
|
|
@@ -631,10 +1198,17 @@ WHEN NEW.ambient_event_id IS NULL
|
|
|
631
1198
|
BEGIN
|
|
632
1199
|
INSERT INTO ambient_events (
|
|
633
1200
|
workspace_id, producer_worker_id, kind, source_record_id, source,
|
|
634
|
-
op, scheme, hostname, pathname, rx, attrs, status_rx
|
|
1201
|
+
op, scheme, hostname, pathname, rx, attrs, tags, status_rx
|
|
635
1202
|
)
|
|
636
1203
|
SELECT w.workspace_id, NEW.worker_id, 'edit', NEW.id, NEW.source,
|
|
637
|
-
'EDIT', NEW.scheme, NEW.hostname, NEW.pathname, NEW.rx, NEW.attrs,
|
|
1204
|
+
'EDIT', NEW.scheme, NEW.hostname, NEW.pathname, NEW.rx, NEW.attrs,
|
|
1205
|
+
COALESCE((
|
|
1206
|
+
SELECT json_group_array(ordered.tag)
|
|
1207
|
+
FROM (
|
|
1208
|
+
SELECT tag FROM log_tags WHERE log_entry_id = NEW.id ORDER BY tag
|
|
1209
|
+
) ordered
|
|
1210
|
+
), '[]'),
|
|
1211
|
+
NEW.status_rx
|
|
638
1212
|
FROM workers w
|
|
639
1213
|
WHERE w.id = NEW.worker_id;
|
|
640
1214
|
UPDATE log_entries SET ambient_event_id = last_insert_rowid() WHERE id = NEW.id;
|
|
@@ -658,87 +1232,22 @@ WHEN OLD.state = 'proposed'
|
|
|
658
1232
|
BEGIN
|
|
659
1233
|
INSERT INTO ambient_events (
|
|
660
1234
|
workspace_id, producer_worker_id, kind, source_record_id, source,
|
|
661
|
-
op, scheme, hostname, pathname, rx, attrs, status_rx
|
|
1235
|
+
op, scheme, hostname, pathname, rx, attrs, tags, status_rx
|
|
662
1236
|
)
|
|
663
1237
|
SELECT w.workspace_id, NEW.worker_id, 'edit', NEW.id, NEW.source,
|
|
664
|
-
'EDIT', NEW.scheme, NEW.hostname, NEW.pathname, NEW.rx, NEW.attrs,
|
|
1238
|
+
'EDIT', NEW.scheme, NEW.hostname, NEW.pathname, NEW.rx, NEW.attrs,
|
|
1239
|
+
COALESCE((
|
|
1240
|
+
SELECT json_group_array(ordered.tag)
|
|
1241
|
+
FROM (
|
|
1242
|
+
SELECT tag FROM log_tags WHERE log_entry_id = NEW.id ORDER BY tag
|
|
1243
|
+
) ordered
|
|
1244
|
+
), '[]'),
|
|
1245
|
+
NEW.status_rx
|
|
665
1246
|
FROM workers w
|
|
666
1247
|
WHERE w.id = NEW.worker_id;
|
|
667
1248
|
UPDATE log_entries SET ambient_event_id = last_insert_rowid() WHERE id = NEW.id;
|
|
668
1249
|
END;
|
|
669
1250
|
|
|
670
|
-
-- cost_rollups
|
|
671
|
-
-- Triggers maintaining denormalized USD totals on workers and workspaces
|
|
672
|
-
-- as turns insert/update. Pure denormalization (textbook trigger use);
|
|
673
|
-
-- no branching state-machine logic lives here.
|
|
674
|
-
CREATE TRIGGER IF NOT EXISTS turns_cost_rollup_insert_worker
|
|
675
|
-
AFTER INSERT ON turns
|
|
676
|
-
BEGIN
|
|
677
|
-
UPDATE workers
|
|
678
|
-
SET cost_usd = (
|
|
679
|
-
SELECT CASE WHEN COUNT(*) FILTER (WHERE t.usage_cost_usd IS NULL) > 0
|
|
680
|
-
THEN NULL ELSE COALESCE(SUM(t.usage_cost_usd), 0) END
|
|
681
|
-
FROM turns t JOIN loops l ON l.id = t.loop_id
|
|
682
|
-
WHERE l.worker_id = workers.id
|
|
683
|
-
)
|
|
684
|
-
WHERE id = (SELECT worker_id FROM loops WHERE id = NEW.loop_id);
|
|
685
|
-
END;
|
|
686
|
-
|
|
687
|
-
CREATE TRIGGER IF NOT EXISTS turns_cost_rollup_insert_workspace
|
|
688
|
-
AFTER INSERT ON turns
|
|
689
|
-
BEGIN
|
|
690
|
-
UPDATE workspaces
|
|
691
|
-
SET cost_usd = (
|
|
692
|
-
SELECT CASE WHEN COUNT(*) FILTER (WHERE t.usage_cost_usd IS NULL) > 0
|
|
693
|
-
THEN NULL ELSE COALESCE(SUM(t.usage_cost_usd), 0) END
|
|
694
|
-
FROM turns t
|
|
695
|
-
JOIN loops l ON l.id = t.loop_id
|
|
696
|
-
JOIN workers r ON r.id = l.worker_id
|
|
697
|
-
WHERE r.workspace_id = workspaces.id
|
|
698
|
-
)
|
|
699
|
-
WHERE id = (
|
|
700
|
-
SELECT r.workspace_id
|
|
701
|
-
FROM workers r
|
|
702
|
-
JOIN loops l ON l.worker_id = r.id
|
|
703
|
-
WHERE l.id = NEW.loop_id
|
|
704
|
-
);
|
|
705
|
-
END;
|
|
706
|
-
|
|
707
|
-
CREATE TRIGGER IF NOT EXISTS turns_cost_rollup_update_worker
|
|
708
|
-
AFTER UPDATE OF usage_cost_usd ON turns
|
|
709
|
-
WHEN NEW.usage_cost_usd IS NOT OLD.usage_cost_usd
|
|
710
|
-
BEGIN
|
|
711
|
-
UPDATE workers
|
|
712
|
-
SET cost_usd = (
|
|
713
|
-
SELECT CASE WHEN COUNT(*) FILTER (WHERE t.usage_cost_usd IS NULL) > 0
|
|
714
|
-
THEN NULL ELSE COALESCE(SUM(t.usage_cost_usd), 0) END
|
|
715
|
-
FROM turns t JOIN loops l ON l.id = t.loop_id
|
|
716
|
-
WHERE l.worker_id = workers.id
|
|
717
|
-
)
|
|
718
|
-
WHERE id = (SELECT worker_id FROM loops WHERE id = NEW.loop_id);
|
|
719
|
-
END;
|
|
720
|
-
|
|
721
|
-
CREATE TRIGGER IF NOT EXISTS turns_cost_rollup_update_workspace
|
|
722
|
-
AFTER UPDATE OF usage_cost_usd ON turns
|
|
723
|
-
WHEN NEW.usage_cost_usd IS NOT OLD.usage_cost_usd
|
|
724
|
-
BEGIN
|
|
725
|
-
UPDATE workspaces
|
|
726
|
-
SET cost_usd = (
|
|
727
|
-
SELECT CASE WHEN COUNT(*) FILTER (WHERE t.usage_cost_usd IS NULL) > 0
|
|
728
|
-
THEN NULL ELSE COALESCE(SUM(t.usage_cost_usd), 0) END
|
|
729
|
-
FROM turns t
|
|
730
|
-
JOIN loops l ON l.id = t.loop_id
|
|
731
|
-
JOIN workers r ON r.id = l.worker_id
|
|
732
|
-
WHERE r.workspace_id = workspaces.id
|
|
733
|
-
)
|
|
734
|
-
WHERE id = (
|
|
735
|
-
SELECT r.workspace_id
|
|
736
|
-
FROM workers r
|
|
737
|
-
JOIN loops l ON l.worker_id = r.id
|
|
738
|
-
WHERE l.id = NEW.loop_id
|
|
739
|
-
);
|
|
740
|
-
END;
|
|
741
|
-
|
|
742
1251
|
-- subscriptions
|
|
743
1252
|
-- Durable subscription lifecycle per SPEC {§subscriptions}. The row records what
|
|
744
1253
|
-- the worker holds and routes cancellation to a separate process-local callable;
|