openclaw / apps /shared /OpenClawKit /Sources /OpenClawChatUI /ClientDatabases+StateMigrations.swift
SaylorTwift's picture
SaylorTwift HF Staff
Add files using upload-large-folder tool
b8370f5 verified
Raw History Blame Contribute Delete
8.48 kB
import GRDB
extension OpenClawClientDatabases {
static func registerClientStateMigrationsV1ThroughV5(_ migrator: inout DatabaseMigrator) {
migrator.registerMigration("client-state-v1") { db in
try db.execute(sql: """
CREATE TABLE forgotten_gateways(
gateway_hash TEXT NOT NULL PRIMARY KEY,
gateway_id TEXT,
forgotten_at REAL NOT NULL,
cleanup_phase INTEGER NOT NULL CHECK(cleanup_phase IN (0, 1, 2, 3)),
restore_finalized INTEGER NOT NULL DEFAULT 0
CHECK(restore_finalized IN (0, 1)),
CHECK((cleanup_phase IN (1, 2) AND gateway_id IS NOT NULL) OR
(cleanup_phase IN (0, 3) AND gateway_id IS NULL AND restore_finalized = 0))
);
CREATE TABLE gateway_routing_identity(
gateway_id TEXT NOT NULL PRIMARY KEY,
scope TEXT NOT NULL,
main_session_key TEXT NOT NULL,
default_agent_id TEXT NOT NULL,
updated_at REAL NOT NULL
);
CREATE TABLE outbox_commands(
enqueue_sequence INTEGER PRIMARY KEY AUTOINCREMENT,
gateway_id TEXT NOT NULL,
client_uuid TEXT NOT NULL,
session_key TEXT NOT NULL,
delivery_session_key TEXT NOT NULL,
routing_contract TEXT NOT NULL,
agent_id TEXT NOT NULL,
text TEXT NOT NULL,
thinking TEXT NOT NULL,
created_at REAL NOT NULL,
status TEXT NOT NULL CHECK(status IN (
'queued', 'sending', 'awaiting_confirmation', 'failed'
)),
retry_count INTEGER NOT NULL DEFAULT 0,
last_error TEXT NOT NULL DEFAULT '',
attachment_bytes INTEGER NOT NULL DEFAULT 0,
UNIQUE(gateway_id, client_uuid)
);
CREATE INDEX outbox_commands_delivery_order
ON outbox_commands(gateway_id, created_at, enqueue_sequence);
CREATE TABLE outbox_attachments(
gateway_id TEXT NOT NULL,
command_id TEXT NOT NULL,
position INTEGER NOT NULL,
type TEXT NOT NULL,
mime_type TEXT NOT NULL,
file_name TEXT NOT NULL,
payload BLOB NOT NULL,
duration_seconds REAL,
PRIMARY KEY(gateway_id, command_id, position),
FOREIGN KEY(gateway_id, command_id)
REFERENCES outbox_commands(gateway_id, client_uuid)
ON DELETE CASCADE
ON UPDATE CASCADE
);
""")
}
// Additive branch ownership remains local client state. Older app
// builds ignore these fields while newer builds fail replay closed.
migrator.registerMigration("client-state-branch-ownership-v2") { db in
try db.execute(sql: """
ALTER TABLE outbox_commands ADD COLUMN branch_epoch INTEGER NOT NULL DEFAULT 0;
ALTER TABLE outbox_commands ADD COLUMN attempt_version INTEGER NOT NULL DEFAULT 1;
ALTER TABLE outbox_commands ADD COLUMN parked_was_accepted INTEGER NOT NULL DEFAULT 0;
CREATE TABLE outbox_branch_scopes(
gateway_id TEXT NOT NULL,
session_key TEXT NOT NULL,
agent_id TEXT NOT NULL DEFAULT '',
branch_epoch INTEGER NOT NULL DEFAULT 0,
last_active_leaf_id TEXT,
switch_pending_since REAL,
needs_reconciliation INTEGER NOT NULL DEFAULT 0,
PRIMARY KEY(gateway_id, session_key, agent_id)
);
""")
}
migrator.registerMigration("client-state-branch-revision-v3") { db in
try db.execute(sql: """
ALTER TABLE outbox_branch_scopes
ADD COLUMN branch_state_revision INTEGER NOT NULL DEFAULT 0;
""")
}
migrator.registerMigration("client-state-agent-id-v4") { db in
try db.execute(sql: "UPDATE outbox_commands SET agent_id = '' WHERE agent_id IS NULL")
try db.execute(sql: "UPDATE outbox_branch_scopes SET agent_id = '' WHERE agent_id IS NULL")
}
migrator.registerMigration("client-state-outbox-attempt-scope-v5") { db in
try db
.execute(
sql: "ALTER TABLE outbox_commands ADD COLUMN had_unacknowledged_send INTEGER NOT NULL DEFAULT 0")
// Legacy rows with prior attempts may have reached the gateway before a
// transport failure; without this evidence a post-park retry would reuse an
// idempotency key the old branch may already own.
try db.execute(
sql: """
UPDATE outbox_commands SET had_unacknowledged_send = 1
WHERE retry_count > 0 OR status IN ('sending', 'awaiting_confirmation')
""")
try db.execute(sql: """
INSERT OR IGNORE INTO outbox_branch_scopes(
gateway_id, session_key, agent_id, branch_epoch, needs_reconciliation
)
SELECT gateway_id, session_key, agent_id, 0, 1 FROM outbox_commands
""")
}
}
static func registerClientStateMigrationsV6ThroughV8(_ migrator: inout DatabaseMigrator) {
migrator.registerMigration("client-state-outbox-attachment-rekey-v6") { db in
try db.execute(sql: """
CREATE TABLE outbox_attachments_v6(
gateway_id TEXT NOT NULL,
command_id TEXT NOT NULL,
position INTEGER NOT NULL,
type TEXT NOT NULL,
mime_type TEXT NOT NULL,
file_name TEXT NOT NULL,
payload BLOB NOT NULL,
duration_seconds REAL,
PRIMARY KEY(gateway_id, command_id, position),
FOREIGN KEY(gateway_id, command_id)
REFERENCES outbox_commands(gateway_id, client_uuid)
ON DELETE CASCADE
ON UPDATE CASCADE
);
INSERT INTO outbox_attachments_v6(
gateway_id, command_id, position, type, mime_type, file_name, payload, duration_seconds
)
SELECT gateway_id, command_id, position, type, mime_type, file_name, payload, duration_seconds
FROM outbox_attachments;
DROP TABLE outbox_attachments;
ALTER TABLE outbox_attachments_v6 RENAME TO outbox_attachments;
""")
}
// A queued command owns the authority it was captured under. Legacy
// rows remain NULL so CAS-capable replay can park them for review.
migrator.registerMigration("client-state-outbox-settings-expectation-v7") { db in
try db.execute(sql: """
ALTER TABLE outbox_commands ADD COLUMN expected_settings_json TEXT;
""")
}
migrator.registerMigration("client-state-outbox-settings-claim-v8") { db in
try db.execute(sql: """
ALTER TABLE outbox_commands ADD COLUMN settings_retry_authorization INTEGER;
CREATE TRIGGER outbox_settings_claim_guard
BEFORE UPDATE OF status ON outbox_commands
WHEN OLD.status = 'queued' AND NEW.status = 'sending'
AND COALESCE(NEW.settings_retry_authorization, 0) =
COALESCE(OLD.settings_retry_authorization, 0)
BEGIN
UPDATE outbox_commands
SET status = 'failed', last_error = 'settings_client_upgrade_required'
WHERE gateway_id = OLD.gateway_id AND client_uuid = OLD.client_uuid
AND status = 'queued';
SELECT RAISE(IGNORE);
END;
CREATE TRIGGER outbox_settings_retry_guard
BEFORE UPDATE OF status ON outbox_commands
WHEN OLD.status = 'failed' AND NEW.status = 'queued'
AND COALESCE(NEW.settings_retry_authorization, 0) =
COALESCE(OLD.settings_retry_authorization, 0)
BEGIN
SELECT RAISE(ABORT, 'settings-fenced outbox retry requires current client');
END;
""")
}
}
}