Files
agent-radar 2ed3415fb7
Publish Claude Code Binaries / Stage and publish binaries (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-arm64 (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build linux-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build windows-amd64 (push) Canceled after 0s
Sync Tutti Agent Skills / sync (push) Canceled after 0s
Windows Agent Adapters / Test Windows agent adapters (push) Canceled after 0s
Windows Daemon Adapters / Test Windows daemon adapters (push) Canceled after 0s
Publish Tutti App Runtime / Publish runtime catalog (push) Canceled after 0s
源码快照 @ 2026-09-01 (source-only, 剔除非源码素材)
2026-09-01 20:17:27 +08:00

377 lines
13 KiB
Go

package storesqlite
import (
"context"
"database/sql"
"encoding/json"
"errors"
"fmt"
"strings"
market "github.com/tutti-os/tutti/packages/connector/daemon/core"
)
const maxLifecycleCleanupBatchSize = 500
func (store *Store) migrateLifecycle(ctx context.Context) error {
if _, err := store.db.ExecContext(ctx, `ALTER TABLE connector_market_operations ADD COLUMN updated_at_unix_ms INTEGER NOT NULL DEFAULT 0`); err != nil &&
!isDuplicateColumnError(err) {
return fmt.Errorf("migrate connector market operation update timestamp: %w", err)
}
if _, err := store.db.ExecContext(ctx, `CREATE TABLE IF NOT EXISTS connector_market_release_installations (
connector_key TEXT NOT NULL,
release_digest TEXT NOT NULL,
release_json TEXT NOT NULL,
PRIMARY KEY (connector_key, release_digest)
)`); err != nil {
return fmt.Errorf("migrate connector release installations: %w", err)
}
if err := store.backfillLifecycleColumns(ctx); err != nil {
return err
}
statements := []string{
`CREATE INDEX IF NOT EXISTS connector_market_operations_terminal_cleanup
ON connector_market_operations(updated_at_unix_ms, operation_id)
WHERE state IN ('completed', 'failed')`,
`CREATE INDEX IF NOT EXISTS connector_market_outbox_published_cleanup
ON connector_market_outbox(published_at_unix_ms, sequence)
WHERE published_at_unix_ms IS NOT NULL`,
}
for _, statement := range statements {
if _, err := store.db.ExecContext(ctx, statement); err != nil {
return fmt.Errorf("migrate connector market lifecycle index: %w", err)
}
}
return nil
}
func isDuplicateColumnError(err error) bool {
return err != nil && strings.Contains(strings.ToLower(err.Error()), "duplicate column")
}
func (store *Store) backfillLifecycleColumns(ctx context.Context) error {
tx, err := store.db.BeginTx(ctx, nil)
if err != nil {
return err
}
defer func() { _ = tx.Rollback() }()
rows, err := tx.QueryContext(ctx, `
SELECT operation_id, operation_json FROM connector_market_operations
WHERE updated_at_unix_ms = 0`)
if err != nil {
return err
}
type timestampBackfill struct {
operationID string
updatedAtMS int64
}
var updates []timestampBackfill
for rows.Next() {
var operationID, payload string
if err := rows.Scan(&operationID, &payload); err != nil {
_ = rows.Close()
return err
}
operation, err := decodeOperation(payload)
if err != nil {
_ = rows.Close()
return err
}
updates = append(updates, timestampBackfill{operationID: operationID, updatedAtMS: operation.UpdatedAt.UTC().UnixMilli()})
}
if err := rows.Close(); err != nil {
return err
}
if err := rows.Err(); err != nil {
return err
}
for _, update := range updates {
if _, err := tx.ExecContext(ctx, `UPDATE connector_market_operations SET updated_at_unix_ms = ? WHERE operation_id = ? AND updated_at_unix_ms = 0`,
update.updatedAtMS, update.operationID); err != nil {
return err
}
}
if err := backfillConnectorInstallationTimes(ctx, tx); err != nil {
return err
}
if err := backfillInstalledReleaseEvidence(ctx, tx); err != nil {
return err
}
return tx.Commit()
}
func backfillConnectorInstallationTimes(ctx context.Context, tx *sql.Tx) error {
rows, err := tx.QueryContext(ctx, `SELECT connector_key, connector_json FROM connector_market_connectors`)
if err != nil {
return err
}
type connectorBackfill struct {
connectorKey string
connector market.Connector
}
var connectors []connectorBackfill
for rows.Next() {
var connectorKey, payload string
if err := rows.Scan(&connectorKey, &payload); err != nil {
_ = rows.Close()
return err
}
connector, err := decodeConnector(payload)
if err != nil {
_ = rows.Close()
return err
}
connectors = append(connectors, connectorBackfill{connectorKey: connectorKey, connector: connector})
}
if err := rows.Close(); err != nil {
return err
}
if err := rows.Err(); err != nil {
return err
}
for _, entry := range connectors {
if entry.connector.Installation.State != market.InstallationStateInstalled ||
entry.connector.Installation.InstalledAtUnixMS > 0 {
continue
}
var operationPayload string
err := tx.QueryRowContext(ctx, `SELECT operation_json FROM connector_market_operations
WHERE connector_key = ? AND kind = ? AND state = ?
ORDER BY updated_at_unix_ms DESC LIMIT 1`, entry.connectorKey, market.OperationKindInstall, market.OperationStateCompleted).Scan(&operationPayload)
if errors.Is(err, sql.ErrNoRows) {
continue
}
if err != nil {
return err
}
operation, err := decodeOperation(operationPayload)
if err != nil {
return err
}
installedAtUnixMS := operation.UpdatedAt.UTC().UnixMilli()
if installedAtUnixMS <= 0 {
continue
}
entry.connector.Installation.InstalledAtUnixMS = installedAtUnixMS
payload, err := json.Marshal(entry.connector)
if err != nil {
return err
}
if _, err := tx.ExecContext(ctx, `UPDATE connector_market_connectors SET connector_json = ? WHERE connector_key = ?`, string(payload), entry.connectorKey); err != nil {
return err
}
}
return nil
}
func backfillInstalledReleaseEvidence(ctx context.Context, tx *sql.Tx) error {
rows, err := tx.QueryContext(ctx, `SELECT connector_json FROM connector_market_connectors`)
if err != nil {
return err
}
var connectors []market.Connector
for rows.Next() {
var payload string
if err := rows.Scan(&payload); err != nil {
_ = rows.Close()
return err
}
connector, err := decodeConnector(payload)
if err != nil {
_ = rows.Close()
return err
}
connectors = append(connectors, connector)
}
if err := rows.Close(); err != nil {
return err
}
if err := rows.Err(); err != nil {
return err
}
for _, connector := range connectors {
digest := connector.Installation.InstalledReleaseDigest
if digest == "" {
continue
}
var historicalDigest string
err := tx.QueryRowContext(ctx, `SELECT release_digest FROM connector_market_release_installations
WHERE connector_key = ? AND release_digest = ?`, connector.Key, digest).Scan(&historicalDigest)
if err == nil {
continue
}
if err != nil && !errors.Is(err, sql.ErrNoRows) {
return err
}
release, found, err := legacyInstalledReleaseEvidence(ctx, tx, connector, digest)
if err != nil {
return err
}
if !found {
// Older stores could promote a prepared candidate in the current-release
// index before runtime convergence completed. If that update then failed,
// the previous release metadata was no longer recoverable from SQLite.
// Keep the store available and let runtime recovery fail this connector
// closed until a subsequent install repairs its durable evidence.
continue
}
if err := saveInstalledReleaseOn(ctx, tx, release); err != nil {
return err
}
}
return nil
}
func legacyInstalledReleaseEvidence(ctx context.Context, tx *sql.Tx, connector market.Connector, digest string) (market.Release, bool, error) {
var currentPayload string
err := tx.QueryRowContext(ctx, `SELECT release_json FROM connector_market_installed_releases
WHERE connector_key = ? AND release_digest = ?`, connector.Key, digest).Scan(&currentPayload)
if err == nil {
var release market.Release
if err := json.Unmarshal([]byte(currentPayload), &release); err != nil {
return market.Release{}, false, fmt.Errorf("decode current installed connector release: %w", err)
}
return release, true, nil
}
if err != nil && !errors.Is(err, sql.ErrNoRows) {
return market.Release{}, false, err
}
if connector.Release.ReleaseDigest == digest {
return connector.Release, true, nil
}
rows, err := tx.QueryContext(ctx, `SELECT operation_json FROM connector_market_operations
WHERE connector_key = ? AND kind = ? AND state = ? ORDER BY updated_at_unix_ms DESC`,
connector.Key, market.OperationKindInstall, market.OperationStateCompleted)
if err != nil {
return market.Release{}, false, err
}
defer rows.Close()
for rows.Next() {
var payload string
if err := rows.Scan(&payload); err != nil {
return market.Release{}, false, err
}
operation, err := decodeOperation(payload)
if err != nil {
return market.Release{}, false, err
}
if operation.Target != nil && operation.Target.Release != nil && operation.Target.ReleaseDigest == digest {
return *operation.Target.Release, true, nil
}
}
return market.Release{}, false, rows.Err()
}
func saveInstalledReleaseEvidenceOn(ctx context.Context, tx *sql.Tx, operation market.Operation) error {
switch operation.Kind {
case market.OperationKindInstall:
if operation.Execution.ReleaseInstallation == nil && operation.State != market.OperationStateCompleted {
return nil
}
if operation.Target == nil || operation.Target.Release == nil || operation.Target.ReleaseDigest == "" {
return errors.New("prepared connector install is missing release evidence")
}
if operation.State == market.OperationStateCompleted {
return saveInstalledReleaseOn(ctx, tx, *operation.Target.Release)
}
return saveReleaseInstallationOn(ctx, tx, *operation.Target.Release)
case market.OperationKindUninstall:
if operation.State != market.OperationStateCompleted {
return nil
}
_, err := tx.ExecContext(ctx, `DELETE FROM connector_market_installed_releases WHERE connector_key = ?`, operation.ConnectorKey)
if err != nil {
return err
}
_, err = tx.ExecContext(ctx, `DELETE FROM connector_market_release_installations WHERE connector_key = ?`, operation.ConnectorKey)
return err
default:
return nil
}
}
func saveInstalledReleaseOn(ctx context.Context, tx *sql.Tx, release market.Release) error {
if err := saveReleaseInstallationOn(ctx, tx, release); err != nil {
return err
}
payload, err := json.Marshal(release)
if err != nil {
return err
}
_, err = tx.ExecContext(ctx, `
INSERT INTO connector_market_installed_releases (connector_key, release_digest, release_json)
VALUES (?, ?, ?)
ON CONFLICT(connector_key) DO UPDATE SET
release_digest = excluded.release_digest,
release_json = excluded.release_json`, release.ConnectorKey, release.ReleaseDigest, string(payload))
return err
}
func saveReleaseInstallationOn(ctx context.Context, tx *sql.Tx, release market.Release) error {
payload, err := json.Marshal(release)
if err != nil {
return err
}
_, err = tx.ExecContext(ctx, `
INSERT INTO connector_market_release_installations (connector_key, release_digest, release_json)
VALUES (?, ?, ?)
ON CONFLICT(connector_key, release_digest) DO UPDATE SET
release_json = excluded.release_json`, release.ConnectorKey, release.ReleaseDigest, string(payload))
return err
}
func (store *Store) CleanupLifecycle(ctx context.Context, request market.LifecycleCleanupRequest) (market.LifecycleCleanupResult, error) {
if request.BatchSize <= 0 || request.BatchSize > maxLifecycleCleanupBatchSize {
return market.LifecycleCleanupResult{}, fmt.Errorf("connector market lifecycle cleanup batch size must be between 1 and %d", maxLifecycleCleanupBatchSize)
}
if request.TerminalOperationsUpdatedThrough.IsZero() || request.PublishedEventsPublishedThrough.IsZero() {
return market.LifecycleCleanupResult{}, errors.New("connector market lifecycle cleanup cutoffs are required")
}
tx, err := store.db.BeginTx(ctx, nil)
if err != nil {
return market.LifecycleCleanupResult{}, err
}
defer func() { _ = tx.Rollback() }()
operationResult, err := tx.ExecContext(ctx, `
DELETE FROM connector_market_operations
WHERE operation_id IN (
SELECT operation_id FROM connector_market_operations
WHERE state IN ('completed', 'failed') AND updated_at_unix_ms <= ?
ORDER BY updated_at_unix_ms, operation_id LIMIT ?
)
AND state IN ('completed', 'failed') AND updated_at_unix_ms <= ?`,
request.TerminalOperationsUpdatedThrough.UTC().UnixMilli(), request.BatchSize,
request.TerminalOperationsUpdatedThrough.UTC().UnixMilli())
if err != nil {
return market.LifecycleCleanupResult{}, err
}
outboxResult, err := tx.ExecContext(ctx, `
DELETE FROM connector_market_outbox
WHERE sequence IN (
SELECT sequence FROM connector_market_outbox
WHERE published_at_unix_ms IS NOT NULL AND published_at_unix_ms <= ?
ORDER BY published_at_unix_ms, sequence LIMIT ?
)
AND published_at_unix_ms IS NOT NULL AND published_at_unix_ms <= ?`,
request.PublishedEventsPublishedThrough.UTC().UnixMilli(), request.BatchSize,
request.PublishedEventsPublishedThrough.UTC().UnixMilli())
if err != nil {
return market.LifecycleCleanupResult{}, err
}
operationsDeleted, err := operationResult.RowsAffected()
if err != nil {
return market.LifecycleCleanupResult{}, err
}
eventsDeleted, err := outboxResult.RowsAffected()
if err != nil {
return market.LifecycleCleanupResult{}, err
}
if err := tx.Commit(); err != nil {
return market.LifecycleCleanupResult{}, err
}
return market.LifecycleCleanupResult{
TerminalOperationsDeleted: operationsDeleted,
PublishedEventsDeleted: eventsDeleted,
}, nil
}